From e66d37606753f856492355a3bd6afe9a455e81ab Mon Sep 17 00:00:00 2001 From: "vandebo@chromium.org" Date: Wed, 23 Oct 2013 20:17:29 +0000 Subject: [PATCH] [PDF] Add unpremultiply support and a GM (try4) This is a resubmit of https://codereview.chromium.org/22831039 which was reverted because it causes issues on Windows (really, release builds). BUG=chromium:175548 R=edisonn@google.com Review URL: https://codereview.chromium.org/33493003 git-svn-id: http://skia.googlecode.com/svn/trunk@11928 2bbb7eff-a529-9590-31e7-b0007b416f81 --- gm/bitmappremul.cpp | 131 ++++++++++++++++++++++++++++++++ gyp/gmslides.gypi | 1 + src/pdf/SkPDFImage.cpp | 164 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 294 insertions(+), 2 deletions(-) create mode 100644 gm/bitmappremul.cpp diff --git a/gm/bitmappremul.cpp b/gm/bitmappremul.cpp new file mode 100644 index 0000000000..8d0bf697cc --- /dev/null +++ b/gm/bitmappremul.cpp @@ -0,0 +1,131 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "gm.h" +#include "SkBitmap.h" +#include "SkCanvas.h" +#include "SkColorPriv.h" + +/** + * This GM checks that bitmap pixels are unpremultiplied before being exported + * to other formats. If unpremultiplication is implemented properly, this + * GM should come out completely white. If not, this GM looks like a row of two + * greyscale gradients above a row of grey lines. + * This tests both the ARGB4444 and ARGB8888 bitmap configurations. + */ + +static const int SLIDE_SIZE = 256; +static const int PIXEL_SIZE_8888 = SLIDE_SIZE / 256; +static const int PIXEL_SIZE_4444 = SLIDE_SIZE / 16; + +static SkBitmap init_bitmap(SkBitmap::Config config) { + SkBitmap bitmap; + bitmap.setConfig(config, SLIDE_SIZE, SLIDE_SIZE); + bitmap.allocPixels(); + bitmap.eraseColor(SK_ColorWHITE); + return bitmap; +} + +static SkBitmap make_argb8888_gradient() { + SkBitmap bitmap = init_bitmap(SkBitmap::kARGB_8888_Config); + uint8_t rowColor = 0; + for (int y = 0; y < SLIDE_SIZE; y++) { + uint32_t* dst = bitmap.getAddr32(0, y); + for (int x = 0; x < SLIDE_SIZE; x++) { + dst[x] = SkPackARGB32(rowColor, rowColor, + rowColor, rowColor); + } + if (y % PIXEL_SIZE_8888 == PIXEL_SIZE_8888 - 1) { + rowColor++; + } + } + return bitmap; +} + +static SkBitmap make_argb4444_gradient() { + SkBitmap bitmap = init_bitmap(SkBitmap::kARGB_4444_Config); + uint8_t rowColor = 0; + for (int y = 0; y < SLIDE_SIZE; y++) { + uint16_t* dst = bitmap.getAddr16(0, y); + for (int x = 0; x < SLIDE_SIZE; x++) { + dst[x] = SkPackARGB4444(rowColor, rowColor, + rowColor, rowColor); + } + if (y % PIXEL_SIZE_4444 == PIXEL_SIZE_4444 - 1) { + rowColor++; + } + } + return bitmap; +} + +static SkBitmap make_argb8888_stripes() { + SkBitmap bitmap = init_bitmap(SkBitmap::kARGB_8888_Config); + uint8_t rowColor = 0; + for (int y = 0; y < SLIDE_SIZE; y++) { + uint32_t* dst = bitmap.getAddr32(0, y); + for (int x = 0; x < SLIDE_SIZE; x++) { + dst[x] = SkPackARGB32(rowColor, rowColor, + rowColor, rowColor); + } + if (rowColor == 0) { + rowColor = 255; + } else { + rowColor = 0; + } + } + return bitmap; +} + +static SkBitmap make_argb4444_stripes() { + SkBitmap bitmap = init_bitmap(SkBitmap::kARGB_4444_Config); + uint8_t rowColor = 0;; + for (int y = 0; y < SLIDE_SIZE; y++) { + uint16_t* dst = bitmap.getAddr16(0, y); + for (int x = 0; x < SLIDE_SIZE; x++) { + dst[x] = SkPackARGB4444(rowColor, rowColor, + rowColor, rowColor); + } + if (rowColor == 0) { + rowColor = 15; + } else { + rowColor = 0; + } + } + return bitmap; +} + +namespace skiagm { + +class BitmapPremulGM : public GM { +public: + BitmapPremulGM() { + this->setBGColor(SK_ColorWHITE); + } + +protected: + SkString onShortName() SK_OVERRIDE { + return SkString("bitmap_premul"); + } + + virtual SkISize onISize() SK_OVERRIDE { + return SkISize::Make(SLIDE_SIZE * 2, SLIDE_SIZE * 2); + } + + virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE { + SkScalar slideSize = SkIntToScalar(SLIDE_SIZE); + canvas->drawBitmap(make_argb8888_gradient(), 0, 0); + canvas->drawBitmap(make_argb4444_gradient(), slideSize, 0); + canvas->drawBitmap(make_argb8888_stripes(), 0, slideSize); + canvas->drawBitmap(make_argb4444_stripes(), slideSize, slideSize); + } + +private: + typedef GM INHERITED; +}; + +DEF_GM( return new BitmapPremulGM; ) +} diff --git a/gyp/gmslides.gypi b/gyp/gmslides.gypi index a83ae2e1dd..459134609e 100644 --- a/gyp/gmslides.gypi +++ b/gyp/gmslides.gypi @@ -15,6 +15,7 @@ '../gm/bitmapcopy.cpp', '../gm/bitmapmatrix.cpp', '../gm/bitmapfilters.cpp', + '../gm/bitmappremul.cpp', '../gm/bitmaprect.cpp', '../gm/bitmaprecttest.cpp', '../gm/bitmapscroll.cpp', diff --git a/src/pdf/SkPDFImage.cpp b/src/pdf/SkPDFImage.cpp index 19ad79e4a5..ed204550e7 100644 --- a/src/pdf/SkPDFImage.cpp +++ b/src/pdf/SkPDFImage.cpp @@ -336,6 +336,157 @@ static SkPDFArray* make_indexed_color_space(SkColorTable* table) { return result; } +/** + * Removes the alpha component of an ARGB color (including unpremultiply) while + * keeping the output in the same format as the input. + */ +static uint32_t remove_alpha_argb8888(uint32_t pmColor) { + SkColor color = SkUnPreMultiply::PMColorToColor(pmColor); + return SkPackARGB32NoCheck(SK_AlphaOPAQUE, + SkColorGetR(color), + SkColorGetG(color), + SkColorGetB(color)); +} + +static uint16_t remove_alpha_argb4444(uint16_t pmColor) { + return SkPixel32ToPixel4444( + remove_alpha_argb8888(SkPixel4444ToPixel32(pmColor))); +} + +static uint32_t get_argb8888_neighbor_avg_color(const SkBitmap& bitmap, + int xOrig, int yOrig) { + uint8_t count = 0; + uint16_t r = 0; + uint16_t g = 0; + uint16_t b = 0; + + for (int y = yOrig - 1; y <= yOrig + 1; y++) { + if (y < 0 || y >= bitmap.height()) { + continue; + } + uint32_t* src = bitmap.getAddr32(0, y); + for (int x = xOrig - 1; x <= xOrig + 1; x++) { + if (x < 0 || x >= bitmap.width()) { + continue; + } + if (SkGetPackedA32(src[x]) != SK_AlphaTRANSPARENT) { + uint32_t color = remove_alpha_argb8888(src[x]); + r += SkGetPackedR32(color); + g += SkGetPackedG32(color); + b += SkGetPackedB32(color); + count++; + } + } + } + + if (count == 0) { + return SkPackARGB32NoCheck(SK_AlphaOPAQUE, 0, 0, 0); + } else { + return SkPackARGB32NoCheck(SK_AlphaOPAQUE, + r / count, g / count, b / count); + } +} + +static uint16_t get_argb4444_neighbor_avg_color(const SkBitmap& bitmap, + int xOrig, int yOrig) { + uint8_t count = 0; + uint8_t r = 0; + uint8_t g = 0; + uint8_t b = 0; + + for (int y = yOrig - 1; y <= yOrig + 1; y++) { + if (y < 0 || y >= bitmap.height()) { + continue; + } + uint16_t* src = bitmap.getAddr16(0, y); + for (int x = xOrig - 1; x <= xOrig + 1; x++) { + if (x < 0 || x >= bitmap.width()) { + continue; + } + if ((SkGetPackedA4444(src[x]) & 0x0F) != SK_AlphaTRANSPARENT) { + uint16_t color = remove_alpha_argb4444(src[x]); + r += SkGetPackedR4444(color); + g += SkGetPackedG4444(color); + b += SkGetPackedB4444(color); + count++; + } + } + } + + if (count == 0) { + return SkPackARGB4444(SK_AlphaOPAQUE & 0x0F, 0, 0, 0); + } else { + return SkPackARGB4444(SK_AlphaOPAQUE & 0x0F, + r / count, g / count, b / count); + } +} + +static SkBitmap unpremultiply_bitmap(const SkBitmap& bitmap, + const SkIRect& srcRect) { + SkBitmap outBitmap; + outBitmap.setConfig(bitmap.config(), srcRect.width(), srcRect.height()); + outBitmap.allocPixels(); + size_t dstRow = 0; + + outBitmap.lockPixels(); + bitmap.lockPixels(); + switch (bitmap.config()) { + case SkBitmap::kARGB_4444_Config: { + for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { + uint16_t* dst = outBitmap.getAddr16(0, dstRow); + uint16_t* src = bitmap.getAddr16(0, y); + for (int x = srcRect.fLeft; x < srcRect.fRight; x++) { + uint8_t a = SkGetPackedA4444(src[x]); + // It is necessary to average the color component of + // transparent pixels with their surrounding neighbors + // since the PDF renderer may separately re-sample the + // alpha and color channels when the image is not + // displayed at its native resolution. Since an alpha of + // zero gives no information about the color component, + // the pathological case is a white image with sharp + // transparency bounds - the color channel goes to black, + // and the should-be-transparent pixels are rendered + // as grey because of the separate soft mask and color + // resizing. + if (a == (SK_AlphaTRANSPARENT & 0x0F)) { + *dst = get_argb4444_neighbor_avg_color(bitmap, x, y); + } else { + *dst = remove_alpha_argb4444(src[x]); + } + dst++; + } + dstRow++; + } + break; + } + case SkBitmap::kARGB_8888_Config: { + for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { + uint32_t* dst = outBitmap.getAddr32(0, dstRow); + uint32_t* src = bitmap.getAddr32(0, y); + for (int x = srcRect.fLeft; x < srcRect.fRight; x++) { + uint8_t a = SkGetPackedA32(src[x]); + if (a == SK_AlphaTRANSPARENT) { + *dst = get_argb8888_neighbor_avg_color(bitmap, x, y); + } else { + *dst = remove_alpha_argb8888(src[x]); + } + dst++; + } + dstRow++; + } + break; + } + default: + SkASSERT(false); + } + bitmap.unlockPixels(); + outBitmap.unlockPixels(); + + outBitmap.setImmutable(); + + return outBitmap; +} + // static SkPDFImage* SkPDFImage::CreateImage(const SkBitmap& bitmap, const SkIRect& srcRect, @@ -358,8 +509,17 @@ SkPDFImage* SkPDFImage::CreateImage(const SkBitmap& bitmap, return NULL; } - SkPDFImage* image = SkNEW_ARGS(SkPDFImage, (NULL, bitmap, - false, srcRect, encoder)); + SkPDFImage* image; + SkBitmap::Config config = bitmap.config(); + if (alphaData.get() != NULL && (config == SkBitmap::kARGB_8888_Config || + config == SkBitmap::kARGB_4444_Config)) { + SkBitmap unpremulBitmap = unpremultiply_bitmap(bitmap, srcRect); + image = SkNEW_ARGS(SkPDFImage, (NULL, unpremulBitmap, false, + SkIRect::MakeWH(srcRect.width(), srcRect.height()), + encoder)); + } else { + image = SkNEW_ARGS(SkPDFImage, (NULL, bitmap, false, srcRect, encoder)); + } if (alphaData.get() != NULL) { SkAutoTUnref mask( SkNEW_ARGS(SkPDFImage, (alphaData.get(), bitmap,