diff mupdf-source/source/fitz/load-psd.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/load-psd.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,508 @@
+// 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 "pixmap-imp.h"
+
+#include <limits.h>
+#include <string.h>
+
+struct info
+{
+	unsigned int width, height, n;
+	int xres, yres;
+	fz_colorspace *cs;
+};
+
+typedef struct
+{
+	fz_context *ctx;
+	const unsigned char *p;
+	size_t total;
+	int packbits;
+	int packbits_n;
+	int packbits_rep;
+} source_t;
+
+static int
+get8(source_t *source)
+{
+	if (source->total < 1)
+		fz_throw(source->ctx, FZ_ERROR_FORMAT, "Truncated PSD");
+	source->total--;
+
+	return *source->p++;
+}
+
+static int
+get16be(source_t *source)
+{
+	int v;
+
+	if (source->total < 2)
+	{
+		source->total = 0;
+		fz_throw(source->ctx, FZ_ERROR_FORMAT, "Truncated PSD");
+	}
+
+	source->total -= 2;
+
+	v = *source->p++;
+	v = (v<<8) | *source->p++;
+
+	return v;
+}
+
+static int
+get32be(source_t *source)
+{
+	int v;
+
+	if (source->total < 4)
+	{
+		source->total = 0;
+		fz_throw(source->ctx, FZ_ERROR_FORMAT, "Truncated PSD");
+	}
+
+	source->total -= 4;
+
+	v = *source->p++;
+	v = (v<<8) | *source->p++;
+	v = (v<<8) | *source->p++;
+	v = (v<<8) | *source->p++;
+
+	return v;
+}
+
+static uint32_t
+getu32be(source_t *source)
+{
+	return (uint32_t)get32be(source);
+}
+
+static int
+unpack8(source_t *source)
+{
+	int i;
+
+	if (source->packbits == 0)
+		return get8(source);
+
+	i = source->packbits_n;
+	if (i == 128)
+	{
+		do
+		{
+			i = source->packbits_n = get8(source);
+		}
+		while (i == 128);
+		if (i > 128)
+			source->packbits_rep = get8(source);
+	}
+	if (i < 128)
+	{
+		/* Literal n+1 */
+		i--;
+		if (i < 0)
+			i = 128;
+		source->packbits_n = i;
+		return get8(source);
+	}
+	else
+	{
+		i++;
+		if (i == 257)
+			i = 128;
+		source->packbits_n = i;
+		return source->packbits_rep;
+	}
+}
+
+static char *getString(source_t *source)
+{
+	size_t len = get8(source);
+	size_t odd = !(len & 1);
+	char *s;
+
+	if (source->total < len + odd)
+	{
+		source->total = 0;
+		fz_throw(source->ctx, FZ_ERROR_FORMAT, "Truncated string in PSD");
+	}
+
+	s = fz_malloc(source->ctx, len+1);
+	memcpy(s, source->p, len);
+	s[len] = 0;
+
+	source->p += len + odd;
+	source->total -= len + odd;
+
+	return s;
+}
+
+static fz_pixmap *
+psd_read_image(fz_context *ctx, struct info *info, const unsigned char *p, size_t total, int only_metadata)
+{
+	int v, bpc, c, n;
+	source_t source;
+	size_t ir_len, data_len;
+	fz_separations *seps = NULL;
+	fz_pixmap *image = NULL;
+	size_t m;
+	unsigned char *q;
+	int alpha = 0;
+
+	source.ctx = ctx;
+	source.p = p;
+	source.total = total;
+	source.packbits = 0;
+
+	memset(info, 0, sizeof(*info));
+
+	fz_var(image);
+	fz_var(seps);
+
+	fz_try(ctx)
+	{
+		info->xres = 96;
+		info->yres = 96;
+
+		v = get32be(&source);
+		/* Read signature */
+		if (v != 0x38425053 /* 8BPS */)
+			fz_throw(ctx, FZ_ERROR_FORMAT, "not a psd image (wrong signature)");
+
+		/* Version */
+		v = get16be(&source);
+		if (v != 1)
+			fz_throw(ctx, FZ_ERROR_FORMAT, "Bad PSD version");
+
+		(void)get16be(&source);
+		(void)get32be(&source);
+
+		info->n = n = get16be(&source);
+		info->height = getu32be(&source);
+		info->width = getu32be(&source);
+		bpc = get16be(&source);
+		if (bpc != 8 && bpc != 16)
+			fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "Only 8 or 16 bpc PSD files supported!");
+
+		c = get16be(&source);
+		if (c == 4) /* CMYK (+ Spots?) */
+		{
+			if (n != 4)
+				fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "CMYK PSD with %d chans not supported!", n);
+			info->cs = fz_keep_colorspace(ctx, fz_device_cmyk(ctx));
+		}
+		else if (c == 3) /* RGB */
+		{
+			if (n == 4)
+				alpha = 1;
+			else if (n != 3)
+				fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "RGB PSD with %d chans not supported!", n);
+			info->cs = fz_keep_colorspace(ctx, fz_device_rgb(ctx));
+		}
+		else if (c == 1) /* Greyscale */
+		{
+			if (n != 1)
+				fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "Greyscale PSD with %d chans not supported!", n);
+			info->cs = fz_keep_colorspace(ctx, fz_device_gray(ctx));
+		}
+		else
+			fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "Unsupported PSD colorspace (%d)!", c);
+
+		v = get32be(&source);
+		if (v != 0)
+			fz_throw(ctx, FZ_ERROR_FORMAT, "Unexpected color data in PSD!");
+
+		/* Now read image resources... */
+		ir_len = getu32be(&source);
+		while (ir_len >= 12)
+		{
+			size_t start = source.p - p;
+
+			v = get32be(&source);
+			if (v != 0x3842494d) /* 8BIM */
+				fz_throw(ctx, FZ_ERROR_FORMAT, "Failed to find expected 8BIM in PSD");
+			v = get16be(&source);
+
+			fz_free(ctx, getString(&source));
+
+			data_len = getu32be(&source);
+			ir_len -= (source.p - p) - start;
+			switch (v)
+			{
+			case 0x3ef: /* Spot */
+			{
+				int spots = 0;
+				int alpha_found = 0;
+
+				while (data_len > 0)
+				{
+					int C, M, Y, K;
+					char text[32];
+
+					v = get16be(&source);
+					if (v == 0 && alpha_found == 0)
+						alpha_found = 1, alpha = 1;
+					else if (v != 2)
+						fz_throw(ctx, FZ_ERROR_FORMAT, "Non CMYK spot found in PSD");
+
+					C = 0xff - (get16be(&source)>>8);
+					M = 0xff - (get16be(&source)>>8);
+					Y = 0xff - (get16be(&source)>>8);
+					K = 0xff - (get16be(&source)>>8);
+					(void)get16be(&source); /* opacity */
+					(void)get8(&source); /* kind */
+					(void)get8(&source); /* padding */
+					if (v == 2)
+					{
+						uint32_t cmyk = C | (M<<8) | (Y<<16) | (K<<24);
+						int R = fz_clampi(255-C-K, 0, 255);
+						int G = fz_clampi(255-M-K, 0, 255);
+						int B = fz_clampi(255-Y-K, 0, 255);
+						uint32_t rgba = R | (G<<8) | (B<<16);
+						if (seps == NULL)
+							seps = fz_new_separations(ctx, 1);
+						snprintf(text, sizeof(text), "s%d", spots);
+						/* Use the old entry-point until we fix the new one */
+						fz_add_separation_equivalents(ctx, seps, rgba, cmyk, text);
+						spots++;
+					}
+					data_len -= 14;
+					ir_len -= 14;
+				}
+			}
+			}
+			/* Skip any unread data */
+			if (data_len & 1)
+				data_len++;
+			ir_len -= data_len;
+			while (data_len--)
+				get8(&source);
+		}
+		if (fz_count_separations(ctx, seps) + info->cs->n + 1 == n && alpha == 0)
+			alpha = 1;
+		if (fz_count_separations(ctx, seps) + info->cs->n + alpha != n)
+			fz_throw(ctx, FZ_ERROR_FORMAT, "PSD contains mismatching spot/alpha data");
+
+		/* Skip over the Layer data. */
+		v = get32be(&source);
+		if (v != 0)
+		{
+		  if (source.total < (size_t)v)
+				fz_throw(ctx, FZ_ERROR_FORMAT, "Truncated PSD");
+			source.total -= v;
+			source.p += v;
+		}
+		if (source.total == 0)
+			fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "Unflattened PSD not supported");
+
+		v = get16be(&source);
+		switch (v)
+		{
+		case 0:
+			/* No compression */
+			break;
+		case 1:
+			/* Packbits */
+			source.packbits = 1;
+			source.packbits_n = 128;
+
+			/* Skip over rows * channels * byte counts. */
+			m = ((size_t)info->height) * info->n * 2;
+			if (m > source.total)
+				fz_throw(ctx, FZ_ERROR_FORMAT, "Truncated RLE PSD");
+			source.total -= m;
+			source.p += m;
+			break;
+		case 2: /* Deflate */
+		case 3: /* Deflate with prediction */
+			fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "Deflate PSD not supported");
+		default:
+			fz_throw(ctx, FZ_ERROR_FORMAT, "Unexpected compression (%d) found in PSD", v);
+		}
+
+		if (only_metadata)
+			break;
+
+		m = ((size_t)info->width) * info->height;
+		image = fz_new_pixmap(ctx, info->cs, info->width, info->height, seps, alpha);
+		q = image->samples;
+		if (bpc == 8)
+		{
+			if (n == 1)
+			{
+				while (m--)
+				{
+					*q++ = 255 - unpack8(&source);
+				}
+			}
+			else if (n - alpha == 3)
+			{
+				int N = n;
+
+				while (N--)
+				{
+					size_t M = m;
+					while (M--)
+					{
+						*q = unpack8(&source);
+						q += n;
+					}
+					q -= m*n - 1;
+				}
+			}
+			else
+			{
+				int N = n - alpha;
+
+				/* CMYK is inverted */
+				while (N--)
+				{
+					size_t M = m;
+					while (M--)
+					{
+						*q = 255 - unpack8(&source);
+						q += n;
+					}
+					q -= m*n - 1;
+				}
+
+				/* But alpha is not */
+				if (alpha)
+				{
+					size_t M = m;
+					while (M--)
+					{
+						*q = unpack8(&source);
+						q += n;
+					}
+					q -= m*n - 1;
+				}
+			}
+		}
+		else
+		{
+			if (n == 1)
+			{
+				while (m--)
+				{
+					*q++ = 255 - unpack8(&source);
+					(void)unpack8(&source);
+				}
+			}
+			else if (n - alpha == 3)
+			{
+				int N = n;
+
+				while (N--)
+				{
+					size_t M = m;
+
+					while (M--)
+					{
+						*q = unpack8(&source);
+						(void)unpack8(&source);
+						q += n;
+					}
+					q -= m*n - 1;
+				}
+			}
+			else
+			{
+				int N = n - alpha;
+
+				/* CMYK is inverted */
+				while (N--)
+				{
+					size_t M = m;
+
+					while (M--)
+					{
+						*q = 255 - unpack8(&source);
+						(void)unpack8(&source);
+						q += n;
+					}
+					q -= m*n - 1;
+				}
+
+				/* But alpha is not */
+				if (alpha)
+				{
+					size_t M = m;
+
+					while (M--)
+					{
+						*q = unpack8(&source);
+						(void)unpack8(&source);
+						q += n;
+					}
+					q -= m*n - 1;
+				}
+			}
+		}
+
+		if (alpha)
+			fz_premultiply_pixmap(ctx, image);
+	}
+	fz_always(ctx)
+	{
+		fz_drop_separations(ctx, seps);
+	}
+	fz_catch(ctx)
+	{
+		fz_drop_pixmap(ctx, image);
+		fz_drop_colorspace(ctx, info->cs);
+		fz_rethrow(ctx);
+	}
+
+	return image;
+}
+
+fz_pixmap *
+fz_load_psd(fz_context *ctx, const unsigned char *p, size_t total)
+{
+	fz_pixmap *image = NULL;
+	struct info psd;
+
+	image = psd_read_image(ctx, &psd, p, total, 0);
+
+	fz_drop_colorspace(ctx, psd.cs);
+
+	return image;
+}
+
+void
+fz_load_psd_info(fz_context *ctx, const unsigned char *p, size_t total, int *wp, int *hp, int *xresp, int *yresp, fz_colorspace **cspacep)
+{
+	struct info psd;
+
+	psd_read_image(ctx, &psd, p, total, 1);
+
+	*cspacep = psd.cs;
+	*wp = psd.width;
+	*hp = psd.height;
+	*xresp = psd.xres;
+	*yresp = psd.xres;
+}