Coverage Report

Created: 2026-04-01 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ghostpdl/xps/xpsjpeg.c
Line
Count
Source
1
/* Copyright (C) 2001-2026 Artifex Software, Inc.
2
   All Rights Reserved.
3
4
   This software is provided AS-IS with no warranty, either express or
5
   implied.
6
7
   This software is distributed under license and may not be copied,
8
   modified or distributed except as expressly authorized under the terms
9
   of the license contained in the file LICENSE in this distribution.
10
11
   Refer to licensing information at http://www.artifex.com or contact
12
   Artifex Software, Inc.,  39 Mesa Street, Suite 108A, San Francisco,
13
   CA 94129, USA, for further information.
14
*/
15
16
17
/* XPS interpreter - JPEG image support */
18
19
#include "ghostxps.h"
20
21
#include "stream.h"
22
#include "strimpl.h"
23
#include "gsstate.h"
24
#include "jpeglib_.h"
25
#include "sdct.h"
26
#include "sjpeg.h"
27
28
static int
29
xps_report_error(stream_state * st, const char *str)
30
0
{
31
0
    (void) gs_throw1(-1, "%s", str);
32
0
    return 0;
33
0
}
34
35
/* These four routines, extract_exif_resolution and extract_app13_resolution,
36
   float_can_be_int and read_value are borrowed from MuPDF, see load_jpeg.c in the MuPDF tree.
37
   float_can_be_int and read_value are utility functions used by the other two.
38
 */
39
/* Returns true if <x> can be represented as an integer without overflow.
40
 *
41
 * We can't use comparisons such as 'return x < INT_MAX' because INT_MAX is
42
 * not safely convertible to float - it ends up as INT_MAX+1 so the comparison
43
 * doesn't do what we want.
44
 *
45
 * Instead we do a round-trip conversion and return true if this differs by
46
 * less than 1. This relies on high adjacent float values that differ by more
47
 * than 1, actually being exact integers, so the round-trip doesn't change the
48
 * value.
49
 */
50
static int float_can_be_int(float x)
51
0
{
52
0
  return fabsf(x - (float)(int) x) < 1;
53
0
}
54
static inline int read_value(const unsigned char *data, int bytes, int is_big_endian)
55
0
{
56
0
  int value = 0;
57
0
  if (!is_big_endian)
58
0
    data += bytes;
59
0
  for (; bytes > 0; bytes--)
60
0
    value = (value << 8) | (is_big_endian ? *data++ : *--data);
61
0
  return value;
62
0
}
63
64
static int extract_exif_resolution(jpeg_saved_marker_ptr marker,
65
  int *xres, int *yres, uint8_t *orientation)
