Coverage Report

Created: 2026-04-09 07:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ghostpdl/xps/xpsimage.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 - image support */
18
19
/* TODO: we should be smarter here and do incremental decoding
20
 * and rendering instead of uncompressing the whole image to
21
 * memory before drawing.
22
 */
23
24
#include "ghostxps.h"
25
#include "gxdevice.h"
26
27
/*
28
 * Un-interleave the alpha channel.
29
 */
30
31
static void
32
xps_isolate_alpha_channel_8(xps_context_t *ctx, xps_image_t *image)
33
0
{
34
0
    int n = image->comps;
35
0
    int y, x, k;
36
0
    uint32_t size;
37
0
    byte *sp, *dp, *ap;
38
39
0
    if (check_uint32_multiply((uint32_t)image->width, (uint32_t)image->height, &size) != 0) {
40
0
        gs_throw(-1, "image alpha is too large");
41
0
        return;
42
0
    }
43
0
    image->alpha = xps_alloc(ctx, size);
44
0
    if (!image->alpha) {
45
0
        gs_throw(gs_error_VMerror, "out of memory: image->alpha.\n");
46
0
        return;
47
0
    }
48
49
0
    for (y = 0; y < image->height; y++)
50
0
    {
51
0
        sp = image->samples + (size_t)image->width * n * y;
52
0
        dp = image->samples + (size_t)image->width * (n - 1) * y;
53
0
        ap = image->alpha + (size_t)image->width * y;
54
0
        for (x = 0; x < (size_t)image->width; x++)
55
0
        {
56
0
            for (k = 0; k < n - 1; k++)
57
0
                *dp++ = *sp++;
58
0
            *ap++ = *sp++;
59
0
        }
60
0
    }
61
62
0
    image->hasalpha = 0;
63
0
    image->comps --;
64
0
    image->stride = image->width * image->comps;
65
0
}
66
67
static void
68
xps_isolate_alpha_channel_16(xps_context_t *ctx, xps_image_t *image)
69
0
{
70
0
    int n = image->comps;
71
0
    int y, x, k;
72
0
    unsigned short *sp, *dp, *ap;
73
0
    uint32_t size;
74
75
0
    if (check_uint32_multiply((uint32_t)image->width, (uint32_t)image->height, &size) != 0) {
76
0
        gs_throw(-1, "image alpha is too large");
77
0
        return;
78
0
    }
79
80
0
    if (size > INT_MAX / 2) {
81
0
        gs_throw(gs_error_limitcheck, "image alpha is too large");
82
0
        return;
83
0
    }
84
85
0
    image->alpha = xps_alloc(ctx, (size_t)image->width * image->height * 2);
86
0
    if (!image->alpha) {
87
0
        gs_throw(gs_error_VMerror, "out of memory: image->alpha.\n");
88
0
        return;
89
0
    }
90
91
0
    for (y = 0; y < image->height; y++)
92
0
    {
93
0
        sp = ((unsigned short*)image->samples) + ((size_t)image->width * n * y);
94
0
        dp = ((unsigned short*)image->samples) + ((size_t)image->width * (n - 1) * y);
95
0
        ap = ((unsigned short*)image->alpha) + ((size_t)image->width * y);
96
0
        for (x = 0; x < image->width; x++)
97
0
        {
98
0
            for (k = 0; k < n - 1; k++)
99
0
                *dp++ = *sp++;
100
0
            *ap++ = *sp++;
101
0
        }
102
0
    }
103
104
0
    image->hasalpha = 0;
105
0
    image->comps --;
106
0
    image->stride = (size_t)image->width * image->comps * 2;
107
0
}
108
109
static int
110
xps_image_has_alpha(xps_context_t *ctx, xps_part_t *part)
111
0
{
112
0
    byte *buf = part->data;
113
0
    int len = part->size;
114
115
0
    if (len < 8)
116
0
    {
117
0
        gs_warn("unknown image file format");
118
0
        return 0;
119
0
    }
120
121
0
    if (buf[0] == 0xff && buf[1] == 0xd8)
122
0
        return 0; /* JPEG never has an alpha channel */
123
0
    else if (memcmp(buf, "\211PNG\r\n\032\n", 8) == 0)
124
0
        return xps_png_has_alpha(ctx, buf, len);
125
0
    else if (memcmp(buf, "II", 2) == 0 && buf[2] == 0xBC)
126
0
        return xps_jpegxr_has_alpha(ctx, buf, len);
127
0
    else if (memcmp(buf, "MM", 2) == 0)
128
0
        return xps_tiff_has_alpha(ctx, buf, len);
129
0
    else if (memcmp(buf, "II", 2) == 0)
130
0
        return xps_tiff_has_alpha(ctx, buf, len);
131
132
0
    return 0;
133
0
}
134
135
static int
136
xps_decode_image(xps_context_t *ctx, xps_part_t *part, xps_image_t *image)
137
0
{
138
0
    byte *buf = part->data;
139
0
    int len = part->size;
140
0
    cmm_profile_t *profile;
141
0
    int error;
142
0
    int code;
143
144
0
    if (len < 8)
145
0
        return gs_throw(-1, "unknown image file format");
146
147
0
    memset(image, 0, sizeof(xps_image_t));
148
0
    image->samples = NULL;
149
0
    image->alpha = NULL;
150
151
0
    if (buf[0] == 0xff && buf[1] == 0xd8)
152
0
    {
153
0
        error = xps_decode_jpeg(ctx, buf, len, image);
154
0
        if (error)
155
0
            return gs_rethrow(error, "could not decode jpeg image");
156
0
    }
157
0
    else if (memcmp(buf, "\211PNG\r\n\032\n", 8) == 0)
158
0
    {
159
0
        error = xps_decode_png(ctx, buf, len, image);
160
0
        if (error)
161
0
            return gs_rethrow(error, "could not decode png image");
162
0
    }
163
0
    else if (memcmp(buf, "II", 2) == 0 && buf[2] == 0xBC)
164
0
    {
165
0
        error = xps_decode_jpegxr(ctx, buf, len, image);
166
0
        if (error)
167
0
            return gs_rethrow(error, "could not decode jpeg-xr image");
168
0
    }
169
0
    else if (memcmp(buf, "MM", 2) == 0 || memcmp(buf, "II", 2) == 0)
170
0
    {
171
0
        error = xps_decode_tiff(ctx, buf, len, image);
172
0
        if (error)
173
0
            return gs_rethrow(error, "could not decode tiff image");
174
0
    }
175
0
    else
176
0
        return gs_throw(-1, "unknown image file format");
177
178
    /* TODO: refcount image->colorspace */
179
180
    /* See if we need to use the embedded profile. */
181
0
    if (image->profile)
182
0
    {
183
        /*
184
        See if we can set up to use the embedded profile.
185
        Note these profiles are NOT added to the xps color cache.
186
        As such, they must be destroyed when the image brush ends.
187
        */
188
189
        /* Create the profile */
190
0
        profile = gsicc_profile_new(NULL, ctx->memory, NULL, 0);
191
0
        if (profile == NULL)
192
0
            return gs_throw(gs_error_VMerror, "Profile allocation failed");
193
194
        /* Set buffer - profile takes ownership! */
195
0
        profile->buffer = image->profile;
196
0
        profile->buffer_size = image->profilesize;
197
0
        image->profile = NULL;
198
199
        /* Parse */
200
0
        code = gsicc_init_profile_info(profile);
201
202
0
        if (code < 0)
203
0
        {
204
            /* Problem with profile. Just ignore it */
205
0
            gs_warn("ignoring problem with icc profile embedded in an image");
206
0
            gsicc_adjust_profile_rc(profile, -1, "xps_decode_image");
207
0
        }
208
0
        else
209
0
        {
210
            /* Check the profile is OK for channel data count.
211
             * Need to be careful here since alpha is put into comps */
212
0
            if ((image->comps - image->hasalpha) == gsicc_getsrc_channel_count(profile))
213
0
            {
214
                /* Create a new colorspace and associate with the profile */
215
0
                rc_decrement(image->colorspace, "xps_decode_image");
216
0
                gs_cspace_build_ICC(&image->colorspace, NULL, ctx->memory);
217
0
                image->colorspace->cmm_icc_profile_data = profile;
218
0
            }
219
0
            else
220
0
            {
221
                /* Problem with profile. Just ignore it */
222
0
                gs_warn("ignoring icc profile embedded in an image with wrong number of components");
223
0
                gsicc_adjust_profile_rc(profile, -1, "xps_decode_image");
224
0
                image->profile = NULL;
225
0
            }
226
0
        }
227
0
    }
228
229
0
    if (image->hasalpha)
230
0
    {
231
0
        if (image->bits < 8)
232
0
            dmprintf1(ctx->memory, "cannot isolate alpha channel in %d bpc images\n", image->bits);
233
0
        if (image->bits == 8)
234
0
            xps_isolate_alpha_channel_8(ctx, image);
235
0
        if (image->bits == 16)
236
0
            xps_isolate_alpha_channel_16(ctx, image);
237
0
    }
238
239
0
    return gs_okay;
240
0
}
241
242
static int
243
xps_paint_image_brush_imp(xps_context_t *ctx, xps_image_t *image, int alpha)
244
0
{
245
0
    gs_image_enum *penum;
246
0
    gs_color_space *colorspace;
247
0
    gs_image_t gsimage;
248
0
    int code;
249
250
0
    unsigned int count;
251
0
    unsigned int used;
252
0
    byte *samples;
253
254
0
    if (image->xres == 0 || image->yres == 0)
255
0
        return 0;
256
257
0
    if (alpha)
258
0
    {
259
0
        size_t z;
260
0
        colorspace = ctx->gray_lin;
261
0
        samples = image->alpha;
262
0
        if (check_size_multiply(image->width, image->bits, &z))
263
0
            return gs_throw(-1, "image size overflow");
264
0
        if (z & 7)
265
0
            z = (z>>3)+1;
266
0
        else
267
0
            z = (z>>3);
268
269
0
        if (check_size_multiply(z, image->height, &z))
270
0
            return gs_throw(-1, "image size overflow");
271
0
        if (z > (size_t)UINT_MAX)
272
0
            return gs_throw(-1, "image size overflow");
273
0
        count = z;
274
0
        used = 0;
275
0
    }
276
0
    else
277
0
    {
278
0
        colorspace = image->colorspace;
279
0
        samples = image->samples;
280
0
        if (check_int_multiply(image->stride, image->height, (int *)&count))
281
0
            return gs_throw(-1, "image size overflow");
282
0
        used = 0;
283
0
    }
284
285
0
    memset(&gsimage, 0, sizeof(gsimage));
286
0
    gs_image_t_init(&gsimage, colorspace);
287
0
    gsimage.ColorSpace = colorspace;
288
0
    gsimage.BitsPerComponent = image->bits;
289
0
    gsimage.Width = image->width;
290
0
    gsimage.Height = image->height;
291
292
0
    gsimage.ImageMatrix.xx = image->xres / 96.0;
293
0
    gsimage.ImageMatrix.yy = image->yres / 96.0;
294
295
0
    gsimage.Interpolate = 1;
296
297
0
    if (image->invert_decode) {
298
0
        int comp = 0;
299
300
0
        for (comp = 0;comp < image->comps;comp++) {
301
0
            gsimage.Decode[(comp * 2)] = 1.0;
302
0
            gsimage.Decode[(comp * 2) + 1] = 0.0;
303
0
        }
304
0
    }
305
306
    /* FIXME: leak enum in case of error */
307
0
    penum = gs_image_enum_alloc(ctx->memory, "xps_parse_image_brush (gs_image_enum_alloc)");
308
0
    if (!penum)
309
0
        return gs_throw(gs_error_VMerror, "gs_enum_allocate failed");
310
311
0
    if ((code = gs_image_init(penum, &gsimage, false, false, ctx->pgs)) < 0)
312
0
        return gs_throw(code, "gs_image_init failed");
313
314
0
    if ((code = gs_image_next(penum, samples, count, &used)) < 0)
315
0
        return gs_throw(code, "gs_image_next failed");
316
317
0
    if (count < used)
318
0
        return gs_throw2(-1, "not enough image data (image=%d used=%d)", count, used);
319
320
0
    if (count > used)
321
0
        return gs_throw2(0, "too much image data (image=%d used=%d)", count, used);
322
323
0
    gs_image_cleanup_and_free_enum(penum, ctx->pgs);
324
325
0
    return 0;
326
0
}
327
328
static int
329
xps_paint_image_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root, void *vimage)
330
0
{
331
0
    xps_image_t *image = vimage;
332
0
    int code;
333
334
0
    if (ctx->opacity_only)
335
0
    {
336
0
        if (image->alpha)
337
0
        {
338
0
            code = xps_paint_image_brush_imp(ctx, image, 1);
339
0
            if (code < 0)
340
0
                return gs_rethrow(code, "cannot draw alpha channel image");
341
0
        }
342
0
        return 0;
343
0
    }
344
345
0
    if (image->alpha)
346
0
    {
347
0
        gs_transparency_mask_params_t params;
348
0
        gs_transparency_group_params_t tgp;
349
0
        gs_rect bbox;
350
351
0
        xps_bounds_in_user_space(ctx, &bbox);
352
353
0
        code = gs_gsave(ctx->pgs);
354
0
        if (code < 0)
355
0
            return gs_rethrow(code, "cannot gsave before transparency group");
356
357
        /* You do not want the opacity to be used in the image soft mask filling */
358
0
        gs_setfillconstantalpha(ctx->pgs, 1.0);
359
0
        gs_setstrokeconstantalpha(ctx->pgs, 1.0);
360
0
        gs_trans_mask_params_init(&params, TRANSPARENCY_MASK_Luminosity);
361
0
        params.ColorSpace = gs_currentcolorspace_inline(ctx->pgs);
362
0
        gs_begin_transparency_mask(ctx->pgs, &params, &bbox, 0);
363
0
        code = xps_paint_image_brush_imp(ctx, image, 1);
364
0
        if (code < 0)
365
0
        {
366
0
            gs_end_transparency_mask(ctx->pgs, TRANSPARENCY_CHANNEL_Opacity);
367
0
            gs_grestore(ctx->pgs);
368
0
            return gs_rethrow(code, "cannot draw alpha channel image");
369
0
        }
370
0
        gs_end_transparency_mask(ctx->pgs, TRANSPARENCY_CHANNEL_Opacity);
371
372
0
        gs_setcolorspace(ctx->pgs, image->colorspace);
373
0
        gs_setblendmode(ctx->pgs, BLEND_MODE_Normal);
374
0
        gs_trans_group_params_init(&tgp, 1.0);
375
0
        gs_begin_transparency_group(ctx->pgs, &tgp, &bbox, PDF14_BEGIN_TRANS_GROUP);
376
0
        code = xps_paint_image_brush_imp(ctx, image, 0);
377
0
        if (code < 0)
378
0
        {
379
0
            gs_end_transparency_group(ctx->pgs);
380
0
            gs_grestore(ctx->pgs);
381
0
            return gs_rethrow(code, "cannot draw color channel image");
382
0
        }
383
0
        gs_end_transparency_group(ctx->pgs);
384
        /* Need to remove the soft mask from the graphic state.  Otherwise
385
           we may end up using it in subsequent drawings.  Note that there
386
           is not a push of the state made since there is already a soft
387
           mask present from gs_end_transparency_mask.  In this case,
388
           we are removing the mask with this forced pop. */
389
0
        gs_pop_transparency_state(ctx->pgs, true);
390
0
        code = gs_grestore(ctx->pgs);
391
0
        if (code < 0)
392
0
            return gs_rethrow(code, "cannot grestore after transparency group");
393
0
    }
394
0
    else
395
0
    {
396
0
        code = xps_paint_image_brush_imp(ctx, image, 0);
397
0
        if (code < 0)
398
0
            return gs_rethrow(code, "cannot draw image");
399
0
    }
400
0
    return 0;
401
0
}
402
403
static int
404
xps_find_image_brush_source_part(xps_context_t *ctx, char *base_uri, xps_item_t *root,
405
    xps_part_t **partp, char **profilep)
