diff options
Diffstat (limited to 'xps/xpsimage.c')
-rw-r--r-- | xps/xpsimage.c | 470 |
1 files changed, 470 insertions, 0 deletions
diff --git a/xps/xpsimage.c b/xps/xpsimage.c new file mode 100644 index 00000000..7a69ba09 --- /dev/null +++ b/xps/xpsimage.c @@ -0,0 +1,470 @@ +/* Copyright (C) 2006-2010 Artifex Software, Inc. + All Rights Reserved. + + This software is provided AS-IS with no warranty, either express or + implied. + + This software is distributed under license and may not be copied, modified + or distributed except as expressly authorized under the terms of that + license. Refer to licensing information at http://www.artifex.com/ + or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134, + San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information. +*/ + +/* XPS interpreter - image support */ + +/* TODO: we should be smarter here and do incremental decoding + * and rendering instead of uncompressing the whole image to + * memory before drawing. + */ + +#include "ghostxps.h" + +/* + * Un-interleave the alpha channel. + */ + +static void +xps_isolate_alpha_channel_8(xps_context_t *ctx, xps_image_t *image) +{ + int n = image->comps; + int y, x, k; + byte *sp, *dp, *ap; + + image->alpha = xps_alloc(ctx, image->width * image->height); + + for (y = 0; y < image->height; y++) + { + sp = image->samples + image->width * n * y; + dp = image->samples + image->width * (n - 1) * y; + ap = image->alpha + image->width * y; + for (x = 0; x < image->width; x++) + { + for (k = 0; k < n - 1; k++) + *dp++ = *sp++; + *ap++ = *sp++; + } + } + + image->hasalpha = 0; + image->comps --; + image->stride = image->width * image->comps; +} + +static void +xps_isolate_alpha_channel_16(xps_context_t *ctx, xps_image_t *image) +{ + int n = image->comps; + int y, x, k; + unsigned short *sp, *dp, *ap; + + image->alpha = xps_alloc(ctx, image->width * image->height * 2); + + for (y = 0; y < image->height; y++) + { + sp = ((unsigned short*)image->samples) + (image->width * n * y); + dp = ((unsigned short*)image->samples) + (image->width * (n - 1) * y); + ap = ((unsigned short*)image->alpha) + (image->width * y); + for (x = 0; x < image->width; x++) + { + for (k = 0; k < n - 1; k++) + *dp++ = *sp++; + *ap++ = *sp++; + } + } + + image->hasalpha = 0; + image->comps --; + image->stride = image->width * image->comps * 2; +} + +static int +xps_image_has_alpha(xps_context_t *ctx, xps_part_t *part) +{ + byte *buf = part->data; + int len = part->size; + + if (len < 8) + { + gs_warn("unknown image file format"); + return 0; + } + + if (buf[0] == 0xff && buf[1] == 0xd8) + return 0; /* JPEG never has an alpha channel */ + else if (memcmp(buf, "\211PNG\r\n\032\n", 8) == 0) + return xps_png_has_alpha(ctx, buf, len); + else if (memcmp(buf, "II", 2) == 0 && buf[2] == 0xBC) + return xps_jpegxr_has_alpha(ctx, buf, len); + else if (memcmp(buf, "MM", 2) == 0) + return xps_tiff_has_alpha(ctx, buf, len); + else if (memcmp(buf, "II", 2) == 0) + return xps_tiff_has_alpha(ctx, buf, len); + + return 0; +} + +static int +xps_decode_image(xps_context_t *ctx, xps_part_t *part, xps_image_t *image) +{ + byte *buf = part->data; + int len = part->size; + cmm_profile_t *profile; + int error; + + if (len < 8) + return gs_throw(-1, "unknown image file format"); + + memset(image, 0, sizeof(xps_image_t)); + image->samples = NULL; + image->alpha = NULL; + + if (buf[0] == 0xff && buf[1] == 0xd8) + { + error = xps_decode_jpeg(ctx, buf, len, image); + if (error) + return gs_rethrow(error, "could not decode jpeg image"); + } + else if (memcmp(buf, "\211PNG\r\n\032\n", 8) == 0) + { + error = xps_decode_png(ctx, buf, len, image); + if (error) + return gs_rethrow(error, "could not decode png image"); + } + else if (memcmp(buf, "II", 2) == 0 && buf[2] == 0xBC) + { + error = xps_decode_jpegxr(ctx, buf, len, image); + if (error) + return gs_rethrow(error, "could not decode jpeg-xr image"); + } + else if (memcmp(buf, "MM", 2) == 0 || memcmp(buf, "II", 2) == 0) + { + error = xps_decode_tiff(ctx, buf, len, image); + if (error) + return gs_rethrow(error, "could not decode tiff image"); + } + else + return gs_throw(-1, "unknown image file format"); + + // TODO: refcount image->colorspace + + /* See if we need to use the embedded profile. */ + if (image->profile) + { + /* + See if we can set up to use the embedded profile. + Note these profiles are NOT added to the xps color cache. + As such, they must be destroyed when the image brush ends. + */ + + /* Create the profile */ + profile = gsicc_profile_new(NULL, ctx->memory, NULL, 0); + + /* Set buffer */ + profile->buffer = image->profile; + profile->buffer_size = image->profilesize; + + /* Parse */ + gsicc_init_profile_info(profile); + + if (profile->profile_handle == NULL) + { + /* Problem with profile. Just ignore it */ + gs_warn("ignoring problem with icc profile embedded in an image"); + gsicc_profile_reference(profile, -1); + } + else + { + /* Check the profile is OK for channel data count. + * Need to be careful here since alpha is put into comps */ + if ((image->comps - image->hasalpha) == gsicc_getsrc_channel_count(profile)) + { + /* Create a new colorspace and associate with the profile */ + // TODO: refcount image->colorspace + gs_cspace_build_ICC(&image->colorspace, NULL, ctx->memory); + image->colorspace->cmm_icc_profile_data = profile; + } + else + { + /* Problem with profile. Just ignore it */ + gs_warn("ignoring icc profile embedded in an image with wrong number of components"); + gsicc_profile_reference(profile, -1); + } + } + } + + if (image->hasalpha) + { + if (image->bits < 8) + dprintf1("cannot isolate alpha channel in %d bpc images\n", image->bits); + if (image->bits == 8) + xps_isolate_alpha_channel_8(ctx, image); + if (image->bits == 16) + xps_isolate_alpha_channel_16(ctx, image); + } + + return gs_okay; +} + +static int +xps_paint_image_brush_imp(xps_context_t *ctx, xps_image_t *image, int alpha) +{ + gs_image_enum *penum; + gs_color_space *colorspace; + gs_image_t gsimage; + int code; + + unsigned int count; + unsigned int used; + byte *samples; + + if (alpha) + { + colorspace = ctx->gray; + samples = image->alpha; + count = (image->width * image->bits + 7) / 8 * image->height; + used = 0; + } + else + { + colorspace = image->colorspace; + samples = image->samples; + count = image->stride * image->height; + used = 0; + } + + memset(&gsimage, 0, sizeof(gsimage)); + gs_image_t_init(&gsimage, colorspace); + gsimage.ColorSpace = colorspace; + gsimage.BitsPerComponent = image->bits; + gsimage.Width = image->width; + gsimage.Height = image->height; + + gsimage.ImageMatrix.xx = image->xres / 96.0; + gsimage.ImageMatrix.yy = image->yres / 96.0; + + gsimage.Interpolate = 1; + + penum = gs_image_enum_alloc(ctx->memory, "xps_parse_image_brush (gs_image_enum_alloc)"); + if (!penum) + return gs_throw(-1, "gs_enum_allocate failed"); + + if ((code = gs_image_init(penum, &gsimage, false, ctx->pgs)) < 0) + return gs_throw(code, "gs_image_init failed"); + + if ((code = gs_image_next(penum, samples, count, &used)) < 0) + return gs_throw(code, "gs_image_next failed"); + + if (count < used) + return gs_throw2(-1, "not enough image data (image=%d used=%d)", count, used); + + if (count > used) + return gs_throw2(0, "too much image data (image=%d used=%d)", count, used); + + gs_image_cleanup_and_free_enum(penum, ctx->pgs); + + return 0; +} + +static int +xps_paint_image_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root, void *vimage) +{ + xps_image_t *image = vimage; + int code; + + if (ctx->opacity_only) + { + if (image->alpha) + { + code = xps_paint_image_brush_imp(ctx, image, 1); + if (code < 0) + return gs_rethrow(code, "cannot draw alpha channel image"); + } + return 0; + } + + if (image->alpha) + { + gs_transparency_mask_params_t params; + gs_transparency_group_params_t tgp; + gs_rect bbox; + + xps_bounds_in_user_space(ctx, &bbox); + + code = gs_gsave(ctx->pgs); + if (code < 0) + return gs_rethrow(code, "cannot gsave before transparency group"); + + gs_setcolorspace(ctx->pgs, ctx->gray); + gs_trans_mask_params_init(¶ms, TRANSPARENCY_MASK_Luminosity); + gs_begin_transparency_mask(ctx->pgs, ¶ms, &bbox, 0); + code = xps_paint_image_brush_imp(ctx, image, 1); + if (code < 0) + { + gs_end_transparency_mask(ctx->pgs, TRANSPARENCY_CHANNEL_Opacity); + gs_grestore(ctx->pgs); + return gs_rethrow(code, "cannot draw alpha channel image"); + } + gs_end_transparency_mask(ctx->pgs, TRANSPARENCY_CHANNEL_Opacity); + + gs_setcolorspace(ctx->pgs, image->colorspace); + gs_trans_group_params_init(&tgp); + gs_begin_transparency_group(ctx->pgs, &tgp, &bbox); + code = xps_paint_image_brush_imp(ctx, image, 0); + if (code < 0) + { + gs_end_transparency_group(ctx->pgs); + gs_grestore(ctx->pgs); + return gs_rethrow(code, "cannot draw color channel image"); + } + gs_end_transparency_group(ctx->pgs); + + code = gs_grestore(ctx->pgs); + if (code < 0) + return gs_rethrow(code, "cannot grestore after transparency group"); + } + else + { + code = xps_paint_image_brush_imp(ctx, image, 0); + if (code < 0) + return gs_rethrow(code, "cannot draw image"); + } + return 0; +} + +static int +xps_find_image_brush_source_part(xps_context_t *ctx, char *base_uri, xps_item_t *root, + xps_part_t **partp, char **profilep) +{ + xps_part_t *part; + char *image_source_att; + char buf[1024]; + char partname[1024]; + char *image_name; + char *profile_name; + char *p; + + image_source_att = xps_att(root, "ImageSource"); + if (!image_source_att) + return gs_throw(-1, "missing ImageSource attribute"); + + /* "{ColorConvertedBitmap /Resources/Image.tiff /Resources/Profile.icc}" */ + if (strstr(image_source_att, "{ColorConvertedBitmap") == image_source_att) + { + image_name = NULL; + profile_name = NULL; + + xps_strlcpy(buf, image_source_att, sizeof buf); + p = strchr(buf, ' '); + if (p) + { + image_name = p + 1; + p = strchr(p + 1, ' '); + if (p) + { + *p = 0; + profile_name = p + 1; + p = strchr(p + 1, '}'); + if (p) + *p = 0; + } + } + } + else + { + image_name = image_source_att; + profile_name = NULL; + } + + if (!image_name) + return gs_throw1(-1, "cannot parse image resource name '%s'", image_source_att); + + xps_absolute_path(partname, base_uri, image_name, sizeof partname); + part = xps_read_part(ctx, partname); + if (!part) + return gs_throw1(-1, "cannot find image resource part '%s'", partname); + + *partp = part; + *profilep = xps_strdup(ctx, profile_name); + + return 0; +} + +int +xps_parse_image_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root) +{ + xps_part_t *part; + xps_image_t *image; + gs_color_space *colorspace; + char *profilename; + int code; + + code = xps_find_image_brush_source_part(ctx, base_uri, root, &part, &profilename); + if (code < 0) + return gs_rethrow(code, "cannot find image source"); + + image = xps_alloc(ctx, sizeof(xps_image_t)); + if (!image) + return gs_throw(-1, "out of memory: image struct"); + + code = xps_decode_image(ctx, part, image); + if (code < 0) + return gs_rethrow1(code, "cannot decode image '%s'", part->name); + + /* Override any embedded colorspace profiles if the external one matches. */ + if (profilename) + { + colorspace = xps_read_icc_colorspace(ctx, base_uri, profilename); + if (colorspace && cs_num_components(colorspace) == cs_num_components(image->colorspace)) + { + // TODO: refcount image->colorspace + image->colorspace = colorspace; + } + } + + code = xps_parse_tiling_brush(ctx, base_uri, dict, root, xps_paint_image_brush, image); + if (code < 0) + return gs_rethrow(-1, "cannot parse tiling brush"); + + if (profilename) + xps_free(ctx, profilename); + xps_free_image(ctx, image); + xps_free_part(ctx, part); + + return 0; +} + +int +xps_image_brush_has_transparency(xps_context_t *ctx, char *base_uri, xps_item_t *root) +{ + xps_part_t *imagepart; + int code; + int has_alpha; + char *profilename; + + code = xps_find_image_brush_source_part(ctx, base_uri, root, &imagepart, &profilename); + if (code < 0) + { + gs_catch(code, "cannot find image source"); + return 0; + } + + has_alpha = xps_image_has_alpha(ctx, imagepart); + + xps_free_part(ctx, imagepart); + + return has_alpha; +} + +void +xps_free_image(xps_context_t *ctx, xps_image_t *image) +{ + // TODO: refcount image->colorspace + if (image->samples) + xps_free(ctx, image->samples); + if (image->alpha) + xps_free(ctx, image->alpha); + if (image->profile) + xps_free(ctx, image->profile); + xps_free(ctx, image); +} |