#include "fitz.h" /* PDF 1.4 blend modes. These are slow. */ 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_find_blendmode(char *name) { int i; for (i = 0; i < nelem(fz_blendmode_names); i++) if (!strcmp(name, fz_blendmode_names[i])) return i; return FZ_BLEND_NORMAL; } char *fz_blendmode_name(int blendmode) { if (blendmode >= 0 && blendmode < nelem(fz_blendmode_names)) return (char*)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 MIN(b, s); } static inline int fz_lighten_byte(int b, int s) { return MAX(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) { /* review this */ 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) - 12, b) + 4, 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 ABS(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(int *rd, int *gd, int *bd, int rb, int gb, int bb, int rs, int gs, int bs) { int delta, scale; int r, g, b, y; /* 0.3, 0.59, 0.11 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 = MAX(r, MAX(g, b)); scale = ((255 - y) << 16) / (max - y); } else { int min; min = MIN(r, MIN(g, b)); scale = (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 = r; *gd = g; *bd = b; } static void fz_saturation_rgb(int *rd, int *gd, int *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 = MIN(rb, MIN(gb, bb)); maxb = MAX(rb, MAX(gb, bb)); if (minb == maxb) { /* backdrop has zero saturation, avoid divide by 0 */ *rd = gb; *gd = gb; *bd = gb; return; } mins = MIN(rs, MIN(gs, bs)); maxs = MAX(rs, MAX(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 = MIN(r, MIN(g, b)); max = MAX(r, MAX(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 = MIN(scalemin, scalemax); r = y + (((r - y) * scale + 0x8000) >> 16); g = y + (((g - y) * scale + 0x8000) >> 16); b = y + (((b - y) * scale + 0x8000) >> 16); } *rd = r; *gd = g; *bd = b; } static void fz_color_rgb(int *rr, int *rg, int *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(int *rr, int *rg, int *rb, int br, int bg, int bb, int sr, int sg, int sb) { int 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 */ void fz_blend_separable(byte * restrict bp, byte * restrict sp, int n, int w, int blendmode) { int k; int n1 = n - 1; while (w--) { int sa = sp[n1]; int ba = bp[n1]; 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; for (k = 0; k < n1; k++) { int sc = (sp[k] * invsa) >> 8; int bc = (bp[k] * invba) >> 8; int rc; 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; } bp[k] = fz_mul255(255 - sa, bp[k]) + fz_mul255(255 - ba, sp[k]) + fz_mul255(saba, rc); } bp[k] = ba + sa - saba; sp += n; bp += n; } } void fz_blend_nonseparable(byte * restrict bp, byte * restrict sp, int w, int blendmode) { while (w--) { int rr, rg, rb; int sa = sp[3]; int ba = bp[3]; 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; 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; 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; } 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); bp[3] = ba + sa - saba; sp += 4; bp += 4; } } static void fz_blend_separable_nonisolated(byte * restrict bp, byte * restrict sp, int n, int w, int blendmode, byte * restrict hp, int alpha) { int k; int n1 = n - 1; if (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? */ while (w--) { int ha = fz_mul255(*hp++, alpha); /* ha = shape_alpha */ /* If ha == 0 then leave everything unchanged */ if (ha != 0) { for (k = 0; k < n; k++) { bp[k] = sp[k]; } } sp += n; bp += n; } return; } while (w--) { int ha = *hp++; int haa = fz_mul255(ha, alpha); /* ha = shape_alpha */ /* If haa == 0 then leave everything unchanged */ if (haa != 0) { int sa = sp[n1]; int ba = bp[n1]; int baha = fz_mul255(ba, haa); /* ugh, division to get non-premul components */ int invsa = sa ? 255 * 256 / sa : 0; int invba = ba ? 255 * 256 / ba : 0; /* Calculate result_alpha */ int ra = bp[n1] = ba - baha + haa; /* 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; if (ra != 0) for (k = 0; k < n1; k++) { int sc = (sp[k] * invsa) >> 8; int bc = (bp[k] * invba) >> 8; int rc; /* Uncomposite */ sc = (((sc-bc)*invha)>>8) + bc; if (sc < 0) sc = 0; if (sc > 255) sc = 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; } rc = fz_mul255(255 - haa, bc) + fz_mul255(fz_mul255(255 - ba, sc), haa) + fz_mul255(baha, rc); if (rc < 0) rc = 0; if (rc > 255) rc = 255; bp[k] = fz_mul255(rc, ra); } } sp += n; bp += n; } } static void fz_blend_nonseparable_nonisolated(byte * restrict bp, byte * restrict sp, int w, int blendmode, byte * restrict hp, int alpha) { while (w--) { int ha = *hp++; int haa = fz_mul255(ha, alpha); if (haa != 0) { int sa = sp[3]; int ba = bp[3]; int baha = fz_mul255(ba, haa); /* Calculate result_alpha */ int ra = bp[3] = ba - baha + haa; 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 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; /* Uncomposite */ sr = (((sr-br)*invha)>>8) + br; sg = (((sg-bg)*invha)>>8) + bg; sb = (((sb-bb)*invha)>>8) + 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; } rr = fz_mul255(255 - haa, bp[0]) + fz_mul255(fz_mul255(255 - ba, sr), haa) + fz_mul255(baha, rr); rg = fz_mul255(255 - haa, bp[1]) + fz_mul255(fz_mul255(255 - ba, sg), haa) + fz_mul255(baha, rg); rb = fz_mul255(255 - haa, bp[2]) + fz_mul255(fz_mul255(255 - ba, sb), haa) + fz_mul255(baha, rb); bp[0] = fz_mul255(ra, rr); bp[1] = fz_mul255(ra, rg); bp[2] = fz_mul255(ra, rb); } } sp += 4; bp += 4; } } void fz_blend_pixmap(fz_pixmap *dst, fz_pixmap *src, int alpha, int blendmode, int isolated, fz_pixmap *shape) { unsigned char *sp, *dp; fz_bbox bbox; int x, y, w, h, n; /* TODO: fix this hack! */ if (isolated && alpha < 255) { sp = src->samples; n = src->w * src->h * src->n; while (n--) { *sp = fz_mul255(*sp, alpha); sp++; } } bbox = fz_bound_pixmap(dst); bbox = fz_intersect_bbox(bbox, fz_bound_pixmap(src)); x = bbox.x0; y = bbox.y0; w = bbox.x1 - bbox.x0; h = bbox.y1 - bbox.y0; n = src->n; sp = src->samples + ((y - src->y) * src->w + (x - src->x)) * n; dp = dst->samples + ((y - dst->y) * dst->w + (x - dst->x)) * n; assert(src->n == dst->n); if (!isolated) { unsigned char *hp = shape->samples + (y - shape->y) * shape->w + (x - shape->x); while (h--) { if (n == 4 && blendmode >= FZ_BLEND_HUE) fz_blend_nonseparable_nonisolated(dp, sp, w, blendmode, hp, alpha); else fz_blend_separable_nonisolated(dp, sp, n, w, blendmode, hp, alpha); sp += src->w * n; dp += dst->w * n; hp += shape->w; } } else { while (h--) { if (n == 4 && blendmode >= FZ_BLEND_HUE) fz_blend_nonseparable(dp, sp, w, blendmode); else fz_blend_separable(dp, sp, n, w, blendmode); sp += src->w * n; dp += dst->w * n; } } }