diff mupdf-source/source/fitz/color-fast.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/color-fast.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,1736 @@
+// Copyright (C) 2004-2024 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 "color-imp.h"
+
+#include <math.h>
+
+/* Fast color transforms */
+
+static void gray_to_gray(fz_context *ctx, fz_color_converter *cc, const float *gray, float *xyz)
+{
+	xyz[0] = gray[0];
+}
+
+static void gray_to_rgb(fz_context *ctx, fz_color_converter *cc, const float *gray, float *rgb)
+{
+	rgb[0] = gray[0];
+	rgb[1] = gray[0];
+	rgb[2] = gray[0];
+}
+
+static void rgb_to_gray(fz_context *ctx, fz_color_converter *cc, const float *rgb, float *gray)
+{
+	gray[0] = rgb[0] * 0.3f + rgb[1] * 0.59f + rgb[2] * 0.11f;
+}
+
+static void bgr_to_gray(fz_context *ctx, fz_color_converter *cc, const float *bgr, float *gray)
+{
+	gray[0] = bgr[0] * 0.11f + bgr[1] * 0.59f + bgr[2] * 0.3f;
+}
+
+static void rgb_to_rgb(fz_context *ctx, fz_color_converter *cc, const float *rgb, float *xyz)
+{
+	xyz[0] = rgb[0];
+	xyz[1] = rgb[1];
+	xyz[2] = rgb[2];
+}
+
+static void rgb_to_bgr(fz_context *ctx, fz_color_converter *cc, const float *rgb, float *bgr)
+{
+	bgr[0] = rgb[2];
+	bgr[1] = rgb[1];
+	bgr[2] = rgb[0];
+}
+
+static void cmyk_to_cmyk(fz_context *ctx, fz_color_converter *cc, const float *cmyk, float *xyz)
+{
+	xyz[0] = cmyk[0];
+	xyz[1] = cmyk[1];
+	xyz[2] = cmyk[2];
+	xyz[3] = cmyk[3];
+}
+
+static void gray_to_cmyk(fz_context *ctx, fz_color_converter *cc, const float *gray, float *cmyk)
+{
+	cmyk[0] = 0;
+	cmyk[1] = 0;
+	cmyk[2] = 0;
+	cmyk[3] = 1 - gray[0];
+}
+
+static void cmyk_to_gray(fz_context *ctx, fz_color_converter *cc, const float *cmyk, float *gray)
+{
+	float c = cmyk[0] * 0.3f;
+	float m = cmyk[1] * 0.59f;
+	float y = cmyk[2] * 0.11f;
+	gray[0] = 1 - fz_min(c + m + y + cmyk[3], 1);
+}
+
+static void rgb_to_cmyk(fz_context *ctx, fz_color_converter *cc, const float *rgb, float *cmyk)
+{
+	float c, m, y, k;
+	c = 1 - rgb[0];
+	m = 1 - rgb[1];
+	y = 1 - rgb[2];
+	k = fz_min(c, fz_min(m, y));
+	cmyk[0] = c - k;
+	cmyk[1] = m - k;
+	cmyk[2] = y - k;
+	cmyk[3] = k;
+}
+
+static void bgr_to_cmyk(fz_context *ctx, fz_color_converter *cc, const float *bgr, float *cmyk)
+{
+	float c, m, y, k;
+	c = 1 - bgr[2];
+	m = 1 - bgr[1];
+	y = 1 - bgr[0];
+	k = fz_min(c, fz_min(m, y));
+	cmyk[0] = c - k;
+	cmyk[1] = m - k;
+	cmyk[2] = y - k;
+	cmyk[3] = k;
+}
+
+static void cmyk_to_rgb(fz_context *ctx, fz_color_converter *cc, const float *cmyk, float *rgb)
+{
+	rgb[0] = 1 - fz_min(1, cmyk[0] + cmyk[3]);
+	rgb[1] = 1 - fz_min(1, cmyk[1] + cmyk[3]);
+	rgb[2] = 1 - fz_min(1, cmyk[2] + cmyk[3]);
+}
+
+static void cmyk_to_bgr(fz_context *ctx, fz_color_converter *cc, const float *cmyk, float *bgr)
+{
+	bgr[0] = 1 - fz_min(cmyk[2] + cmyk[3], 1);
+	bgr[1] = 1 - fz_min(cmyk[1] + cmyk[3], 1);
+	bgr[2] = 1 - fz_min(cmyk[0] + cmyk[3], 1);
+}
+
+static inline float fung(float x)
+{
+	if (x >= 6.0f / 29.0f)
+		return x * x * x;
+	return (108.0f / 841.0f) * (x - (4.0f / 29.0f));
+}
+
+static void lab_to_rgb(fz_context *ctx, fz_color_converter *cc, const float *lab, float *rgb)
+{
+	/* input is in range (0..100, -128..127, -128..127) not (0..1, 0..1, 0..1) */
+	float lstar, astar, bstar, l, m, n, x, y, z, r, g, b;
+	lstar = lab[0];
+	astar = lab[1];
+	bstar = lab[2];
+	m = (lstar + 16) / 116;
+	l = m + astar / 500;
+	n = m - bstar / 200;
+	x = fung(l);
+	y = fung(m);
+	z = fung(n);
+	r = (3.240449f * x + -1.537136f * y + -0.498531f * z) * 0.830026f;
+	g = (-0.969265f * x + 1.876011f * y + 0.041556f * z) * 1.05452f;
+	b = (0.055643f * x + -0.204026f * y + 1.057229f * z) * 1.1003f;
+	rgb[0] = sqrtf(fz_clamp(r, 0, 1));
+	rgb[1] = sqrtf(fz_clamp(g, 0, 1));
+	rgb[2] = sqrtf(fz_clamp(b, 0, 1));
+}
+
+static void lab_to_gray(fz_context *ctx, fz_color_converter *cc, const float *lab, float *gray)
+{
+	gray[0] = lab[0] / 100;
+}
+
+static void lab_to_bgr(fz_context *ctx, fz_color_converter *cc, const float *lab, float *bgr)
+{
+	float rgb[3];
+	lab_to_rgb(ctx, cc, lab, rgb);
+	rgb_to_bgr(ctx, cc, rgb, bgr);
+}
+
+static void lab_to_cmyk(fz_context *ctx, fz_color_converter *cc, const float *lab, float *cmyk)
+{
+	float rgb[3];
+	lab_to_rgb(ctx, cc, lab, rgb);
+	rgb_to_cmyk(ctx, cc, rgb, cmyk);
+}
+
+fz_color_convert_fn *
+fz_lookup_fast_color_converter(fz_context *ctx, fz_colorspace *ss, fz_colorspace *ds)
+{
+	int stype = ss->type;
+	int dtype = ds->type;
+
+	if (stype == FZ_COLORSPACE_GRAY)
+	{
+		if (dtype == FZ_COLORSPACE_GRAY) return gray_to_gray;
+		if (dtype == FZ_COLORSPACE_RGB) return gray_to_rgb;
+		if (dtype == FZ_COLORSPACE_BGR) return gray_to_rgb;
+		if (dtype == FZ_COLORSPACE_CMYK) return gray_to_cmyk;
+	}
+
+	else if (stype == FZ_COLORSPACE_RGB)
+	{
+		if (dtype == FZ_COLORSPACE_GRAY) return rgb_to_gray;
+		if (dtype == FZ_COLORSPACE_RGB) return rgb_to_rgb;
+		if (dtype == FZ_COLORSPACE_BGR) return rgb_to_bgr;
+		if (dtype == FZ_COLORSPACE_CMYK) return rgb_to_cmyk;
+	}
+
+	else if (stype == FZ_COLORSPACE_BGR)
+	{
+		if (dtype == FZ_COLORSPACE_GRAY) return bgr_to_gray;
+		if (dtype == FZ_COLORSPACE_RGB) return rgb_to_bgr;
+		if (dtype == FZ_COLORSPACE_BGR) return rgb_to_rgb;
+		if (dtype == FZ_COLORSPACE_CMYK) return bgr_to_cmyk;
+	}
+
+	else if (stype == FZ_COLORSPACE_CMYK)
+	{
+		if (dtype == FZ_COLORSPACE_GRAY) return cmyk_to_gray;
+		if (dtype == FZ_COLORSPACE_RGB) return cmyk_to_rgb;
+		if (dtype == FZ_COLORSPACE_BGR) return cmyk_to_bgr;
+		if (dtype == FZ_COLORSPACE_CMYK) return cmyk_to_cmyk;
+	}
+
+	else if (stype == FZ_COLORSPACE_LAB)
+	{
+		if (dtype == FZ_COLORSPACE_GRAY) return lab_to_gray;
+		if (dtype == FZ_COLORSPACE_RGB) return lab_to_rgb;
+		if (dtype == FZ_COLORSPACE_BGR) return lab_to_bgr;
+		if (dtype == FZ_COLORSPACE_CMYK) return lab_to_cmyk;
+	}
+
+	fz_throw(ctx, FZ_ERROR_ARGUMENT, "cannot find color converter");
+}
+
+/* Fast pixmap color conversions */
+
+static void fast_gray_to_rgb(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, int copy_spots)
+{
+	unsigned char *s = src->samples;
+	unsigned char *d = dst->samples;
+	size_t w = src->w;
+	int h = src->h;
+	int sn = src->n;
+	int ss = src->s;
+	int sa = src->alpha;
+	int dn = dst->n;
+	int ds = dst->s;
+	int da = dst->alpha;
+	ptrdiff_t d_line_inc = dst->stride - w * dn;
+	ptrdiff_t s_line_inc = src->stride - w * sn;
+
+	/* If copying spots, they must match, and we can never drop alpha (but we can invent it) */
+	if (copy_spots && ss != ds)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "incompatible number of spots when converting pixmap");
+	if (!da && sa)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "cannot drop alpha when converting pixmap");
+
+	if ((int)w < 0 || h < 0)
+		return;
+
+	if (d_line_inc == 0 && s_line_inc == 0)
+	{
+		w *= h;
+		h = 1;
+	}
+
+	if (ss == 0 && ds == 0)
+	{
+		/* Common, no spots case */
+		if (da)
+		{
+			if (sa)
+			{
+				while (h--)
+				{
+					size_t ww = w;
+					while (ww--)
+					{
+						d[0] = s[0];
+						d[1] = s[0];
+						d[2] = s[0];
+						d[3] = s[1];
+						s += 2;
+						d += 4;
+					}
+					d += d_line_inc;
+					s += s_line_inc;
+				}
+			}
+			else
+			{
+				while (h--)
+				{
+					size_t ww = w;
+					while (ww--)
+					{
+						d[0] = s[0];
+						d[1] = s[0];
+						d[2] = s[0];
+						d[3] = 255;
+						s++;
+						d += 4;
+					}
+					d += d_line_inc;
+					s += s_line_inc;
+				}
+			}
+		}
+		else
+		{
+			while (h--)
+			{
+				size_t ww = w;
+				while (ww--)
+				{
+					d[0] = s[0];
+					d[1] = s[0];
+					d[2] = s[0];
+					s++;
+					d += 3;
+				}
+				d += d_line_inc;
+				s += s_line_inc;
+			}
+		}
+	}
+	else if (copy_spots)
+	{
+		/* Slower, spots capable version */
+		int i;
+		while (h--)
+		{
+			size_t ww = w;
+			while (ww--)
+			{
+				d[0] = s[0];
+				d[1] = s[0];
+				d[2] = s[0];
+				s += 1;
+				d += 3;
+				for (i=ss; i > 0; i--)
+					*d++ = *s++;
+				if (da)
+					*d++ = sa ? *s++ : 255;
+			}
+			d += d_line_inc;
+			s += s_line_inc;
+		}
+	}
+	else
+	{
+		while (h--)
+		{
+			size_t ww = w;
+			while (ww--)
+			{
+				d[0] = s[0];
+				d[1] = s[0];
+				d[2] = s[0];
+				s += sn;
+				d += dn;
+				if (da)
+					d[-1] = sa ? s[-1] : 255;
+			}
+			d += d_line_inc;
+			s += s_line_inc;
+		}
+	}
+}
+
+static void fast_gray_to_cmyk(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, int copy_spots)
+{
+	unsigned char *s = src->samples;
+	unsigned char *d = dst->samples;
+	size_t w = src->w;
+	int h = src->h;
+	int sn = src->n;
+	int ss = src->s;
+	int sa = src->alpha;
+	int dn = dst->n;
+	int ds = dst->s;
+	int da = dst->alpha;
+	ptrdiff_t d_line_inc = dst->stride - w * dn;
+	ptrdiff_t s_line_inc = src->stride - w * sn;
+	int k, g;
+	int a = 255;
+	int i;
+
+	if (copy_spots && ss != ds)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "incompatible number of spots when converting pixmap");
+
+	if ((int)w < 0 || h < 0)
+		fz_throw(ctx, FZ_ERROR_LIMIT, "integer overflow");
+
+	while (h--)
+	{
+		size_t ww = w;
+		while (ww--)
+		{
+			g = s[0];
+
+			if (sa)
+			{
+				a = s[1+ss];
+				g = fz_div255(g, a);
+			}
+
+			k = 255 - g;
+
+			if (da)
+			{
+				*d++ = 0;
+				*d++ = 0;
+				*d++ = 0;
+				*d++ = fz_mul255(k, a);
+			}
+			else
+			{
+				*d++ = 0;
+				*d++ = 0;
+				*d++ = 0;
+				*d++ = k;
+			}
+
+			if (copy_spots)
+			{
+				s += 1;
+				for (i=ss; i > 0; --i)
+					*d++ = *s++;
+				s += sa;
+			}
+			else
+			{
+				s += 1 + ss + sa;
+				d += ds;
+			}
+
+			if (da)
+			{
+				*d++ = a;
+			}
+		}
+		d += d_line_inc;
+		s += s_line_inc;
+	}
+}
+
+static void fast_rgb_to_gray(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, int copy_spots)
+{
+	unsigned char *s = src->samples;
+	unsigned char *d = dst->samples;
+	size_t w = src->w;
+	int h = src->h;
+	int sn = src->n;
+	int ss = src->s;
+	int sa = src->alpha;
+	int dn = dst->n;
+	int ds = dst->s;
+	int da = dst->alpha;
+	ptrdiff_t d_line_inc = dst->stride - w * dn;
+	ptrdiff_t s_line_inc = src->stride - w * sn;
+
+	/* If copying spots, they must match, and we can never drop alpha (but we can invent it) */
+	if (copy_spots && ss != ds)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "incompatible number of spots when converting pixmap");
+	if (!da && sa)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "cannot drop alpha when converting pixmap");
+
+	if ((int)w < 0 || h < 0)
+		return;
+
+	if (d_line_inc == 0 && s_line_inc == 0)
+	{
+		w *= h;
+		h = 1;
+	}
+
+	if (ss == 0 && ds == 0)
+	{
+		/* Common, no spots case */
+		if (da)
+		{
+			if (sa)
+			{
+				while (h--)
+				{
+					size_t ww = w;
+					while (ww--)
+					{
+						d[0] = ((s[0]+1) * 77 + (s[1]+1) * 150 + (s[2]+1) * 28) >> 8;
+						d[1] = s[3];
+						s += 4;
+						d += 2;
+					}
+					d += d_line_inc;
+					s += s_line_inc;
+				}
+			}
+			else
+			{
+				while (h--)
+				{
+					size_t ww = w;
+					while (ww--)
+					{
+						d[0] = ((s[0]+1) * 77 + (s[1]+1) * 150 + (s[2]+1) * 28) >> 8;
+						d[1] = 255;
+						s += 3;
+						d += 2;
+					}
+					d += d_line_inc;
+					s += s_line_inc;
+				}
+			}
+		}
+		else
+		{
+			while (h--)
+			{
+				size_t ww = w;
+				while (ww--)
+				{
+					d[0] = ((s[0]+1) * 77 + (s[1]+1) * 150 + (s[2]+1) * 28) >> 8;
+					s += 3;
+					d++;
+				}
+				d += d_line_inc;
+				s += s_line_inc;
+			}
+		}
+	}
+	else if (copy_spots)
+	{
+		/* Slower, spots capable version */
+		int i;
+		while (h--)
+		{
+			size_t ww = w;
+			while (ww--)
+			{
+				d[0] = ((s[0]+1) * 77 + (s[1]+1) * 150 + (s[2]+1) * 28) >> 8;
+				s += 3;
+				d++;
+				for (i=ss; i > 0; i--)
+					*d++ = *s++;
+				if (da)
+					*d++ = sa ? *s++ : 255;
+			}
+			d += d_line_inc;
+			s += s_line_inc;
+		}
+	}
+	else
+	{
+		while (h--)
+		{
+			size_t ww = w;
+			while (ww--)
+			{
+				d[0] = ((s[0]+1) * 77 + (s[1]+1) * 150 + (s[2]+1) * 28) >> 8;
+				s += sn;
+				d += dn;
+				if (da)
+					d[-1] = sa ? s[-1] : 255;
+			}
+			d += d_line_inc;
+			s += s_line_inc;
+		}
+	}
+}
+
+static void fast_bgr_to_gray(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, int copy_spots)
+{
+	unsigned char *s = src->samples;
+	unsigned char *d = dst->samples;
+	size_t w = src->w;
+	int h = src->h;
+	int sn = src->n;
+	int ss = src->s;
+	int sa = src->alpha;
+	int dn = dst->n;
+	int ds = dst->s;
+	int da = dst->alpha;
+	ptrdiff_t d_line_inc = dst->stride - w * dn;
+	ptrdiff_t s_line_inc = src->stride - w * sn;
+
+	/* If copying spots, they must match, and we can never drop alpha (but we can invent it) */
+	if (copy_spots && ss != ds)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "incompatible number of spots when converting pixmap");
+	if (!da && sa)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "cannot drop alpha when converting pixmap");
+
+	if ((int)w < 0 || h < 0)
+		return;
+
+	if (d_line_inc == 0 && s_line_inc == 0)
+	{
+		w *= h;
+		h = 1;
+	}
+
+	if (ss == 0 && ds == 0)
+	{
+		/* Common, no spots case */
+		if (da)
+		{
+			if (sa)
+			{
+				while (h--)
+				{
+					size_t ww = w;
+					while (ww--)
+					{
+						d[0] = ((s[0]+1) * 28 + (s[1]+1) * 150 + (s[2]+1) * 77) >> 8;
+						d[1] = s[3];
+						s += 4;
+						d += 2;
+					}
+					d += d_line_inc;
+					s += s_line_inc;
+				}
+			}
+			else
+			{
+				while (h--)
+				{
+					size_t ww = w;
+					while (ww--)
+					{
+						d[0] = ((s[0]+1) * 28 + (s[1]+1) * 150 + (s[2]+1) * 77) >> 8;
+						d[1] = 255;
+						s += 3;
+						d += 2;
+					}
+					d += d_line_inc;
+					s += s_line_inc;
+				}
+			}
+		}
+		else
+		{
+			int si = 3 + src->alpha;
+
+			while (h--)
+			{
+				size_t ww = w;
+				while (ww--)
+				{
+					d[0] = ((s[0]+1) * 28 + (s[1]+1) * 150 + (s[2]+1) * 77) >> 8;
+					s += si;
+					d++;
+				}
+				d += d_line_inc;
+				s += s_line_inc;
+			}
+		}
+	}
+	else if (copy_spots)
+	{
+		/* Slower, spots capable version */
+		while (h--)
+		{
+			int i;
+			size_t ww = w;
+			while (ww--)
+			{
+				d[0] = ((s[0]+1) * 28 + (s[1]+1) * 150 + (s[2]+1) * 77) >> 8;
+				s += 3;
+				d++;
+				for (i=ss; i > 0; i--)
+					*d++ = *s++;
+				if (da)
+					*d++ = sa ? *s++ : 255;
+			}
+			d += d_line_inc;
+			s += s_line_inc;
+		}
+	}
+	else
+	{
+		/* Slower, spots capable version */
+		while (h--)
+		{
+			size_t ww = w;
+			while (ww--)
+			{
+				d[0] = ((s[0]+1) * 28 + (s[1]+1) * 150 + (s[2]+1) * 77) >> 8;
+				s += sn;
+				d += dn;
+				if (da)
+					d[-1] = sa ? s[-1] : 255;
+			}
+			d += d_line_inc;
+			s += s_line_inc;
+		}
+	}
+}
+
+static void fast_rgb_to_cmyk(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, int copy_spots)
+{
+	unsigned char *s = src->samples;
+	unsigned char *d = dst->samples;
+	size_t w = src->w;
+	int h = src->h;
+	int sn = src->n;
+	int ss = src->s;
+	int sa = src->alpha;
+	int dn = dst->n;
+	int ds = dst->s;
+	int da = dst->alpha;
+	ptrdiff_t d_line_inc = dst->stride - w * dn;
+	ptrdiff_t s_line_inc = src->stride - w * sn;
+	int c, m, y, k, r, g, b;
+	int a = 255;
+	int i;
+
+	if (copy_spots && ss != ds)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "incompatible number of spots when converting pixmap");
+
+	if ((int)w < 0 || h < 0)
+		fz_throw(ctx, FZ_ERROR_LIMIT, "integer overflow");
+
+	while (h--)
+	{
+		size_t ww = w;
+		while (ww--)
+		{
+			r = s[0];
+			g = s[1];
+			b = s[2];
+
+			if (sa)
+			{
+				a = s[3+ss];
+				r = fz_div255(r, a);
+				g = fz_div255(g, a);
+				b = fz_div255(b, a);
+			}
+
+			c = 255 - r;
+			m = 255 - g;
+			y = 255 - b;
+			k = fz_mini(c, fz_mini(m, y));
+			c = c - k;
+			m = m - k;
+			y = y - k;
+
+			if (da)
+			{
+				*d++ = fz_mul255(c, a);
+				*d++ = fz_mul255(m, a);
+				*d++ = fz_mul255(y, a);
+				*d++ = fz_mul255(k, a);
+			}
+			else
+			{
+				*d++ = c;
+				*d++ = m;
+				*d++ = y;
+				*d++ = k;
+			}
+
+			if (copy_spots)
+			{
+				s += 3;
+				for (i=ss; i > 0; --i)
+					*d++ = *s++;
+				s += sa;
+			}
+			else
+			{
+				s += 3 + ss + sa;
+				d += ds;
+			}
+
+			if (da)
+			{
+				*d++ = a;
+			}
+		}
+		d += d_line_inc;
+		s += s_line_inc;
+	}
+}
+
+static void fast_bgr_to_cmyk(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, int copy_spots)
+{
+	unsigned char *s = src->samples;
+	unsigned char *d = dst->samples;
+	size_t w = src->w;
+	int h = src->h;
+	int sn = src->n;
+	int ss = src->s;
+	int sa = src->alpha;
+	int dn = dst->n;
+	int ds = dst->s;
+	int da = dst->alpha;
+	ptrdiff_t d_line_inc = dst->stride - w * dn;
+	ptrdiff_t s_line_inc = src->stride - w * sn;
+	int c, m, y, k, r, g, b;
+	int a = 255;
+	int i;
+
+	if (copy_spots && ss != ds)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "incompatible number of spots when converting pixmap");
+
+	if ((int)w < 0 || h < 0)
+		fz_throw(ctx, FZ_ERROR_LIMIT, "integer overflow");
+
+	while (h--)
+	{
+		size_t ww = w;
+		while (ww--)
+		{
+			b = s[0];
+			g = s[1];
+			r = s[2];
+
+			if (sa)
+			{
+				a = s[3+ss];
+				r = fz_div255(r, a);
+				g = fz_div255(g, a);
+				b = fz_div255(b, a);
+			}
+
+			c = 255 - r;
+			m = 255 - g;
+			y = 255 - b;
+			k = fz_mini(c, fz_mini(m, y));
+			c = c - k;
+			m = m - k;
+			y = y - k;
+
+			if (da)
+			{
+				*d++ = fz_mul255(c, a);
+				*d++ = fz_mul255(m, a);
+				*d++ = fz_mul255(y, a);
+				*d++ = fz_mul255(k, a);
+			}
+			else
+			{
+				*d++ = c;
+				*d++ = m;
+				*d++ = y;
+				*d++ = k;
+			}
+
+			if (copy_spots)
+			{
+				s += 3;
+				for (i=ss; i > 0; --i)
+					*d++ = *s++;
+				s += sa;
+			}
+			else
+			{
+				s += 3 + ss + sa;
+				d += ds;
+			}
+
+			if (da)
+			{
+				*d++ = a;
+			}
+		}
+		d += d_line_inc;
+		s += s_line_inc;
+	}
+}
+
+static void fast_cmyk_to_gray(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, int copy_spots)
+{
+	unsigned char *s = src->samples;
+	unsigned char *d = dst->samples;
+	size_t w = src->w;
+	int h = src->h;
+	int sn = src->n;
+	int ss = src->s;
+	int sa = src->alpha;
+	int dn = dst->n;
+	int ds = dst->s;
+	int da = dst->alpha;
+	ptrdiff_t d_line_inc = dst->stride - w * dn;
+	ptrdiff_t s_line_inc = src->stride - w * sn;
+	int c, m, y, k, g;
+	int a = 255;
+	int i;
+
+	if (copy_spots && ss != ds)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "incompatible number of spots when converting pixmap");
+
+	if ((int)w < 0 || h < 0)
+		fz_throw(ctx, FZ_ERROR_LIMIT, "integer overflow");
+
+	while (h--)
+	{
+		size_t ww = w;
+		while (ww--)
+		{
+			c = s[0];
+			m = s[1];
+			y = s[2];
+			k = s[3];
+
+			if (sa)
+			{
+				a = s[4+ss];
+				c = fz_div255(c, a);
+				m = fz_div255(m, a);
+				y = fz_div255(y, a);
+				k = fz_div255(k, a);
+			}
+
+			g = 255 - fz_mini(c + m + y + k, 255);
+
+			if (da)
+			{
+				*d++ = fz_mul255(g, a);
+			}
+			else
+			{
+				*d++ = g;
+			}
+
+			if (copy_spots)
+			{
+				s += 4;
+				for (i=ss; i > 0; --i)
+					*d++ = *s++;
+				s += sa;
+			}
+			else
+			{
+				s += 4 + ss + sa;
+				d += ds;
+			}
+
+			if (da)
+			{
+				*d++ = a;
+			}
+		}
+		d += d_line_inc;
+		s += s_line_inc;
+	}
+}
+
+static void fast_cmyk_to_rgb(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, int copy_spots)
+{
+	unsigned char *s = src->samples;
+	unsigned char *d = dst->samples;
+	size_t w = src->w;
+	int h = src->h;
+	int sn = src->n;
+	int ss = src->s;
+	int sa = src->alpha;
+	int dn = dst->n;
+	int ds = dst->s;
+	int da = dst->alpha;
+	ptrdiff_t d_line_inc = dst->stride - w * dn;
+	ptrdiff_t s_line_inc = src->stride - w * sn;
+	int c, m, y, k, r, g, b;
+	int a = 255;
+	int i;
+
+	if (copy_spots && ss != ds)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "incompatible number of spots when converting pixmap");
+
+	if ((int)w < 0 || h < 0)
+		fz_throw(ctx, FZ_ERROR_LIMIT, "integer overflow");
+
+	while (h--)
+	{
+		size_t ww = w;
+		while (ww--)
+		{
+			c = s[0];
+			m = s[1];
+			y = s[2];
+			k = s[3];
+
+			if (sa)
+			{
+				a = s[4+ss];
+				c = fz_div255(c, a);
+				m = fz_div255(m, a);
+				y = fz_div255(y, a);
+				k = fz_div255(k, a);
+			}
+
+			r = 255 - fz_mini(c + k, 255);
+			g = 255 - fz_mini(m + k, 255);
+			b = 255 - fz_mini(y + k, 255);
+
+			if (da)
+			{
+				*d++ = fz_mul255(r, a);
+				*d++ = fz_mul255(g, a);
+				*d++ = fz_mul255(b, a);
+			}
+			else
+			{
+				*d++ = r;
+				*d++ = g;
+				*d++ = b;
+			}
+
+			if (copy_spots)
+			{
+				s += 4;
+				for (i=ss; i > 0; --i)
+					*d++ = *s++;
+				s += sa;
+			}
+			else
+			{
+				s += 4 + ss + sa;
+				d += ds;
+			}
+
+			if (da)
+			{
+				*d++ = a;
+			}
+		}
+		d += d_line_inc;
+		s += s_line_inc;
+	}
+}
+
+static void fast_cmyk_to_bgr(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, int copy_spots)
+{
+	unsigned char *s = src->samples;
+	unsigned char *d = dst->samples;
+	size_t w = src->w;
+	int h = src->h;
+	int sn = src->n;
+	int ss = src->s;
+	int sa = src->alpha;
+	int dn = dst->n;
+	int ds = dst->s;
+	int da = dst->alpha;
+	ptrdiff_t d_line_inc = dst->stride - w * dn;
+	ptrdiff_t s_line_inc = src->stride - w * sn;
+	int c, m, y, k, r, g, b;
+	int a = 255;
+	int i;
+
+	if (copy_spots && ss != ds)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "incompatible number of spots when converting pixmap");
+
+	if ((int)w < 0 || h < 0)
+		fz_throw(ctx, FZ_ERROR_LIMIT, "integer overflow");
+
+	while (h--)
+	{
+		size_t ww = w;
+		while (ww--)
+		{
+			c = s[0];
+			m = s[1];
+			y = s[2];
+			k = s[3];
+
+			if (sa)
+			{
+				a = s[4+ss];
+				c = fz_div255(c, a);
+				m = fz_div255(m, a);
+				y = fz_div255(y, a);
+				k = fz_div255(k, a);
+			}
+
+			r = 255 - fz_mini(c + k, 255);
+			g = 255 - fz_mini(m + k, 255);
+			b = 255 - fz_mini(y + k, 255);
+
+			if (da)
+			{
+				*d++ = fz_mul255(b, a);
+				*d++ = fz_mul255(g, a);
+				*d++ = fz_mul255(r, a);
+			}
+			else
+			{
+				*d++ = b;
+				*d++ = g;
+				*d++ = r;
+			}
+
+			if (copy_spots)
+			{
+				s += 4;
+				for (i=ss; i > 0; --i)
+					*d++ = *s++;
+				s += sa;
+			}
+			else
+			{
+				s += 4 + ss + sa;
+				d += ds;
+			}
+
+			if (da)
+			{
+				*d++ = a;
+			}
+		}
+		d += d_line_inc;
+		s += s_line_inc;
+	}
+}
+
+static void fast_rgb_to_bgr(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, int copy_spots)
+{
+	unsigned char *s = src->samples;
+	unsigned char *d = dst->samples;
+	size_t w = src->w;
+	int h = src->h;
+	int sn = src->n;
+	int ss = src->s;
+	int sa = src->alpha;
+	int dn = dst->n;
+	int ds = dst->s;
+	int da = dst->alpha;
+	ptrdiff_t d_line_inc = dst->stride - w * dn;
+	ptrdiff_t s_line_inc = src->stride - w * sn;
+
+	/* If copying spots, they must match, and we can never drop alpha (but we can invent it) */
+	if (copy_spots && ss != ds)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "incompatible number of spots when converting pixmap");
+	if (!da && sa)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "cannot drop alpha when converting pixmap");
+
+	if ((int)w < 0 || h < 0)
+		return;
+
+	if (d_line_inc == 0 && s_line_inc == 0)
+	{
+		w *= h;
+		h = 1;
+	}
+
+	if (ss == 0 && ds == 0)
+	{
+		/* Common, no spots case */
+		if (da)
+		{
+			if (sa)
+			{
+				while (h--)
+				{
+					size_t ww = w;
+					while (ww--)
+					{
+						d[0] = s[2];
+						d[1] = s[1];
+						d[2] = s[0];
+						d[3] = s[3];
+						s += 4;
+						d += 4;
+					}
+				}
+			}
+			else
+			{
+				while (h--)
+				{
+					size_t ww = w;
+					while (ww--)
+					{
+						d[0] = s[2];
+						d[1] = s[1];
+						d[2] = s[0];
+						d[3] = 255;
+						s += 3;
+						d += 4;
+					}
+				}
+			}
+		}
+		else
+		{
+			while (h--)
+			{
+				size_t ww = w;
+				while (ww--)
+				{
+					d[0] = s[2];
+					d[1] = s[1];
+					d[2] = s[0];
+					s += 3;
+					d += 3;
+				}
+			}
+		}
+	}
+	else if (copy_spots)
+	{
+		/* Slower, spots capable version */
+		while (h--)
+		{
+			int i;
+			size_t ww = w;
+			while (ww--)
+			{
+				d[0] = s[2];
+				d[1] = s[1];
+				d[2] = s[0];
+				s += 3;
+				d += 3;
+				for (i=ss; i > 0; i--)
+					*d++ = *s++;
+				if (da)
+					*d++ = sa ? *s++ : 255;
+			}
+			d += d_line_inc;
+			s += s_line_inc;
+		}
+	}
+	else
+	{
+		while (h--)
+		{
+			size_t ww = w;
+			while (ww--)
+			{
+				d[0] = s[2];
+				d[1] = s[1];
+				d[2] = s[0];
+				s += sn;
+				d += dn;
+				if (da)
+					d[-1] = sa ? s[-1] : 255;
+			}
+			d += d_line_inc;
+			s += s_line_inc;
+		}
+	}
+}
+
+static void fast_gray_to_gray(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, int copy_spots)
+{
+	unsigned char *s = src->samples;
+	unsigned char *d = dst->samples;
+	size_t w = src->w;
+	int h = src->h;
+	int sn = src->n;
+	int ss = src->s;
+	int sa = src->alpha;
+	int dn = dst->n;
+	int ds = dst->s;
+	int da = dst->alpha;
+	ptrdiff_t d_line_inc = dst->stride - w * dn;
+	ptrdiff_t s_line_inc = src->stride - w * sn;
+
+	if ((int)w < 0 || h < 0)
+		return;
+
+	/* Alpha-only pixmaps count as device_gray with no alpha. */
+	if (sn == 1 && sa)
+		sa = 0;
+	if (dn == 1 && da)
+		da = 0;
+
+	/* If copying spots, they must match, and we can never drop alpha (but we can invent it) */
+	if (copy_spots && ss != ds)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "incompatible number of spots when converting pixmap");
+	if (!da && sa)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "cannot drop alpha when converting pixmap");
+
+	if (d_line_inc == 0 && s_line_inc == 0)
+	{
+		w *= h;
+		h = 1;
+	}
+
+	if (ss == 0 && ds == 0)
+	{
+		/* Common, no spots case */
+		if (da)
+		{
+			if (sa)
+			{
+				while (h--)
+				{
+					size_t ww = w;
+					while (ww--)
+					{
+						d[0] = s[0];
+						d[1] = s[1];
+						s += 2;
+						d += 2;
+					}
+				}
+			}
+			else
+			{
+				while (h--)
+				{
+					size_t ww = w;
+					while (ww--)
+					{
+						d[0] = s[0];
+						d[1] = 255;
+						s += 1;
+						d += 2;
+					}
+				}
+			}
+		}
+		else
+		{
+			while (h--)
+			{
+				size_t ww = w;
+				while (ww--)
+				{
+					d[0] = s[0];
+					s += 1;
+					d += 1;
+				}
+			}
+		}
+	}
+	else if (copy_spots)
+	{
+		/* Slower, spots capable version */
+		while (h--)
+		{
+			int i;
+			size_t ww = w;
+			while (ww--)
+			{
+				d[0] = s[0];
+				s += 1;
+				d += 1;
+				for (i=ss; i > 0; i--)
+					*d++ = *s++;
+				if (da)
+					*d++ = sa ? *s++ : 255;
+			}
+			d += d_line_inc;
+			s += s_line_inc;
+		}
+	}
+	else
+	{
+		while (h--)
+		{
+			size_t ww = w;
+			while (ww--)
+			{
+				d[0] = s[0];
+				s += sn;
+				d += dn;
+				if (da)
+					d[-1] = sa ? s[-1] : 255;
+			}
+			d += d_line_inc;
+			s += s_line_inc;
+		}
+	}
+}
+
+static void fast_rgb_to_rgb(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, int copy_spots)
+{
+	unsigned char *s = src->samples;
+	unsigned char *d = dst->samples;
+	size_t w = src->w;
+	int h = src->h;
+	int sn = src->n;
+	int ss = src->s;
+	int sa = src->alpha;
+	int dn = dst->n;
+	int ds = dst->s;
+	int da = dst->alpha;
+	ptrdiff_t d_line_inc = dst->stride - w * dn;
+	ptrdiff_t s_line_inc = src->stride - w * sn;
+
+	/* If copying spots, they must match, and we can never drop alpha (but we can invent it) */
+	if (copy_spots && ss != ds)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "incompatible number of spots when converting pixmap");
+	if (!da && sa)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "cannot drop alpha when converting pixmap");
+
+	if ((int)w < 0 || h < 0)
+		return;
+
+	if (d_line_inc == 0 && s_line_inc == 0)
+	{
+		w *= h;
+		h = 1;
+	}
+
+	if (ss == 0 && ds == 0)
+	{
+		/* Common, no spots case */
+		if (da)
+		{
+			if (sa)
+			{
+				while (h--)
+				{
+					size_t ww = w;
+					while (ww--)
+					{
+						d[0] = s[0];
+						d[1] = s[1];
+						d[2] = s[2];
+						d[3] = s[3];
+						s += 4;
+						d += 4;
+					}
+				}
+			}
+			else
+			{
+				while (h--)
+				{
+					size_t ww = w;
+					while (ww--)
+					{
+						d[0] = s[0];
+						d[1] = s[1];
+						d[2] = s[2];
+						d[3] = 255;
+						s += 3;
+						d += 4;
+					}
+				}
+			}
+		}
+		else
+		{
+			while (h--)
+			{
+				size_t ww = w;
+				while (ww--)
+				{
+					d[0] = s[0];
+					d[1] = s[1];
+					d[2] = s[2];
+					s += 3;
+					d += 3;
+				}
+			}
+		}
+	}
+	else if (copy_spots)
+	{
+		/* Slower, spots capable version */
+		while (h--)
+		{
+			int i;
+			size_t ww = w;
+			while (ww--)
+			{
+				d[0] = s[0];
+				d[1] = s[1];
+				d[2] = s[2];
+				s += 3;
+				d += 3;
+				for (i=ss; i > 0; i--)
+					*d++ = *s++;
+				if (da)
+					*d++ = sa ? *s++ : 255;
+			}
+			d += d_line_inc;
+			s += s_line_inc;
+		}
+	}
+	else
+	{
+		while (h--)
+		{
+			size_t ww = w;
+			while (ww--)
+			{
+				d[0] = s[0];
+				d[1] = s[1];
+				d[2] = s[2];
+				s += sn;
+				d += dn;
+				if (da)
+					d[-1] = sa ? s[-1] : 255;
+			}
+			d += d_line_inc;
+			s += s_line_inc;
+		}
+	}
+}
+
+static void fast_cmyk_to_cmyk(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, int copy_spots)
+{
+	unsigned char *s = src->samples;
+	unsigned char *d = dst->samples;
+	size_t w = src->w;
+	int h = src->h;
+	int sn = src->n;
+	int ss = src->s;
+	int sa = src->alpha;
+	int dn = dst->n;
+	int ds = dst->s;
+	int da = dst->alpha;
+	ptrdiff_t d_line_inc = dst->stride - w * dn;
+	ptrdiff_t s_line_inc = src->stride - w * sn;
+
+	/* If copying spots, they must match, and we can never drop alpha (but we can invent it) */
+	if (copy_spots && ss != ds)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "incompatible number of spots when converting pixmap");
+	if (!da && sa)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "cannot drop alpha when converting pixmap");
+
+	if ((int)w < 0 || h < 0)
+		return;
+
+	if (d_line_inc == 0 && s_line_inc == 0)
+	{
+		w *= h;
+		h = 1;
+	}
+
+	if (ss == 0 && ds == 0)
+	{
+		/* Common, no spots case */
+		if (da)
+		{
+			if (sa)
+			{
+				while (h--)
+				{
+					size_t ww = w;
+					while (ww--)
+					{
+						d[0] = s[0];
+						d[1] = s[1];
+						d[2] = s[2];
+						d[3] = s[3];
+						d[4] = s[4];
+						s += 5;
+						d += 5;
+					}
+				}
+			}
+			else
+			{
+				while (h--)
+				{
+					size_t ww = w;
+					while (ww--)
+					{
+						d[0] = s[0];
+						d[1] = s[1];
+						d[2] = s[2];
+						d[3] = s[3];
+						d[4] = 255;
+						s += 4;
+						d += 5;
+					}
+				}
+			}
+		}
+		else
+		{
+			while (h--)
+			{
+				size_t ww = w;
+				while (ww--)
+				{
+					d[0] = s[0];
+					d[1] = s[1];
+					d[2] = s[2];
+					d[3] = s[3];
+					s += 4;
+					d += 4;
+				}
+			}
+		}
+	}
+	else if (copy_spots)
+	{
+		/* Slower, spots capable version */
+		while (h--)
+		{
+			int i;
+			size_t ww = w;
+			while (ww--)
+			{
+				d[0] = s[0];
+				d[1] = s[1];
+				d[2] = s[2];
+				d[3] = s[3];
+				s += 4;
+				d += 4;
+				for (i=ss; i > 0; i--)
+					*d++ = *s++;
+				if (da)
+					*d++ = sa ? *s++ : 255;
+			}
+			d += d_line_inc;
+			s += s_line_inc;
+		}
+	}
+	else
+	{
+		while (h--)
+		{
+			size_t ww = w;
+			while (ww--)
+			{
+				d[0] = s[0];
+				d[1] = s[1];
+				d[2] = s[2];
+				d[3] = s[3];
+				s += sn;
+				d += dn;
+				if (da)
+					d[-1] = sa ? s[-1] : 255;
+			}
+			d += d_line_inc;
+			s += s_line_inc;
+		}
+	}
+}
+
+void
+fz_fast_any_to_alpha(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, int copy_spots)
+{
+	if (!src->alpha)
+		fz_clear_pixmap_with_value(ctx, dst, 255);
+	else
+	{
+		unsigned char *s = src->samples;
+		unsigned char *d = dst->samples;
+		size_t w = src->w;
+		int h = src->h;
+		int n = src->n;
+		ptrdiff_t d_line_inc = dst->stride - w * dst->n;
+		ptrdiff_t s_line_inc = src->stride - w * src->n;
+
+		if ((int)w < 0 || h < 0)
+			return;
+
+		if (d_line_inc == 0 && s_line_inc == 0)
+		{
+			w *= h;
+			h = 1;
+		}
+
+		s += n-1;
+		while (h--)
+		{
+			size_t ww = w;
+			while (ww--)
+			{
+				*d++ = *s;
+				s += n;
+			}
+			d += d_line_inc;
+			s += s_line_inc;
+		}
+	}
+}
+
+void
+fz_convert_fast_pixmap_samples(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, int copy_spots)
+{
+	fz_colorspace *ss = src->colorspace;
+	fz_colorspace *ds = dst->colorspace;
+	int dtype = ds ? ds->type : FZ_COLORSPACE_GRAY;
+	int stype = ss ? ss->type : FZ_COLORSPACE_GRAY;
+
+	if (!ds)
+	{
+		fz_fast_any_to_alpha(ctx, src, dst, copy_spots);
+	}
+
+	else if (stype == FZ_COLORSPACE_GRAY)
+	{
+		if (dtype == FZ_COLORSPACE_GRAY)
+			fast_gray_to_gray(ctx, src, dst, copy_spots);
+		else if (dtype == FZ_COLORSPACE_RGB)
+			fast_gray_to_rgb(ctx, src, dst, copy_spots);
+		else if (dtype == FZ_COLORSPACE_BGR)
+			fast_gray_to_rgb(ctx, src, dst, copy_spots);
+		else if (dtype == FZ_COLORSPACE_CMYK)
+			fast_gray_to_cmyk(ctx, src, dst, copy_spots);
+		else
+			goto slow;
+	}
+
+	else if (stype == FZ_COLORSPACE_RGB)
+	{
+		if (dtype == FZ_COLORSPACE_GRAY)
+			fast_rgb_to_gray(ctx, src, dst, copy_spots);
+		else if (dtype == FZ_COLORSPACE_RGB)
+			fast_rgb_to_rgb(ctx, src, dst, copy_spots);
+		else if (dtype == FZ_COLORSPACE_BGR)
+			fast_rgb_to_bgr(ctx, src, dst, copy_spots);
+		else if (dtype == FZ_COLORSPACE_CMYK)
+			fast_rgb_to_cmyk(ctx, src, dst, copy_spots);
+		else
+			goto slow;
+	}
+
+	else if (stype == FZ_COLORSPACE_BGR)
+	{
+		if (dtype == FZ_COLORSPACE_GRAY)
+			fast_bgr_to_gray(ctx, src, dst, copy_spots);
+		else if (dtype == FZ_COLORSPACE_RGB)
+			fast_rgb_to_bgr(ctx, src, dst, copy_spots);
+		else if (dtype == FZ_COLORSPACE_BGR)
+			fast_rgb_to_rgb(ctx, src, dst, copy_spots);
+		else if (dtype == FZ_COLORSPACE_CMYK)
+			fast_bgr_to_cmyk(ctx, src, dst, copy_spots);
+		else
+			goto slow;
+	}
+
+	else if (stype == FZ_COLORSPACE_CMYK)
+	{
+		if (dtype == FZ_COLORSPACE_GRAY)
+			fast_cmyk_to_gray(ctx, src, dst, copy_spots);
+		else if (dtype == FZ_COLORSPACE_RGB)
+			fast_cmyk_to_rgb(ctx, src, dst, copy_spots);
+		else if (dtype == FZ_COLORSPACE_BGR)
+			fast_cmyk_to_bgr(ctx, src, dst, copy_spots);
+		else if (dtype == FZ_COLORSPACE_CMYK)
+			fast_cmyk_to_cmyk(ctx, src, dst, copy_spots);
+		else
+			goto slow;
+	}
+	else
+	{
+		goto slow;
+	}
+	return;
+
+slow:
+	fz_convert_slow_pixmap_samples(ctx, src, dst, NULL, fz_default_color_params, copy_spots);
+}