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
+}