66
0
{
67
0
  int is_big_endian;
68
0
  const unsigned char *data;
69
0
  unsigned int offset, ifd_len, res_type = 0;
70
0
  float x_res = 0, y_res = 0;
71
72
0
  if (!marker || marker->marker != JPEG_APP0 + 1 || marker->data_length < 14)
73
0
    return 0;
74
0
  data = (const unsigned char *)marker->data;
75
0
  if (read_value(data, 4, 1) != 0x45786966 /* Exif */ || read_value(data + 4, 2, 1) != 0x0000)
76
0
    return 0;
77
0
  if (read_value(data + 6, 4, 1) == 0x49492A00)
78
0
    is_big_endian = 0;
79
0
  else if (read_value(data + 6, 4, 1) == 0x4D4D002A)
80
0
    is_big_endian = 1;
81
0
  else
82
0
    return 0;
83
84
0
  offset = read_value(data + 10, 4, is_big_endian) + 6;
85
0
  if (offset < 14 || offset > marker->data_length - 2)
86
0
    return 0;
87
0
  ifd_len = read_value(data + offset, 2, is_big_endian);
88
0
  for (offset += 2; ifd_len > 0 && offset + 12 < marker->data_length; ifd_len--, offset += 12)
89
0
  {
90
0
    int tag = read_value(data + offset, 2, is_big_endian);
91
0
    int type = read_value(data + offset + 2, 2, is_big_endian);
92
0
    int count = read_value(data + offset + 4, 4, is_big_endian);
93
0
    unsigned int value_off = read_value(data + offset + 8, 4, is_big_endian) + 6;
94
0
    switch (tag)
95
0
    {
96
0
    case 0x112:
97
            /* Orientation: we don't use this */
98
0
      break;
99
0
    case 0x11A:
100
0
      if (type == 5 && value_off > offset && value_off <= marker->data_length - 8)
101
0
        x_res = 1.0f * read_value(data + value_off, 4, is_big_endian) / read_value(data + value_off + 4, 4, is_big_endian);
102
0
      break;
103
0
    case 0x11B:
104
0
      if (type == 5 && value_off > offset && value_off <= marker->data_length - 8)
105
0
        y_res = 1.0f * read_value(data + value_off, 4, is_big_endian) / read_value(data + value_off + 4, 4, is_big_endian);
106
0
      break;
107
0
    case 0x128:
108
0
      if (type == 3 && count == 1)
109
0
        res_type = read_value(data + offset + 8, 2, is_big_endian);
110
0
      break;
111
0
    }
112
0
  }
113
114
0
  if (x_res <= 0 || !float_can_be_int(x_res) || y_res <= 0 || !float_can_be_int(y_res))
115
0
    return 0;
116
0
  if (res_type == 2)
117
0
  {
118
0
    *xres = (int)x_res;
119
0
    *yres = (int)y_res;
120
0
  }
121
0
  else if (res_type == 3)
122
0
  {
123
0
    *xres = (int)(x_res * 254 / 100);
124
0
    *yres = (int)(y_res * 254 / 100);
125
0
  }
126
0
  else
127
0
  {
128
0
    *xres = 0;
129
0
    *yres = 0;
130
0
  }
131
0
  return 1;
132
0
}
133
134
static int extract_app13_resolution(jpeg_saved_marker_ptr marker, int *xres, int *yres)
135
0
{
136
0
  const unsigned char *data, *data_end;
137
138
0
  if (!marker || marker->marker != JPEG_APP0 + 13 || marker->data_length < 42 ||
139
0
    strcmp((const char *)marker->data, "Photoshop 3.0") != 0)
140
0
  {
141
0
    return 0;
142
0
  }
143
144
0
  data = (const unsigned char *)marker->data;
145
0
  data_end = data + marker->data_length;
146
0
  for (data += 14; data + 12 < data_end; ) {
147
0
    int data_size = -1;
148
0
    int tag = read_value(data + 4, 2, 1);
149
0
    int value_off = 11 + read_value(data + 6, 2, 1);
150
0
    if (value_off % 2 == 1)
151
0
      value_off++;
152
0
    if (read_value(data, 4, 1) == 0x3842494D /* 8BIM */ && value_off <= data_end - data)
153
0
      data_size = read_value(data + value_off - 4, 4, 1);
154
0
    if (data_size < 0 || data_size > data_end - data - value_off)
155
0
      return 0;
156
0
    if (tag == 0x3ED && data_size == 16)
157
0
    {
158
0
      *xres = read_value(data + value_off, 2, 1);
159
0
      *yres = read_value(data + value_off + 8, 2, 1);
160
0
      return 1;
161
0
    }
162
0
    if (data_size % 2 == 1)
163
0
      data_size++;
164
0
    data += value_off + data_size;
165
0
  }
166
167
0
  return 0;
168
0
}
169
170
int
171
xps_decode_jpeg(xps_context_t *ctx, byte *rbuf, int rlen, xps_image_t *image)
172
0
{
173
0
    jpeg_decompress_data jddp;
174
0
    stream_DCT_state state;
175
0
    stream_cursor_read rp;
176
0
    stream_cursor_write wp;
177
0
    int code;
178
0
    int wlen;
179
0
    byte *wbuf;
180
0
    jpeg_saved_marker_ptr curr_marker;
181
182
0
    s_init_state((stream_state*)&state, &s_DCTD_template, ctx->memory);
183
0
    state.report_error = xps_report_error;
184
185
0
    s_DCTD_template.set_defaults((stream_state*)&state);
186
187
0
    state.jpeg_memory = ctx->memory;
188
0
    state.data.decompress = &jddp;
189
190
0
    jddp.templat = s_DCTD_template;
191
0
    jddp.memory = ctx->memory;
192
0
    jddp.scanline_buffer = NULL;
193
0
    jddp.PassThrough = 0;
194
0
    jddp.device = (void *)NULL;
195
0
    jddp.PassThroughfn = 0;
196
197
0
    if ((code = gs_jpeg_create_decompress(&state)) < 0)
198
0
        return gs_throw(-1, "cannot gs_jpeg_create_decompress");
199
200
0
    s_DCTD_template.init((stream_state*)&state);
201
202
0
    rp.ptr = rbuf - 1;
203
0
    rp.limit = rbuf + rlen - 1;
204
205
    /* read the header only by not having a write buffer */
206
0
    wp.ptr = 0;
207
0
    wp.limit = 0;
208
209
    /* Set up to save the ICC marker APP2.
210
     * According to the spec we should be getting APP1 APP2 and APP13.
211
     * Library gets APP0 and APP14. */
212
0
  jpeg_save_markers(&(jddp.dinfo), JPEG_APP0+1, 0xffff);
213
0
  jpeg_save_markers(&(jddp.dinfo), JPEG_APP0+13, 0xffff);
214
0
  jpeg_save_markers(&(jddp.dinfo), JPEG_APP0+2, 0xffff);
215
216
0
    code = s_DCTD_template.process((stream_state*)&state, &rp, &wp, true);
217
0
    if (code != 1) {
218
0
        code = gs_throw(-1, "premature EOF or error in jpeg");
219
0
        goto error;
220
0
    }
221
222
    /* Check if we had an ICC profile */
223
0
    curr_marker = jddp.dinfo.marker_list;
224
0
    while (curr_marker != NULL)
225
0
    {
226
0
        if (curr_marker->marker == 0xe2)
227
0
        {
228
            /* Found ICC profile. Create a buffer and copy over now.
229
             * Strip JPEG APP2 14 byte header */
230
0
            image->profilesize = curr_marker->data_length - 14;
231
0
            image->profile = xps_alloc(ctx, image->profilesize);
232
0
            if (image->profile)
233
0
            {
234
                /* If we can't create it, just ignore */
235
0
                memcpy(image->profile, &(curr_marker->data[14]), image->profilesize);
236
0
            }
237
0
            break;
238
0
        }
239
0
        curr_marker = curr_marker->next;
240
0
    }
241
242
0
    image->width = jddp.dinfo.output_width;
243
0
    image->height = jddp.dinfo.output_height;
244
0
    image->comps = jddp.dinfo.output_components;
245
0
    image->bits = 8;
246
0
    if (image->width <= 0 || image->height <= 0 || image->comps <= 0 || image->bits <= 0)
247
0
        return gs_throw(-1, "bad image dimension");
248
0
    image->stride = image->width * image->comps;
249
0
    image->invert_decode = false;
250
251
0
    if (image->comps == 1) {
252
0
        rc_increment(ctx->gray);
253
0
        image->colorspace = ctx->gray;
254
0
    }
255
0
    if (image->comps == 3) {
256
0
        rc_increment(ctx->srgb);
257
0
        image->colorspace = ctx->srgb;
258
0
    }
259
0
    if (image->comps == 4) {
260
0
        rc_increment(ctx->cmyk);
261
0
        image->colorspace = ctx->cmyk;
262
0
        image->invert_decode = true;
263
0
    }
264
265
0
    if (extract_exif_resolution(jddp.dinfo.marker_list, &image->xres, &image->yres, NULL))
266
0
        /* XPS prefers EXIF resolution to JFIF density */;
267
0
    else if (extract_app13_resolution(jddp.dinfo.marker_list, &image->xres, &image->yres))
268
0
        /* XPS prefers APP13 resolution to JFIF density */;
269
0
    else if (jddp.dinfo.density_unit == 1)
270
0
    {
271
        /* According to the XPS specification we should also use the EXIF and APP13 marker segments
272
         * to set the resolution, but we don't and adding that would be more effort than we want to
273
         * go to for XPS currently. This at least means that images won't go completely AWOL.
274
         */
275
0
        if (jddp.dinfo.X_density != 0)
276
0
            image->xres = jddp.dinfo.X_density;
277
0
        else
278
0
            image->xres = 96;
279
0
        if (jddp.dinfo.Y_density != 0)
280
0
            image->yres = jddp.dinfo.Y_density;
281
0
        else
282
0
            image->yres = 96;
283
0
    }
284
0
    else if (jddp.dinfo.density_unit == 2)
285
0
    {
286
0
        image->xres = (int)(jddp.dinfo.X_density * 2.54 + 0.5);
287
0
        image->yres = (int)(jddp.dinfo.Y_density * 2.54 + 0.5);
288
0
    }
289
0
    else
290
0
    {
291
0
        image->xres = 96;
292
0
        image->yres = 96;
293
0
    }
294
295
0
    if (check_int_multiply(image->stride, image->height, &wlen)) {
296
0
        code = gs_throw(-1, "image dimensions overflow");
297
0
        goto error;
298
0
    }
299
0
    wbuf = xps_alloc(ctx, wlen);
300
0
    if (!wbuf) {
301
0
        code = gs_throw1(gs_error_VMerror, "out of memory allocating samples: %d", wlen);
302
0
        goto error;
303
0
    }
304
305
0
    image->samples = wbuf;
306
307
0
    wp.ptr = wbuf - 1;
308
0
    wp.limit = wbuf + wlen - 1;
309
310
0
    code = s_DCTD_template.process((stream_state*)&state, &rp, &wp, true);
311
0
    if (code != EOFC) {
312
0
        code = gs_throw1(-1, "error in jpeg (code = %d)", code);
313
0
        goto error;
314
0
    }
315
316
0
    code = gs_okay;
317
0
error:
318
0
    gs_jpeg_destroy(&state);
319
0
    if (jddp.scanline_buffer != NULL) {
320
0
        gs_free_object(gs_memory_stable(ctx->memory),
321
0
                       jddp.scanline_buffer,
322
0
                       "xps_decode_jpeg");
323
0
    }
324
325
0
    return code;
326
0
}