Coverage Report

Created: 2026-04-01 07:17

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