From 665cd05a528bff8a234133bd0a21053b833b1b09 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Sun, 11 Nov 2012 18:53:12 +1030 Subject: [PATCH 2/2] cairo: make drawImage work with images > 32767 in width/height Cairo images are limited to 32767 in width and height due to the 16.16 format used by pixman. Make drawImage work with large images by scaling down the image before a cairo image is created. CairoRescaleBox.cc has been turned into a class with a virtual function to get the next row of the source image. This allows the rescale code to access the source data one row at a time to avoid needing to allocate an image the size of the source image. A RescaleDrawImage class derived from CairoRescaleBox has been written to create the cairo source image to be used by drawImage. The code from drawImage that created the cairo source image has been moved into RescaleDrawImage::getSourceImage and RescaleDrawImage::getNextRow. Bug 56858 --- poppler/CairoOutputDev.cc | 264 ++++++++++++++++++++++---------------------- poppler/CairoRescaleBox.cc | 182 +++++++++++++++--------------- poppler/CairoRescaleBox.h | 21 +++- 3 files changed, 247 insertions(+), 220 deletions(-) diff --git a/poppler/CairoOutputDev.cc b/poppler/CairoOutputDev.cc index 2cd67c9..02c0461 100644 --- a/poppler/CairoOutputDev.cc +++ b/poppler/CairoOutputDev.cc @@ -1750,49 +1750,6 @@ void CairoOutputDev::getScaledSize(int orig_width, } } -cairo_surface_t *CairoOutputDev::downscaleSurface(cairo_surface_t *orig_surface) { - cairo_surface_t *dest_surface; - unsigned char *dest_buffer; - int dest_stride; - unsigned char *orig_buffer; - int orig_width, orig_height; - int orig_stride; - int scaledHeight; - int scaledWidth; - GBool res; - - if (printing) - return NULL; - - orig_width = cairo_image_surface_get_width (orig_surface); - orig_height = cairo_image_surface_get_height (orig_surface); - getScaledSize (orig_width, orig_height, &scaledWidth, &scaledHeight); - if (scaledWidth >= orig_width || scaledHeight >= orig_height) - return NULL; - - dest_surface = cairo_surface_create_similar (orig_surface, - cairo_surface_get_content (orig_surface), - scaledWidth, scaledHeight); - dest_buffer = cairo_image_surface_get_data (dest_surface); - dest_stride = cairo_image_surface_get_stride (dest_surface); - - orig_buffer = cairo_image_surface_get_data (orig_surface); - orig_stride = cairo_image_surface_get_stride (orig_surface); - - res = downscale_box_filter((uint32_t *)orig_buffer, - orig_stride, orig_width, orig_height, - scaledWidth, scaledHeight, 0, 0, - scaledWidth, scaledHeight, - (uint32_t *)dest_buffer, dest_stride); - if (!res) { - cairo_surface_destroy (dest_surface); - return NULL; - } - - return dest_surface; - -} - cairo_filter_t CairoOutputDev::getFilterForSurface(cairo_surface_t *image, GBool interpolate) @@ -2738,62 +2695,106 @@ void CairoOutputDev::setMimeData(Stream *str, Object *ref, cairo_surface_t *imag } } -void CairoOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, - int width, int height, - GfxImageColorMap *colorMap, - GBool interpolate, - int *maskColors, GBool inlineImg) -{ - cairo_surface_t *image; - cairo_pattern_t *pattern, *maskPattern; +class RescaleDrawImage : public CairoRescaleBox { +private: ImageStream *imgStr; - cairo_matrix_t matrix; - unsigned char *buffer; - int stride, i; - GfxRGB *lookup = NULL; - cairo_filter_t filter = CAIRO_FILTER_BILINEAR; - - /* TODO: Do we want to cache these? */ - imgStr = new ImageStream(str, width, - colorMap->getNumPixelComps(), - colorMap->getBits()); - imgStr->reset(); + GfxRGB *lookup; + int width; + GfxImageColorMap *colorMap; + int *maskColors; + +public: + cairo_surface_t *getSourceImage(Stream *str, + int widthA, int height, + int scaledWidth, int scaledHeight, + GBool printing, + GfxImageColorMap *colorMapA, + int *maskColorsA) { + cairo_surface_t *image = NULL; + int i; + + lookup = NULL; + colorMap = colorMapA; + maskColors = maskColorsA; + width = widthA; + + /* TODO: Do we want to cache these? */ + imgStr = new ImageStream(str, width, + colorMap->getNumPixelComps(), + colorMap->getBits()); + imgStr->reset(); #if 0 - /* ICCBased color space doesn't do any color correction - * so check its underlying color space as well */ - int is_identity_transform; - is_identity_transform = colorMap->getColorSpace()->getMode() == csDeviceRGB || - (colorMap->getColorSpace()->getMode() == csICCBased && - ((GfxICCBasedColorSpace*)colorMap->getColorSpace())->getAlt()->getMode() == csDeviceRGB); + /* ICCBased color space doesn't do any color correction + * so check its underlying color space as well */ + int is_identity_transform; + is_identity_transform = colorMap->getColorSpace()->getMode() == csDeviceRGB || + (colorMap->getColorSpace()->getMode() == csICCBased && + ((GfxICCBasedColorSpace*)colorMap->getColorSpace())->getAlt()->getMode() == csDeviceRGB); #endif - image = cairo_image_surface_create (maskColors ? - CAIRO_FORMAT_ARGB32 : - CAIRO_FORMAT_RGB24, - width, height); - if (cairo_surface_status (image)) - goto cleanup; + // special case for one-channel (monochrome/gray/separation) images: + // build a lookup table here + if (colorMap->getNumPixelComps() == 1) { + int n; + Guchar pix; - // special case for one-channel (monochrome/gray/separation) images: - // build a lookup table here - if (colorMap->getNumPixelComps() == 1) { - int n; - Guchar pix; + n = 1 << colorMap->getBits(); + lookup = (GfxRGB *)gmallocn(n, sizeof(GfxRGB)); + for (i = 0; i < n; ++i) { + pix = (Guchar)i; - n = 1 << colorMap->getBits(); - lookup = (GfxRGB *)gmallocn(n, sizeof(GfxRGB)); - for (i = 0; i < n; ++i) { - pix = (Guchar)i; + colorMap->getRGB(&pix, &lookup[i]); + } + } - colorMap->getRGB(&pix, &lookup[i]); + if (printing || scaledWidth >= width || scaledHeight >= height) { + // No downscaling. Create cairo image containing the source image data. + unsigned char *buffer; + int stride; + + image = cairo_image_surface_create (maskColors ? + CAIRO_FORMAT_ARGB32 : + CAIRO_FORMAT_RGB24, + width, height); + if (cairo_surface_status (image)) + goto cleanup; + + buffer = cairo_image_surface_get_data (image); + stride = cairo_image_surface_get_stride (image); + for (int y = 0; y < height; y++) { + uint32_t *dest = (uint32_t *) (buffer + y * stride); + getNextRow(dest); + } + } else { + // // Downscaling required. Create cairo image the size of the rescaled image and + // // downscale the source image data into the cairo image. downScaleImage() will + // call getNextRow() for each source image row. This avoids having to create an + // image the size of the source image which may exceed cairo's 32676x32767 image + // size limit (and also saves a lot of memory). + image = cairo_image_surface_create (maskColors ? + CAIRO_FORMAT_ARGB32 : + CAIRO_FORMAT_RGB24, + scaledWidth, scaledHeight); + if (cairo_surface_status (image)) + goto cleanup; + + downScaleImage(width, height, + scaledWidth, scaledHeight, + 0, 0, scaledWidth, scaledHeight, + image); } + cairo_surface_mark_dirty (image); + + cleanup: + gfree(lookup); + imgStr->close(); + delete imgStr; + return image; } - buffer = cairo_image_surface_get_data (image); - stride = cairo_image_surface_get_stride (image); - for (int y = 0; y < height; y++) { - uint32_t *dest = (uint32_t *) (buffer + y * stride); + void getNextRow(uint32_t *row) { + int i; Guchar *pix = imgStr->getLine(); if (lookup) { @@ -2802,54 +2803,63 @@ void CairoOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, for (i = 0; i < width; i++) { rgb = lookup[*p]; - dest[i] = - ((int) colToByte(rgb.r) << 16) | - ((int) colToByte(rgb.g) << 8) | - ((int) colToByte(rgb.b) << 0); - p++; + row[i] = + ((int) colToByte(rgb.r) << 16) | + ((int) colToByte(rgb.g) << 8) | + ((int) colToByte(rgb.b) << 0); + p++; } } else { - colorMap->getRGBLine (pix, dest, width); + colorMap->getRGBLine (pix, row, width); } if (maskColors) { for (int x = 0; x < width; x++) { - bool is_opaque = false; - for (int i = 0; i < colorMap->getNumPixelComps(); ++i) { - if (pix[i] < maskColors[2*i] || - pix[i] > maskColors[2*i+1]) { - is_opaque = true; - break; - } - } - if (is_opaque) - *dest |= 0xff000000; - else - *dest = 0; - dest++; - pix += colorMap->getNumPixelComps(); + bool is_opaque = false; + for (int i = 0; i < colorMap->getNumPixelComps(); ++i) { + if (pix[i] < maskColors[2*i] || + pix[i] > maskColors[2*i+1]) { + is_opaque = true; + break; + } + } + if (is_opaque) + *row |= 0xff000000; + else + *row = 0; + row++; + pix += colorMap->getNumPixelComps(); } } } - gfree(lookup); - LOG (printf ("drawImage %dx%d\n", width, height)); +}; + +void CairoOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, + int widthA, int heightA, + GfxImageColorMap *colorMap, + GBool interpolate, + int *maskColors, GBool inlineImg) +{ + cairo_surface_t *image; + cairo_pattern_t *pattern, *maskPattern; + cairo_matrix_t matrix; + int width, height; + int scaledWidth, scaledHeight; + cairo_filter_t filter = CAIRO_FILTER_BILINEAR; + RescaleDrawImage rescale; + + LOG (printf ("drawImage %dx%d\n", widthA, heightA)); - cairo_surface_t *scaled_surface; + getScaledSize (widthA, heightA, &scaledWidth, &scaledHeight); + image = rescale.getSourceImage(str, widthA, heightA, scaledWidth, scaledHeight, printing, colorMap, maskColors); + if (!image) + return; - scaled_surface = downscaleSurface (image); - if (scaled_surface) { - if (cairo_surface_status (scaled_surface)) - goto cleanup; - cairo_surface_destroy (image); - image = scaled_surface; - width = cairo_image_surface_get_width (image); - height = cairo_image_surface_get_height (image); - } else { + width = cairo_image_surface_get_width (image); + height = cairo_image_surface_get_height (image); + if (width != widthA || height != heightA) filter = getFilterForSurface (image, interpolate); - } - - cairo_surface_mark_dirty (image); if (!inlineImg) /* don't read stream twice if it is an inline image */ setMimeData(str, ref, image); @@ -2857,7 +2867,7 @@ void CairoOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, pattern = cairo_pattern_create_for_surface (image); cairo_surface_destroy (image); if (cairo_pattern_status (pattern)) - goto cleanup; + return; cairo_pattern_set_filter (pattern, filter); @@ -2869,7 +2879,7 @@ void CairoOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, cairo_pattern_set_matrix (pattern, &matrix); if (cairo_pattern_status (pattern)) { cairo_pattern_destroy (pattern); - goto cleanup; + return; } if (!mask && fill_opacity != 1.0) { @@ -2912,10 +2922,6 @@ void CairoOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, } cairo_pattern_destroy (pattern); - -cleanup: - imgStr->close(); - delete imgStr; } diff --git a/poppler/CairoRescaleBox.cc b/poppler/CairoRescaleBox.cc index 68a0c4c..5e814a9 100644 --- a/poppler/CairoRescaleBox.cc +++ b/poppler/CairoRescaleBox.cc @@ -57,6 +57,7 @@ /* we work in fixed point where 1. == 1 << 24 */ #define FIXED_SHIFT 24 + static void downsample_row_box_filter ( int start, int width, uint32_t *src, uint32_t *dest, @@ -261,106 +262,115 @@ static int compute_coverage (int coverage[], int src_length, int dest_length) return ratio; } -GBool downscale_box_filter(uint32_t *orig, int orig_stride, unsigned orig_width, unsigned orig_height, - signed scaled_width, signed scaled_height, - uint16_t start_column, uint16_t start_row, - uint16_t width, uint16_t height, - uint32_t *dest, int dst_stride) -{ - int pixel_coverage_x, pixel_coverage_y; - int dest_y; - int src_y = 0; - uint32_t *scanline = orig; - int *x_coverage = NULL; - int *y_coverage = NULL; - uint32_t *temp_buf = NULL; - GBool retval = gFalse; - - x_coverage = (int *)gmallocn3 (orig_width, 1, sizeof(int)); - y_coverage = (int *)gmallocn3 (orig_height, 1, sizeof(int)); - - /* we need to allocate enough room for ceil(src_height/dest_height)+1 - Example: - src_height = 140 - dest_height = 50 - src_height/dest_height = 2.8 - - |-------------| 2.8 pixels - |----|----|----|----| 4 pixels - need to sample 3 pixels - - |-------------| 2.8 pixels - |----|----|----|----| 4 pixels - need to sample 4 pixels - */ - temp_buf = (uint32_t *)gmallocn3 ((orig_height + scaled_height-1)/scaled_height+1, scaled_width, sizeof(uint32_t)); - - if (!x_coverage || !y_coverage || !scanline || !temp_buf) - goto cleanup; +GBool CairoRescaleBox::downScaleImage(unsigned orig_width, unsigned orig_height, + signed scaled_width, signed scaled_height, + unsigned short int start_column, unsigned short int start_row, + unsigned short int width, unsigned short int height, + cairo_surface_t *dest_surface) { + int pixel_coverage_x, pixel_coverage_y; + int dest_y; + int src_y = 0; + uint32_t *scanline; + int *x_coverage = NULL; + int *y_coverage = NULL; + uint32_t *temp_buf = NULL; + GBool retval = gFalse; + unsigned int *dest; + int dst_stride; + + dest = (unsigned int *)cairo_image_surface_get_data (dest_surface); + dst_stride = cairo_image_surface_get_stride (dest_surface); + + scanline = (uint32_t*)gmallocn3 (orig_width, 1, sizeof(int)); + + x_coverage = (int *)gmallocn3 (orig_width, 1, sizeof(int)); + y_coverage = (int *)gmallocn3 (orig_height, 1, sizeof(int)); + + /* we need to allocate enough room for ceil(src_height/dest_height)+1 + Example: + src_height = 140 + dest_height = 50 + src_height/dest_height = 2.8 + + |-------------| 2.8 pixels + |----|----|----|----| 4 pixels + need to sample 3 pixels + + |-------------| 2.8 pixels + |----|----|----|----| 4 pixels + need to sample 4 pixels + */ + + temp_buf = (uint32_t *)gmallocn3 ((orig_height + scaled_height-1)/scaled_height+1, scaled_width, sizeof(uint32_t)); + + if (!x_coverage || !y_coverage || !scanline || !temp_buf) + goto cleanup; + + pixel_coverage_x = compute_coverage (x_coverage, orig_width, scaled_width); + pixel_coverage_y = compute_coverage (y_coverage, orig_height, scaled_height); + + assert (width + start_column <= scaled_width); + + /* skip the rows at the beginning */ + for (dest_y = 0; dest_y < start_row; dest_y++) + { + int box = 1 << FIXED_SHIFT; + int start_coverage_y = y_coverage[dest_y]; + box -= start_coverage_y; + getNextRow(scanline); + src_y++; + while (box >= pixel_coverage_y) + { + box -= pixel_coverage_y; + getNextRow(scanline); + src_y++; + } + } - pixel_coverage_x = compute_coverage (x_coverage, orig_width, scaled_width); - pixel_coverage_y = compute_coverage (y_coverage, orig_height, scaled_height); + for (; dest_y < start_row + height; dest_y++) + { + int columns = 0; + int box = 1 << FIXED_SHIFT; + int start_coverage_y = y_coverage[dest_y]; - assert (width + start_column <= scaled_width); + downsample_row_box_filter (start_column, width, scanline, temp_buf + width * columns, x_coverage, pixel_coverage_x); + columns++; + getNextRow(scanline); + src_y++; + box -= start_coverage_y; - /* skip the rows at the beginning */ - for (dest_y = 0; dest_y < start_row; dest_y++) + while (box >= pixel_coverage_y) { - int box = 1 << FIXED_SHIFT; - int start_coverage_y = y_coverage[dest_y]; - box -= start_coverage_y; - src_y++; - while (box >= pixel_coverage_y) - { - box -= pixel_coverage_y; - src_y++; - } + downsample_row_box_filter (start_column, width, scanline, temp_buf + width * columns, x_coverage, pixel_coverage_x); + columns++; + getNextRow(scanline); + src_y++; + box -= pixel_coverage_y; } - for (; dest_y < start_row + height; dest_y++) + /* downsample any leftovers */ + if (box > 0) { - int columns = 0; - int box = 1 << FIXED_SHIFT; - int start_coverage_y = y_coverage[dest_y]; - - scanline = orig + src_y * orig_stride / 4; - downsample_row_box_filter (start_column, width, scanline, temp_buf + width * columns, x_coverage, pixel_coverage_x); - columns++; - src_y++; - box -= start_coverage_y; - - while (box >= pixel_coverage_y) - { - scanline = orig + src_y * orig_stride / 4; - downsample_row_box_filter (start_column, width, scanline, temp_buf + width * columns, x_coverage, pixel_coverage_x); - columns++; - src_y++; - box -= pixel_coverage_y; - } - - /* downsample any leftovers */ - if (box > 0) - { - scanline = orig + src_y * orig_stride / 4; - downsample_row_box_filter (start_column, width, scanline, temp_buf + width * columns, x_coverage, pixel_coverage_x); - columns++; - } + downsample_row_box_filter (start_column, width, scanline, temp_buf + width * columns, x_coverage, pixel_coverage_x); + columns++; + } - /* now scale the rows we just downsampled in the y direction */ - downsample_columns_box_filter (width, start_coverage_y, pixel_coverage_y, temp_buf, dest); - dest += dst_stride / 4; + /* now scale the rows we just downsampled in the y direction */ + downsample_columns_box_filter (width, start_coverage_y, pixel_coverage_y, temp_buf, dest); + dest += dst_stride / 4; // assert(width*columns <= ((orig_height + scaled_height-1)/scaled_height+1) * width); - } + } // assert (src_y<=orig_height); - retval = gTrue; + retval = gTrue; cleanup: - free (x_coverage); - free (y_coverage); - free (temp_buf); + free (x_coverage); + free (y_coverage); + free (temp_buf); + free (scanline); - return retval; + return retval; } diff --git a/poppler/CairoRescaleBox.h b/poppler/CairoRescaleBox.h index 5349c87..8f6856a 100644 --- a/poppler/CairoRescaleBox.h +++ b/poppler/CairoRescaleBox.h @@ -2,11 +2,22 @@ #define CAIRO_RESCALE_BOX_H #include "goo/gtypes.h" +#include -GBool downscale_box_filter(unsigned int *orig, int orig_stride, unsigned orig_width, unsigned orig_height, - signed scaled_width, signed scaled_height, - unsigned short int start_column, unsigned short int start_row, - unsigned short int width, unsigned short int height, - unsigned int *dest, int dst_stride); +class CairoRescaleBox { +public: + + CairoRescaleBox() {}; + virtual ~CairoRescaleBox() {}; + + virtual GBool downScaleImage(unsigned orig_width, unsigned orig_height, + signed scaled_width, signed scaled_height, + unsigned short int start_column, unsigned short int start_row, + unsigned short int width, unsigned short int height, + cairo_surface_t *dest_surface); + + virtual void getNextRow(uint32_t *row) = 0; + +}; #endif /* CAIRO_RESCALE_BOX_H */ -- 1.7.10.4