diff options
author | Robin Watts <robin.watts@artifex.com> | 2017-09-06 14:57:48 +0100 |
---|---|---|
committer | Robin Watts <robin.watts@artifex.com> | 2017-10-24 15:16:36 +0100 |
commit | 313c72b9b3f3c888a51c9eb02187228b9e2aad11 (patch) | |
tree | 44230ad3d852cec8608fa766dea5bfe3714a30a5 | |
parent | c413f4db6a54846ae85393a87fa1b53b93a966ee (diff) | |
download | mupdf-313c72b9b3f3c888a51c9eb02187228b9e2aad11.tar.xz |
Fix knockout operation.
The "blend back" at the end of the inner knockout groups was
attempting to reuse the existing blending code. This was going
wrong for all sorts of reasons (not least the uncomposition
phase) for knockout groups containing alpha, such as found on
page 7 of Altona_Technical_v20_x4.pdf.
Use a dedicated routine. This is much simpler as it doesn't have
to cope with blend modes etc.
-rw-r--r-- | source/fitz/draw-blend.c | 100 | ||||
-rw-r--r-- | source/fitz/draw-device.c | 70 | ||||
-rw-r--r-- | source/fitz/draw-imp.h | 1 |
3 files changed, 149 insertions, 22 deletions
diff --git a/source/fitz/draw-blend.c b/source/fitz/draw-blend.c index ac9e0492..6c2c58c7 100644 --- a/source/fitz/draw-blend.c +++ b/source/fitz/draw-blend.c @@ -1250,3 +1250,103 @@ fz_blend_pixmap(fz_context *ctx, fz_pixmap * restrict dst, fz_pixmap * restrict verify_premultiply(ctx, dst); #endif } + +static inline void +fz_blend_knockout(byte * restrict bp, int bal, const byte * restrict sp, int sal, int n1, int w, const byte * 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 * restrict dst, fz_pixmap * restrict src, const fz_pixmap * restrict shape) +{ + unsigned char *sp; + unsigned char *dp; + fz_irect bbox; + fz_irect bbox2; + int x, y, w, h, n; + int da, sa; + const unsigned char *hp; + + fz_pixmap_bbox_no_ctx(dst, &bbox); + fz_pixmap_bbox_no_ctx(src, &bbox2); + fz_intersect_irect(&bbox, &bbox2); + + x = bbox.x0; + y = bbox.y0; + w = bbox.x1 - bbox.x0; + h = bbox.y1 - bbox.y0; + + if (w == 0 || h == 0) + return; + + n = src->n; + sp = src->samples + (unsigned int)((y - src->y) * src->stride + (x - src->x) * src->n); + sa = src->alpha; + dp = dst->samples + (unsigned int)((y - dst->y) * dst->stride + (x - dst->x) * dst->n); + da = dst->alpha; + hp = shape->samples + (unsigned int)((y - shape->y) * 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 +} diff --git a/source/fitz/draw-device.c b/source/fitz/draw-device.c index 64de42ae..260ca59e 100644 --- a/source/fitz/draw-device.c +++ b/source/fitz/draw-device.c @@ -195,8 +195,9 @@ static void emergency_pop_stack(fz_context *ctx, fz_draw_device *dev, fz_draw_st static fz_draw_state * fz_knockout_begin(fz_context *ctx, fz_draw_device *dev) { - fz_irect bbox; + fz_irect bbox, ga_bbox; fz_pixmap *dest, *shape; + fz_pixmap *ga = NULL; fz_draw_state *state = &dev->stack[dev->top]; int isolated = state->blendmode & FZ_BLEND_ISOLATED; @@ -209,26 +210,47 @@ fz_knockout_begin(fz_context *ctx, fz_draw_device *dev) fz_pixmap_bbox(ctx, state->dest, &bbox); fz_intersect_irect(&bbox, &state->scissor); dest = fz_new_pixmap_with_bbox(ctx, state->dest->colorspace, &bbox, state->dest->seps, state->dest->alpha); + if (state[0].group_alpha) + { + fz_pixmap_bbox(ctx, state->group_alpha, &ga_bbox); + fz_intersect_irect(&ga_bbox, &state->scissor); + ga = fz_new_pixmap_with_bbox(ctx, state->group_alpha->colorspace, &ga_bbox, state->group_alpha->seps, state->group_alpha->alpha); + } if (isolated) { fz_clear_pixmap(ctx, dest); + if (ga) + fz_clear_pixmap(ctx, ga); } else { /* Find the last but one destination to copy */ int i = dev->top-1; /* i = the one on entry (i.e. the last one) */ - fz_pixmap *prev = state->dest; + fz_draw_state *prev = state; while (i > 0) { - prev = dev->stack[--i].dest; - if (prev != state->dest) + prev = &dev->stack[--i]; + if (prev->dest != state->dest) break; } - if (prev) - fz_copy_pixmap_rect(ctx, dest, prev, &bbox, dev->default_cs); + if (prev->dest) + { + fz_copy_pixmap_rect(ctx, dest, prev->dest, &bbox, dev->default_cs); + if (ga) + { + if (prev->group_alpha) + fz_copy_pixmap_rect(ctx, ga, prev->group_alpha, &ga_bbox, dev->default_cs); + else + fz_clear_pixmap(ctx, ga); + } + } else + { fz_clear_pixmap(ctx, dest); + if (ga) + fz_clear_pixmap(ctx, ga); + } } /* Knockout groups (and only knockout groups) rely on shape */ @@ -239,8 +261,11 @@ fz_knockout_begin(fz_context *ctx, fz_draw_device *dev) fz_dump_blend(ctx, "Knockout begin: background is ", dest); if (shape) fz_dump_blend(ctx, "/S=", shape); + if (ga) + fz_dump_blend(ctx, "/GA=", ga); printf("\n"); #endif + state[1].group_alpha = ga; state[1].scissor = bbox; state[1].dest = dest; state[1].shape = shape; @@ -252,8 +277,6 @@ fz_knockout_begin(fz_context *ctx, fz_draw_device *dev) static void fz_knockout_end(fz_context *ctx, fz_draw_device *dev) { fz_draw_state *state; - int blendmode; - int isolated; if (dev->top == 0) { @@ -265,8 +288,8 @@ static void fz_knockout_end(fz_context *ctx, fz_draw_device *dev) if ((state[0].blendmode & FZ_BLEND_KNOCKOUT) == 0) return; - blendmode = state->blendmode & FZ_BLEND_MODEMASK; - isolated = state->blendmode & FZ_BLEND_ISOLATED; + assert((state[1].blendmode & FZ_BLEND_ISOLATED) == 0); + assert((state[1].blendmode & FZ_BLEND_MODEMASK) == 0); #ifdef DUMP_GROUP_BLENDS dump_spaces(dev->top, ""); @@ -280,32 +303,32 @@ static void fz_knockout_end(fz_context *ctx, fz_draw_device *dev) fz_dump_blend(ctx, "/S=", state[0].shape); if (state[0].group_alpha) fz_dump_blend(ctx, "/GA=", state[0].group_alpha); - if (blendmode != 0) - printf(" (blend %d)", blendmode); - if (isolated != 0) + if ((state->blendmode & FZ_BLEND_MODEMASK) != 0) + printf(" (blend %d)", state->blendmode & FZ_BLEND_MODEMASK); + if ((state->blendmode & FZ_BLEND_ISOLATED) != 0) printf(" (isolated)"); printf(" (knockout)"); #endif assert(state[1].shape); - fz_blend_pixmap(ctx, state[0].dest, state[1].dest, 255, blendmode, 0, state[1].shape); + fz_blend_pixmap_knockout(ctx, state[0].dest, state[1].dest, state[1].shape); /* The following test should not be required, but just occasionally * errors can cause the stack to get out of sync, and this saves our * bacon. */ if (state[0].dest != state[1].dest) fz_drop_pixmap(ctx, state[1].dest); + if (state[1].group_alpha && state[0].group_alpha != state[1].group_alpha) + { + if (state[0].group_alpha) + fz_blend_pixmap_knockout(ctx, state[0].group_alpha, state[1].group_alpha, state[1].shape); + fz_drop_pixmap(ctx, state[1].group_alpha); + } if (state[0].shape != state[1].shape) { if (state[0].shape) fz_paint_pixmap(state[0].shape, state[1].shape, 255); fz_drop_pixmap(ctx, state[1].shape); } - if (state[0].group_alpha != state[1].group_alpha) - { - if (state[0].group_alpha) - fz_paint_pixmap(state[0].group_alpha, state[1].group_alpha, 255); - fz_drop_pixmap(ctx, state[1].group_alpha); - } #ifdef DUMP_GROUP_BLENDS fz_dump_blend(ctx, " to get ", state[0].dest); if (state[0].shape) @@ -2260,6 +2283,7 @@ fz_draw_begin_group(fz_context *ctx, fz_device *devp, const fz_rect *rect, fz_co fz_clear_pixmap(ctx, state[1].group_alpha); } + /* shape is inherited from the previous group */ state[1].alpha = alpha; #ifdef DUMP_GROUP_BLENDS dump_spaces(dev->top-1, ""); @@ -2349,12 +2373,14 @@ fz_draw_end_group(fz_context *ctx, fz_device *devp) if (state[0].shape != state[1].shape) { + /* The 'D' on page 7 of Altona_Technical_v20_x4.pdf goes wrong if this + * isn't alpha * 255, as the blend back fails to take account of alpha. */ if (state[0].shape) { if (state[1].shape) - fz_paint_pixmap(state[0].shape, state[1].shape, 255); + fz_paint_pixmap(state[0].shape, state[1].shape, alpha * 255); else - fz_paint_pixmap_alpha(state[0].shape, state[1].dest, 255); + fz_paint_pixmap_alpha(state[0].shape, state[1].dest, alpha * 255); } fz_drop_pixmap(ctx, state[1].shape); } diff --git a/source/fitz/draw-imp.h b/source/fitz/draw-imp.h index e855b40a..f6cc1367 100644 --- a/source/fitz/draw-imp.h +++ b/source/fitz/draw-imp.h @@ -464,6 +464,7 @@ void fz_paint_pixmap_with_bbox(fz_pixmap * restrict dst, const fz_pixmap * restr void fz_paint_pixmap_with_overprint(fz_pixmap * restrict dst, const fz_pixmap * restrict src, const fz_overprint *op); void fz_blend_pixmap(fz_context *ctx, fz_pixmap * restrict dst, fz_pixmap * restrict src, int alpha, int blendmode, int isolated, const fz_pixmap * restrict shape); +void fz_blend_pixmap_knockout(fz_context *ctx, fz_pixmap * restrict dst, fz_pixmap * restrict src, const fz_pixmap * restrict shape); void fz_paint_glyph(const unsigned char * restrict colorbv, fz_pixmap * restrict dst, unsigned char * restrict dp, const fz_glyph * restrict glyph, int w, int h, int skip_x, int skip_y, const fz_overprint * restrict eop); |