/src/ghostpdl/xps/xpsjxr.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 | | /* JPEG-XR (formerly HD-Photo (formerly Windows Media Photo)) support */ |
18 | | |
19 | | #include "ghostxps.h" |
20 | | |
21 | | #ifdef _MSC_VER |
22 | | #undef _MSC_VER |
23 | | #endif |
24 | | |
25 | | #include "jpegxr.h" |
26 | | #include "gxdevice.h" |
27 | | |
28 | | struct state { xps_context_t *ctx; xps_image_t *output; }; |
29 | | |
30 | | static const char * |
31 | | jxr_error_string(int code) |
32 | 0 | { |
33 | 0 | switch (code) |
34 | 0 | { |
35 | 0 | case JXR_EC_OK: return "No error"; |
36 | 0 | default: |
37 | 0 | case JXR_EC_ERROR: return "Unspecified error"; |
38 | 0 | case JXR_EC_BADMAGIC: return "Stream lacks proper magic number"; |
39 | 0 | case JXR_EC_FEATURE_NOT_IMPLEMENTED: return "Feature not implemented"; |
40 | 0 | case JXR_EC_IO: return "Error reading/writing data"; |
41 | 0 | case JXR_EC_BADFORMAT: return "Bad file format"; |
42 | 0 | } |
43 | 0 | } |
44 | | |
45 | 0 | #define CLAMP(v, mn, mx) (v < mn ? mn : v > mx ? mx : v) |
46 | | |
47 | | static inline int |
48 | | scale_bits(int depth, int value) |
49 | 0 | { |
50 | 0 | union { int iv; float fv; } bd32f; |
51 | |
|
52 | 0 | switch (depth) |
53 | 0 | { |
54 | 0 | case JXR_BD1WHITE1: |
55 | 0 | return value * 255; |
56 | 0 | case JXR_BD1BLACK1: |
57 | 0 | return value ? 0 : 255; |
58 | 0 | case JXR_BD8: |
59 | 0 | return value; |
60 | 0 | case JXR_BD16: |
61 | 0 | return value >> 8; |
62 | 0 | case JXR_BD16S: /* -4 .. 4 ; 8192 = 1.0 */ |
63 | 0 | value = value >> 5; |
64 | 0 | return CLAMP(value, 0, 255); |
65 | 0 | case JXR_BD32S: /* -128 .. 128 ; 16777216 = 1.0 */ |
66 | 0 | value = value >> 16; |
67 | 0 | return CLAMP(value, 0, 255); |
68 | 0 | case JXR_BD32F: |
69 | 0 | bd32f.iv = value; |
70 | 0 | value = (int)(bd32f.fv * 255); |
71 | 0 | return CLAMP(value, 0, 255); |
72 | | #if 0 |
73 | | case JXR_BDRESERVED: return value; |
74 | | case JXR_BD16F: return value; |
75 | | case JXR_BD5: return value; |
76 | | case JXR_BD10: return value; |
77 | | case JXR_BD565: return value; |
78 | | #endif |
79 | 0 | } |
80 | 0 | return value; |
81 | 0 | } |
82 | | |
83 | | static void |
84 | | xps_decode_jpegxr_block(jxr_image_t image, int mx, int my, int *data) |
85 | 0 | { |
86 | 0 | struct state *state = jxr_get_user_data(image); |
87 | 0 | xps_context_t *ctx = state->ctx; |
88 | 0 | xps_image_t *output = state->output; |
89 | 0 | int depth; |
90 | 0 | unsigned char *p; |
91 | 0 | int x, y, k; |
92 | |
|
93 | 0 | if (!output->samples) |
94 | 0 | { |
95 | 0 | size_t z; |
96 | 0 | gs_color_space *old_cs; |
97 | 0 | output->width = jxr_get_IMAGE_WIDTH(image); |
98 | 0 | output->height = jxr_get_IMAGE_HEIGHT(image); |
99 | 0 | output->comps = jxr_get_IMAGE_CHANNELS(image); |
100 | 0 | output->hasalpha = jxr_get_ALPHACHANNEL_FLAG(image); |
101 | 0 | output->bits = 8; |
102 | 0 | if (check_int_multiply(output->width, output->comps, &output->stride)) { |
103 | 0 | gs_throw(gs_error_VMerror, "image too large\n"); |
104 | 0 | return; |
105 | 0 | } |
106 | 0 | if (check_size_multiply(output->stride, output->height, &z)) { |
107 | 0 | gs_throw(gs_error_VMerror, "image too large\n"); |
108 | 0 | return; |
109 | 0 | } |
110 | 0 | output->samples = xps_alloc(ctx, z); |
111 | 0 | if (!output->samples) { |
112 | 0 | gs_throw(gs_error_VMerror, "out of memory: output->samples.\n"); |
113 | 0 | return; |
114 | 0 | } |
115 | | |
116 | 0 | old_cs = output->colorspace; |
117 | 0 | switch (output->comps) |
118 | 0 | { |
119 | 0 | default: |
120 | 0 | case 1: output->colorspace = ctx->gray; break; |
121 | 0 | case 3: output->colorspace = ctx->srgb; break; |
122 | 0 | case 4: output->colorspace = ctx->cmyk; break; |
123 | 0 | } |
124 | 0 | rc_increment(output->colorspace); |
125 | 0 | rc_decrement(old_cs, "xps_decode_jpegxr_block"); |
126 | 0 | } |
127 | | |
128 | 0 | depth = jxr_get_OUTPUT_BITDEPTH(image); |
129 | |
|
130 | 0 | my = my * 16; |
131 | 0 | mx = mx * 16; |
132 | |
|
133 | 0 | for (y = 0; y < 16; y++) |
134 | 0 | { |
135 | 0 | if (my + y >= output->height) |
136 | 0 | return; |
137 | 0 | p = output->samples + (my + y) * (size_t)output->stride + mx * output->comps; |
138 | 0 | for (x = 0; x < 16; x++) |
139 | 0 | { |
140 | 0 | if (mx + x >= output->width) |
141 | 0 | data += output->comps; |
142 | 0 | else |
143 | 0 | for (k = 0; k < output->comps; k++) |
144 | 0 | *p++ = scale_bits(depth, *data++); |
145 | 0 | } |
146 | 0 | } |
147 | 0 | } |
148 | | |
149 | | static void |
150 | | xps_decode_jpegxr_alpha_block(jxr_image_t image, int mx, int my, int *data) |
151 | 0 | { |
152 | 0 | struct state *state = jxr_get_user_data(image); |
153 | 0 | xps_context_t *ctx = state->ctx; |
154 | 0 | xps_image_t *output = state->output; |
155 | 0 | int depth; |
156 | 0 | unsigned char *p; |
157 | 0 | int x, y; |
158 | |
|
159 | 0 | if (!output->alpha) |
160 | 0 | { |
161 | 0 | uint32_t size; |
162 | |
|
163 | 0 | if (check_uint32_multiply((uint32_t)output->width, (uint32_t)output->height, &size) != 0) { |
164 | 0 | gs_throw(gs_error_limitcheck, "image alpha is too large"); |
165 | 0 | return; |
166 | 0 | } |
167 | | |
168 | 0 | output->alpha = xps_alloc(ctx, size); |
169 | 0 | if (!output->alpha) { |
170 | 0 | gs_throw(gs_error_VMerror, "out of memory: output->alpha.\n"); |
171 | 0 | return; |
172 | 0 | } |
173 | 0 | } |
174 | | |
175 | 0 | depth = jxr_get_OUTPUT_BITDEPTH(image); |
176 | |
|
177 | 0 | my = my * 16; |
178 | 0 | mx = mx * 16; |
179 | |
|
180 | 0 | for (y = 0; y < 16; y++) |
181 | 0 | { |
182 | 0 | if (my + y >= output->height) |
183 | 0 | return; |
184 | 0 | p = output->alpha + (my + y) * (size_t)output->width + mx; |
185 | 0 | for (x = 0; x < 16; x++) |
186 | 0 | { |
187 | 0 | if (mx + x >= output->width) |
188 | 0 | data ++; |
189 | 0 | else |
190 | 0 | *p++ = scale_bits(depth, *data++); |
191 | 0 | } |
192 | 0 | } |
193 | 0 | } |
194 | | |
195 | | #ifdef JXR_REDIRECTED_MALLOCS |
196 | | static void * |
197 | | my_jxr_malloc(void *handle, size_t z) |
198 | 0 | { |
199 | 0 | xps_context_t *ctx = (xps_context_t *)handle; |
200 | 0 | return xps_alloc(ctx, z); |
201 | 0 | } |
202 | | |
203 | | static void * |
204 | | my_jxr_calloc(void *handle, size_t z, size_t n) |
205 | 0 | { |
206 | 0 | void *p; |
207 | 0 | size_t zn; |
208 | 0 | xps_context_t *ctx = (xps_context_t *)handle; |
209 | 0 | if (check_size_multiply(z, n, &zn)) |
210 | 0 | return NULL; |
211 | 0 | p = xps_alloc(ctx, zn); |
212 | 0 | if (p) |
213 | 0 | memset(p, 0, zn); |
214 | 0 | return p; |
215 | 0 | } |
216 | | |
217 | | static void * |
218 | | my_jxr_realloc(void *handle, void *ptr, size_t z) |
219 | 0 | { |
220 | 0 | xps_context_t *ctx = (xps_context_t *)handle; |
221 | 0 | return xps_realloc(ctx, ptr, z); |
222 | 0 | } |
223 | | |
224 | | static void |
225 | | my_jxr_free(void *handle, void *ptr) |
226 | 0 | { |
227 | 0 | xps_context_t *ctx = (xps_context_t *)handle; |
228 | 0 | xps_free(ctx, ptr); |
229 | 0 | } |
230 | | #endif |
231 | | |
232 | | int |
233 | | xps_decode_jpegxr(xps_context_t *ctx, byte *buf, int len, xps_image_t *output) |
234 | 0 | { |
235 | 0 | gp_file *file; |
236 | 0 | char *name = xps_alloc(ctx, gp_file_name_sizeof); |
237 | 0 | struct state state; |
238 | 0 | jxr_container_t container; |
239 | 0 | jxr_image_t image; |
240 | 0 | int offset, alpha_offset; |
241 | 0 | int rc; |
242 | 0 | #ifdef JXR_REDIRECTED_MALLOCS |
243 | 0 | jxr_alloc alloc = { 0 }; |
244 | |
|
245 | 0 | alloc.handle = ctx; |
246 | 0 | alloc.malloc = my_jxr_malloc; |
247 | 0 | alloc.calloc = my_jxr_calloc; |
248 | 0 | alloc.realloc = my_jxr_realloc; |
249 | 0 | alloc.free = my_jxr_free; |
250 | 0 | #endif |
251 | |
|
252 | 0 | if (!name) { |
253 | 0 | return gs_throw(gs_error_VMerror, "cannot allocate scratch file name buffer"); |
254 | 0 | } |
255 | | |
256 | 0 | memset(output, 0, sizeof(*output)); |
257 | |
|
258 | 0 | file = gp_open_scratch_file(ctx->memory, "jpegxr-scratch-", name, "wb+"); |
259 | 0 | if (!file) { |
260 | 0 | xps_free(ctx, name); |
261 | 0 | return gs_throw(gs_error_invalidfileaccess, "cannot open scratch file"); |
262 | 0 | } |
263 | 0 | rc = gp_fwrite(buf, 1, len, file); |
264 | 0 | if (rc != len) { |
265 | 0 | xps_free(ctx, name); |
266 | 0 | return gs_throw(gs_error_invalidfileaccess, "cannot write to scratch file"); |
267 | 0 | } |
268 | 0 | rc = xps_fseek(file, 0, SEEK_SET); |
269 | 0 | if (rc != 0) { |
270 | 0 | xps_free(ctx, name); |
271 | 0 | return gs_throw(gs_error_invalidfileaccess, "cannot write to scratch file"); |
272 | 0 | } |
273 | | |
274 | 0 | #ifdef JXR_REDIRECTED_MALLOCS |
275 | 0 | container = jxr_create_container_alloc(&alloc); |
276 | | #else |
277 | | container = jxr_create_container(); |
278 | | #endif |
279 | 0 | rc = jxr_read_image_container(container, gp_get_file(file)); |
280 | 0 | if (rc < 0) { |
281 | 0 | xps_free(ctx, name); |
282 | 0 | jxr_destroy_container(container); |
283 | 0 | return gs_throw1(-1, "jxr_read_image_container: %s", jxr_error_string(rc)); |
284 | 0 | } |
285 | | |
286 | 0 | offset = jxrc_image_offset(container, 0); |
287 | 0 | alpha_offset = jxrc_alpha_offset(container, 0); |
288 | |
|
289 | 0 | output->xres = (int)jxrc_width_resolution(container, 0); |
290 | 0 | output->yres = (int)jxrc_height_resolution(container, 0); |
291 | 0 | output->invert_decode = false; |
292 | |
|
293 | 0 | #ifdef JXR_REDIRECTED_MALLOCS |
294 | 0 | image = jxr_create_input_alloc(&alloc); |
295 | | #else |
296 | | image = jxr_create_input(); |
297 | | #endif |
298 | 0 | if (image == NULL) { |
299 | 0 | xps_free(ctx, name); |
300 | 0 | jxr_destroy_container(container); |
301 | 0 | return gs_throw(-1, "jxr creation failed"); |
302 | 0 | } |
303 | 0 | jxr_set_PROFILE_IDC(image, 111); |
304 | 0 | jxr_set_LEVEL_IDC(image, 255); |
305 | | //jxr_set_pixel_format(image, jxrc_image_pixelformat(container, 0)); |
306 | | //jxr_set_container_parameters(image, |
307 | | // _jxrc_image_pixelformat(container, 0), |
308 | | // jxrc_image_width(container, 0), |
309 | | // jxrc_image_height(container, 0), |
310 | | // jxrc_alpha_offset(container, 0), |
311 | | // jxrc_image_band_presence(container, 0), |
312 | | // jxrc_alpha_band_presence(container, 0), 0); |
313 | |
|
314 | 0 | jxr_set_block_output(image, xps_decode_jpegxr_block); |
315 | 0 | state.ctx = ctx; |
316 | 0 | state.output = output; |
317 | 0 | jxr_set_user_data(image, &state); |
318 | |
|
319 | 0 | rc = xps_fseek(file, offset, SEEK_SET); |
320 | 0 | if (rc != 0) { |
321 | 0 | xps_free(ctx, name); |
322 | 0 | jxr_destroy_container(container); |
323 | 0 | jxr_destroy(image); |
324 | 0 | return gs_throw1(-1, "jxr_read_image_bitstream: %s", jxr_error_string(rc)); |
325 | 0 | } |
326 | | |
327 | 0 | rc = jxr_read_image_bitstream(image, gp_get_file(file)); |
328 | 0 | if (rc < 0) { |
329 | 0 | xps_free(ctx, name); |
330 | 0 | jxr_destroy_container(container); |
331 | 0 | jxr_destroy(image); |
332 | 0 | return gs_throw1(-1, "jxr_read_image_bitstream: %s", jxr_error_string(rc)); |
333 | 0 | } |
334 | | |
335 | 0 | jxr_destroy(image); |
336 | |
|
337 | 0 | if (alpha_offset > 0) |
338 | 0 | { |
339 | 0 | image = jxr_create_input(); |
340 | 0 | if (image == NULL) { |
341 | 0 | xps_free(ctx, name); |
342 | 0 | jxr_destroy_container(container); |
343 | 0 | return gs_throw(-1, "jxr creation failed"); |
344 | 0 | } |
345 | 0 | jxr_set_PROFILE_IDC(image, 111); |
346 | 0 | jxr_set_LEVEL_IDC(image, 255); |
347 | | //jxr_set_pixel_format(image, jxrc_image_pixelformat(container, 0)); |
348 | | //jxr_set_container_parameters(image, |
349 | | // _jxrc_image_pixelformat(container, 0), |
350 | | // jxrc_image_width(container, 0), |
351 | | // jxrc_image_height(container, 0), |
352 | | // jxrc_alpha_offset(container, 0), |
353 | | // jxrc_image_band_presence(container, 0), |
354 | | // jxrc_alpha_band_presence(container, 0), 0); |
355 | |
|
356 | 0 | jxr_set_block_output(image, xps_decode_jpegxr_alpha_block); |
357 | 0 | state.ctx = ctx; |
358 | 0 | state.output = output; |
359 | 0 | jxr_set_user_data(image, &state); |
360 | |
|
361 | 0 | rc = xps_fseek(file, alpha_offset, SEEK_SET); |
362 | 0 | if (rc != 0) { |
363 | 0 | xps_free(ctx, name); |
364 | 0 | jxr_destroy_container(container); |
365 | 0 | jxr_destroy(image); |
366 | 0 | return gs_throw1(-1, "jxr_read_image_bitstream: %s", jxr_error_string(rc)); |
367 | 0 | } |
368 | | |
369 | 0 | rc = jxr_read_image_bitstream(image, gp_get_file(file)); |
370 | 0 | if (rc < 0) { |
371 | 0 | xps_free(ctx, name); |
372 | 0 | jxr_destroy_container(container); |
373 | 0 | jxr_destroy(image); |
374 | 0 | return gs_throw1(-1, "jxr_read_image_bitstream: %s", jxr_error_string(rc)); |
375 | 0 | } |
376 | | |
377 | 0 | jxr_destroy(image); |
378 | 0 | } |
379 | | |
380 | 0 | jxr_destroy_container(container); |
381 | |
|
382 | 0 | gp_fclose(file); |
383 | 0 | gp_unlink(ctx->memory, name); |
384 | 0 | xps_free(ctx, name); |
385 | |
|
386 | 0 | return gs_okay; |
387 | 0 | } |
388 | | |
389 | | int |
390 | | xps_jpegxr_has_alpha(xps_context_t *ctx, byte *buf, int len) |
391 | 0 | { |
392 | 0 | return 1; |
393 | 0 | } |