From 6886306e2b13ea54319ee51313ddcb9574535dfd 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 | 119 ++++++++++++++++++++ poppler/CairoRescaleBox.h | 21 +++- 3 files changed, 270 insertions(+), 134 deletions(-) diff --git a/poppler/CairoOutputDev.cc b/poppler/CairoOutputDev.cc index 2cd67c9..2f863ec 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,100 @@ 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 downscale, + 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 (downscale) { + 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); + } else { + 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); + } } + 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 +2797,69 @@ 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)); +}; - cairo_surface_t *scaled_surface; +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; + GBool downscale; - 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 { - filter = getFilterForSurface (image, interpolate); - } + LOG (printf ("drawImage %dx%d\n", widthA, heightA)); - cairo_surface_mark_dirty (image); + getScaledSize (widthA, heightA, &scaledWidth, &scaledHeight); + if (printing || scaledWidth >= widthA || scaledHeight >= heightA) + downscale = gFalse; + else + downscale = gTrue; + + image = rescale.getSourceImage(str, widthA, heightA, scaledWidth, scaledHeight, downscale, colorMap, maskColors); + if (!image) + return; + + width = cairo_image_surface_get_width (image); + height = cairo_image_surface_get_height (image); + if (width != widthA || height != heightA) + filter = getFilterForSurface (image, interpolate); 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..7683d31 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,6 +262,123 @@ static int compute_coverage (int coverage[], int src_length, int dest_length) return ratio; } + +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++; + } + } + + for (; dest_y < start_row + height; dest_y++) + { + 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++; + getNextRow(scanline); + 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++; + getNextRow(scanline); + 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++; + } + + /* 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; + +cleanup: + free (x_coverage); + free (y_coverage); + free (temp_buf); + free (scanline); + + return retval; +} + +#if 0 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, @@ -364,3 +482,4 @@ cleanup: return retval; } +#endif 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