/* This file is part of MyPaint. * Copyright (C) 2008-2014 by Martin Renold * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include "pixops.hpp" #include "common.hpp" #include "compositing.hpp" #include "blending.hpp" #include "fastapprox/fastpow.h" #include #include #include #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION #define NO_IMPORT_ARRAY #include #include #include void tile_downscale_rgba16_c(const uint16_t *src, int src_strides, uint16_t *dst, int dst_strides, int dst_x, int dst_y) { for (int y=0; ystrides[0]; dst_p += dst_arr->strides[0]; } */ tile_copy_rgba16_into_rgba16_c((uint16_t *)PyArray_DATA(src_arr), (uint16_t *)PyArray_DATA(dst_arr)); } void tile_clear_rgba8(PyObject * dst) { PyArrayObject* dst_arr = ((PyArrayObject*)dst); #ifdef HEAVY_DEBUG assert(PyArray_Check(dst)); assert(PyArray_DIM(dst_arr, 0) == MYPAINT_TILE_SIZE); assert(PyArray_DIM(dst_arr, 1) == MYPAINT_TILE_SIZE); assert(PyArray_TYPE(dst_arr) == NPY_UINT8); assert(PyArray_ISBEHAVED(dst_arr)); assert(PyArray_STRIDES(dst_arr)[1] <= 8); #endif for (int y=0; ysave cycle. const uint32_t add_r = rand() % (1<<15); const uint32_t add_g = rand() % (1<<15); const uint32_t add_b = rand() % (1<<15); const uint32_t add_a = rand() % (1<<15); */ /* // Variant C) slightly better dithering // make sure we don't dither rounding errors (those did occur when converting 8bit-->16bit) // this preserves the alpha channel, but we still add noise to the highly transparent colors const uint32_t add_r = (rand() % (1<<15)) * 240/256 + (1<<15) * 8/256; const uint32_t add_g = add_r; // hm... do not produce too much color noise const uint32_t add_b = add_r; const uint32_t add_a = (rand() % (1<<15)) * 240/256 + (1<<15) * 8/256; // TODO: error diffusion might work better than random dithering... */ // Variant C) but with precalculated noise (much faster) // const float add_r = (float)dithering_noise[noise_idx+0] / (1<<30); //const uint32_t add_g = add_r; // hm... do not produce too much color noise //const uint32_t add_b = add_r; const uint32_t add_a = dithering_noise[noise_idx+1]; noise_idx += 4; #ifdef HEAVY_DEBUG assert(add_a < (1<<15)); assert(add_a >= 0); assert(noise_idx <= dithering_noise_size); #endif *dst_p++ = uint8_t(fastpow((float)r / (1<<15) + add_r, 1.0/EOTF) * 255 + 0.5); *dst_p++ = uint8_t(fastpow((float)g / (1<<15) + add_r, 1.0/EOTF) * 255 + 0.5); *dst_p++ = uint8_t(fastpow((float)b / (1<<15) + add_r, 1.0/EOTF) * 255 + 0.5); *dst_p++ = ((a * 255 + add_a) / (1<<15)); } src_p += src_strides; dst_p += dst_strides; } } void tile_convert_rgba16_to_rgba8 (PyObject *src, PyObject *dst, const float EOTF) { PyArrayObject* src_arr = ((PyArrayObject*)src); PyArrayObject* dst_arr = ((PyArrayObject*)dst); #ifdef HEAVY_DEBUG assert(PyArray_Check(dst)); assert(PyArray_DIM(dst_arr, 0) == MYPAINT_TILE_SIZE); assert(PyArray_DIM(dst_arr, 1) == MYPAINT_TILE_SIZE); assert(PyArray_DIM(dst_arr, 2) == 4); assert(PyArray_TYPE(dst_arr) == NPY_UINT8); assert(PyArray_ISBEHAVED(dst_arr)); assert(PyArray_STRIDE(dst_arr, 1) == 4*sizeof(uint8_t)); assert(PyArray_STRIDE(dst_arr, 2) == sizeof(uint8_t)); assert(PyArray_Check(src)); assert(PyArray_DIM(src_arr, 0) == MYPAINT_TILE_SIZE); assert(PyArray_DIM(src_arr, 1) == MYPAINT_TILE_SIZE); assert(PyArray_DIM(src_arr, 2) == 4); assert(PyArray_TYPE(src_arr) == NPY_UINT16); assert(PyArray_ISBEHAVED(src_arr)); assert(PyArray_STRIDE(src_arr, 1) == 4*sizeof(uint16_t)); assert(PyArray_STRIDE(src_arr, 2) == sizeof(uint16_t)); #endif tile_convert_rgba16_to_rgba8_c((uint16_t*)PyArray_DATA(src_arr), PyArray_STRIDES(src_arr)[0], (uint8_t*)PyArray_DATA(dst_arr), PyArray_STRIDES(dst_arr)[0], EOTF); } 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) { precalculate_dithering_noise_if_required(); for (int y=0; y 0) { minimal_alpha = (int64_t)color_change*(1<<15) / ((1<<15) - bg_p[i]); } else if (color_change < 0) { minimal_alpha = (int64_t)-color_change*(1<<15) / bg_p[i]; } else { minimal_alpha = 0; } final_alpha = MAX(final_alpha, minimal_alpha); #ifdef HEAVY_DEBUG assert(minimal_alpha <= (1<<15)); assert(final_alpha <= (1<<15)); #endif } // 2. calculate dst.color and update dst dst_p[3] = final_alpha; if (final_alpha > 0) { for (int i=0; i<3;i++) { int32_t color_change = (int32_t)dst_p[i] - bg_p[i]; //int64_t res = bg_p[i] + (int64_t)color_change*(1<<15) / final_alpha; // premultiplied with final_alpha int64_t res = (uint32_t)bg_p[i]*final_alpha/(1<<15) + (int64_t)color_change; res = CLAMP(res, 0, final_alpha); // fix rounding errors dst_p[i] = res; #ifdef HEAVY_DEBUG assert(dst_p[i] <= dst_p[3]); #endif } } else { dst_p[0] = 0; dst_p[1] = 0; dst_p[2] = 0; } dst_p += 4; bg_p += 4; } } void tile_perceptual_change_strokemap(PyObject * a_obj, PyObject * b_obj, PyObject * res_obj) { PyArrayObject *a = (PyArrayObject *)a_obj; PyArrayObject *b = (PyArrayObject *)b_obj; PyArrayObject *res = (PyArrayObject *)res_obj; #ifdef HEAVY_DEBUG assert(PyArray_TYPE(a) == NPY_UINT16); assert(PyArray_TYPE(b) == NPY_UINT16); assert(PyArray_TYPE(res) == NPY_UINT8); assert(PyArray_ISCARRAY(a)); assert(PyArray_ISCARRAY(b)); assert(PyArray_ISCARRAY(res)); #endif uint16_t * a_p = (uint16_t*)PyArray_DATA(a); uint16_t * b_p = (uint16_t*)PyArray_DATA(b); uint8_t * res_p = (uint8_t*)PyArray_DATA(res); for (int y=0; y MAX(alpha_old, alpha_new)/16; int32_t alpha_diff = alpha_new - alpha_old; // no abs() here (ignore erasers) // We check the alpha increase relative to the previous alpha. bool is_perceptual_alpha_increase = alpha_diff > (1<<15)/4; // this one is responsible for making fat big ugly easy-to-hit pointer targets bool is_big_relative_alpha_increase = alpha_diff > (1<<15)/64 && alpha_diff > alpha_old/2; if (is_perceptual_alpha_increase || is_big_relative_alpha_increase || is_perceptual_color_change) { res_p[0] = 1; } else { res_p[0] = 0; } a_p += 4; b_p += 4; res_p += 1; } } } // A named tile combine operation: what the user sees as a "blend mode" or // the "layer composite" modes in the application. template class TileDataCombine : public TileDataCombineOp { private: // The canonical name for the combine mode const char *name; // Alpha/nonalpha functors; must be members to keep GCC4.6 builds happy static const int bufsize = MYPAINT_TILE_SIZE*MYPAINT_TILE_SIZE*4; BufferCombineFunc combine_dstalpha; BufferCombineFunc combine_dstnoalpha; public: TileDataCombine(const char *name) { this->name = name; } // Apply this combine operation to source and destination tile-sized // buffers of uint16_t (15ish-bit) RGBA data. The output is written back // into the destination buffer. void combine_data (const fix15_short_t *src_p, fix15_short_t *dst_p, const bool dst_has_alpha, const float src_opacity) const { const fix15_short_t opac = fix15_short_clamp(src_opacity * fix15_one); if (dst_has_alpha) { combine_dstalpha(src_p, dst_p, opac); } else { combine_dstnoalpha(src_p, dst_p, opac); } } // True if a zero-alpha source pixel can ever affect a destination pixel bool zero_alpha_has_effect() const { return C::zero_alpha_has_effect; } // True if a source pixel can ever reduce the alpha of a destination pixel bool can_decrease_alpha() const { return C::can_decrease_alpha; } // True if a zero-alpha src pixel always clears the dst pixel bool zero_alpha_clears_backdrop() const { return C::zero_alpha_clears_backdrop; } // Returns the canonical name of the mode const char* get_name() const { return name; } }; // Integer-indexed LUT for the layer mode definitions, defining their canonical // names. static const TileDataCombineOp * combine_mode_info[NumCombineModes] = { // Source-over compositing + various blend modes new TileDataCombine("svg:src-over"), new TileDataCombine("svg:multiply"), new TileDataCombine("svg:screen"), new TileDataCombine("svg:overlay"), new TileDataCombine("svg:darken"), new TileDataCombine("svg:lighten"), new TileDataCombine("svg:hard-light"), new TileDataCombine("svg:soft-light"), new TileDataCombine("svg:color-burn"), new TileDataCombine("svg:color-dodge"), new TileDataCombine("svg:difference"), new TileDataCombine("svg:exclusion"), new TileDataCombine("svg:hue"), new TileDataCombine("svg:saturation"), new TileDataCombine("svg:color"), new TileDataCombine("svg:luminosity"), // Normal blend mode + various compositing operators new TileDataCombine("svg:plus"), new TileDataCombine("svg:dst-in"), new TileDataCombine("svg:dst-out"), new TileDataCombine("svg:src-atop"), new TileDataCombine("svg:dst-atop"), new TileDataCombine("mypaint:spectral-wgm") }; /* combine_mode_get_info(): extracts Python-readable metadata for a mode */ PyObject * combine_mode_get_info(enum CombineMode mode) { if (mode >= NumCombineModes || mode < 0) { return Py_BuildValue("{}"); } const TileDataCombineOp *op = combine_mode_info[mode]; return Py_BuildValue("{s:i,s:i,s:i,s:s}", "zero_alpha_has_effect", op->zero_alpha_has_effect(), "can_decrease_alpha", op->can_decrease_alpha(), "zero_alpha_clears_backdrop", op->zero_alpha_clears_backdrop(), "name", op->get_name() ); } /* tile_combine(): primary Python interface for blending+compositing tiles */ void tile_combine (enum CombineMode mode, PyObject *src_obj, PyObject *dst_obj, const bool dst_has_alpha, const float src_opacity) { PyArrayObject* src = ((PyArrayObject*)src_obj); PyArrayObject* dst = ((PyArrayObject*)dst_obj); #ifdef HEAVY_DEBUG assert(PyArray_Check(src_obj)); assert(PyArray_DIM(src, 0) == MYPAINT_TILE_SIZE); assert(PyArray_DIM(src, 1) == MYPAINT_TILE_SIZE); assert(PyArray_DIM(src, 2) == 4); assert(PyArray_TYPE(src) == NPY_UINT16); assert(PyArray_ISCARRAY(src)); assert(PyArray_Check(dst_obj)); assert(PyArray_DIM(dst, 0) == MYPAINT_TILE_SIZE); assert(PyArray_DIM(dst, 1) == MYPAINT_TILE_SIZE); assert(PyArray_DIM(dst, 2) == 4); assert(PyArray_TYPE(dst) == NPY_UINT16); assert(PyArray_ISCARRAY(dst)); assert(PyArray_STRIDES(dst)[0] == 4*sizeof(fix15_short_t)*MYPAINT_TILE_SIZE); assert(PyArray_STRIDES(dst)[1] == 4*sizeof(fix15_short_t)); assert(PyArray_STRIDES(dst)[2] == sizeof(fix15_short_t)); #endif const fix15_short_t* const src_p = (fix15_short_t *)PyArray_DATA(src); fix15_short_t* const dst_p = (fix15_short_t *)PyArray_DATA(dst); if (mode >= NumCombineModes || mode < 0) { return; } const TileDataCombineOp *op = combine_mode_info[mode]; op->combine_data(src_p, dst_p, dst_has_alpha, src_opacity); }