Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/source/fitz/draw-blend.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/source/fitz/draw-blend.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,1369 @@ +// Copyright (C) 2004-2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see <https://www.artifex.com/> or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +#include "mupdf/fitz.h" + +#include "draw-imp.h" +#include "pixmap-imp.h" + +#include <string.h> +#include <math.h> +#include <assert.h> + +/* PDF 1.4 blend modes. These are slow. */ + +/* Define PARANOID_PREMULTIPLY to check premultiplied values are + * properly in range. */ +#undef PARANOID_PREMULTIPLY + +/* + +Some notes on the transparency maths: + +Compositing equation: +===================== + +In section 7.2.2 (page 517) of pdf_reference17.pdf, it says: + + Cr = (1 - As/Ar) * Cb + As/Ar * [ (1-Ab) * Cs + Ab * B(Cb,Cs) ] + +It says that this is a simplified version of the more general form. + +This equation is then restated in section 7.2.2 and it says: + +The formula shown above is a simplification of the following formula: + + Ar * Cr = [(1-As)*Ab*Cb] + [(1-Ab)*As*Cs] + [Ab*As*B(Cb, Cs)] + +At first glance this always appears to be a mistake to me, as it looks +like they have make a mistake in the division. + +However, if we consider the result alpha equation: + + Ar = Union(Ab, As) = Ab + As - Ab * As + +we can rearrange that to give: + + Ar - As = (1 - As) * Ab + + 1 - As/Ar = (1 - As) * Ab / Ar + +So substituting into the first equation above, we get: + + Cr = ((1 - As) * Ab/Ar) * Cb + As/Ar * [ (1-Ab) * Cs + Ab * B(Cb,Cs) ] + +And thus: + + Ar * Cr = (1 - As) * Ab * Cb + As * [ (1-Ab)*Cs + Ab * B(Cb,Cs) ] + +as required. + +Alpha blending on top of compositing: +===================================== + +Suppose we have a group to blend using blend mode B, and we want +to apply alpha too. Let's apply the blending first to get an +intermediate result (Ir), then apply the alpha to that to get the +result (Cr): + + Ir = (1 - As/Ar) * Cb + As/Ar * [ (1-Ab) * Cs + Ab * B(Cb,Cs) ] + + Cr = (1-alpha) * Cb + alpha * Ir + = Cb - alpha * Cb + alpha * Cb - alpha * Cb * As / Ar + alpha * As / Ar * [ (1 - Ab) * Cs + Ab * B(Cb, Cs) ] + = Cb - alpha * Cb * As / Ar + alpha * As / Ar * [ (1 - Ab) * Cs + Ab * B(Cb, Cs) ] + = Cb * (1 - alpha * As / Ar) + alpha * As / Ar * [ (1 - Ab) * Cs + Ab * B(Cb, Cs) ] + +We want premultiplied results, so: + + Ar*Cr = Cb * (Ar - alpha * As) + alpha * As * (1 - Ab) * Cs + alpha * As * Ab * B(Cb, Cs) ] + +In the same way, for the alpha values: + + Ia = Union(Ab, As) = Ab + As - As*Ab + Ar = (1-alpha) * Ab + alpha * Ia + = Ab - alpha * Ab + alpha * Ab + alpha * As - alpha * As * Ab + = Ab + alpha * As - alpha * As * Ab + = Union(Ab, alpha * As) + +*/ + +typedef unsigned char byte; + +static const char *fz_blendmode_names[] = +{ + "Normal", + "Multiply", + "Screen", + "Overlay", + "Darken", + "Lighten", + "ColorDodge", + "ColorBurn", + "HardLight", + "SoftLight", + "Difference", + "Exclusion", + "Hue", + "Saturation", + "Color", + "Luminosity", +}; + +int fz_lookup_blendmode(const char *name) +{ + int i; + for (i = 0; i < (int)nelem(fz_blendmode_names); i++) + if (!strcmp(name, fz_blendmode_names[i])) + return i; + return FZ_BLEND_NORMAL; +} + +const char *fz_blendmode_name(int blendmode) +{ + if (blendmode >= 0 && blendmode < (int)nelem(fz_blendmode_names)) + return fz_blendmode_names[blendmode]; + return "Normal"; +} + +/* Separable blend modes */ + +static inline int fz_screen_byte(int b, int s) +{ + return b + s - fz_mul255(b, s); +} + +static inline int fz_hard_light_byte(int b, int s) +{ + int s2 = s << 1; + if (s <= 127) + return fz_mul255(b, s2); + else + return fz_screen_byte(b, s2 - 255); +} + +static inline int fz_overlay_byte(int b, int s) +{ + return fz_hard_light_byte(s, b); /* note swapped order */ +} + +static inline int fz_darken_byte(int b, int s) +{ + return fz_mini(b, s); +} + +static inline int fz_lighten_byte(int b, int s) +{ + return fz_maxi(b, s); +} + +static inline int fz_color_dodge_byte(int b, int s) +{ + s = 255 - s; + if (b <= 0) + return 0; + else if (b >= s) + return 255; + else + return (0x1fe * b + s) / (s << 1); +} + +static inline int fz_color_burn_byte(int b, int s) +{ + b = 255 - b; + if (b <= 0) + return 255; + else if (b >= s) + return 0; + else + return 0xff - (0x1fe * b + s) / (s << 1); +} + +static inline int fz_soft_light_byte(int b, int s) +{ + if (s < 128) { + return b - fz_mul255(fz_mul255((255 - (s<<1)), b), 255 - b); + } + else { + int dbd; + if (b < 64) + dbd = fz_mul255(fz_mul255((b << 4) - 3060, b) + 1020, b); + else + dbd = (int)sqrtf(255.0f * b); + return b + fz_mul255(((s<<1) - 255), (dbd - b)); + } +} + +static inline int fz_difference_byte(int b, int s) +{ + return fz_absi(b - s); +} + +static inline int fz_exclusion_byte(int b, int s) +{ + return b + s - (fz_mul255(b, s)<<1); +} + +/* Non-separable blend modes */ + +static void +fz_luminosity_rgb(unsigned char *rd, unsigned char *gd, unsigned char *bd, int rb, int gb, int bb, int rs, int gs, int bs) +{ + int delta, scale; + int r, g, b, y; + + /* 0.3f, 0.59f, 0.11f in fixed point */ + delta = ((rs - rb) * 77 + (gs - gb) * 151 + (bs - bb) * 28 + 0x80) >> 8; + r = rb + delta; + g = gb + delta; + b = bb + delta; + + if ((r | g | b) & 0x100) + { + y = (rs * 77 + gs * 151 + bs * 28 + 0x80) >> 8; + if (delta > 0) + { + int max; + max = fz_maxi(r, fz_maxi(g, b)); + scale = (max == y ? 0 : ((255 - y) << 16) / (max - y)); + } + else + { + int min; + min = fz_mini(r, fz_mini(g, b)); + scale = (y == min ? 0 : (y << 16) / (y - min)); + } + r = y + (((r - y) * scale + 0x8000) >> 16); + g = y + (((g - y) * scale + 0x8000) >> 16); + b = y + (((b - y) * scale + 0x8000) >> 16); + } + + *rd = fz_clampi(r, 0, 255); + *gd = fz_clampi(g, 0, 255); + *bd = fz_clampi(b, 0, 255); +} + +static void +fz_saturation_rgb(unsigned char *rd, unsigned char *gd, unsigned char *bd, int rb, int gb, int bb, int rs, int gs, int bs) +{ + int minb, maxb; + int mins, maxs; + int y; + int scale; + int r, g, b; + + minb = fz_mini(rb, fz_mini(gb, bb)); + maxb = fz_maxi(rb, fz_maxi(gb, bb)); + if (minb == maxb) + { + /* backdrop has zero saturation, avoid divide by 0 */ + gb = fz_clampi(gb, 0, 255); + *rd = gb; + *gd = gb; + *bd = gb; + return; + } + + mins = fz_mini(rs, fz_mini(gs, bs)); + maxs = fz_maxi(rs, fz_maxi(gs, bs)); + + scale = ((maxs - mins) << 16) / (maxb - minb); + y = (rb * 77 + gb * 151 + bb * 28 + 0x80) >> 8; + r = y + ((((rb - y) * scale) + 0x8000) >> 16); + g = y + ((((gb - y) * scale) + 0x8000) >> 16); + b = y + ((((bb - y) * scale) + 0x8000) >> 16); + + if ((r | g | b) & 0x100) + { + int scalemin, scalemax; + int min, max; + + min = fz_mini(r, fz_mini(g, b)); + max = fz_maxi(r, fz_maxi(g, b)); + + if (min < 0) + scalemin = (y << 16) / (y - min); + else + scalemin = 0x10000; + + if (max > 255) + scalemax = ((255 - y) << 16) / (max - y); + else + scalemax = 0x10000; + + scale = fz_mini(scalemin, scalemax); + r = y + (((r - y) * scale + 0x8000) >> 16); + g = y + (((g - y) * scale + 0x8000) >> 16); + b = y + (((b - y) * scale + 0x8000) >> 16); + } + + *rd = fz_clampi(r, 0, 255); + *gd = fz_clampi(g, 0, 255); + *bd = fz_clampi(b, 0, 255); +} + +static void +fz_color_rgb(unsigned char *rr, unsigned char *rg, unsigned char *rb, int br, int bg, int bb, int sr, int sg, int sb) +{ + fz_luminosity_rgb(rr, rg, rb, sr, sg, sb, br, bg, bb); +} + +static void +fz_hue_rgb(unsigned char *rr, unsigned char *rg, unsigned char *rb, int br, int bg, int bb, int sr, int sg, int sb) +{ + unsigned char tr, tg, tb; + fz_luminosity_rgb(&tr, &tg, &tb, sr, sg, sb, br, bg, bb); + fz_saturation_rgb(rr, rg, rb, tr, tg, tb, br, bg, bb); +} + +/* Blending loops */ + +static inline void +fz_blend_separable(byte * FZ_RESTRICT bp, int bal, const byte * FZ_RESTRICT sp, int sal, int n1, int w, int blendmode, int complement, int first_spot) +{ + int k; + do + { + int sa = (sal ? sp[n1] : 255); + + if (sa != 0) + { + int ba = (bal ? bp[n1] : 255); + if (ba == 0) + { + memcpy(bp, sp, n1 + (sal && bal)); + if (bal && !sal) + bp[n1+1] = 255; + } + else + { + int saba = fz_mul255(sa, ba); + + /* ugh, division to get non-premul components */ + int invsa = sa ? 255 * 256 / sa : 0; + int invba = ba ? 255 * 256 / ba : 0; + + /* Process colorants */ + for (k = 0; k < first_spot; k++) + { + int sc = (sp[k] * invsa) >> 8; + int bc = (bp[k] * invba) >> 8; + int rc; + + if (complement) + { + sc = 255 - sc; + bc = 255 - bc; + } + + switch (blendmode) + { + default: + case FZ_BLEND_NORMAL: rc = sc; break; + case FZ_BLEND_MULTIPLY: rc = fz_mul255(bc, sc); break; + case FZ_BLEND_SCREEN: rc = fz_screen_byte(bc, sc); break; + case FZ_BLEND_OVERLAY: rc = fz_overlay_byte(bc, sc); break; + case FZ_BLEND_DARKEN: rc = fz_darken_byte(bc, sc); break; + case FZ_BLEND_LIGHTEN: rc = fz_lighten_byte(bc, sc); break; + case FZ_BLEND_COLOR_DODGE: rc = fz_color_dodge_byte(bc, sc); break; + case FZ_BLEND_COLOR_BURN: rc = fz_color_burn_byte(bc, sc); break; + case FZ_BLEND_HARD_LIGHT: rc = fz_hard_light_byte(bc, sc); break; + case FZ_BLEND_SOFT_LIGHT: rc = fz_soft_light_byte(bc, sc); break; + case FZ_BLEND_DIFFERENCE: rc = fz_difference_byte(bc, sc); break; + case FZ_BLEND_EXCLUSION: rc = fz_exclusion_byte(bc, sc); break; + } + + if (complement) + { + rc = 255 - rc; + } + + bp[k] = fz_mul255(255 - sa, bp[k]) + fz_mul255(255 - ba, sp[k]) + fz_mul255(saba, rc); + } + + /* spots */ + for (; k < n1; k++) + { + int sc = 255 - ((sp[k] * invsa) >> 8); + int bc = 255 - ((bp[k] * invba) >> 8); + int rc; + + switch (blendmode) + { + default: + case FZ_BLEND_NORMAL: + case FZ_BLEND_DIFFERENCE: + case FZ_BLEND_EXCLUSION: + rc = sc; break; + case FZ_BLEND_MULTIPLY: rc = fz_mul255(bc, sc); break; + case FZ_BLEND_SCREEN: rc = fz_screen_byte(bc, sc); break; + case FZ_BLEND_OVERLAY: rc = fz_overlay_byte(bc, sc); break; + case FZ_BLEND_DARKEN: rc = fz_darken_byte(bc, sc); break; + case FZ_BLEND_LIGHTEN: rc = fz_lighten_byte(bc, sc); break; + case FZ_BLEND_COLOR_DODGE: rc = fz_color_dodge_byte(bc, sc); break; + case FZ_BLEND_COLOR_BURN: rc = fz_color_burn_byte(bc, sc); break; + case FZ_BLEND_HARD_LIGHT: rc = fz_hard_light_byte(bc, sc); break; + case FZ_BLEND_SOFT_LIGHT: rc = fz_soft_light_byte(bc, sc); break; + } + bp[k] = fz_mul255(255 - sa, bp[k]) + fz_mul255(255 - ba, sp[k]) + fz_mul255(saba, 255 - rc); + } + + if (bal) + bp[k] = ba + sa - saba; + } + } + sp += n1 + sal; + bp += n1 + bal; + } + while (--w); +} + +static inline void +fz_blend_nonseparable_gray(byte * FZ_RESTRICT bp, int bal, const byte * FZ_RESTRICT sp, int sal, int n, int w, int blendmode, int first_spot) +{ + do + { + int sa = (sal ? sp[n] : 255); + + if (sa != 0) + { + int ba = (bal ? bp[n] : 255); + if (ba == 0) + { + memcpy(bp, sp, n + (sal && bal)); + if (bal && !sal) + bp [n + 1] = 255; + } + else + { + int saba = fz_mul255(sa, ba); + + /* ugh, division to get non-premul components */ + int invsa = 255 * 256 / sa; + int invba = 255 * 256 / ba; + int k; + + switch (blendmode) + { + default: + case FZ_BLEND_HUE: + case FZ_BLEND_SATURATION: + case FZ_BLEND_COLOR: + { + int bg = (bp[0] * invba) >> 8; + bp[0] = fz_mul255(255 - sa, bp[0]) + fz_mul255(255 - ba, sp[0]) + fz_mul255(saba, bg); + break; + } + case FZ_BLEND_LUMINOSITY: + { + int sg = (sp[0] * invsa) >> 8; + bp[0] = fz_mul255(255 - sa, bp[0]) + fz_mul255(255 - ba, sp[0]) + fz_mul255(saba, sg); + break; + } + } + + /* Normal blend for spots */ + for (k = first_spot; k < n; k++) + { + int sc = (sp[k] * invsa) >> 8; + bp[k] = fz_mul255(255 - sa, bp[k]) + fz_mul255(255 - ba, sp[k]) + fz_mul255(saba, sc); + } + if (bal) + bp[n] = ba + sa - saba; + } + } + sp += n + sal; + bp += n + bal; + } while (--w); +} + +static inline void +fz_blend_nonseparable(byte * FZ_RESTRICT bp, int bal, const byte * FZ_RESTRICT sp, int sal, int n, int w, int blendmode, int complement, int first_spot) +{ + do + { + unsigned char rr, rg, rb; + + int sa = (sal ? sp[n] : 255); + + if (sa != 0) + { + int ba = (bal ? bp[n] : 255); + if (ba == 0) + { + memcpy(bp, sp, n + (sal && bal)); + if (bal && !sal) + bp [n + 1] = 255; + } + else + { + int k; + int saba = fz_mul255(sa, ba); + + /* ugh, division to get non-premul components */ + int invsa = 255 * 256 / sa; + int invba = 255 * 256 / ba; + + int sr = (sp[0] * invsa) >> 8; + int sg = (sp[1] * invsa) >> 8; + int sb = (sp[2] * invsa) >> 8; + + int br = (bp[0] * invba) >> 8; + int bg = (bp[1] * invba) >> 8; + int bb = (bp[2] * invba) >> 8; + + /* CMYK */ + if (complement) + { + sr = 255 - sr; + sg = 255 - sg; + sb = 255 - sb; + br = 255 - br; + bg = 255 - bg; + bb = 255 - bb; + } + + switch (blendmode) + { + default: + case FZ_BLEND_HUE: + fz_hue_rgb(&rr, &rg, &rb, br, bg, bb, sr, sg, sb); + break; + case FZ_BLEND_SATURATION: + fz_saturation_rgb(&rr, &rg, &rb, br, bg, bb, sr, sg, sb); + break; + case FZ_BLEND_COLOR: + fz_color_rgb(&rr, &rg, &rb, br, bg, bb, sr, sg, sb); + break; + case FZ_BLEND_LUMINOSITY: + fz_luminosity_rgb(&rr, &rg, &rb, br, bg, bb, sr, sg, sb); + break; + } + + /* CMYK */ + if (complement) + { + rr = 255 - rr; + rg = 255 - rg; + rb = 255 - rb; + bp[0] = fz_mul255(255 - sa, bp[0]) + fz_mul255(255 - ba, sp[0]) + fz_mul255(saba, rr); + bp[1] = fz_mul255(255 - sa, bp[1]) + fz_mul255(255 - ba, sp[1]) + fz_mul255(saba, rg); + bp[2] = fz_mul255(255 - sa, bp[2]) + fz_mul255(255 - ba, sp[2]) + fz_mul255(saba, rb); + + switch (blendmode) + { + default: + case FZ_BLEND_HUE: + case FZ_BLEND_SATURATION: + case FZ_BLEND_COLOR: + k = (bp[3] * invba) >> 8; + break; + case FZ_BLEND_LUMINOSITY: + k = (sp[3] * invsa) >> 8; + break; + } + bp[3] = fz_mul255(255 - sa, bp[3]) + fz_mul255(255 - ba, sp[3]) + fz_mul255(saba, k); + } + else + { + bp[0] = fz_mul255(255 - sa, bp[0]) + fz_mul255(255 - ba, sp[0]) + fz_mul255(saba, rr); + bp[1] = fz_mul255(255 - sa, bp[1]) + fz_mul255(255 - ba, sp[1]) + fz_mul255(saba, rg); + bp[2] = fz_mul255(255 - sa, bp[2]) + fz_mul255(255 - ba, sp[2]) + fz_mul255(saba, rb); + } + + if (bal) + bp[n] = ba + sa - saba; + + /* Normal blend for spots */ + for (k = first_spot; k < n; k++) + { + int sc = (sp[k] * invsa) >> 8; + bp[k] = fz_mul255(255 - sa, bp[k]) + fz_mul255(255 - ba, sp[k]) + fz_mul255(saba, sc); + } + } + } + sp += n + sal; + bp += n + bal; + } + while (--w); +} + +static inline void +fz_blend_separable_nonisolated(byte * FZ_RESTRICT bp, int bal, const byte * FZ_RESTRICT sp, int sal, int n1, int w, int blendmode, int complement, const byte * FZ_RESTRICT hp, int alpha, int first_spot) +{ + int k; + + if (sal == 0 && alpha == 255 && blendmode == 0) + { + /* In this case, the uncompositing and the recompositing + * cancel one another out, and it's just a simple copy. */ + /* FIXME: Maybe we can avoid using the shape plane entirely + * and just copy? */ + do + { + int ha = fz_mul255(*hp++, alpha); /* ha = shape_alpha */ + /* If ha == 0 then leave everything unchanged */ + if (ha != 0) + { + for (k = 0; k < n1; k++) + bp[k] = sp[k]; + if (bal) + bp[k] = 255; + } + + sp += n1; + bp += n1 + bal; + } + while (--w); + return; + } + do + { + int ha = *hp++; + int haa = fz_mul255(ha, alpha); /* ha = shape_alpha */ + /* If haa == 0 then leave everything unchanged */ + while (haa != 0) /* Use while, so we can break out */ + { + int sa, ba, bahaa, ra, ra0, invsa, invba, scale; + sa = (sal ? sp[n1] : 255); + if (sa == 0) + break; /* No change! */ + invsa = 255 * 256 / sa; + ba = (bal ? bp[n1] : 255); + if (ba == 0) + { + /* Just copy pixels (allowing for change in + * premultiplied alphas) */ + for (k = 0; k < n1; k++) + bp[k] = fz_mul255((sp[k] * invsa) >> 8, haa); + if (bal) + bp[n1] = haa; + break; + } + invba = 255 * 256 / ba; + + /* Because we are in a non-isolated group, we need to + * do some 'uncomposition' magic before we blend. + * My attempts to understand what is going on here have + * utterly failed, so I've resorted (after much patient + * help from Michael) to copying what the gs code does. + * This seems to be an implementation of the equations + * given on page 236 (section 7.3.3) of pdf_reference17. + * My understanding is that this is "composition" when + * we actually want to do "decomposition", hence my + * confusion. It appears to work though. + */ + scale = (512 * ba + ha) / (ha*2) - FZ_EXPAND(ba); + + sa = haa; + + /* Calculate result_alpha - a combination of the + * background alpha, and 'shape' */ + bahaa = fz_mul255(ba, haa); + ra0 = ba - bahaa; + ra = ra0 + haa; + if (bal) + bp[n1] = ra; + + if (ra == 0) + break; + + /* Process colorants */ + for (k = 0; k < first_spot; k++) + { + /* Read pixels (and convert to non-premultiplied form) */ + int sc = (sp[k] * invsa) >> 8; + int bc = (bp[k] * invba) >> 8; + int rc; + + if (complement) + { + sc = 255 - sc; + bc = 255 - bc; + } + + /* Uncomposite (see above) */ + sc = sc + (((sc-bc) * scale)>>8); + sc = fz_clampi(sc, 0, 255); + + switch (blendmode) + { + default: + case FZ_BLEND_NORMAL: rc = sc; break; + case FZ_BLEND_MULTIPLY: rc = fz_mul255(bc, sc); break; + case FZ_BLEND_SCREEN: rc = fz_screen_byte(bc, sc); break; + case FZ_BLEND_OVERLAY: rc = fz_overlay_byte(bc, sc); break; + case FZ_BLEND_DARKEN: rc = fz_darken_byte(bc, sc); break; + case FZ_BLEND_LIGHTEN: rc = fz_lighten_byte(bc, sc); break; + case FZ_BLEND_COLOR_DODGE: rc = fz_color_dodge_byte(bc, sc); break; + case FZ_BLEND_COLOR_BURN: rc = fz_color_burn_byte(bc, sc); break; + case FZ_BLEND_HARD_LIGHT: rc = fz_hard_light_byte(bc, sc); break; + case FZ_BLEND_SOFT_LIGHT: rc = fz_soft_light_byte(bc, sc); break; + case FZ_BLEND_DIFFERENCE: rc = fz_difference_byte(bc, sc); break; + case FZ_BLEND_EXCLUSION: rc = fz_exclusion_byte(bc, sc); break; + } + + /* From the notes at the top: + * + * Ar * Cr = Cb * (Ar - alpha * As) + alpha * As * (1 - Ab) * Cs + alpha * As * Ab * B(Cb, Cs) ] + * + * And: + * + * Ar = ba + haa - bahaa + * + * In our 0..255 world, with our current variables: + * + * ra.rc = bc * (ra - haa) + haa * (255 - ba) * sc + bahaa * B(Cb, Cs) + * = bc * ra0 + haa * (255 - ba) * sc + bahaa * B(Cb, Cs) + */ + + if (bahaa != 255) + rc = fz_mul255(bahaa, rc); + if (ba != 255) + { + int t = fz_mul255(255 - ba, haa); + rc += fz_mul255(t, sc); + } + if (ra0 != 0) + rc += fz_mul255(ra0, bc); + + if (complement) + rc = ra - rc; + + bp[k] = fz_clampi(rc, 0, ra); + } + + /* Spots */ + for (; k < n1; k++) + { + int sc = 255 - ((sp[k] * invsa + 128) >> 8); + int bc = 255 - ((bp[k] * invba + 128) >> 8); + int rc; + + sc = sc + (((sc-bc) * scale)>>8); + + /* Non-white preserving use Normal */ + switch (blendmode) + { + default: + case FZ_BLEND_NORMAL: + case FZ_BLEND_DIFFERENCE: + case FZ_BLEND_EXCLUSION: + rc = sc; break; + case FZ_BLEND_MULTIPLY: rc = fz_mul255(bc, sc); break; + case FZ_BLEND_SCREEN: rc = fz_screen_byte(bc, sc); break; + case FZ_BLEND_OVERLAY: rc = fz_overlay_byte(bc, sc); break; + case FZ_BLEND_DARKEN: rc = fz_darken_byte(bc, sc); break; + case FZ_BLEND_LIGHTEN: rc = fz_lighten_byte(bc, sc); break; + case FZ_BLEND_COLOR_DODGE: rc = fz_color_dodge_byte(bc, sc); break; + case FZ_BLEND_COLOR_BURN: rc = fz_color_burn_byte(bc, sc); break; + case FZ_BLEND_HARD_LIGHT: rc = fz_hard_light_byte(bc, sc); break; + case FZ_BLEND_SOFT_LIGHT: rc = fz_soft_light_byte(bc, sc); break; + } + + if (bahaa != 255) + rc = fz_mul255(bahaa, rc); + if (ba != 255) + { + int t = fz_mul255(255 - ba, haa); + rc += fz_mul255(t, sc); + } + if (ra0 != 0) + rc += fz_mul255(ra0, bc); + + bp[k] = ra - rc; + } + break; + } + + sp += n1 + sal; + bp += n1 + bal; + } + while (--w); +} + +static inline void +fz_blend_nonseparable_nonisolated_gray(byte * FZ_RESTRICT bp, int bal, const byte * FZ_RESTRICT sp, int sal, int n, int w, int blendmode, const byte * FZ_RESTRICT hp, int alpha, int first_spot) +{ + do + { + int ha = *hp++; + int haa = fz_mul255(ha, alpha); + if (haa != 0) + { + int ba = (bal ? bp[n] : 255); + + if (ba == 0 && alpha == 255) + { + memcpy(bp, sp, n + (sal && bal)); + if (bal && !sal) + bp[n+1] = 255; + } + else + { + int sa = (sal ? sp[n] : 255); + int bahaa = fz_mul255(ba, haa); + int k; + + /* Calculate result_alpha */ + int ra = ba - bahaa + haa; + if (bal) + bp[n] = ra; + if (ra != 0) + { + int invha = ha ? 255 * 256 / ha : 0; + + /* ugh, division to get non-premul components */ + int invsa = sa ? 255 * 256 / sa : 0; + int invba = ba ? 255 * 256 / ba : 0; + + int sg = (sp[0] * invsa) >> 8; + int bg = (bp[0] * invba) >> 8; + + /* Uncomposite */ + sg = (((sg - bg)*invha) >> 8) + bg; + sg = fz_clampi(sg, 0, 255); + + switch (blendmode) + { + default: + case FZ_BLEND_HUE: + case FZ_BLEND_SATURATION: + case FZ_BLEND_COLOR: + bp[0] = fz_mul255(ra, bg); + break; + case FZ_BLEND_LUMINOSITY: + bp[0] = fz_mul255(ra, sg); + break; + } + + /* Normal blend for spots */ + for (k = first_spot; k < n; k++) + { + int sc = (sp[k] * invsa + 128) >> 8; + int bc = (bp[k] * invba + 128) >> 8; + int rc; + + sc = (((sc - bc) * invha + 128) >> 8) + bc; + sc = fz_clampi(sc, 0, 255); + rc = bc + fz_mul255(sa, fz_mul255(255 - ba, sc) + fz_mul255(ba, sc) - bc); + rc = fz_clampi(rc, 0, 255); + bp[k] = fz_mul255(rc, ra); + } + } + } + } + sp += n + sal; + bp += n + bal; + } while (--w); +} + +static inline void +fz_blend_nonseparable_nonisolated(byte * FZ_RESTRICT bp, int bal, const byte * FZ_RESTRICT sp, int sal, int n, int w, int blendmode, int complement, const byte * FZ_RESTRICT hp, int alpha, int first_spot) +{ + do + { + int ha = *hp++; + int haa = fz_mul255(ha, alpha); + if (haa != 0) + { + int sa = (sal ? sp[n] : 255); + int ba = (bal ? bp[n] : 255); + + if (ba == 0 && alpha == 255) + { + memcpy(bp, sp, n + (sal && bal)); + if (bal && !sal) + bp[n] = 255; + } + else + { + int bahaa = fz_mul255(ba, haa); + + /* Calculate result_alpha */ + int ra0 = ba - bahaa; + int ra = ra0 + haa; + + if (bal) + bp[n] = ra; + + if (ra != 0) + { + /* Because we are a non-isolated group, we + * need to 'uncomposite' before we blend + * (recomposite). We assume that normal + * blending has been done inside the group, + * so: ra.rc = (1-ha).bc + ha.sc + * A bit of rearrangement, and that gives us + * that: sc = (ra.rc - bc)/ha + bc + * Now, the result of the blend was stored in + * src, so: */ + int invha = ha ? 255 * 256 / ha : 0; + int k; + unsigned char rr, rg, rb; + + /* ugh, division to get non-premul components */ + int invsa = sa ? 255 * 256 / sa : 0; + int invba = ba ? 255 * 256 / ba : 0; + + int sr = (sp[0] * invsa) >> 8; + int sg = (sp[1] * invsa) >> 8; + int sb = (sp[2] * invsa) >> 8; + + int br = (bp[0] * invba) >> 8; + int bg = (bp[1] * invba) >> 8; + int bb = (bp[2] * invba) >> 8; + + if (complement) + { + sr = 255 - sr; + sg = 255 - sg; + sb = 255 - sb; + br = 255 - br; + bg = 255 - bg; + bb = 255 - bb; + } + + /* Uncomposite */ + sr = (((sr - br)*invha) >> 8) + br; + sr = fz_clampi(sr, 0, 255); + sg = (((sg - bg)*invha) >> 8) + bg; + sg = fz_clampi(sg, 0, 255); + sb = (((sb - bb)*invha) >> 8) + bb; + sb = fz_clampi(sb, 0, 255); + + switch (blendmode) + { + default: + case FZ_BLEND_HUE: + fz_hue_rgb(&rr, &rg, &rb, br, bg, bb, sr, sg, sb); + break; + case FZ_BLEND_SATURATION: + fz_saturation_rgb(&rr, &rg, &rb, br, bg, bb, sr, sg, sb); + break; + case FZ_BLEND_COLOR: + fz_color_rgb(&rr, &rg, &rb, br, bg, bb, sr, sg, sb); + break; + case FZ_BLEND_LUMINOSITY: + fz_luminosity_rgb(&rr, &rg, &rb, br, bg, bb, sr, sg, sb); + break; + } + + /* From the notes at the top: + * + * Ar * Cr = Cb * (Ar - alpha * As) + alpha * As * (1 - Ab) * Cs + alpha * As * Ab * B(Cb, Cs) ] + * + * And: + * + * Ar = ba + haa - bahaa + * + * In our 0..255 world, with our current variables: + * + * ra.rc = bc * (ra - haa) + haa * (255 - ba) * sc + bahaa * B(Cb, Cs) + * = bc * ra0 + haa * (255 - ba) * sc + bahaa * B(Cb, Cs) + */ + + if (bahaa != 255) + { + rr = fz_mul255(bahaa, rr); + rg = fz_mul255(bahaa, rg); + rb = fz_mul255(bahaa, rb); + } + if (ba != 255) + { + int t = fz_mul255(255 - ba, haa); + rr += fz_mul255(t, sr); + rg += fz_mul255(t, sg); + rb += fz_mul255(t, sb); + } + if (ra0 != 0) + { + rr += fz_mul255(ra0, br); + rg += fz_mul255(ra0, bg); + rb += fz_mul255(ra0, bb); + } + + /* CMYK */ + if (complement) + { + int sk, bk, rk; + + /* Care must be taking when inverting here, as r = alpha * col. + * We want to store alpha * (255 - col) = alpha * 255 - alpha * col + */ + rr = ra - rr; + rg = ra - rg; + rb = ra - rb; + + sk = sa ? (sp[3] * invsa) >> 8 : 255; + bk = ba ? (bp[3] * invba) >> 8 : 255; + + bk = fz_clampi(bk, 0, 255); + sk = fz_clampi(sk, 0, 255); + + if (blendmode == FZ_BLEND_LUMINOSITY) + rk = sk; + else + rk = bk; + + if (bahaa != 255) + rk = fz_mul255(bahaa, rk); + + if (ba != 255) + { + int t = fz_mul255(255 - ba, haa); + rk += fz_mul255(t, sk); + } + + if (ra0 != 0) + rk += fz_mul255(ra0, bk); + + bp[3] = rk; + } + + bp[0] = rr; + bp[1] = rg; + bp[2] = rb; + + /* Normal blend for spots */ + for (k = first_spot; k < n; k++) + { + int sc = (sp[k] * invsa + 128) >> 8; + int bc = (bp[k] * invba + 128) >> 8; + int rc; + + sc = (((sc - bc) * invha + 128) >> 8) + bc; + sc = fz_clampi(sc, 0, 255); + rc = bc + fz_mul255(ha, fz_mul255(255 - ba, sc) + fz_mul255(ba, sc) - bc); + rc = fz_clampi(rc, 0, 255); + bp[k] = fz_mul255(rc, ra); + } + } + } + } + sp += n + sal; + bp += n + bal; + } + while (--w); +} + +#ifdef PARANOID_PREMULTIPLY +static void +verify_premultiply(fz_context *ctx, const fz_pixmap * FZ_RESTRICT dst) +{ + unsigned char *dp = dst->samples; + int w = dst->w; + int h = dst->h; + int n = dst->n; + int x, y, i; + int s = dst->stride - n * w; + + for (y = h; y > 0; y--) + { + for (x = w; x > 0; x--) + { + int a = dp[n-1]; + for (i = n-1; i > 0; i--) + if (*dp++ > a) + abort(); + dp++; + } + dp += s; + } +} +#endif + +void +fz_blend_pixmap(fz_context *ctx, fz_pixmap * FZ_RESTRICT dst, fz_pixmap * FZ_RESTRICT src, int alpha, int blendmode, int isolated, const fz_pixmap * FZ_RESTRICT shape) +{ + unsigned char *sp; + unsigned char *dp; + fz_irect bbox; + int x, y, w, h, n; + int da, sa; + int complement; + + /* TODO: fix this hack! */ + if (isolated && alpha < 255) + { + unsigned char *sp2; + int nn; + h = src->h; + sp2 = src->samples; + nn = src->w * src->n; + while (h--) + { + n = nn; + while (n--) + { + *sp2 = fz_mul255(*sp2, alpha); + sp2++; + } + sp2 += src->stride - nn; + } + } + + bbox = fz_intersect_irect(fz_pixmap_bbox(ctx, src), fz_pixmap_bbox(ctx, dst)); + + x = bbox.x0; + y = bbox.y0; + w = fz_irect_width(bbox); + h = fz_irect_height(bbox); + if (w == 0 || h == 0) + return; + + complement = fz_colorspace_is_subtractive(ctx, src->colorspace); + n = src->n; + sp = src->samples + (y - src->y) * (size_t)src->stride + (x - src->x) * (size_t)src->n; + sa = src->alpha; + dp = dst->samples + (y - dst->y) * (size_t)dst->stride + (x - dst->x) * (size_t)dst->n; + da = dst->alpha; + + if (n == 1) + sa = da = 0; + +#ifdef PARANOID_PREMULTIPLY + if (sa) + verify_premultiply(ctx, src); + if (da) + verify_premultiply(ctx, dst); +#endif + + n -= sa; + assert(n == dst->n - da); + + if (!isolated) + { + const unsigned char *hp = shape->samples + (y - shape->y) * (size_t)shape->stride + (x - shape->x); + + while (h--) + { + if (blendmode >= FZ_BLEND_HUE) + { + if (complement || src->s > 0) + if ((n - src->s) == 1) + fz_blend_nonseparable_nonisolated_gray(dp, da, sp, sa, n, w, blendmode, hp, alpha, 1); + else + fz_blend_nonseparable_nonisolated(dp, da, sp, sa, n, w, blendmode, complement, hp, alpha, n - src->s); + else + if (da) + if (sa) + if (n == 1) + fz_blend_nonseparable_nonisolated_gray(dp, 1, sp, 1, 1, w, blendmode, hp, alpha, 1); + else + fz_blend_nonseparable_nonisolated(dp, 1, sp, 1, n, w, blendmode, complement, hp, alpha, n); + else + if (n == 1) + fz_blend_nonseparable_nonisolated_gray(dp, 1, sp, 0, 1, w, blendmode, hp, alpha, 1); + else + fz_blend_nonseparable_nonisolated(dp, 1, sp, 0, n, w, blendmode, complement, hp, alpha, n); + else + if (sa) + if (n == 1) + fz_blend_nonseparable_nonisolated_gray(dp, 0, sp, 1, 1, w, blendmode, hp, alpha, 1); + else + fz_blend_nonseparable_nonisolated(dp, 0, sp, 1, n, w, blendmode, complement, hp, alpha, n); + else + if (n == 1) + fz_blend_nonseparable_nonisolated_gray(dp, 0, sp, 0, 1, w, blendmode, hp, alpha, 1); + else + fz_blend_nonseparable_nonisolated(dp, 0, sp, 0, n, w, blendmode, complement, hp, alpha, n); + } + else + { + if (complement || src->s > 0) + fz_blend_separable_nonisolated(dp, da, sp, sa, n, w, blendmode, complement, hp, alpha, n - src->s); + else + if (da) + if (sa) + fz_blend_separable_nonisolated(dp, 1, sp, 1, n, w, blendmode, 0, hp, alpha, n); + else + fz_blend_separable_nonisolated(dp, 1, sp, 0, n, w, blendmode, 0, hp, alpha, n); + else + if (sa) + fz_blend_separable_nonisolated(dp, 0, sp, 1, n, w, blendmode, 0, hp, alpha, n); + else + fz_blend_separable_nonisolated(dp, 0, sp, 0, n, w, blendmode, 0, hp, alpha, n); + } + sp += src->stride; + dp += dst->stride; + hp += shape->stride; + } + } + else + { + while (h--) + { + if (blendmode >= FZ_BLEND_HUE) + { + if (complement || src->s > 0) + if ((n - src->s) == 1) + fz_blend_nonseparable_gray(dp, da, sp, sa, n, w, blendmode, 1); + else + fz_blend_nonseparable(dp, da, sp, sa, n, w, blendmode, complement, n - src->s); + else + if (da) + if (sa) + if (n == 1) + fz_blend_nonseparable_gray(dp, 1, sp, 1, 1, w, blendmode, 1); + else + fz_blend_nonseparable(dp, 1, sp, 1, n, w, blendmode, complement, n); + else + if (n == 1) + fz_blend_nonseparable_gray(dp, 1, sp, 0, 1, w, blendmode, 1); + else + fz_blend_nonseparable(dp, 1, sp, 0, n, w, blendmode, complement, n); + else + if (sa) + if (n == 1) + fz_blend_nonseparable_gray(dp, 0, sp, 1, 1, w, blendmode, 1); + else + fz_blend_nonseparable(dp, 0, sp, 1, n, w, blendmode, complement, n); + else + if (n == 1) + fz_blend_nonseparable_gray(dp, 0, sp, 0, 1, w, blendmode, 1); + else + fz_blend_nonseparable(dp, 0, sp, 0, n, w, blendmode, complement, n); + } + else + { + if (complement || src->s > 0) + fz_blend_separable(dp, da, sp, sa, n, w, blendmode, complement, n - src->s); + else + if (da) + if (sa) + fz_blend_separable(dp, 1, sp, 1, n, w, blendmode, 0, n); + else + fz_blend_separable(dp, 1, sp, 0, n, w, blendmode, 0, n); + else + if (sa) + fz_blend_separable(dp, 0, sp, 1, n, w, blendmode, 0, n); + else + fz_blend_separable(dp, 0, sp, 0, n, w, blendmode, 0, n); + } + sp += src->stride; + dp += dst->stride; + } + } + +#ifdef PARANOID_PREMULTIPLY + if (da) + verify_premultiply(ctx, dst); +#endif +} + +static inline void +fz_blend_knockout(byte * FZ_RESTRICT bp, int bal, const byte * FZ_RESTRICT sp, int sal, int n1, int w, const byte * FZ_RESTRICT hp) +{ + int k; + do + { + int ha = *hp++; + + if (ha != 0) + { + int sa = (sal ? sp[n1] : 255); + int ba = (bal ? bp[n1] : 255); + if (ba == 0 && ha == 0xFF) + { + memcpy(bp, sp, n1); + if (bal) + bp[n1] = sa; + } + else + { + int hasa = fz_mul255(ha, sa); + /* ugh, division to get non-premul components */ + int invsa = sa ? 255 * 256 / sa : 0; + int invba = ba ? 255 * 256 / ba : 0; + int ra = hasa + fz_mul255(255-ha, ba); + + /* Process colorants + spots */ + for (k = 0; k < n1; k++) + { + int sc = (sp[k] * invsa) >> 8; + int bc = (bp[k] * invba) >> 8; + int rc = fz_mul255(255 - ha, bc) + fz_mul255(ha, sc); + + bp[k] = fz_mul255(ra, rc); + } + + if (bal) + bp[k] = ra; + } + } + sp += n1 + sal; + bp += n1 + bal; + } + while (--w); +} + +void +fz_blend_pixmap_knockout(fz_context *ctx, fz_pixmap * FZ_RESTRICT dst, fz_pixmap * FZ_RESTRICT src, const fz_pixmap * FZ_RESTRICT shape) +{ + unsigned char *sp; + unsigned char *dp; + fz_irect sbox, dbox, bbox; + int x, y, w, h, n; + int da, sa; + const unsigned char *hp; + + dbox = fz_pixmap_bbox_no_ctx(dst); + sbox = fz_pixmap_bbox_no_ctx(src); + bbox = fz_intersect_irect(dbox, sbox); + + x = bbox.x0; + y = bbox.y0; + w = fz_irect_width(bbox); + h = fz_irect_height(bbox); + if (w == 0 || h == 0) + return; + + n = src->n; + sp = src->samples + (y - src->y) * (size_t)src->stride + (x - src->x) * (size_t)src->n; + sa = src->alpha; + dp = dst->samples + (y - dst->y) * (size_t)dst->stride + (x - dst->x) * (size_t)dst->n; + da = dst->alpha; + hp = shape->samples + (y - shape->y) * (size_t)shape->stride + (x - shape->x); + +#ifdef PARANOID_PREMULTIPLY + if (sa) + verify_premultiply(ctx, src); + if (da) + verify_premultiply(ctx, dst); +#endif + + n -= sa; + assert(n == dst->n - da); + + while (h--) + { + fz_blend_knockout(dp, da, sp, sa, n, w, hp); + sp += src->stride; + dp += dst->stride; + hp += shape->stride; + } + +#ifdef PARANOID_PREMULTIPLY + if (da) + verify_premultiply(ctx, dst); +#endif +}
