/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 | } |