1 /* This file is part of MyPaint.
2 * Copyright (C) 2008-2014 by Martin Renold <martinxyz@gmx.ch>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 */
9
10 #include "pixops.hpp"
11
12 #include "common.hpp"
13 #include "compositing.hpp"
14 #include "blending.hpp"
15 #include "fastapprox/fastpow.h"
16
17 #include <mypaint-tiled-surface.h>
18
19 #include <glib.h>
20 #include <math.h>
21
22 #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
23 #define NO_IMPORT_ARRAY
24 #include <numpy/arrayobject.h>
25
26 #include <stdlib.h>
27 #include <math.h>
28
29
30 void
tile_downscale_rgba16_c(const uint16_t * src,int src_strides,uint16_t * dst,int dst_strides,int dst_x,int dst_y)31 tile_downscale_rgba16_c(const uint16_t *src, int src_strides, uint16_t *dst,
32 int dst_strides, int dst_x, int dst_y)
33 {
34 for (int y=0; y<MYPAINT_TILE_SIZE/2; y++) {
35 uint16_t * src_p = (uint16_t*)((char *)src + (2*y)*src_strides);
36 uint16_t * dst_p = (uint16_t*)((char *)dst + (y+dst_y)*dst_strides);
37 dst_p += 4*dst_x;
38 for(int x=0; x<MYPAINT_TILE_SIZE/2; x++) {
39 dst_p[0] = src_p[0]/4 + (src_p+4)[0]/4 + (src_p+4*MYPAINT_TILE_SIZE)[0]/4 + (src_p+4*MYPAINT_TILE_SIZE+4)[0]/4;
40 dst_p[1] = src_p[1]/4 + (src_p+4)[1]/4 + (src_p+4*MYPAINT_TILE_SIZE)[1]/4 + (src_p+4*MYPAINT_TILE_SIZE+4)[1]/4;
41 dst_p[2] = src_p[2]/4 + (src_p+4)[2]/4 + (src_p+4*MYPAINT_TILE_SIZE)[2]/4 + (src_p+4*MYPAINT_TILE_SIZE+4)[2]/4;
42 dst_p[3] = src_p[3]/4 + (src_p+4)[3]/4 + (src_p+4*MYPAINT_TILE_SIZE)[3]/4 + (src_p+4*MYPAINT_TILE_SIZE+4)[3]/4;
43 src_p += 8;
44 dst_p += 4;
45 }
46 }
47 }
48
tile_downscale_rgba16(PyObject * src,PyObject * dst,int dst_x,int dst_y)49 void tile_downscale_rgba16(PyObject *src, PyObject *dst, int dst_x, int dst_y) {
50
51 PyArrayObject* src_arr = ((PyArrayObject*)src);
52 PyArrayObject* dst_arr = ((PyArrayObject*)dst);
53
54 #ifdef HEAVY_DEBUG
55 assert(PyArray_Check(src));
56 assert(PyArray_DIM(src_arr, 0) == MYPAINT_TILE_SIZE);
57 assert(PyArray_DIM(src_arr, 1) == MYPAINT_TILE_SIZE);
58 assert(PyArray_DIM(src_arr, 2) == 4);
59 assert(PyArray_TYPE(src_arr) == NPY_UINT16);
60 assert(PyArray_ISCARRAY(src_arr));
61
62 assert(PyArray_Check(dst));
63 assert(PyArray_DIM(dst_arr, 2) == 4);
64 assert(PyArray_TYPE(dst_arr) == NPY_UINT16);
65 assert(PyArray_ISCARRAY(dst_arr));
66 #endif
67
68 tile_downscale_rgba16_c((uint16_t*)PyArray_DATA(src_arr), PyArray_STRIDES(src_arr)[0],
69 (uint16_t*)PyArray_DATA(dst_arr), PyArray_STRIDES(dst_arr)[0],
70 dst_x, dst_y);
71
72 }
73
74
tile_copy_rgba16_into_rgba16_c(const uint16_t * src,uint16_t * dst)75 void tile_copy_rgba16_into_rgba16_c(const uint16_t *src, uint16_t *dst) {
76 memcpy(dst, src, MYPAINT_TILE_SIZE*MYPAINT_TILE_SIZE*4*sizeof(uint16_t));
77 }
78
tile_copy_rgba16_into_rgba16(PyObject * src,PyObject * dst)79 void tile_copy_rgba16_into_rgba16(PyObject * src, PyObject * dst) {
80 PyArrayObject* src_arr = ((PyArrayObject*)src);
81 PyArrayObject* dst_arr = ((PyArrayObject*)dst);
82
83 #ifdef HEAVY_DEBUG
84 assert(PyArray_Check(dst));
85 assert(PyArray_DIM(dst_arr, 0) == MYPAINT_TILE_SIZE);
86 assert(PyArray_DIM(dst_arr, 1) == MYPAINT_TILE_SIZE);
87 assert(PyArray_DIM(dst_arr, 2) == 4);
88 assert(PyArray_TYPE(dst_arr) == NPY_UINT16);
89 assert(PyArray_ISCARRAY(dst_arr));
90 assert(PyArray_STRIDES(dst_arr)[1] == 4*sizeof(uint16_t));
91 assert(PyArray_STRIDES(dst_arr)[2] == sizeof(uint16_t));
92
93 assert(PyArray_Check(src));
94 assert(PyArray_DIM(src_arr, 0) == MYPAINT_TILE_SIZE);
95 assert(PyArray_DIM(src_arr, 1) == MYPAINT_TILE_SIZE);
96 assert(PyArray_DIM(src_arr, 2) == 4);
97 assert(PyArray_TYPE(src_arr) == NPY_UINT16);
98 assert(PyArray_ISCARRAY(src_arr));
99 assert(PyArray_STRIDES(src_arr)[1] == 4*sizeof(uint16_t));
100 assert(PyArray_STRIDES(src_arr)[2] == sizeof(uint16_t));
101 #endif
102
103 /* the code below can be used if it is not ISCARRAY, but only ISBEHAVED:
104 char * src_p = PyArray_DATA(src_arr);
105 char * dst_p = PyArray_DATA(dst_arr);
106 for (int y=0; y<MYPAINT_TILE_SIZE; y++) {
107 memcpy(dst_p, src_p, MYPAINT_TILE_SIZE*4);
108 src_p += src_arr->strides[0];
109 dst_p += dst_arr->strides[0];
110 }
111 */
112
113 tile_copy_rgba16_into_rgba16_c((uint16_t *)PyArray_DATA(src_arr),
114 (uint16_t *)PyArray_DATA(dst_arr));
115 }
116
tile_clear_rgba8(PyObject * dst)117 void tile_clear_rgba8(PyObject * dst) {
118 PyArrayObject* dst_arr = ((PyArrayObject*)dst);
119
120 #ifdef HEAVY_DEBUG
121 assert(PyArray_Check(dst));
122 assert(PyArray_DIM(dst_arr, 0) == MYPAINT_TILE_SIZE);
123 assert(PyArray_DIM(dst_arr, 1) == MYPAINT_TILE_SIZE);
124 assert(PyArray_TYPE(dst_arr) == NPY_UINT8);
125 assert(PyArray_ISBEHAVED(dst_arr));
126 assert(PyArray_STRIDES(dst_arr)[1] <= 8);
127 #endif
128
129 for (int y=0; y<MYPAINT_TILE_SIZE; y++) {
130 uint8_t * dst_p = (uint8_t*)((char *)PyArray_DATA(dst_arr) + y*PyArray_STRIDES(dst_arr)[0]);
131 memset(dst_p, 0, MYPAINT_TILE_SIZE*PyArray_STRIDES(dst_arr)[1]);
132 dst_p += PyArray_STRIDES(dst_arr)[0];
133 }
134 }
135
tile_clear_rgba16(PyObject * dst)136 void tile_clear_rgba16(PyObject * dst) {
137 PyArrayObject* dst_arr = ((PyArrayObject*)dst);
138
139 #ifdef HEAVY_DEBUG
140 assert(PyArray_Check(dst));
141 assert(PyArray_DIM(dst_arr, 0) == MYPAINT_TILE_SIZE);
142 assert(PyArray_DIM(dst_arr, 1) == MYPAINT_TILE_SIZE);
143 assert(PyArray_TYPE(dst_arr) == NPY_UINT16);
144 assert(PyArray_ISBEHAVED(dst_arr));
145 assert(PyArray_STRIDES(dst_arr)[1] <= 8);
146 #endif
147
148 for (int y=0; y<MYPAINT_TILE_SIZE; y++) {
149 uint16_t * dst_p = (uint16_t*)((char *)PyArray_DATA(dst_arr) + y*PyArray_STRIDES(dst_arr)[0]);
150 memset(dst_p, 0, MYPAINT_TILE_SIZE*PyArray_STRIDES(dst_arr)[1]);
151 dst_p += PyArray_STRIDES(dst_arr)[0];
152 }
153 }
154
155
156 // Noise used for dithering (the same for each tile).
157
158 static const int dithering_noise_size = MYPAINT_TILE_SIZE*MYPAINT_TILE_SIZE*4;
159 static uint16_t dithering_noise[dithering_noise_size];
precalculate_dithering_noise_if_required()160 static void precalculate_dithering_noise_if_required()
161 {
162 static bool have_noise = false;
163 if (!have_noise) {
164 // let's make some noise
165 for (int i=0; i<dithering_noise_size; i++) {
166 // random number in range [0.03 .. 0.2] * (1<<15)
167 //
168 // We could use the full range, but like this it is much easier
169 // to guarantee 8bpc load-save roundtrips don't alter the
170 // image. With the full range we would have to pay a lot
171 // attention to rounding converting 8bpc to our internal format.
172 dithering_noise[i] = (rand() % (1<<15)) * 5/256 + (1<<15) * 2/256;
173 }
174 have_noise = true;
175 }
176 }
177
178 // Used for saving layers (transparent PNG), and for display when there
179 // can be transparent areas in the output.
180
181 static inline void
tile_convert_rgba16_to_rgba8_const_c(const uint16_t * const src,const int src_strides,const uint8_t * dst,const int dst_strides)182 tile_convert_rgba16_to_rgba8_const_c (const uint16_t* const src,
183 const int src_strides,
184 const uint8_t* dst,
185 const int dst_strides)
186 {
187 precalculate_dithering_noise_if_required();
188
189 for (int y=0; y<MYPAINT_TILE_SIZE; y++) {
190 int noise_idx = y*MYPAINT_TILE_SIZE*4;
191 const uint16_t *src_p = (uint16_t*)((char *)src + y*src_strides);
192 uint8_t *dst_p = (uint8_t*)((char *)dst + y*dst_strides);
193 for (int x=0; x<MYPAINT_TILE_SIZE; x++) {
194 uint32_t r, g, b, a;
195 r = *src_p++;
196 g = *src_p++;
197 b = *src_p++;
198 a = *src_p++;
199 // un-premultiply alpha (with rounding)
200 if (a != 0) {
201 const uint32_t rnd_a = a/2;
202 r = ((r << 15) + rnd_a) / a;
203 g = ((g << 15) + rnd_a) / a;
204 b = ((b << 15) + rnd_a) / a;
205 } else {
206 r = g = b = 0;
207 }
208 const uint32_t add_r = dithering_noise[noise_idx+0];
209 const uint32_t add_g = add_r; // hm... do not produce too much color noise
210 const uint32_t add_b = add_r;
211 const uint32_t add_a = dithering_noise[noise_idx+1];
212 noise_idx += 4;
213
214
215 *dst_p++ = (r * 255 + add_r) / (1<<15);
216 *dst_p++ = (g * 255 + add_g) / (1<<15);
217 *dst_p++ = (b * 255 + add_b) / (1<<15);
218 *dst_p++ = (a * 255 + add_a) / (1<<15);
219 }
220 src_p += src_strides;
221 dst_p += dst_strides;
222 }
223 }
224
225
226 static inline void
tile_convert_rgba16_to_rgba8_c(const uint16_t * const src,const int src_strides,const uint8_t * dst,const int dst_strides,const float EOTF)227 tile_convert_rgba16_to_rgba8_c (const uint16_t* const src,
228 const int src_strides,
229 const uint8_t* dst,
230 const int dst_strides,
231 const float EOTF)
232 {
233 if (EOTF == 1.0) {
234 tile_convert_rgba16_to_rgba8_const_c(src, src_strides, dst, dst_strides);
235 return;
236 }
237 precalculate_dithering_noise_if_required();
238
239 for (int y=0; y<MYPAINT_TILE_SIZE; y++) {
240 int noise_idx = y*MYPAINT_TILE_SIZE*4;
241 const uint16_t *src_p = (uint16_t*)((char *)src + y*src_strides);
242 uint8_t *dst_p = (uint8_t*)((char *)dst + y*dst_strides);
243 for (int x=0; x<MYPAINT_TILE_SIZE; x++) {
244 uint32_t r, g, b, a;
245 r = *src_p++;
246 g = *src_p++;
247 b = *src_p++;
248 a = *src_p++;
249 #ifdef HEAVY_DEBUG
250 assert(a<=(1<<15));
251 assert(r<=(1<<15));
252 assert(g<=(1<<15));
253 assert(b<=(1<<15));
254 assert(r<=a);
255 assert(g<=a);
256 assert(b<=a);
257 #endif
258 // un-premultiply alpha (with rounding)
259 if (a != 0) {
260 const uint32_t rnd_a = a/2;
261 r = ((r << 15) + rnd_a) / a;
262 g = ((g << 15) + rnd_a) / a;
263 b = ((b << 15) + rnd_a) / a;
264 } else {
265 r = g = b = 0;
266 }
267 #ifdef HEAVY_DEBUG
268 assert(a<=(1<<15));
269 assert(r<=(1<<15));
270 assert(g<=(1<<15));
271 assert(b<=(1<<15));
272 #endif
273 /*
274 // Variant A) rounding
275 const uint32_t add_r = (1<<15)/2;
276 const uint32_t add_g = (1<<15)/2;
277 const uint32_t add_b = (1<<15)/2;
278 const uint32_t add_a = (1<<15)/2;
279 */
280
281 /*
282 // Variant B) naive dithering
283 // This can alter the alpha channel during a load->save cycle.
284 const uint32_t add_r = rand() % (1<<15);
285 const uint32_t add_g = rand() % (1<<15);
286 const uint32_t add_b = rand() % (1<<15);
287 const uint32_t add_a = rand() % (1<<15);
288 */
289
290 /*
291 // Variant C) slightly better dithering
292 // make sure we don't dither rounding errors (those did occur when converting 8bit-->16bit)
293 // this preserves the alpha channel, but we still add noise to the highly transparent colors
294 const uint32_t add_r = (rand() % (1<<15)) * 240/256 + (1<<15) * 8/256;
295 const uint32_t add_g = add_r; // hm... do not produce too much color noise
296 const uint32_t add_b = add_r;
297 const uint32_t add_a = (rand() % (1<<15)) * 240/256 + (1<<15) * 8/256;
298 // TODO: error diffusion might work better than random dithering...
299 */
300
301 // Variant C) but with precalculated noise (much faster)
302 //
303 const float add_r = (float)dithering_noise[noise_idx+0] / (1<<30);
304 //const uint32_t add_g = add_r; // hm... do not produce too much color noise
305 //const uint32_t add_b = add_r;
306 const uint32_t add_a = dithering_noise[noise_idx+1];
307 noise_idx += 4;
308
309 #ifdef HEAVY_DEBUG
310 assert(add_a < (1<<15));
311 assert(add_a >= 0);
312 assert(noise_idx <= dithering_noise_size);
313 #endif
314
315 *dst_p++ = uint8_t(fastpow((float)r / (1<<15) + add_r, 1.0/EOTF) * 255 + 0.5);
316 *dst_p++ = uint8_t(fastpow((float)g / (1<<15) + add_r, 1.0/EOTF) * 255 + 0.5);
317 *dst_p++ = uint8_t(fastpow((float)b / (1<<15) + add_r, 1.0/EOTF) * 255 + 0.5);
318 *dst_p++ = ((a * 255 + add_a) / (1<<15));
319 }
320 src_p += src_strides;
321 dst_p += dst_strides;
322 }
323 }
324
325
326
327 void
tile_convert_rgba16_to_rgba8(PyObject * src,PyObject * dst,const float EOTF)328 tile_convert_rgba16_to_rgba8 (PyObject *src,
329 PyObject *dst, const float EOTF)
330 {
331 PyArrayObject* src_arr = ((PyArrayObject*)src);
332 PyArrayObject* dst_arr = ((PyArrayObject*)dst);
333
334 #ifdef HEAVY_DEBUG
335 assert(PyArray_Check(dst));
336 assert(PyArray_DIM(dst_arr, 0) == MYPAINT_TILE_SIZE);
337 assert(PyArray_DIM(dst_arr, 1) == MYPAINT_TILE_SIZE);
338 assert(PyArray_DIM(dst_arr, 2) == 4);
339 assert(PyArray_TYPE(dst_arr) == NPY_UINT8);
340 assert(PyArray_ISBEHAVED(dst_arr));
341 assert(PyArray_STRIDE(dst_arr, 1) == 4*sizeof(uint8_t));
342 assert(PyArray_STRIDE(dst_arr, 2) == sizeof(uint8_t));
343
344 assert(PyArray_Check(src));
345 assert(PyArray_DIM(src_arr, 0) == MYPAINT_TILE_SIZE);
346 assert(PyArray_DIM(src_arr, 1) == MYPAINT_TILE_SIZE);
347 assert(PyArray_DIM(src_arr, 2) == 4);
348 assert(PyArray_TYPE(src_arr) == NPY_UINT16);
349 assert(PyArray_ISBEHAVED(src_arr));
350 assert(PyArray_STRIDE(src_arr, 1) == 4*sizeof(uint16_t));
351 assert(PyArray_STRIDE(src_arr, 2) == sizeof(uint16_t));
352 #endif
353
354 tile_convert_rgba16_to_rgba8_c((uint16_t*)PyArray_DATA(src_arr),
355 PyArray_STRIDES(src_arr)[0],
356 (uint8_t*)PyArray_DATA(dst_arr),
357 PyArray_STRIDES(dst_arr)[0],
358 EOTF);
359 }
360
361 static inline void
tile_convert_rgbu16_to_rgbu8_const_c(const uint16_t * const src,const int src_strides,const uint8_t * dst,const int dst_strides)362 tile_convert_rgbu16_to_rgbu8_const_c(const uint16_t *const src,
363 const int src_strides,
364 const uint8_t *dst,
365 const int dst_strides)
366 {
367 precalculate_dithering_noise_if_required();
368
369 for (int y=0; y<MYPAINT_TILE_SIZE; y++) {
370 int noise_idx = y*MYPAINT_TILE_SIZE*4;
371 const uint16_t *src_p = (uint16_t*)((char *)src + y*src_strides);
372 uint8_t *dst_p = (uint8_t*)((char *)dst + y*dst_strides);
373 for (int x=0; x<MYPAINT_TILE_SIZE; x++) {
374 uint32_t r, g, b;
375 r = *src_p++;
376 g = *src_p++;
377 b = *src_p++;
378 src_p++;
379 const uint32_t add = dithering_noise[noise_idx++];
380 *dst_p++ = (r * 255 + add) / (1<<15);
381 *dst_p++ = (g * 255 + add) / (1<<15);
382 *dst_p++ = (b * 255 + add) / (1<<15);
383 *dst_p++ = 255;
384 }
385 src_p += src_strides;
386 dst_p += dst_strides;
387 }
388 }
389
390 static inline void
tile_convert_rgbu16_to_rgbu8_c(const uint16_t * const src,const int src_strides,const uint8_t * dst,const int dst_strides,const float EOTF)391 tile_convert_rgbu16_to_rgbu8_c(const uint16_t* const src,
392 const int src_strides,
393 const uint8_t* dst,
394 const int dst_strides,
395 const float EOTF)
396 {
397 if (EOTF == 1.0) {
398 tile_convert_rgbu16_to_rgbu8_const_c(src, src_strides, dst, dst_strides);
399 return;
400 }
401 precalculate_dithering_noise_if_required();
402
403 for (int y=0; y<MYPAINT_TILE_SIZE; y++) {
404 int noise_idx = y*MYPAINT_TILE_SIZE*4;
405 const uint16_t *src_p = (uint16_t*)((char *)src + y*src_strides);
406 uint8_t *dst_p = (uint8_t*)((char *)dst + y*dst_strides);
407 for (int x=0; x<MYPAINT_TILE_SIZE; x++) {
408 float r, g, b;
409 r = ((float)*src_p++ / (1<<15));
410 g = ((float)*src_p++ / (1<<15));
411 b = ((float)*src_p++ / (1<<15));
412 src_p++; // alpha unused
413 #ifdef HEAVY_DEBUG
414 assert(r<=(1<<15));
415 assert(g<=(1<<15));
416 assert(b<=(1<<15));
417 #endif
418
419 /*
420 // rounding
421 const uint32_t add = (1<<15)/2;
422 */
423 // dithering
424 const float add = (float)dithering_noise[noise_idx++] / (1<<30);
425
426 *dst_p++ = (fastpow(r + add, 1.0/EOTF) ) * 255 + 0.5;
427 *dst_p++ = (fastpow(g + add, 1.0/EOTF) ) * 255 + 0.5;
428 *dst_p++ = (fastpow(b + add, 1.0/EOTF) ) * 255 + 0.5;
429 *dst_p++ = 255;
430 }
431 #ifdef HEAVY_DEBUG
432 assert(noise_idx <= dithering_noise_size);
433 #endif
434 src_p += src_strides;
435 dst_p += dst_strides;
436 }
437 }
438
439
tile_convert_rgbu16_to_rgbu8(PyObject * src,PyObject * dst,const float EOTF)440 void tile_convert_rgbu16_to_rgbu8(PyObject * src, PyObject * dst, const float EOTF) {
441 PyArrayObject* src_arr = ((PyArrayObject*)src);
442 PyArrayObject* dst_arr = ((PyArrayObject*)dst);
443
444 #ifdef HEAVY_DEBUG
445 assert(PyArray_Check(dst));
446 assert(PyArray_DIM(dst_arr, 0) == MYPAINT_TILE_SIZE);
447 assert(PyArray_DIM(dst_arr, 1) == MYPAINT_TILE_SIZE);
448 assert(PyArray_DIM(dst_arr, 2) == 4);
449 assert(PyArray_TYPE(dst_arr) == NPY_UINT8);
450 assert(PyArray_ISBEHAVED(dst_arr));
451 assert(PyArray_STRIDE(dst_arr, 1) == 4*sizeof(uint8_t));
452 assert(PyArray_STRIDE(dst_arr, 2) == sizeof(uint8_t));
453
454 assert(PyArray_Check(src));
455 assert(PyArray_DIM(src_arr, 0) == MYPAINT_TILE_SIZE);
456 assert(PyArray_DIM(src_arr, 1) == MYPAINT_TILE_SIZE);
457 assert(PyArray_DIM(src_arr, 2) == 4);
458 assert(PyArray_TYPE(src_arr) == NPY_UINT16);
459 assert(PyArray_ISBEHAVED(src_arr));
460 assert(PyArray_STRIDE(src_arr, 1) == 4*sizeof(uint16_t));
461 assert(PyArray_STRIDE(src_arr, 2) == sizeof(uint16_t));
462 #endif
463
464 tile_convert_rgbu16_to_rgbu8_c((uint16_t*)PyArray_DATA(src_arr), PyArray_STRIDES(src_arr)[0],
465 (uint8_t*)PyArray_DATA(dst_arr), PyArray_STRIDES(dst_arr)[0],
466 EOTF);
467 }
468
tile_convert_rgba8_to_rgba16_const(PyObject * src,PyObject * dst)469 void tile_convert_rgba8_to_rgba16_const(PyObject * src, PyObject * dst) {
470 PyArrayObject* src_arr = ((PyArrayObject*)src);
471 PyArrayObject* dst_arr = ((PyArrayObject*)dst);
472 for (int y=0; y<MYPAINT_TILE_SIZE; y++) {
473 uint8_t * src_p = (uint8_t*)((char *)PyArray_DATA(src_arr) + y*PyArray_STRIDES(src_arr)[0]);
474 uint16_t * dst_p = (uint16_t*)((char *)PyArray_DATA(dst_arr) + y*PyArray_STRIDES(dst_arr)[0]);
475 for (int x=0; x<MYPAINT_TILE_SIZE; x++) {
476 uint32_t r, g, b, a;
477 r = *src_p++;
478 g = *src_p++;
479 b = *src_p++;
480 a = *src_p++;
481
482 // convert to fixed point (with rounding)
483 r = (r * (1<<15) + 255/2) / 255;
484 g = (g * (1<<15) + 255/2) / 255;
485 b = (b * (1<<15) + 255/2) / 255;
486 a = (a * (1<<15) + 255/2) / 255;
487
488 // premultiply alpha (with rounding), save back
489 *dst_p++ = (r * a + (1<<15)/2) / (1<<15);
490 *dst_p++ = (g * a + (1<<15)/2) / (1<<15);
491 *dst_p++ = (b * a + (1<<15)/2) / (1<<15);
492 *dst_p++ = a;
493 }
494 }
495 }
496
497
498 // used mainly for loading layers (transparent PNG)
tile_convert_rgba8_to_rgba16(PyObject * src,PyObject * dst,const float EOTF)499 void tile_convert_rgba8_to_rgba16(PyObject * src, PyObject * dst, const float EOTF) {
500
501 if (EOTF == 1.0) {
502 tile_convert_rgba8_to_rgba16_const(src, dst);
503 return;
504 }
505 PyArrayObject* src_arr = ((PyArrayObject*)src);
506 PyArrayObject* dst_arr = ((PyArrayObject*)dst);
507
508 #ifdef HEAVY_DEBUG
509 assert(PyArray_Check(dst));
510 assert(PyArray_DIM(dst_arr, 0) == MYPAINT_TILE_SIZE);
511 assert(PyArray_DIM(dst_arr, 1) == MYPAINT_TILE_SIZE);
512 assert(PyArray_DIM(dst_arr, 2) == 4);
513 assert(PyArray_TYPE(dst_arr) == NPY_UINT16);
514 assert(PyArray_ISBEHAVED(dst_arr));
515 assert(PyArray_STRIDES(dst_arr)[1] == 4*sizeof(uint16_t));
516 assert(PyArray_STRIDES(dst_arr)[2] == sizeof(uint16_t));
517
518 assert(PyArray_Check(src));
519 assert(PyArray_DIM(src_arr, 0) == MYPAINT_TILE_SIZE);
520 assert(PyArray_DIM(src_arr, 1) == MYPAINT_TILE_SIZE);
521 assert(PyArray_DIM(src_arr, 2) == 4);
522 assert(PyArray_TYPE(src_arr) == NPY_UINT8);
523 assert(PyArray_ISBEHAVED(src_arr));
524 assert(PyArray_STRIDES(src_arr)[1] == 4*sizeof(uint8_t));
525 assert(PyArray_STRIDES(src_arr)[2] == sizeof(uint8_t));
526 #endif
527
528 for (int y=0; y<MYPAINT_TILE_SIZE; y++) {
529 uint8_t * src_p = (uint8_t*)((char *)PyArray_DATA(src_arr) + y*PyArray_STRIDES(src_arr)[0]);
530 uint16_t * dst_p = (uint16_t*)((char *)PyArray_DATA(dst_arr) + y*PyArray_STRIDES(dst_arr)[0]);
531 for (int x=0; x<MYPAINT_TILE_SIZE; x++) {
532 uint32_t r, g, b, a;
533 r = *src_p++;
534 g = *src_p++;
535 b = *src_p++;
536 a = *src_p++;
537
538 // convert to fixed point (with rounding)
539 r = uint32_t(fastpow((float)r/255.0, EOTF) * (1<<15) + 0.5);
540 g = uint32_t(fastpow((float)g/255.0, EOTF) * (1<<15) + 0.5);
541 b = uint32_t(fastpow((float)b/255.0, EOTF) * (1<<15) + 0.5);
542 a = (a * (1<<15) + 255/2) / 255;
543
544 // premultiply alpha (with rounding), save back
545 *dst_p++ = (r * a + (1<<15)/2) / (1<<15);
546 *dst_p++ = (g * a + (1<<15)/2) / (1<<15);
547 *dst_p++ = (b * a + (1<<15)/2) / (1<<15);
548 *dst_p++ = a;
549 }
550 }
551 }
552
553
tile_rgba2flat(PyObject * dst_obj,PyObject * bg_obj)554 void tile_rgba2flat(PyObject * dst_obj, PyObject * bg_obj) {
555 PyArrayObject* bg = ((PyArrayObject*)bg_obj);
556 PyArrayObject* dst = ((PyArrayObject*)dst_obj);
557
558 #ifdef HEAVY_DEBUG
559 assert(PyArray_Check(dst_obj));
560 assert(PyArray_DIM(dst, 0) == MYPAINT_TILE_SIZE);
561 assert(PyArray_DIM(dst, 1) == MYPAINT_TILE_SIZE);
562 assert(PyArray_DIM(dst, 2) == 4);
563 assert(PyArray_TYPE(dst) == NPY_UINT16);
564 assert(PyArray_ISCARRAY(dst));
565
566 assert(PyArray_Check(bg_obj));
567 assert(PyArray_DIM(bg, 0) == MYPAINT_TILE_SIZE);
568 assert(PyArray_DIM(bg, 1) == MYPAINT_TILE_SIZE);
569 assert(PyArray_DIM(bg, 2) == 4);
570 assert(PyArray_TYPE(bg) == NPY_UINT16);
571 assert(PyArray_ISCARRAY(bg));
572 #endif
573
574 uint16_t * dst_p = (uint16_t*)PyArray_DATA(dst);
575 uint16_t * bg_p = (uint16_t*)PyArray_DATA(bg);
576 for (int i=0; i<MYPAINT_TILE_SIZE*MYPAINT_TILE_SIZE; i++) {
577 // resultAlpha = 1.0 (thus it does not matter if resultColor is premultiplied alpha or not)
578 // resultColor = topColor + (1.0 - topAlpha) * bottomColor
579 const uint32_t one_minus_top_alpha = (1<<15) - dst_p[3];
580 dst_p[0] += ((uint32_t)one_minus_top_alpha*bg_p[0]) / (1<<15);
581 dst_p[1] += ((uint32_t)one_minus_top_alpha*bg_p[1]) / (1<<15);
582 dst_p[2] += ((uint32_t)one_minus_top_alpha*bg_p[2]) / (1<<15);
583 dst_p += 4;
584 bg_p += 4;
585 }
586 }
587
588
tile_flat2rgba(PyObject * dst_obj,PyObject * bg_obj)589 void tile_flat2rgba(PyObject * dst_obj, PyObject * bg_obj) {
590
591 PyArrayObject *dst = (PyArrayObject *)dst_obj;
592 PyArrayObject *bg = (PyArrayObject *)bg_obj;
593 #ifdef HEAVY_DEBUG
594 assert(PyArray_Check(dst_obj));
595 assert(PyArray_DIM(dst, 0) == MYPAINT_TILE_SIZE);
596 assert(PyArray_DIM(dst, 1) == MYPAINT_TILE_SIZE);
597 assert(PyArray_DIM(dst, 2) == 4);
598 assert(PyArray_TYPE(dst) == NPY_UINT16);
599 assert(PyArray_ISCARRAY(dst));
600
601 assert(PyArray_Check(bg_obj));
602 assert(PyArray_DIM(bg, 0) == MYPAINT_TILE_SIZE);
603 assert(PyArray_DIM(bg, 1) == MYPAINT_TILE_SIZE);
604 assert(PyArray_DIM(bg, 2) == 4);
605 assert(PyArray_TYPE(bg) == NPY_UINT16);
606 assert(PyArray_ISCARRAY(bg));
607 #endif
608
609 uint16_t * dst_p = (uint16_t*)PyArray_DATA(dst);
610 uint16_t * bg_p = (uint16_t*)PyArray_DATA(bg);
611 for (int i=0; i<MYPAINT_TILE_SIZE*MYPAINT_TILE_SIZE; i++) {
612
613 // 1. calculate final dst.alpha
614 uint16_t final_alpha = dst_p[3];
615 for (int i=0; i<3;i++) {
616 int32_t color_change = (int32_t)dst_p[i] - bg_p[i];
617 uint16_t minimal_alpha;
618 if (color_change > 0) {
619 minimal_alpha = (int64_t)color_change*(1<<15) / ((1<<15) - bg_p[i]);
620 } else if (color_change < 0) {
621 minimal_alpha = (int64_t)-color_change*(1<<15) / bg_p[i];
622 } else {
623 minimal_alpha = 0;
624 }
625 final_alpha = MAX(final_alpha, minimal_alpha);
626 #ifdef HEAVY_DEBUG
627 assert(minimal_alpha <= (1<<15));
628 assert(final_alpha <= (1<<15));
629 #endif
630 }
631
632 // 2. calculate dst.color and update dst
633 dst_p[3] = final_alpha;
634 if (final_alpha > 0) {
635 for (int i=0; i<3;i++) {
636 int32_t color_change = (int32_t)dst_p[i] - bg_p[i];
637 //int64_t res = bg_p[i] + (int64_t)color_change*(1<<15) / final_alpha;
638 // premultiplied with final_alpha
639 int64_t res = (uint32_t)bg_p[i]*final_alpha/(1<<15) + (int64_t)color_change;
640 res = CLAMP(res, 0, final_alpha); // fix rounding errors
641 dst_p[i] = res;
642 #ifdef HEAVY_DEBUG
643 assert(dst_p[i] <= dst_p[3]);
644 #endif
645 }
646 } else {
647 dst_p[0] = 0;
648 dst_p[1] = 0;
649 dst_p[2] = 0;
650 }
651 dst_p += 4;
652 bg_p += 4;
653 }
654 }
655
656
tile_perceptual_change_strokemap(PyObject * a_obj,PyObject * b_obj,PyObject * res_obj)657 void tile_perceptual_change_strokemap(PyObject * a_obj, PyObject * b_obj, PyObject * res_obj) {
658
659 PyArrayObject *a = (PyArrayObject *)a_obj;
660 PyArrayObject *b = (PyArrayObject *)b_obj;
661 PyArrayObject *res = (PyArrayObject *)res_obj;
662
663 #ifdef HEAVY_DEBUG
664 assert(PyArray_TYPE(a) == NPY_UINT16);
665 assert(PyArray_TYPE(b) == NPY_UINT16);
666 assert(PyArray_TYPE(res) == NPY_UINT8);
667 assert(PyArray_ISCARRAY(a));
668 assert(PyArray_ISCARRAY(b));
669 assert(PyArray_ISCARRAY(res));
670 #endif
671
672 uint16_t * a_p = (uint16_t*)PyArray_DATA(a);
673 uint16_t * b_p = (uint16_t*)PyArray_DATA(b);
674 uint8_t * res_p = (uint8_t*)PyArray_DATA(res);
675
676 for (int y=0; y<MYPAINT_TILE_SIZE; y++) {
677 for (int x=0; x<MYPAINT_TILE_SIZE; x++) {
678
679 int32_t color_change = 0;
680 // We want to compare a.color with b.color, but we only know
681 // (a.color * a.alpha) and (b.color * b.alpha). We multiply
682 // each component with the alpha of the other image, so they are
683 // scaled the same and can be compared.
684
685 for (int i=0; i<3; i++) {
686 int32_t a_col = (uint32_t)a_p[i] * b_p[3] / (1<<15); // a.color * a.alpha*b.alpha
687 int32_t b_col = (uint32_t)b_p[i] * a_p[3] / (1<<15); // b.color * a.alpha*b.alpha
688 color_change += abs(b_col - a_col);
689 }
690 // "color_change" is in the range [0, 3*a_a]
691 // if either old or new alpha is (near) zero, "color_change" is (near) zero
692
693 int32_t alpha_old = a_p[3];
694 int32_t alpha_new = b_p[3];
695
696 // Note: the thresholds below are arbitrary choices found to work okay
697
698 // We report a color change only if both old and new color are
699 // well-defined (big enough alpha).
700 bool is_perceptual_color_change = color_change > MAX(alpha_old, alpha_new)/16;
701
702 int32_t alpha_diff = alpha_new - alpha_old; // no abs() here (ignore erasers)
703 // We check the alpha increase relative to the previous alpha.
704 bool is_perceptual_alpha_increase = alpha_diff > (1<<15)/4;
705
706 // this one is responsible for making fat big ugly easy-to-hit pointer targets
707 bool is_big_relative_alpha_increase = alpha_diff > (1<<15)/64 && alpha_diff > alpha_old/2;
708
709 if (is_perceptual_alpha_increase || is_big_relative_alpha_increase || is_perceptual_color_change) {
710 res_p[0] = 1;
711 } else {
712 res_p[0] = 0;
713 }
714
715 a_p += 4;
716 b_p += 4;
717 res_p += 1;
718 }
719 }
720 }
721
722
723 // A named tile combine operation: what the user sees as a "blend mode" or
724 // the "layer composite" modes in the application.
725
726 template <class B, class C>
727 class TileDataCombine : public TileDataCombineOp
728 {
729 private:
730 // The canonical name for the combine mode
731 const char *name;
732 // Alpha/nonalpha functors; must be members to keep GCC4.6 builds happy
733 static const int bufsize = MYPAINT_TILE_SIZE*MYPAINT_TILE_SIZE*4;
734 BufferCombineFunc<true, bufsize, B, C> combine_dstalpha;
735 BufferCombineFunc<false, bufsize, B, C> combine_dstnoalpha;
736
737 public:
TileDataCombine(const char * name)738 TileDataCombine(const char *name) {
739 this->name = name;
740 }
741
742 // Apply this combine operation to source and destination tile-sized
743 // buffers of uint16_t (15ish-bit) RGBA data. The output is written back
744 // into the destination buffer.
combine_data(const fix15_short_t * src_p,fix15_short_t * dst_p,const bool dst_has_alpha,const float src_opacity) const745 void combine_data (const fix15_short_t *src_p,
746 fix15_short_t *dst_p,
747 const bool dst_has_alpha,
748 const float src_opacity) const
749 {
750 const fix15_short_t opac = fix15_short_clamp(src_opacity * fix15_one);
751 if (dst_has_alpha) {
752 combine_dstalpha(src_p, dst_p, opac);
753 }
754 else {
755 combine_dstnoalpha(src_p, dst_p, opac);
756 }
757 }
758
759 // True if a zero-alpha source pixel can ever affect a destination pixel
zero_alpha_has_effect() const760 bool zero_alpha_has_effect() const {
761 return C::zero_alpha_has_effect;
762 }
763
764 // True if a source pixel can ever reduce the alpha of a destination pixel
can_decrease_alpha() const765 bool can_decrease_alpha() const {
766 return C::can_decrease_alpha;
767 }
768
769 // True if a zero-alpha src pixel always clears the dst pixel
zero_alpha_clears_backdrop() const770 bool zero_alpha_clears_backdrop() const {
771 return C::zero_alpha_clears_backdrop;
772 }
773
774 // Returns the canonical name of the mode
get_name() const775 const char* get_name() const {
776 return name;
777 }
778 };
779
780
781 // Integer-indexed LUT for the layer mode definitions, defining their canonical
782 // names.
783
784 static const TileDataCombineOp * combine_mode_info[NumCombineModes] =
785 {
786 // Source-over compositing + various blend modes
787 new TileDataCombine<BlendNormal, CompositeSourceOver>("svg:src-over"),
788 new TileDataCombine<BlendMultiply, CompositeSourceOver>("svg:multiply"),
789 new TileDataCombine<BlendScreen, CompositeSourceOver>("svg:screen"),
790 new TileDataCombine<BlendOverlay, CompositeSourceOver>("svg:overlay"),
791 new TileDataCombine<BlendDarken, CompositeSourceOver>("svg:darken"),
792 new TileDataCombine<BlendLighten, CompositeSourceOver>("svg:lighten"),
793 new TileDataCombine<BlendHardLight, CompositeSourceOver>("svg:hard-light"),
794 new TileDataCombine<BlendSoftLight, CompositeSourceOver>("svg:soft-light"),
795 new TileDataCombine<BlendColorBurn, CompositeSourceOver>("svg:color-burn"),
796 new TileDataCombine<BlendColorDodge, CompositeSourceOver>("svg:color-dodge"),
797 new TileDataCombine<BlendDifference, CompositeSourceOver>("svg:difference"),
798 new TileDataCombine<BlendExclusion, CompositeSourceOver>("svg:exclusion"),
799 new TileDataCombine<BlendHue, CompositeSourceOver>("svg:hue"),
800 new TileDataCombine<BlendSaturation, CompositeSourceOver>("svg:saturation"),
801 new TileDataCombine<BlendColor, CompositeSourceOver>("svg:color"),
802 new TileDataCombine<BlendLuminosity, CompositeSourceOver>("svg:luminosity"),
803
804 // Normal blend mode + various compositing operators
805 new TileDataCombine<BlendNormal, CompositeLighter>("svg:plus"),
806 new TileDataCombine<BlendNormal, CompositeDestinationIn>("svg:dst-in"),
807 new TileDataCombine<BlendNormal, CompositeDestinationOut>("svg:dst-out"),
808 new TileDataCombine<BlendNormal, CompositeSourceAtop>("svg:src-atop"),
809 new TileDataCombine<BlendNormal, CompositeDestinationAtop>("svg:dst-atop"),
810 new TileDataCombine<BlendNormal, CompositeSpectralWGM>("mypaint:spectral-wgm")
811 };
812
813
814
815 /* combine_mode_get_info(): extracts Python-readable metadata for a mode */
816
817
818 PyObject *
combine_mode_get_info(enum CombineMode mode)819 combine_mode_get_info(enum CombineMode mode)
820 {
821 if (mode >= NumCombineModes || mode < 0) {
822 return Py_BuildValue("{}");
823 }
824 const TileDataCombineOp *op = combine_mode_info[mode];
825 return Py_BuildValue("{s:i,s:i,s:i,s:s}",
826 "zero_alpha_has_effect", op->zero_alpha_has_effect(),
827 "can_decrease_alpha", op->can_decrease_alpha(),
828 "zero_alpha_clears_backdrop", op->zero_alpha_clears_backdrop(),
829 "name", op->get_name()
830 );
831 }
832
833
834
835 /* tile_combine(): primary Python interface for blending+compositing tiles */
836
837
838 void
tile_combine(enum CombineMode mode,PyObject * src_obj,PyObject * dst_obj,const bool dst_has_alpha,const float src_opacity)839 tile_combine (enum CombineMode mode,
840 PyObject *src_obj,
841 PyObject *dst_obj,
842 const bool dst_has_alpha,
843 const float src_opacity)
844 {
845 PyArrayObject* src = ((PyArrayObject*)src_obj);
846 PyArrayObject* dst = ((PyArrayObject*)dst_obj);
847 #ifdef HEAVY_DEBUG
848 assert(PyArray_Check(src_obj));
849 assert(PyArray_DIM(src, 0) == MYPAINT_TILE_SIZE);
850 assert(PyArray_DIM(src, 1) == MYPAINT_TILE_SIZE);
851 assert(PyArray_DIM(src, 2) == 4);
852 assert(PyArray_TYPE(src) == NPY_UINT16);
853 assert(PyArray_ISCARRAY(src));
854
855 assert(PyArray_Check(dst_obj));
856 assert(PyArray_DIM(dst, 0) == MYPAINT_TILE_SIZE);
857 assert(PyArray_DIM(dst, 1) == MYPAINT_TILE_SIZE);
858 assert(PyArray_DIM(dst, 2) == 4);
859 assert(PyArray_TYPE(dst) == NPY_UINT16);
860 assert(PyArray_ISCARRAY(dst));
861
862 assert(PyArray_STRIDES(dst)[0] == 4*sizeof(fix15_short_t)*MYPAINT_TILE_SIZE);
863 assert(PyArray_STRIDES(dst)[1] == 4*sizeof(fix15_short_t));
864 assert(PyArray_STRIDES(dst)[2] == sizeof(fix15_short_t));
865 #endif
866
867 const fix15_short_t* const src_p = (fix15_short_t *)PyArray_DATA(src);
868 fix15_short_t* const dst_p = (fix15_short_t *)PyArray_DATA(dst);
869
870 if (mode >= NumCombineModes || mode < 0) {
871 return;
872 }
873 const TileDataCombineOp *op = combine_mode_info[mode];
874 op->combine_data(src_p, dst_p, dst_has_alpha, src_opacity);
875 }
876
877