406
0
{
407
0
    xps_part_t *part;
408
0
    char *image_source_att;
409
0
    char buf[1024];
410
0
    char partname[1024];
411
0
    char *image_name;
412
0
    char *profile_name;
413
0
    char *p;
414
415
0
    image_source_att = xps_att(root, "ImageSource");
416
0
    if (!image_source_att)
417
0
        return gs_throw(-1, "missing ImageSource attribute");
418
419
    /* "{ColorConvertedBitmap /Resources/Image.tiff /Resources/Profile.icc}" */
420
0
    if (strstr(image_source_att, "{ColorConvertedBitmap") == image_source_att)
421
0
    {
422
0
        image_name = NULL;
423
0
        profile_name = NULL;
424
425
0
        gs_strlcpy(buf, image_source_att, sizeof buf);
426
0
        p = strchr(buf, ' ');
427
0
        if (p)
428
0
        {
429
0
            image_name = p + 1;
430
0
            p = strchr(p + 1, ' ');
431
0
            if (p)
432
0
            {
433
0
                *p = 0;
434
0
                profile_name = p + 1;
435
0
                p = strchr(p + 1, '}');
436
0
                if (p)
437
0
                    *p = 0;
438
0
            }
439
0
        }
440
0
    }
441
0
    else
442
0
    {
443
0
        image_name = image_source_att;
444
0
        profile_name = NULL;
445
0
    }
446
447
0
    if (!image_name)
448
0
        return gs_throw1(-1, "cannot parse image resource name '%s'", image_source_att);
449
450
0
    xps_absolute_path(partname, base_uri, image_name, sizeof partname);
451
0
    part = xps_read_part(ctx, partname);
452
0
    if (!part)
453
0
        return gs_rethrow1(-1, "cannot find image resource part '%s'", partname);
454
455
0
    *partp = part;
456
0
    if (profilep)
457
0
        *profilep = xps_strdup(ctx, profile_name);
458
459
0
    return 0;
460
0
}
461
462
int
463
xps_parse_image_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root)
464
0
{
465
0
    xps_part_t *part = NULL;
466
0
    xps_image_t *image = NULL;
467
0
    gs_color_space *colorspace;
468
0
    char *profilename = NULL;
469
0
    int code;
470
471
0
    code = xps_find_image_brush_source_part(ctx, base_uri, root, &part, &profilename);
472
0
    if (code < 0) {
473
0
        gs_rethrow(code, "cannot find image source");
474
0
        goto fail;
475
0
    }
476
477
0
    image = xps_alloc(ctx, sizeof(xps_image_t));
478
0
    if (!image) {
479
0
        gs_throw(-1, "out of memory: image struct");
480
0
        goto fail;
481
0
    }
482
483
0
    code = xps_decode_image(ctx, part, image);
484
0
    if (code < 0) {
485
0
        gs_rethrow1(code, "cannot decode image '%s'", part->name);
486
0
        goto fail;
487
0
    }
488
489
    /* Override any embedded colorspace profiles if the external one matches. */
490
0
    if (profilename)
491
0
    {
492
0
        colorspace = xps_read_icc_colorspace(ctx, base_uri, profilename);
493
0
        if (colorspace && cs_num_components(colorspace) == cs_num_components(image->colorspace))
494
0
        {
495
0
            rc_decrement(image->colorspace, "xps_parse_image_brush");
496
0
            image->colorspace = colorspace;
497
0
        }
498
0
        else
499
0
            rc_decrement(colorspace, "xps_parse_image_brush");
500
0
    }
501
502
0
    code = xps_parse_tiling_brush(ctx, base_uri, dict, root, xps_paint_image_brush, image);
503
0
    if (code < 0) {
504
0
        code = gs_rethrow(-1, "cannot parse tiling brush");
505
0
    }
506
507
0
fail:
508
0
    if (profilename)
509
0
        xps_free(ctx, profilename);
510
0
    xps_free_image(ctx, image);
511
0
    xps_free_part(ctx, part);
512
513
0
    return code;
514
0
}
515
516
int
517
xps_image_brush_has_transparency(xps_context_t *ctx, char *base_uri, xps_item_t *root)
518
0
{
519
0
    xps_part_t *imagepart;
520
0
    int code;
521
0
    int has_alpha;
522
523
0
    code = xps_find_image_brush_source_part(ctx, base_uri, root, &imagepart, NULL);
524
0
    if (code < 0)
525
0
    {
526
0
        gs_catch(code, "cannot find image source");
527
0
        return 0;
528
0
    }
529
530
0
    has_alpha = xps_image_has_alpha(ctx, imagepart);
531
532
0
    xps_free_part(ctx, imagepart);
533
534
0
    return has_alpha;
535
0
}
536
537
void
538
xps_free_image(xps_context_t *ctx, xps_image_t *image)
539
0
{
540
0
    if (image == NULL)
541
0
        return;
542
0
    rc_decrement(image->colorspace, "xps_free_image");
543
0
    if (image->samples)
544
0
        xps_free(ctx, image->samples);
545
0
    if (image->alpha)
546
0
        xps_free(ctx, image->alpha);
547
0
    if (image->profile)
548
0
        xps_free(ctx, image->profile);
549
    xps_free(ctx, image);
550
0
}