From e347a7a7c394fc2638faa2ff52e4b96545ac1ee3 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Sat, 13 Oct 2007 22:59:20 +0930 Subject: [PATCH] PS: Add support for images with bilevel alpha This patch eliminates two sources of fallback images. The alpha value of each pixel in argb32 images are checked. If all alpha values are 255 the image is treated the same as rgb24 images. If all alpha values are either 0 or 255 and the PS level is 3, a Type 3 image (image + mask) is emitted. --- src/cairo-ps-surface.c | 527 ++++++++++++++++++++++++++++++----------- 1 file changed, 392 insertions(+), 135 deletions(-) diff --git a/src/cairo-ps-surface.c b/src/cairo-ps-surface.c index 0e4e5bc05..806c2445f 100644 --- a/src/cairo-ps-surface.c +++ b/src/cairo-ps-surface.c @@ -2,6 +2,7 @@ * * Copyright © 2003 University of Southern California * Copyright © 2005 Red Hat, Inc + * Copyright © 2007 Adrian Johnson * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public @@ -35,6 +36,7 @@ * Carl D. Worth * Kristian Høgsberg * Keith Packard + * Adrian Johnson */ #include "cairoint.h" @@ -51,6 +53,12 @@ #define DEBUG_PS 0 +typedef enum _cairo_image_transparency { + CAIRO_IMAGE_IS_OPAQUE, + CAIRO_IMAGE_HAS_BILEVEL_ALPHA, + CAIRO_IMAGE_HAS_ALPHA +} cairo_image_transparency_t; + static const cairo_surface_backend_t cairo_ps_surface_backend; static const cairo_paginated_surface_backend_t cairo_ps_surface_paginated_backend; @@ -1481,6 +1489,92 @@ color_is_gray (double red, double green, double blue) fabs (red - blue) < epsilon); } +static cairo_status_t +_analyze_image_transparency (cairo_image_surface_t *image, + cairo_image_transparency_t *transparency) +{ + cairo_status_t status; + int x, y; + + if (image->format == CAIRO_FORMAT_RGB24) { + *transparency = CAIRO_IMAGE_IS_OPAQUE; + return CAIRO_STATUS_SUCCESS; + } + + if (image->format != CAIRO_FORMAT_ARGB32) { + /* If the PS surface does not support the image format, assume + * that it does have alpha. The image will be converted to + * rgb24 when the PS surface blends the image into the page + * color to remove the transparency. */ + *transparency = CAIRO_IMAGE_HAS_ALPHA; + return CAIRO_STATUS_SUCCESS; + } + + *transparency = CAIRO_IMAGE_IS_OPAQUE; + for (y = 0; y < image->height; y++) { + int a; + uint32_t *pixel = (uint32_t *) (image->data + y * image->stride); + + for (x = 0; x < image->width; x++, pixel++) { + a = (*pixel & 0xff000000) >> 24; + if (a > 0 && a < 255) { + *transparency = CAIRO_IMAGE_HAS_ALPHA; + return CAIRO_STATUS_SUCCESS; + } else if (a == 0) { + *transparency = CAIRO_IMAGE_HAS_BILEVEL_ALPHA; + } + } + } + status = CAIRO_STATUS_SUCCESS; + + return status; +} + +static cairo_int_status_t +_cairo_ps_surface_analyze_surface_pattern_transparency (cairo_ps_surface_t *surface, + cairo_surface_pattern_t *pattern) +{ + cairo_image_surface_t *image; + void *image_extra; + cairo_int_status_t status; + cairo_image_transparency_t transparency; + + status = _cairo_surface_acquire_source_image (pattern->surface, + &image, + &image_extra); + if (status) + return status; + + if (image->base.status) + return image->base.status; + + status = _analyze_image_transparency (image, &transparency); + if (status) + goto RELEASE_SOURCE; + + switch (transparency) { + case CAIRO_IMAGE_IS_OPAQUE: + status = CAIRO_STATUS_SUCCESS; + break; + + case CAIRO_IMAGE_HAS_BILEVEL_ALPHA: + if (surface->ps_level == CAIRO_PS_LEVEL_2) + status = CAIRO_INT_STATUS_FLATTEN_TRANSPARENCY; + else + status = CAIRO_STATUS_SUCCESS; + break; + + case CAIRO_IMAGE_HAS_ALPHA: + status = CAIRO_INT_STATUS_FLATTEN_TRANSPARENCY; + break; + } + +RELEASE_SOURCE: + _cairo_surface_release_source_image (pattern->surface, image, image_extra); + + return status; +} + static cairo_bool_t surface_pattern_supported (cairo_surface_pattern_t *pattern) { @@ -1578,7 +1672,7 @@ pattern_supported (cairo_ps_surface_t *surface, cairo_pattern_t *pattern) static cairo_int_status_t _cairo_ps_surface_analyze_operation (cairo_ps_surface_t *surface, cairo_operator_t op, - cairo_pattern_t *pattern) + cairo_pattern_t *pattern) { if (surface->force_fallbacks && surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) return CAIRO_INT_STATUS_UNSUPPORTED; @@ -1590,16 +1684,19 @@ _cairo_ps_surface_analyze_operation (cairo_ps_surface_t *surface, op == CAIRO_OPERATOR_OVER)) return CAIRO_INT_STATUS_UNSUPPORTED; - if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE) { - cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) pattern; - if ( _cairo_surface_is_meta (surface_pattern->surface)) { - return CAIRO_INT_STATUS_ANALYZE_META_SURFACE_PATTERN; - } - } - if (op == CAIRO_OPERATOR_SOURCE) return CAIRO_STATUS_SUCCESS; + if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE) { + cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) pattern; + + if ( _cairo_surface_is_meta (surface_pattern->surface)) + return CAIRO_INT_STATUS_ANALYZE_META_SURFACE_PATTERN; + else + return _cairo_ps_surface_analyze_surface_pattern_transparency (surface, + surface_pattern); + } + /* CAIRO_OPERATOR_OVER is only supported for opaque patterns. If * the pattern contains transparency, we return * CAIRO_INT_STATUS_FLATTEN_TRANSPARENCY to the analysis @@ -1752,100 +1849,191 @@ _string_array_stream_create (cairo_output_stream_t *output) /* PS Output - this section handles output of the parts of the meta * surface we can render natively in PS. */ +static cairo_status_t +_cairo_ps_surface_flatten_image_transparency (cairo_ps_surface_t *surface, + cairo_image_surface_t *image, + cairo_image_surface_t **opaque_image) +{ + const cairo_color_t *background_color; + cairo_surface_t *opaque; + cairo_pattern_union_t pattern; + cairo_status_t status; + + if (surface->content == CAIRO_CONTENT_COLOR_ALPHA) + background_color = CAIRO_COLOR_WHITE; + else + background_color = CAIRO_COLOR_BLACK; + + opaque = cairo_image_surface_create (CAIRO_FORMAT_RGB24, + image->width, + image->height); + if (opaque->status) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + return status; + } + + _cairo_pattern_init_for_surface (&pattern.surface, &image->base); + + status = _cairo_surface_fill_rectangle (opaque, + CAIRO_OPERATOR_SOURCE, + background_color, + 0, 0, + image->width, image->height); + if (status) + goto fail; + + status = _cairo_surface_composite (CAIRO_OPERATOR_OVER, + &pattern.base, + NULL, + opaque, + 0, 0, + 0, 0, + 0, 0, + image->width, + image->height); + if (status) + goto fail; + + _cairo_pattern_fini (&pattern.base); + *opaque_image = (cairo_image_surface_t *) opaque; + + return CAIRO_STATUS_SUCCESS; + +fail: + _cairo_pattern_fini (&pattern.base); + cairo_surface_destroy (opaque); + + return status; +} + +static cairo_status_t +_cairo_ps_surface_emit_base85_string (cairo_ps_surface_t *surface, + unsigned char *data, + unsigned long length) +{ + cairo_output_stream_t *base85_stream, *string_array_stream; + cairo_status_t status, status2; + + string_array_stream = _string_array_stream_create (surface->stream); + status = _cairo_output_stream_get_status (string_array_stream); + if (status) + return status; + + base85_stream = _cairo_base85_stream_create (string_array_stream); + status = _cairo_output_stream_get_status (base85_stream); + if (status) { + status2 = _cairo_output_stream_destroy (string_array_stream); + return status; + } + + _cairo_output_stream_write (base85_stream, data, length); + + status = _cairo_output_stream_destroy (base85_stream); + status2 = _cairo_output_stream_destroy (string_array_stream); + if (status == CAIRO_STATUS_SUCCESS) + status = status2; + + return status; +} + static cairo_status_t _cairo_ps_surface_emit_image (cairo_ps_surface_t *surface, cairo_image_surface_t *image, - const char *name) + const char *name, + cairo_operator_t op) { - cairo_status_t status, status2; - unsigned char *rgb, *compressed; - unsigned long rgb_size, compressed_size; - cairo_surface_t *opaque; - cairo_image_surface_t *opaque_image; - cairo_pattern_union_t pattern; + cairo_status_t status; + unsigned char *rgb, *rgb_compressed; + unsigned long rgb_size, rgb_compressed_size; + unsigned char *mask = NULL, *mask_compressed = NULL; + unsigned long mask_size = 0, mask_compressed_size = 0; + cairo_image_surface_t *opaque_image = NULL; int x, y, i; - cairo_output_stream_t *base85_stream, *string_array_stream; + cairo_image_transparency_t transparency; + cairo_bool_t use_mask; + + if (image->base.status) + return image->base.status; + + status = _analyze_image_transparency (image, &transparency); + if (status) + return status; /* PostScript can not represent the alpha channel, so we blend the current image over a white (or black for CONTENT_COLOR surfaces) RGB surface to eliminate it. */ - if (image->base.status) - return image->base.status; - - if (image->format != CAIRO_FORMAT_RGB24) { - const cairo_color_t *background_color; - - if (surface->content == CAIRO_CONTENT_COLOR_ALPHA) - background_color = CAIRO_COLOR_WHITE; - else - background_color = CAIRO_COLOR_BLACK; - - opaque = cairo_image_surface_create (CAIRO_FORMAT_RGB24, - image->width, - image->height); - if (opaque->status) { - status = _cairo_error (CAIRO_STATUS_NO_MEMORY); - goto bail0; - } - opaque_image = (cairo_image_surface_t *) opaque; - - _cairo_pattern_init_for_surface (&pattern.surface, &image->base); - - status = _cairo_surface_fill_rectangle (opaque, - CAIRO_OPERATOR_SOURCE, - background_color, - 0, 0, - image->width, image->height); - if (status) { - _cairo_pattern_fini (&pattern.base); - goto bail1; - } - - status = _cairo_surface_composite (CAIRO_OPERATOR_OVER, - &pattern.base, - NULL, - opaque, - 0, 0, - 0, 0, - 0, 0, - image->width, - image->height); - if (status) { - _cairo_pattern_fini (&pattern.base); - goto bail1; - } - - _cairo_pattern_fini (&pattern.base); - } else { - opaque = &image->base; + if (op == CAIRO_OPERATOR_SOURCE || + transparency == CAIRO_IMAGE_HAS_ALPHA || + (transparency == CAIRO_IMAGE_HAS_BILEVEL_ALPHA && + surface->ps_level == CAIRO_PS_LEVEL_2)) { + _cairo_ps_surface_flatten_image_transparency (surface, + image, + &opaque_image); + use_mask = FALSE; + } else if (transparency == CAIRO_IMAGE_IS_OPAQUE) { opaque_image = image; + use_mask = FALSE; + } else { + use_mask = TRUE; } - rgb_size = 3 * opaque_image->width * opaque_image->height; + rgb_size = 3 * image->width * image->height; rgb = malloc (rgb_size); if (rgb == NULL) { status = _cairo_error (CAIRO_STATUS_NO_MEMORY); goto bail1; } - i = 0; - for (y = 0; y < opaque_image->height; y++) { - uint32_t *pixel = (uint32_t *) (opaque_image->data + y * opaque_image->stride); - for (x = 0; x < opaque_image->width; x++, pixel++) { - rgb[i++] = (*pixel & 0x00ff0000) >> 16; - rgb[i++] = (*pixel & 0x0000ff00) >> 8; - rgb[i++] = (*pixel & 0x000000ff) >> 0; + if (use_mask) { + mask_size = (image->width * image->height + 7)/8; + mask = malloc (mask_size); + if (mask == NULL) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto bail2; + } + } + + if (use_mask) { + int byte = 0; + int bit = 7; + i = 0; + for (y = 0; y < image->height; y++) { + uint32_t *pixel = (uint32_t *) (image->data + y * image->stride); + for (x = 0; x < image->width; x++, pixel++) { + if (bit == 7) + mask[byte] = 0; + if (((*pixel & 0xff000000) >> 24) > 0x80) + mask[byte] |= (1 << bit); + bit--; + if (bit < 0) { + bit = 7; + byte++; + } + rgb[i++] = (*pixel & 0x00ff0000) >> 16; + rgb[i++] = (*pixel & 0x0000ff00) >> 8; + rgb[i++] = (*pixel & 0x000000ff) >> 0; + } + } + } else { + i = 0; + for (y = 0; y < opaque_image->height; y++) { + uint32_t *pixel = (uint32_t *) (opaque_image->data + y * opaque_image->stride); + for (x = 0; x < opaque_image->width; x++, pixel++) { + rgb[i++] = (*pixel & 0x00ff0000) >> 16; + rgb[i++] = (*pixel & 0x0000ff00) >> 8; + rgb[i++] = (*pixel & 0x000000ff) >> 0; + } } } /* XXX: Should fix cairo-lzw to provide a stream-based interface * instead. */ - compressed_size = rgb_size; - compressed = _cairo_lzw_compress (rgb, &compressed_size); - if (compressed == NULL) { + rgb_compressed_size = rgb_size; + rgb_compressed = _cairo_lzw_compress (rgb, &rgb_compressed_size); + if (rgb_compressed == NULL) { status = _cairo_error (CAIRO_STATUS_NO_MEMORY); - goto bail2; + goto bail3; } /* First emit the image data as a base85-encoded string which will @@ -1853,65 +2041,128 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t *surface, _cairo_output_stream_printf (surface->stream, "/%sData [\n", name); - string_array_stream = _string_array_stream_create (surface->stream); - status = _cairo_output_stream_get_status (string_array_stream); + status = _cairo_ps_surface_emit_base85_string (surface, + rgb_compressed, + rgb_compressed_size); if (status) - goto bail3; - - base85_stream = _cairo_base85_stream_create (string_array_stream); - status = _cairo_output_stream_get_status (base85_stream); - if (status) { - status2 = _cairo_output_stream_destroy (string_array_stream); - goto bail3; - } - - _cairo_output_stream_write (base85_stream, compressed, compressed_size); - - status = _cairo_output_stream_destroy (base85_stream); - status2 = _cairo_output_stream_destroy (string_array_stream); - if (status == CAIRO_STATUS_SUCCESS) - status = status2; - if (status) - goto bail3; + goto bail4; _cairo_output_stream_printf (surface->stream, "] def\n"); _cairo_output_stream_printf (surface->stream, "/%sDataIndex 0 def\n", name); - _cairo_output_stream_printf (surface->stream, - "/%s {\n" - " /DeviceRGB setcolorspace\n" - " <<\n" - " /ImageType 1\n" - " /Width %d\n" - " /Height %d\n" - " /BitsPerComponent 8\n" - " /Decode [ 0 1 0 1 0 1 ]\n" - " /DataSource {\n" - " %sData %sDataIndex get\n" - " /%sDataIndex %sDataIndex 1 add def\n" - " %sDataIndex %sData length 1 sub gt { /%sDataIndex 0 def } if\n" - " } /ASCII85Decode filter /LZWDecode filter\n" - " /ImageMatrix [ 1 0 0 1 0 0 ]\n" - " >>\n" - " image\n" - "} def\n", - name, - opaque_image->width, - opaque_image->height, - name, name, name, name, name, name, name); + /* Emit the mask data as a base85-encoded string which will + * be used as the mask source for the image operator later. */ + if (mask) { + mask_compressed_size = mask_size; + mask_compressed = _cairo_lzw_compress (mask, &mask_compressed_size); + if (mask_compressed == NULL) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto bail4; + } + + _cairo_output_stream_printf (surface->stream, + "/%sMask [\n", name); + + status = _cairo_ps_surface_emit_base85_string (surface, + mask_compressed, + mask_compressed_size); + if (status) + goto bail5; + + _cairo_output_stream_printf (surface->stream, + "] def\n"); + _cairo_output_stream_printf (surface->stream, + "/%sMaskIndex 0 def\n", name); + } + + if (mask) { + _cairo_output_stream_printf (surface->stream, + "/%s {\n" + " /DeviceRGB setcolorspace\n" + " <<\n" + " /ImageType 3\n" + " /InterleaveType 3\n" + " /DataDict <<\n" + " /ImageType 1\n" + " /Width %d\n" + " /Height %d\n" + " /BitsPerComponent 8\n" + " /Decode [ 0 1 0 1 0 1 ]\n" + " /DataSource {\n" + " %sData %sDataIndex get\n" + " /%sDataIndex %sDataIndex 1 add def\n" + " %sDataIndex %sData length 1 sub gt { /%sDataIndex 0 def } if\n" + " } /ASCII85Decode filter /LZWDecode filter\n" + " /ImageMatrix [ 1 0 0 1 0 0 ]\n" + " >>\n" + " /MaskDict <<\n" + " /ImageType 1\n" + " /Width %d\n" + " /Height %d\n" + " /BitsPerComponent 1\n" + " /Decode [ 1 0 ]\n" + " /DataSource {\n" + " %sMask %sMaskIndex get\n" + " /%sMaskIndex %sMaskIndex 1 add def\n" + " %sMaskIndex %sMask length 1 sub gt { /%sMaskIndex 0 def } if\n" + " } /ASCII85Decode filter /LZWDecode filter\n" + " /ImageMatrix [ 1 0 0 1 0 0 ]\n" + " >>\n" + " >>\n" + " image\n" + "} def\n", + name, + image->width, + image->height, + name, name, name, name, name, name, name, + image->width, + image->height, + name, name, name, name, name, name, name); + } else { + _cairo_output_stream_printf (surface->stream, + "/%s {\n" + " /DeviceRGB setcolorspace\n" + " <<\n" + " /ImageType 1\n" + " /Width %d\n" + " /Height %d\n" + " /BitsPerComponent 8\n" + " /Decode [ 0 1 0 1 0 1 ]\n" + " /DataSource {\n" + " %sData %sDataIndex get\n" + " /%sDataIndex %sDataIndex 1 add def\n" + " %sDataIndex %sData length 1 sub gt { /%sDataIndex 0 def } if\n" + " } /ASCII85Decode filter /LZWDecode filter\n" + " /ImageMatrix [ 1 0 0 1 0 0 ]\n" + " >>\n" + " image\n" + "} def\n", + name, + opaque_image->width, + opaque_image->height, + name, name, name, name, name, name, name); + } status = CAIRO_STATUS_SUCCESS; - bail3: - free (compressed); - bail2: +bail5: + if (use_mask) + free (mask_compressed); +bail4: + free (rgb_compressed); + +bail3: + if (use_mask) + free (mask); +bail2: free (rgb); - bail1: - if (opaque_image != image) - cairo_surface_destroy (opaque); - bail0: + +bail1: + if (!use_mask && opaque_image != image) + cairo_surface_destroy (&opaque_image->base); + return status; } @@ -1919,7 +2170,8 @@ static cairo_status_t _cairo_ps_surface_emit_image_surface (cairo_ps_surface_t *surface, cairo_surface_pattern_t *pattern, int *width, - int *height) + int *height, + cairo_operator_t op) { cairo_image_surface_t *image; void *image_extra; @@ -1931,7 +2183,7 @@ _cairo_ps_surface_emit_image_surface (cairo_ps_surface_t *surface, if (status) return status; - _cairo_ps_surface_emit_image (surface, image, "CairoPattern"); + _cairo_ps_surface_emit_image (surface, image, "CairoPattern", op); if (status) goto fail; @@ -2034,7 +2286,8 @@ _cairo_ps_surface_emit_solid_pattern (cairo_ps_surface_t *surface, static cairo_status_t _cairo_ps_surface_emit_surface_pattern (cairo_ps_surface_t *surface, - cairo_surface_pattern_t *pattern) + cairo_surface_pattern_t *pattern, + cairo_operator_t op) { cairo_status_t status; int pattern_width = 0; /* squelch bogus compiler warning */ @@ -2061,7 +2314,8 @@ _cairo_ps_surface_emit_surface_pattern (cairo_ps_surface_t *surface, status = _cairo_ps_surface_emit_image_surface (surface, pattern, &pattern_width, - &pattern_height); + &pattern_height, + op); if (status) return status; } @@ -2359,7 +2613,9 @@ _cairo_ps_surface_emit_radial_pattern (cairo_ps_surface_t *surface, } static cairo_status_t -_cairo_ps_surface_emit_pattern (cairo_ps_surface_t *surface, cairo_pattern_t *pattern) +_cairo_ps_surface_emit_pattern (cairo_ps_surface_t *surface, + cairo_pattern_t *pattern, + cairo_operator_t op) { /* FIXME: We should keep track of what pattern is currently set in * the postscript file and only emit code if we're setting a @@ -2373,7 +2629,8 @@ _cairo_ps_surface_emit_pattern (cairo_ps_surface_t *surface, cairo_pattern_t *pa case CAIRO_PATTERN_TYPE_SURFACE: status = _cairo_ps_surface_emit_surface_pattern (surface, - (cairo_surface_pattern_t *) pattern); + (cairo_surface_pattern_t *) pattern, + op); if (status) return status; break; @@ -2497,7 +2754,7 @@ _cairo_ps_surface_paint (void *abstract_surface, _cairo_rectangle_intersect (&extents, &pattern_extents); - status = _cairo_ps_surface_emit_pattern (surface, source); + status = _cairo_ps_surface_emit_pattern (surface, source, op); if (status) return status; @@ -2640,7 +2897,7 @@ _cairo_ps_surface_stroke (void *abstract_surface, } } - status = _cairo_ps_surface_emit_pattern (surface, source); + status = _cairo_ps_surface_emit_pattern (surface, source, op); if (status) return status; @@ -2711,7 +2968,7 @@ _cairo_ps_surface_fill (void *abstract_surface, "%% _cairo_ps_surface_fill\n"); #endif - status = _cairo_ps_surface_emit_pattern (surface, source); + status = _cairo_ps_surface_emit_pattern (surface, source, op); if (status) return status; @@ -2780,7 +3037,7 @@ _cairo_ps_surface_show_glyphs (void *abstract_surface, num_glyphs_unsigned = num_glyphs; - status = _cairo_ps_surface_emit_pattern (surface, source); + status = _cairo_ps_surface_emit_pattern (surface, source, op); if (status) return status;