Coverage Report

Created: 2026-09-14 07:34

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
3
{
112
3
    byte *buf = part->data;
113
3
    int len = part->size;
114
115
3
    if (len < 8)
116
0
    {
117
0
        gs_warn("unknown image file format");
118
0
        return 0;
119
0
    }
120
121
3
    if (buf[0] == 0xff && buf[1] == 0xd8)
122
0
        return 0; /* JPEG never has an alpha channel */
123
3
    else if (memcmp(buf, "\211PNG\r\n\032\n", 8) == 0)
124
0
        return xps_png_has_alpha(ctx, buf, len);
125
3
    else if (memcmp(buf, "II", 2) == 0 && buf[2] == 0xBC)
126
0
        return xps_jpegxr_has_alpha(ctx, buf, len);
127
3
    else if (memcmp(buf, "MM", 2) == 0)
128
0
        return xps_tiff_has_alpha(ctx, buf, len);
129
3
    else if (memcmp(buf, "II", 2) == 0)
130
3
        return xps_tiff_has_alpha(ctx, buf, len);
131
132
0
    return 0;
133
3
}
134
135
static int
136
xps_decode_image(xps_context_t *ctx, xps_part_t *part, xps_image_t *image)
137
13
{
138
13
    byte *buf = part->data;
139
13
    int len = part->size;
140
13
    cmm_profile_t *profile;
141
13
    int error;
142
13
    int code;
143
144
13
    if (len < 8)
145
0
        return gs_throw(-1, "unknown image file format");
146
147
13
    memset(image, 0, sizeof(xps_image_t));
148
13
    image->samples = NULL;
149
13
    image->alpha = NULL;
150
151
13
    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
13
    else if (memcmp(buf, "\211PNG\r\n\032\n", 8) == 0)
158
10
    {
159
10
        error = xps_decode_png(ctx, buf, len, image);
160
10
        if (error)
161
0
            return gs_rethrow(error, "could not decode png image");
162
10
    }
163
3
    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
3
    else if (memcmp(buf, "MM", 2) == 0 || memcmp(buf, "II", 2) == 0)
170
3
    {
171
3
        error = xps_decode_tiff(ctx, buf, len, image);
172
3
        if (error)
173
0
            return gs_rethrow(error, "could not decode tiff image");
174
3
    }
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
13
    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
13
    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
13
    return gs_okay;
240
13
}
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
16
{
407
16
    xps_part_t *part;
408
16
    char *image_source_att;
409
16
    char buf[1024];
410
16
    char partname[1024];
411
16
    char *image_name;
412
16
    char *profile_name;
413
16
    char *p;
414
415
16
    image_source_att = xps_att(root, "ImageSource");
416
16
    if (!image_source_att)
417
0
        return gs_throw(-1, "missing ImageSource attribute");
418
419
    /* "{ColorConvertedBitmap /Resources/Image.tiff /Resources/Profile.icc}" */
420
16
    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
16
    else
442
16
    {
443
16
        image_name = image_source_att;
444
16
        profile_name = NULL;
445
16
    }
446
447
16
    if (!image_name)
448
0
        return gs_throw1(-1, "cannot parse image resource name '%s'", image_source_att);
449
450
16
    xps_absolute_path(partname, base_uri, image_name, sizeof partname);
451
16
    part = xps_read_part(ctx, partname);
452
16
    if (!part)
453
0
        return gs_rethrow1(-1, "cannot find image resource part '%s'", partname);
454
455
16
    *partp = part;
456
16
    if (profilep)
457
13
        *profilep = xps_strdup(ctx, profile_name);
458
459
16
    return 0;
460
16
}
461
462
int
463
xps_parse_image_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root)
464
13
{
465
13
    xps_part_t *part = NULL;
466
13
    xps_image_t *image = NULL;
467
13
    gs_color_space *colorspace;
468
13
    char *profilename = NULL;
469
13
    int code;
470
471
13
    code = xps_find_image_brush_source_part(ctx, base_uri, root, &part, &profilename);
472
13
    if (code < 0) {
473
0
        gs_rethrow(code, "cannot find image source");
474
0
        goto fail;
475
0
    }
476
477
13
    image = xps_alloc(ctx, sizeof(xps_image_t));
478
13
    if (!image) {
479
0
        gs_throw(-1, "out of memory: image struct");
480
0
        goto fail;
481
0
    }
482
483
13
    code = xps_decode_image(ctx, part, image);
484
13
    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
13
    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
13
    code = xps_parse_tiling_brush(ctx, base_uri, dict, root, xps_paint_image_brush, image);
503
13
    if (code < 0) {
504
0
        code = gs_rethrow(-1, "cannot parse tiling brush");
505
0
    }
506
507
13
fail:
508
13
    if (profilename)
509
13
        xps_free(ctx, profilename);
510
13
    xps_free_image(ctx, image);
511
13
    xps_free_part(ctx, part);
512
513
13
    return code;
514
13
}
515
516
int
517
xps_image_brush_has_transparency(xps_context_t *ctx, char *base_uri, xps_item_t *root)
518
3
{
519
3
    xps_part_t *imagepart;
520
3
    int code;
521
3
    int has_alpha;
522
523
3
    code = xps_find_image_brush_source_part(ctx, base_uri, root, &imagepart, NULL);
524
3
    if (code < 0)
525
0
    {
526
0
        gs_catch(code, "cannot find image source");
527
0
        return 0;
528
0
    }
529
530
3
    has_alpha = xps_image_has_alpha(ctx, imagepart);
531
532
3
    xps_free_part(ctx, imagepart);
533
534
3
    return has_alpha;
535
3
}
536
537
void
538
xps_free_image(xps_context_t *ctx, xps_image_t *image)
539
13
{
540
13
    if (image == NULL)
541
0
        return;
542
13
    rc_decrement(image->colorspace, "xps_free_image");
543
13
    if (image->samples)
544
13
        xps_free(ctx, image->samples);
545
13
    if (image->alpha)
546
13
        xps_free(ctx, image->alpha);
547
13
    if (image->profile)
548
13
        xps_free(ctx, image->profile);
549
    xps_free(ctx, image);
550
13
}