PDF: Output a stencil mask for cairo_mask() with solid source and A1 mask

In https://bugs.launchpad.net/ubuntu/+source/libcairo/+bug/680628 a
65K PDF printed to PDF using poppler-cairo turns into an 8MB PDF.  The
original PDF contains a very large number of stencil mask images (an
A1 image used as a mask for the current color). The PDF surface was
not optimized for this particular case. It was drawing a solid color
in a group and then using an smask with the image in another group.

Fix this by checking for source = solid and mask = A1 image and
emitting a stencil mask image.
This commit is contained in:
Adrian Johnson 2010-12-04 23:58:48 +10:30
parent 6732dbf299
commit d3accefd3b
2 changed files with 144 additions and 17 deletions

View file

@ -70,6 +70,7 @@ typedef struct _cairo_pdf_source_surface_entry {
unsigned char *unique_id;
unsigned long unique_id_length;
cairo_bool_t interpolate;
cairo_bool_t mask;
cairo_pdf_resource_t surface_res;
int width;
int height;

View file

@ -1148,6 +1148,7 @@ static cairo_status_t
_cairo_pdf_surface_add_source_surface (cairo_pdf_surface_t *surface,
cairo_surface_t *source,
cairo_filter_t filter,
cairo_bool_t mask,
cairo_pdf_resource_t *surface_res,
int *width,
int *height)
@ -1195,6 +1196,7 @@ _cairo_pdf_surface_add_source_surface (cairo_pdf_surface_t *surface,
surface_entry->id = surface_key.id;
surface_entry->interpolate = interpolate;
surface_entry->mask = mask;
cairo_surface_get_mime_data (source, CAIRO_MIME_TYPE_UNIQUE_ID,
&unique_id, &unique_id_length);
if (unique_id && unique_id_length > 0) {
@ -1820,6 +1822,46 @@ _cairo_pdf_surface_supports_fine_grained_fallbacks (void *abstract_surface)
return TRUE;
}
static cairo_status_t
_cairo_pdf_surface_emit_imagemask (cairo_pdf_surface_t *surface,
cairo_image_surface_t *image,
cairo_pdf_resource_t *image_res)
{
cairo_status_t status;
uint8_t *byte, output_byte;
int row, col, num_cols;
/* This is the only image format supported for stencil masking */
assert (image->format == CAIRO_FORMAT_A1);
status = _cairo_pdf_surface_open_stream (surface,
image_res,
TRUE,
" /Type /XObject\n"
" /Subtype /Image\n"
" /ImageMask true\n"
" /Width %d\n"
" /Height %d\n"
" /BitsPerComponent 1\n",
image->width, image->height);
if (unlikely (status))
return status;
num_cols = (image->width + 7) / 8;
for (row = 0; row < image->height; row++) {
byte = image->data + row * image->stride;
for (col = 0; col < num_cols; col++) {
output_byte = CAIRO_BITSWAP8_IF_LITTLE_ENDIAN (*byte);
output_byte = ~output_byte;
_cairo_output_stream_write (surface->output, &output_byte, 1);
byte++;
}
}
return _cairo_pdf_surface_close_stream (surface);
}
/* Emit alpha channel from the image into the given data, providing
* an id that can be used to reference the resulting SMask object.
*
@ -1928,7 +1970,8 @@ static cairo_status_t
_cairo_pdf_surface_emit_image (cairo_pdf_surface_t *surface,
cairo_image_surface_t *image,
cairo_pdf_resource_t *image_res,
cairo_filter_t filter)
cairo_filter_t filter,
cairo_bool_t mask)
{
cairo_status_t status = CAIRO_STATUS_SUCCESS;
char *rgb;
@ -1949,6 +1992,9 @@ _cairo_pdf_surface_emit_image (cairo_pdf_surface_t *surface,
image->format == CAIRO_FORMAT_A8 ||
image->format == CAIRO_FORMAT_A1);
if (mask)
return _cairo_pdf_surface_emit_imagemask (surface, image, image_res);
rgb_size = image->height * image->width * 3;
rgb = _cairo_malloc_abc (image->width, image->height, 3);
if (unlikely (rgb == NULL)) {
@ -2145,7 +2191,8 @@ static cairo_status_t
_cairo_pdf_surface_emit_image_surface (cairo_pdf_surface_t *surface,
cairo_surface_t *source,
cairo_pdf_resource_t resource,
cairo_bool_t interpolate)
cairo_bool_t interpolate,
cairo_bool_t mask)
{
cairo_image_surface_t *image;
void *image_extra;
@ -2164,7 +2211,7 @@ _cairo_pdf_surface_emit_image_surface (cairo_pdf_surface_t *surface,
return status;
status = _cairo_pdf_surface_emit_image (surface, image,
&resource, interpolate);
&resource, interpolate, mask);
if (unlikely (status))
goto BAIL;
@ -2255,7 +2302,7 @@ _cairo_pdf_surface_emit_padded_image_surface (cairo_pdf_surface_t *surface,
}
status = _cairo_pdf_surface_emit_image (surface, (cairo_image_surface_t *)pad_image,
resource, interpolate);
resource, interpolate, FALSE);
if (unlikely (status))
goto BAIL;
@ -2414,7 +2461,8 @@ _cairo_pdf_surface_emit_surface (cairo_pdf_surface_t *surface,
return _cairo_pdf_surface_emit_image_surface (surface,
src_surface->surface,
src_surface->hash_entry->surface_res,
src_surface->hash_entry->interpolate);
src_surface->hash_entry->interpolate,
src_surface->hash_entry->mask);
}
}
@ -2451,6 +2499,7 @@ _cairo_pdf_surface_emit_surface_pattern (cairo_pdf_surface_t *surface,
status = _cairo_pdf_surface_add_source_surface (surface,
pattern->surface,
pdf_pattern->pattern->filter,
FALSE,
&pattern_resource,
&pattern_width,
&pattern_height);
@ -3412,7 +3461,8 @@ _cairo_pdf_surface_emit_pattern (cairo_pdf_surface_t *surface, cairo_pdf_pattern
static cairo_status_t
_cairo_pdf_surface_paint_surface_pattern (cairo_pdf_surface_t *surface,
cairo_surface_pattern_t *source)
cairo_surface_pattern_t *source,
cairo_bool_t mask)
{
cairo_pdf_resource_t surface_res;
int width, height;
@ -3423,6 +3473,7 @@ _cairo_pdf_surface_paint_surface_pattern (cairo_pdf_surface_t *surface,
status = _cairo_pdf_surface_add_source_surface (surface,
source->surface,
source->base.filter,
mask,
&surface_res,
&width,
&height);
@ -3457,10 +3508,16 @@ _cairo_pdf_surface_paint_surface_pattern (cairo_pdf_surface_t *surface,
if (unlikely (status))
return status;
_cairo_output_stream_printf (surface->output,
"/a%d gs /x%d Do\n",
alpha,
surface_res.id);
if (mask) {
_cairo_output_stream_printf (surface->output,
"/x%d Do\n",
surface_res.id);
} else {
_cairo_output_stream_printf (surface->output,
"/a%d gs /x%d Do\n",
alpha,
surface_res.id);
}
return _cairo_pdf_surface_add_xobject (surface, surface_res);
}
@ -5563,6 +5620,68 @@ _cairo_pdf_surface_start_fallback (cairo_pdf_surface_t *surface)
return _cairo_pdf_surface_open_content_stream (surface, NULL, TRUE);
}
/* A PDF stencil mask is an A1 mask used with the current color */
static cairo_int_status_t
_cairo_pdf_surface_emit_stencil_mask (cairo_pdf_surface_t *surface,
const cairo_pattern_t *source,
const cairo_pattern_t *mask)
{
cairo_status_t status;
cairo_surface_pattern_t *surface_pattern;
cairo_image_surface_t *image;
void *image_extra;
cairo_pdf_resource_t pattern_res = {0};
if (! (source->type == CAIRO_PATTERN_TYPE_SOLID &&
mask->type == CAIRO_PATTERN_TYPE_SURFACE))
return CAIRO_INT_STATUS_UNSUPPORTED;
surface_pattern = (cairo_surface_pattern_t *) mask;
if (surface_pattern->surface->type == CAIRO_SURFACE_TYPE_RECORDING)
return CAIRO_INT_STATUS_UNSUPPORTED;
status = _cairo_surface_acquire_source_image (surface_pattern->surface,
&image,
&image_extra);
if (unlikely (status))
return status;
if (image->base.status)
return image->base.status;
if (image->format != CAIRO_FORMAT_A1) {
status = CAIRO_INT_STATUS_UNSUPPORTED;
goto cleanup;
}
status = _cairo_pdf_surface_select_pattern (surface, source,
pattern_res, FALSE);
if (unlikely (status))
return status;
status = _cairo_pdf_operators_flush (&surface->pdf_operators);
if (unlikely (status))
return status;
_cairo_output_stream_printf (surface->output, "q\n");
status = _cairo_pdf_surface_paint_surface_pattern (surface,
(cairo_surface_pattern_t *) surface_pattern,
TRUE);
if (unlikely (status))
return status;
_cairo_output_stream_printf (surface->output, "Q\n");
_cairo_surface_release_source_image (surface_pattern->surface, image, image_extra);
status = _cairo_output_stream_get_status (surface->output);
cleanup:
_cairo_surface_release_source_image (surface_pattern->surface, image, image_extra);
return status;
}
static cairo_int_status_t
_cairo_pdf_surface_paint (void *abstract_surface,
cairo_operator_t op,
@ -5612,7 +5731,8 @@ _cairo_pdf_surface_paint (void *abstract_surface,
{
_cairo_output_stream_printf (surface->output, "q\n");
status = _cairo_pdf_surface_paint_surface_pattern (surface,
(cairo_surface_pattern_t *) source);
(cairo_surface_pattern_t *) source,
FALSE);
if (unlikely (status))
return status;
@ -5679,7 +5799,7 @@ _cairo_pdf_surface_paint (void *abstract_surface,
}
static cairo_int_status_t
_cairo_pdf_surface_mask (void *abstract_surface,
_cairo_pdf_surface_mask (void *abstract_surface,
cairo_operator_t op,
const cairo_pattern_t *source,
const cairo_pattern_t *mask,
@ -5730,6 +5850,15 @@ _cairo_pdf_surface_mask (void *abstract_surface,
if (unlikely (status))
return status;
status = _cairo_pdf_surface_select_operator (surface, op);
if (unlikely (status))
return status;
/* Check if we can use a stencil mask */
status = _cairo_pdf_surface_emit_stencil_mask (surface, source, mask);
if (status != CAIRO_INT_STATUS_UNSUPPORTED)
return status;
group = _cairo_pdf_surface_create_smask_group (surface);
if (unlikely (group == NULL))
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
@ -5769,10 +5898,6 @@ _cairo_pdf_surface_mask (void *abstract_surface,
if (unlikely (status))
return status;
status = _cairo_pdf_surface_select_operator (surface, op);
if (unlikely (status))
return status;
_cairo_output_stream_printf (surface->output,
"q /s%d gs /x%d Do Q\n",
group->group_res.id,
@ -5984,7 +6109,8 @@ _cairo_pdf_surface_fill (void *abstract_surface,
return status;
status = _cairo_pdf_surface_paint_surface_pattern (surface,
(cairo_surface_pattern_t *) source);
(cairo_surface_pattern_t *) source,
FALSE);
if (unlikely (status))
return status;