/src/mupdf/source/fitz/load-psd.c
Line | Count | Source |
1 | | // Copyright (C) 2004-2025 Artifex Software, Inc. |
2 | | // |
3 | | // This file is part of MuPDF. |
4 | | // |
5 | | // MuPDF is free software: you can redistribute it and/or modify it under the |
6 | | // terms of the GNU Affero General Public License as published by the Free |
7 | | // Software Foundation, either version 3 of the License, or (at your option) |
8 | | // any later version. |
9 | | // |
10 | | // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY |
11 | | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
12 | | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more |
13 | | // details. |
14 | | // |
15 | | // You should have received a copy of the GNU Affero General Public License |
16 | | // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> |
17 | | // |
18 | | // Alternative licensing terms are available from the licensor. |
19 | | // For commercial licensing, see <https://www.artifex.com/> or contact |
20 | | // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, |
21 | | // CA 94129, USA, for further information. |
22 | | |
23 | | #include "mupdf/fitz.h" |
24 | | |
25 | | #include "pixmap-imp.h" |
26 | | |
27 | | #include <limits.h> |
28 | | #include <string.h> |
29 | | |
30 | | struct info |
31 | | { |
32 | | unsigned int width, height, n; |
33 | | int xres, yres; |
34 | | fz_colorspace *cs; |
35 | | }; |
36 | | |
37 | | typedef struct |
38 | | { |
39 | | fz_context *ctx; |
40 | | const unsigned char *p; |
41 | | size_t total; |
42 | | int packbits; |
43 | | int packbits_n; |
44 | | int packbits_rep; |
45 | | } source_t; |
46 | | |
47 | | static int |
48 | | get8(source_t *source) |
49 | 0 | { |
50 | 0 | if (source->total < 1) |
51 | 0 | fz_throw(source->ctx, FZ_ERROR_FORMAT, "Truncated PSD"); |
52 | 0 | source->total--; |
53 | |
|
54 | 0 | return *source->p++; |
55 | 0 | } |
56 | | |
57 | | static int |
58 | | get16be(source_t *source) |
59 | 0 | { |
60 | 0 | int v; |
61 | |
|
62 | 0 | if (source->total < 2) |
63 | 0 | { |
64 | 0 | source->total = 0; |
65 | 0 | fz_throw(source->ctx, FZ_ERROR_FORMAT, "Truncated PSD"); |
66 | 0 | } |
67 | | |
68 | 0 | source->total -= 2; |
69 | |
|
70 | 0 | v = *source->p++; |
71 | 0 | v = (v<<8) | *source->p++; |
72 | |
|
73 | 0 | return v; |
74 | 0 | } |
75 | | |
76 | | static int |
77 | | get32be(source_t *source) |
78 | 0 | { |
79 | 0 | int v; |
80 | |
|
81 | 0 | if (source->total < 4) |
82 | 0 | { |
83 | 0 | source->total = 0; |
84 | 0 | fz_throw(source->ctx, FZ_ERROR_FORMAT, "Truncated PSD"); |
85 | 0 | } |
86 | | |
87 | 0 | source->total -= 4; |
88 | |
|
89 | 0 | v = *source->p++; |
90 | 0 | v = (v<<8) | *source->p++; |
91 | 0 | v = (v<<8) | *source->p++; |
92 | 0 | v = (v<<8) | *source->p++; |
93 | |
|
94 | 0 | return v; |
95 | 0 | } |
96 | | |
97 | | static uint32_t |
98 | | getu32be(source_t *source) |
99 | 0 | { |
100 | 0 | return (uint32_t)get32be(source); |
101 | 0 | } |
102 | | |
103 | | static int |
104 | | unpack8(source_t *source) |
105 | 0 | { |
106 | 0 | int i; |
107 | |
|
108 | 0 | if (source->packbits == 0) |
109 | 0 | return get8(source); |
110 | | |
111 | 0 | i = source->packbits_n; |
112 | 0 | if (i == 128) |
113 | 0 | { |
114 | 0 | do |
115 | 0 | { |
116 | 0 | i = source->packbits_n = get8(source); |
117 | 0 | } |
118 | 0 | while (i == 128); |
119 | 0 | if (i > 128) |
120 | 0 | source->packbits_rep = get8(source); |
121 | 0 | } |
122 | 0 | if (i < 128) |
123 | 0 | { |
124 | | /* Literal n+1 */ |
125 | 0 | i--; |
126 | 0 | if (i < 0) |
127 | 0 | i = 128; |
128 | 0 | source->packbits_n = i; |
129 | 0 | return get8(source); |
130 | 0 | } |
131 | 0 | else |
132 | 0 | { |
133 | 0 | i++; |
134 | 0 | if (i == 257) |
135 | 0 | i = 128; |
136 | 0 | source->packbits_n = i; |
137 | 0 | return source->packbits_rep; |
138 | 0 | } |
139 | 0 | } |
140 | | |
141 | | static char *getString(source_t *source) |
142 | 0 | { |
143 | 0 | size_t len = get8(source); |
144 | 0 | size_t odd = !(len & 1); |
145 | 0 | char *s; |
146 | |
|
147 | 0 | if (source->total < len + odd) |
148 | 0 | { |
149 | 0 | source->total = 0; |
150 | 0 | fz_throw(source->ctx, FZ_ERROR_FORMAT, "Truncated string in PSD"); |
151 | 0 | } |
152 | | |
153 | 0 | s = fz_malloc(source->ctx, len+1); |
154 | 0 | memcpy(s, source->p, len); |
155 | 0 | s[len] = 0; |
156 | |
|
157 | 0 | source->p += len + odd; |
158 | 0 | source->total -= len + odd; |
159 | |
|
160 | 0 | return s; |
161 | 0 | } |
162 | | |
163 | | static fz_pixmap * |
164 | | psd_read_image(fz_context *ctx, struct info *info, const unsigned char *p, size_t total, int only_metadata) |
165 | 0 | { |
166 | 0 | int v, bpc, c, n; |
167 | 0 | source_t source; |
168 | 0 | size_t ir_len, data_len; |
169 | 0 | fz_separations *seps = NULL; |
170 | 0 | fz_pixmap *image = NULL; |
171 | 0 | size_t m; |
172 | 0 | unsigned char *q; |
173 | 0 | int alpha = 0; |
174 | |
|
175 | 0 | source.ctx = ctx; |
176 | 0 | source.p = p; |
177 | 0 | source.total = total; |
178 | 0 | source.packbits = 0; |
179 | |
|
180 | 0 | memset(info, 0, sizeof(*info)); |
181 | |
|
182 | 0 | fz_var(image); |
183 | 0 | fz_var(seps); |
184 | |
|
185 | 0 | fz_try(ctx) |
186 | 0 | { |
187 | 0 | info->xres = 96; |
188 | 0 | info->yres = 96; |
189 | |
|
190 | 0 | v = get32be(&source); |
191 | | /* Read signature */ |
192 | 0 | if (v != 0x38425053 /* 8BPS */) |
193 | 0 | fz_throw(ctx, FZ_ERROR_FORMAT, "not a psd image (wrong signature)"); |
194 | | |
195 | | /* Version */ |
196 | 0 | v = get16be(&source); |
197 | 0 | if (v != 1) |
198 | 0 | fz_throw(ctx, FZ_ERROR_FORMAT, "Bad PSD version"); |
199 | | |
200 | 0 | (void)get16be(&source); |
201 | 0 | (void)get32be(&source); |
202 | |
|
203 | 0 | info->n = n = get16be(&source); |
204 | 0 | info->height = getu32be(&source); |
205 | 0 | info->width = getu32be(&source); |
206 | 0 | if (info->height == 0) |
207 | 0 | fz_throw(ctx, FZ_ERROR_FORMAT, "image height must be > 0"); |
208 | 0 | if (info->width == 0) |
209 | 0 | fz_throw(ctx, FZ_ERROR_FORMAT, "image width must be > 0"); |
210 | | |
211 | 0 | bpc = get16be(&source); |
212 | 0 | if (bpc != 8 && bpc != 16) |
213 | 0 | fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "Only 8 or 16 bpc PSD files supported!"); |
214 | | |
215 | 0 | c = get16be(&source); |
216 | 0 | if (c == 4) /* CMYK (+ Spots?) */ |
217 | 0 | { |
218 | 0 | if (n != 4) |
219 | 0 | fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "CMYK PSD with %d chans not supported!", n); |
220 | 0 | info->cs = fz_keep_colorspace(ctx, fz_device_cmyk(ctx)); |
221 | 0 | } |
222 | 0 | else if (c == 3) /* RGB */ |
223 | 0 | { |
224 | 0 | if (n == 4) |
225 | 0 | alpha = 1; |
226 | 0 | else if (n != 3) |
227 | 0 | fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "RGB PSD with %d chans not supported!", n); |
228 | 0 | info->cs = fz_keep_colorspace(ctx, fz_device_rgb(ctx)); |
229 | 0 | } |
230 | 0 | else if (c == 1) /* Greyscale */ |
231 | 0 | { |
232 | 0 | if (n != 1) |
233 | 0 | fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "Greyscale PSD with %d chans not supported!", n); |
234 | 0 | info->cs = fz_keep_colorspace(ctx, fz_device_gray(ctx)); |
235 | 0 | } |
236 | 0 | else |
237 | 0 | fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "Unsupported PSD colorspace (%d)!", c); |
238 | | |
239 | 0 | v = get32be(&source); |
240 | 0 | if (v != 0) |
241 | 0 | fz_throw(ctx, FZ_ERROR_FORMAT, "Unexpected color data in PSD!"); |
242 | | |
243 | | /* Now read image resources... */ |
244 | 0 | ir_len = getu32be(&source); |
245 | 0 | while (ir_len >= 12) |
246 | 0 | { |
247 | 0 | size_t start = source.p - p; |
248 | |
|
249 | 0 | v = get32be(&source); |
250 | 0 | if (v != 0x3842494d) /* 8BIM */ |
251 | 0 | fz_throw(ctx, FZ_ERROR_FORMAT, "Failed to find expected 8BIM in PSD"); |
252 | 0 | v = get16be(&source); |
253 | |
|
254 | 0 | fz_free(ctx, getString(&source)); |
255 | |
|
256 | 0 | data_len = getu32be(&source); |
257 | 0 | ir_len -= (source.p - p) - start; |
258 | 0 | switch (v) |
259 | 0 | { |
260 | 0 | case 0x3ef: /* Spot */ |
261 | 0 | { |
262 | 0 | int spots = 0; |
263 | 0 | int alpha_found = 0; |
264 | |
|
265 | 0 | while (data_len > 0) |
266 | 0 | { |
267 | 0 | uint32_t C, M, Y, K; |
268 | 0 | char text[32]; |
269 | |
|
270 | 0 | v = get16be(&source); |
271 | 0 | if (v == 0 && alpha_found == 0) |
272 | 0 | alpha_found = 1, alpha = 1; |
273 | 0 | else if (v != 2) |
274 | 0 | fz_throw(ctx, FZ_ERROR_FORMAT, "Non CMYK spot found in PSD"); |
275 | | |
276 | 0 | C = 0xff - (get16be(&source)>>8); |
277 | 0 | M = 0xff - (get16be(&source)>>8); |
278 | 0 | Y = 0xff - (get16be(&source)>>8); |
279 | 0 | K = 0xff - (get16be(&source)>>8); |
280 | 0 | (void)get16be(&source); /* opacity */ |
281 | 0 | (void)get8(&source); /* kind */ |
282 | 0 | (void)get8(&source); /* padding */ |
283 | 0 | if (v == 2) |
284 | 0 | { |
285 | 0 | uint32_t cmyk = C | (M<<8) | (Y<<16) | (K<<24); |
286 | 0 | int R = fz_clampi(255-C-K, 0, 255); |
287 | 0 | int G = fz_clampi(255-M-K, 0, 255); |
288 | 0 | int B = fz_clampi(255-Y-K, 0, 255); |
289 | 0 | uint32_t rgba = R | (G<<8) | (B<<16); |
290 | 0 | if (seps == NULL) |
291 | 0 | seps = fz_new_separations(ctx, 1); |
292 | 0 | snprintf(text, sizeof(text), "s%d", spots); |
293 | | /* Use the old entry-point until we fix the new one */ |
294 | 0 | fz_add_separation_equivalents(ctx, seps, rgba, cmyk, text); |
295 | 0 | spots++; |
296 | 0 | } |
297 | 0 | data_len -= 14; |
298 | 0 | ir_len -= 14; |
299 | 0 | } |
300 | 0 | } |
301 | 0 | } |
302 | | /* Skip any unread data */ |
303 | 0 | if (data_len & 1) |
304 | 0 | data_len++; |
305 | 0 | ir_len -= data_len; |
306 | 0 | while (data_len--) |
307 | 0 | get8(&source); |
308 | 0 | } |
309 | 0 | if (fz_count_separations(ctx, seps) + info->cs->n + 1 == n && alpha == 0) |
310 | 0 | alpha = 1; |
311 | 0 | if (fz_count_separations(ctx, seps) + info->cs->n + alpha != n) |
312 | 0 | fz_throw(ctx, FZ_ERROR_FORMAT, "PSD contains mismatching spot/alpha data"); |
313 | | |
314 | | /* Skip over the Layer data. */ |
315 | 0 | v = get32be(&source); |
316 | 0 | if (v != 0) |
317 | 0 | { |
318 | 0 | if (source.total < (size_t)v) |
319 | 0 | fz_throw(ctx, FZ_ERROR_FORMAT, "Truncated PSD"); |
320 | 0 | source.total -= v; |
321 | 0 | source.p += v; |
322 | 0 | } |
323 | 0 | if (source.total == 0) |
324 | 0 | fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "Unflattened PSD not supported"); |
325 | | |
326 | 0 | v = get16be(&source); |
327 | 0 | switch (v) |
328 | 0 | { |
329 | 0 | case 0: |
330 | | /* No compression */ |
331 | 0 | break; |
332 | 0 | case 1: |
333 | | /* Packbits */ |
334 | 0 | source.packbits = 1; |
335 | 0 | source.packbits_n = 128; |
336 | | |
337 | | /* Skip over rows * channels * byte counts. */ |
338 | 0 | m = ((size_t)info->height) * info->n * 2; |
339 | 0 | if (m > source.total) |
340 | 0 | fz_throw(ctx, FZ_ERROR_FORMAT, "Truncated RLE PSD"); |
341 | 0 | source.total -= m; |
342 | 0 | source.p += m; |
343 | 0 | break; |
344 | 0 | case 2: /* Deflate */ |
345 | 0 | case 3: /* Deflate with prediction */ |
346 | 0 | fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "Deflate PSD not supported"); |
347 | 0 | default: |
348 | 0 | fz_throw(ctx, FZ_ERROR_FORMAT, "Unexpected compression (%d) found in PSD", v); |
349 | 0 | } |
350 | | |
351 | 0 | if (only_metadata) |
352 | 0 | break; |
353 | | |
354 | 0 | m = ((size_t)info->width) * info->height; |
355 | 0 | image = fz_new_pixmap(ctx, info->cs, info->width, info->height, seps, alpha); |
356 | 0 | q = image->samples; |
357 | 0 | if (bpc == 8) |
358 | 0 | { |
359 | 0 | if (n == 1) |
360 | 0 | { |
361 | 0 | while (m--) |
362 | 0 | { |
363 | 0 | *q++ = 255 - unpack8(&source); |
364 | 0 | } |
365 | 0 | } |
366 | 0 | else if (n - alpha == 3) |
367 | 0 | { |
368 | 0 | int N = n; |
369 | |
|
370 | 0 | while (N--) |
371 | 0 | { |
372 | 0 | size_t M = m; |
373 | 0 | while (M--) |
374 | 0 | { |
375 | 0 | *q = unpack8(&source); |
376 | 0 | q += n; |
377 | 0 | } |
378 | 0 | q -= m*n - 1; |
379 | 0 | } |
380 | 0 | } |
381 | 0 | else |
382 | 0 | { |
383 | 0 | int N = n - alpha; |
384 | | |
385 | | /* CMYK is inverted */ |
386 | 0 | while (N--) |
387 | 0 | { |
388 | 0 | size_t M = m; |
389 | 0 | while (M--) |
390 | 0 | { |
391 | 0 | *q = 255 - unpack8(&source); |
392 | 0 | q += n; |
393 | 0 | } |
394 | 0 | q -= m*n - 1; |
395 | 0 | } |
396 | | |
397 | | /* But alpha is not */ |
398 | 0 | if (alpha) |
399 | 0 | { |
400 | 0 | size_t M = m; |
401 | 0 | while (M--) |
402 | 0 | { |
403 | 0 | *q = unpack8(&source); |
404 | 0 | q += n; |
405 | 0 | } |
406 | 0 | q -= m*n - 1; |
407 | 0 | } |
408 | 0 | } |
409 | 0 | } |
410 | 0 | else |
411 | 0 | { |
412 | 0 | if (n == 1) |
413 | 0 | { |
414 | 0 | while (m--) |
415 | 0 | { |
416 | 0 | *q++ = 255 - unpack8(&source); |
417 | 0 | (void)unpack8(&source); |
418 | 0 | } |
419 | 0 | } |
420 | 0 | else if (n - alpha == 3) |
421 | 0 | { |
422 | 0 | int N = n; |
423 | |
|
424 | 0 | while (N--) |
425 | 0 | { |
426 | 0 | size_t M = m; |
427 | |
|
428 | 0 | while (M--) |
429 | 0 | { |
430 | 0 | *q = unpack8(&source); |
431 | 0 | (void)unpack8(&source); |
432 | 0 | q += n; |
433 | 0 | } |
434 | 0 | q -= m*n - 1; |
435 | 0 | } |
436 | 0 | } |
437 | 0 | else |
438 | 0 | { |
439 | 0 | int N = n - alpha; |
440 | | |
441 | | /* CMYK is inverted */ |
442 | 0 | while (N--) |
443 | 0 | { |
444 | 0 | size_t M = m; |
445 | |
|
446 | 0 | while (M--) |
447 | 0 | { |
448 | 0 | *q = 255 - unpack8(&source); |
449 | 0 | (void)unpack8(&source); |
450 | 0 | q += n; |
451 | 0 | } |
452 | 0 | q -= m*n - 1; |
453 | 0 | } |
454 | | |
455 | | /* But alpha is not */ |
456 | 0 | if (alpha) |
457 | 0 | { |
458 | 0 | size_t M = m; |
459 | |
|
460 | 0 | while (M--) |
461 | 0 | { |
462 | 0 | *q = unpack8(&source); |
463 | 0 | (void)unpack8(&source); |
464 | 0 | q += n; |
465 | 0 | } |
466 | 0 | q -= m*n - 1; |
467 | 0 | } |
468 | 0 | } |
469 | 0 | } |
470 | |
|
471 | 0 | if (alpha) |
472 | 0 | fz_premultiply_pixmap(ctx, image); |
473 | 0 | } |
474 | 0 | fz_always(ctx) |
475 | 0 | { |
476 | 0 | fz_drop_separations(ctx, seps); |
477 | 0 | } |
478 | 0 | fz_catch(ctx) |
479 | 0 | { |
480 | 0 | fz_drop_pixmap(ctx, image); |
481 | 0 | fz_drop_colorspace(ctx, info->cs); |
482 | 0 | fz_rethrow(ctx); |
483 | 0 | } |
484 | | |
485 | 0 | return image; |
486 | 0 | } |
487 | | |
488 | | fz_pixmap * |
489 | | fz_load_psd(fz_context *ctx, const unsigned char *p, size_t total) |
490 | 0 | { |
491 | 0 | fz_pixmap *image = NULL; |
492 | 0 | struct info psd; |
493 | |
|
494 | 0 | image = psd_read_image(ctx, &psd, p, total, 0); |
495 | |
|
496 | 0 | fz_drop_colorspace(ctx, psd.cs); |
497 | |
|
498 | 0 | return image; |
499 | 0 | } |
500 | | |
501 | | void |
502 | | 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) |
503 | 0 | { |
504 | 0 | struct info psd; |
505 | |
|
506 | 0 | psd_read_image(ctx, &psd, p, total, 1); |
507 | |
|
508 | 0 | *cspacep = psd.cs; |
509 | 0 | *wp = psd.width; |
510 | 0 | *hp = psd.height; |
511 | 0 | *xresp = psd.xres; |
512 | 0 | *yresp = psd.xres; |
513 | 0 | } |