Coverage Report

Created: 2026-03-31 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}