Revert of PDF: remove last use of SkPDFImage (patchset #5 id:120001 of https://codereview.chromium.org/950633003/)
Reason for revert:
static void draw(SkCanvas* canvas,
const SkPaint& p,
const SkBitmap& src,
SkColorType colorType,
const char text[]) {
SkASSERT(src.colorType() == colorType);
canvas->drawBitmap(src, 0.0f, 0.0f);
canvas->drawText(text, strlen(text), 0.0f, 12.0f, p);
}
This assert is firing, at least on macs, where all images get decoded into 32bit at the moment.
Original issue's description:
> PDF: remove last use of SkPDFImage
>
> Add a GM.
>
> BUG=skia:255
>
> Committed: https://skia.googlesource.com/skia/+/86ad8d643624a55b02e529100bbe4e2940115fa1
TBR=mtklein@google.com,halcanary@google.com
NOPRESUBMIT=true
NOTREECHECKS=true
NOTRY=true
BUG=skia:255
Review URL: https://codereview.chromium.org/1024113002
diff --git a/gm/all_bitmap_configs.cpp b/gm/all_bitmap_configs.cpp
deleted file mode 100644
index 95568f2..0000000
--- a/gm/all_bitmap_configs.cpp
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2015 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "sk_tool_utils.h"
-#include "SkSurface.h"
-#include "Resources.h"
-#include "gm.h"
-
-static SkBitmap copy_bitmap(const SkBitmap& src, SkColorType colorType) {
- SkBitmap copy;
- src.copyTo(©, colorType);
- copy.setImmutable();
- return copy;
-}
-
-// Make either A8 or gray8 bitmap.
-static SkBitmap make_bitmap(bool alpha) {
- SkBitmap bm;
- SkImageInfo info = alpha ? SkImageInfo::MakeA8(128, 128)
- : SkImageInfo::Make(128, 128, kGray_8_SkColorType,
- kOpaque_SkAlphaType);
- bm.allocPixels(info);
- SkAutoLockPixels autoLockPixels(bm);
- uint8_t spectrum[256];
- for (int y = 0; y < 256; ++y) {
- spectrum[y] = y;
- }
- for (int y = 0; y < 128; ++y) {
- // Shift over one byte each scanline.
- memcpy(bm.getAddr8(0, y), &spectrum[y], 128);
- }
- bm.setImmutable();
- return bm;
-}
-
-static void draw(SkCanvas* canvas,
- const SkPaint& p,
- const SkBitmap& src,
- SkColorType colorType,
- const char text[]) {
- SkASSERT(src.colorType() == colorType);
- canvas->drawBitmap(src, 0.0f, 0.0f);
- canvas->drawText(text, strlen(text), 0.0f, 12.0f, p);
-}
-
-#define SCALE 128
-DEF_SIMPLE_GM(all_bitmap_configs, canvas, SCALE, 6 * SCALE) {
- SkAutoCanvasRestore autoCanvasRestore(canvas, true);
- SkPaint p;
- p.setColor(SK_ColorBLACK);
- p.setAntiAlias(true);
- sk_tool_utils::set_portable_typeface(&p, NULL, SkTypeface::kBold);
-
- sk_tool_utils::draw_checkerboard(canvas, SK_ColorLTGRAY, SK_ColorWHITE, 8);
-
- SkBitmap bitmap;
- if (GetResourceAsBitmap("color_wheel.png", &bitmap)) {
- bitmap.setImmutable();
- draw(canvas, p, bitmap, kN32_SkColorType, "Native 32");
-
- canvas->translate(0.0f, SkIntToScalar(SCALE));
- SkBitmap copy565 = copy_bitmap(bitmap, kRGB_565_SkColorType);
- p.setColor(SK_ColorRED);
- draw(canvas, p, copy565, kRGB_565_SkColorType, "RGB 565");
- p.setColor(SK_ColorBLACK);
-
- canvas->translate(0.0f, SkIntToScalar(SCALE));
- SkBitmap copy4444 = copy_bitmap(bitmap, kARGB_4444_SkColorType);
- draw(canvas, p, copy4444, kARGB_4444_SkColorType, "ARGB 4444");
- } else {
- canvas->translate(0.0f, SkIntToScalar(2 * SCALE));
- }
-
- canvas->translate(0.0f, SkIntToScalar(SCALE));
- SkBitmap bitmapIndexed;
- if (GetResourceAsBitmap("color_wheel.gif", &bitmapIndexed)) {
- bitmapIndexed.setImmutable();
- draw(canvas, p, bitmapIndexed, kIndex_8_SkColorType, "Index 8");
- }
-
- canvas->translate(0.0f, SkIntToScalar(SCALE));
- SkBitmap bitmapA8 = make_bitmap(true);
- draw(canvas, p, bitmapA8, kAlpha_8_SkColorType, "Alpha 8");
-
- p.setColor(SK_ColorRED);
- canvas->translate(0.0f, SkIntToScalar(SCALE));
- SkBitmap bitmapG8 = make_bitmap(false);
- draw(canvas, p, bitmapG8, kGray_8_SkColorType, "Gray 8");
-}
diff --git a/gyp/gmslides.gypi b/gyp/gmslides.gypi
index ab0a2b3..fdcac44 100644
--- a/gyp/gmslides.gypi
+++ b/gyp/gmslides.gypi
@@ -16,7 +16,6 @@
'../gm/aaclip.cpp',
'../gm/aarectmodes.cpp',
'../gm/addarc.cpp',
- '../gm/all_bitmap_configs.cpp',
'../gm/alphagradients.cpp',
'../gm/arcofzorro.cpp',
'../gm/arithmode.cpp',
diff --git a/gyp/pdf.gypi b/gyp/pdf.gypi
index 8b56495..6d68439 100644
--- a/gyp/pdf.gypi
+++ b/gyp/pdf.gypi
@@ -25,6 +25,8 @@
'<(skia_src_path)/pdf/SkPDFFormXObject.h',
'<(skia_src_path)/pdf/SkPDFGraphicState.cpp',
'<(skia_src_path)/pdf/SkPDFGraphicState.h',
+ '<(skia_src_path)/pdf/SkPDFImage.cpp',
+ '<(skia_src_path)/pdf/SkPDFImage.h',
'<(skia_src_path)/pdf/SkPDFPage.cpp',
'<(skia_src_path)/pdf/SkPDFPage.h',
'<(skia_src_path)/pdf/SkPDFResourceDict.cpp',
diff --git a/src/pdf/SkPDFBitmap.cpp b/src/pdf/SkPDFBitmap.cpp
index 486dac4..668f7de 100644
--- a/src/pdf/SkPDFBitmap.cpp
+++ b/src/pdf/SkPDFBitmap.cpp
@@ -25,214 +25,114 @@
stream->write(streamEnd, strlen(streamEnd));
}
-////////////////////////////////////////////////////////////////////////////////
+static size_t pixel_count(const SkBitmap& bm) {
+ return SkToSizeT(bm.width()) * SkToSizeT(bm.height());
+}
// write a single byte to a stream n times.
static void fill_stream(SkWStream* out, char value, size_t n) {
char buffer[4096];
memset(buffer, value, sizeof(buffer));
- for (size_t i = 0; i < n / sizeof(buffer); ++i) {
- out->write(buffer, sizeof(buffer));
+ while (n) {
+ size_t k = SkTMin(n, sizeof(buffer));
+ out->write(buffer, k);
+ n -= k;
}
- out->write(buffer, n % sizeof(buffer));
}
-// unpremultiply and extract R, G, B components.
-static void pmcolor_to_rgb24(SkPMColor pmColor, uint8_t* rgb) {
- uint32_t s = SkUnPreMultiply::GetScale(SkGetPackedA32(pmColor));
- rgb[0] = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(pmColor));
- rgb[1] = SkUnPreMultiply::ApplyScale(s, SkGetPackedG32(pmColor));
- rgb[2] = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(pmColor));
-}
-
-/* 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. e.g.: gm/bitmappremul.cpp */
-static void get_neighbor_avg_color(const SkBitmap& bm,
- int xOrig,
- int yOrig,
- uint8_t rgb[3]) {
- SkASSERT(kN32_SkColorType == bm.colorType());
- unsigned a = 0, r = 0, g = 0, b = 0;
- // Clamp the range to the edge of the bitmap.
- int ymin = SkTMax(0, yOrig - 1);
- int ymax = SkTMin(yOrig + 1, bm.height() - 1);
- int xmin = SkTMax(0, xOrig - 1);
- int xmax = SkTMin(xOrig + 1, bm.width() - 1);
- for (int y = ymin; y <= ymax; ++y) {
- SkPMColor* scanline = bm.getAddr32(0, y);
- for (int x = xmin; x <= xmax; ++x) {
- SkPMColor pmColor = scanline[x];
- a += SkGetPackedA32(pmColor);
- r += SkGetPackedR32(pmColor);
- g += SkGetPackedG32(pmColor);
- b += SkGetPackedB32(pmColor);
+static SkPMColor get_pmcolor_neighbor_avg_color(const SkBitmap& bitmap,
+ int xOrig,
+ int yOrig) {
+ SkASSERT(kN32_SkColorType == bitmap.colorType());
+ SkASSERT(bitmap.getPixels());
+ uint8_t count = 0;
+ unsigned r = 0;
+ unsigned g = 0;
+ unsigned 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;
+ }
+ SkPMColor pmColor = src[x];
+ U8CPU alpha = SkGetPackedA32(pmColor);
+ if (alpha != SK_AlphaTRANSPARENT) {
+ uint32_t s = SkUnPreMultiply::GetScale(alpha);
+ r += SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(pmColor));
+ g += SkUnPreMultiply::ApplyScale(s, SkGetPackedG32(pmColor));
+ b += SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(pmColor));
+ ++count;
+ }
}
}
- if (a > 0) {
- rgb[0] = SkToU8(255 * r / a);
- rgb[1] = SkToU8(255 * g / a);
- rgb[2] = SkToU8(255 * b / a);
+ if (count == 0) {
+ return SkPackARGB32NoCheck(SK_AlphaOPAQUE, 0, 0, 0);
} else {
- rgb[0] = rgb[1] = rgb[2] = 0;
+ return SkPackARGB32NoCheck(
+ SK_AlphaOPAQUE, r / count, g / count, b / count);
}
}
-static size_t pixel_count(const SkBitmap& bm) {
- return SkToSizeT(bm.width()) * SkToSizeT(bm.height());
-}
-
-static const SkBitmap& not4444(const SkBitmap& input, SkBitmap* copy) {
- if (input.colorType() != kARGB_4444_SkColorType) {
- return input;
- }
- // ARGB_4444 is rarely used, so we can do a wasteful tmp copy.
- SkAssertResult(input.copyTo(copy, kN32_SkColorType));
- copy->setImmutable();
- return *copy;
-}
-
-static size_t pdf_color_component_count(SkColorType ct) {
- switch (ct) {
- case kN32_SkColorType:
- case kRGB_565_SkColorType:
- case kARGB_4444_SkColorType:
- return 3;
- case kAlpha_8_SkColorType:
- case kIndex_8_SkColorType:
- case kGray_8_SkColorType:
- return 1;
- case kUnknown_SkColorType:
- default:
- SkDEBUGFAIL("unexpected color type");
- return 0;
- }
-}
-
-static void bitmap_to_pdf_pixels(const SkBitmap& bitmap, SkWStream* out) {
- if (!bitmap.getPixels()) {
- size_t size = pixel_count(bitmap) *
- pdf_color_component_count(bitmap.colorType());
- fill_stream(out, '\x00', size);
+static void pmcolor_to_rgb24(const SkBitmap& bm, SkWStream* out) {
+ SkASSERT(kN32_SkColorType == bm.colorType());
+ if (!bm.getPixels()) {
+ fill_stream(out, '\xFF', 3 * pixel_count(bm));
return;
}
- SkBitmap copy;
- const SkBitmap& bm = not4444(bitmap, ©);
- SkAutoLockPixels autoLockPixels(bm);
- switch (bm.colorType()) {
- case kN32_SkColorType: {
- SkASSERT(3 == pdf_color_component_count(bitmap.colorType()));
- SkAutoTMalloc<uint8_t> scanline(3 * bm.width());
- for (int y = 0; y < bm.height(); ++y) {
- const SkPMColor* src = bm.getAddr32(0, y);
- uint8_t* dst = scanline.get();
- for (int x = 0; x < bm.width(); ++x) {
- SkPMColor color = *src++;
- U8CPU alpha = SkGetPackedA32(color);
- if (alpha != SK_AlphaTRANSPARENT) {
- pmcolor_to_rgb24(color, dst);
- } else {
- get_neighbor_avg_color(bm, x, y, dst);
- }
- dst += 3;
- }
- out->write(scanline.get(), 3 * bm.width());
+ size_t scanlineLength = 3 * bm.width();
+ SkAutoTMalloc<uint8_t> scanline(scanlineLength);
+ for (int y = 0; y < bm.height(); ++y) {
+ uint8_t* dst = scanline.get();
+ const SkPMColor* src = bm.getAddr32(0, y);
+ for (int x = 0; x < bm.width(); ++x) {
+ SkPMColor color = *src++;
+ U8CPU alpha = SkGetPackedA32(color);
+ if (alpha != SK_AlphaTRANSPARENT) {
+ uint32_t s = SkUnPreMultiply::GetScale(alpha);
+ *dst++ = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(color));
+ *dst++ = SkUnPreMultiply::ApplyScale(s, SkGetPackedG32(color));
+ *dst++ = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(color));
+ } else {
+ /* 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. e.g.: gm/bitmappremul.cpp */
+ color = get_pmcolor_neighbor_avg_color(bm, x, y);
+ *dst++ = SkGetPackedR32(color);
+ *dst++ = SkGetPackedG32(color);
+ *dst++ = SkGetPackedB32(color);
}
- return;
}
- case kRGB_565_SkColorType: {
- SkASSERT(3 == pdf_color_component_count(bitmap.colorType()));
- SkAutoTMalloc<uint8_t> scanline(3 * bm.width());
- for (int y = 0; y < bm.height(); ++y) {
- const uint16_t* src = bm.getAddr16(0, y);
- uint8_t* dst = scanline.get();
- for (int x = 0; x < bm.width(); ++x) {
- U16CPU color565 = *src++;
- *dst++ = SkPacked16ToR32(color565);
- *dst++ = SkPacked16ToG32(color565);
- *dst++ = SkPacked16ToB32(color565);
- }
- out->write(scanline.get(), 3 * bm.width());
- }
- return;
- }
- case kAlpha_8_SkColorType:
- SkASSERT(1 == pdf_color_component_count(bitmap.colorType()));
- fill_stream(out, '\x00', pixel_count(bm));
- return;
- case kGray_8_SkColorType:
- case kIndex_8_SkColorType:
- SkASSERT(1 == pdf_color_component_count(bitmap.colorType()));
- // these two formats need no transformation to serialize.
- for (int y = 0; y < bm.height(); ++y) {
- out->write(bm.getAddr8(0, y), bm.width());
- }
- return;
- case kUnknown_SkColorType:
- case kARGB_4444_SkColorType:
- default:
- SkDEBUGFAIL("unexpected color type");
+ out->write(scanline.get(), scanlineLength);
}
}
-////////////////////////////////////////////////////////////////////////////////
-
-static void bitmap_alpha_to_a8(const SkBitmap& bitmap, SkWStream* out) {
- if (!bitmap.getPixels()) {
- fill_stream(out, '\xFF', pixel_count(bitmap));
+static void pmcolor_alpha_to_a8(const SkBitmap& bm, SkWStream* out) {
+ SkASSERT(kN32_SkColorType == bm.colorType());
+ if (!bm.getPixels()) {
+ fill_stream(out, '\xFF', pixel_count(bm));
return;
}
- SkBitmap copy;
- const SkBitmap& bm = not4444(bitmap, ©);
- SkAutoLockPixels autoLockPixels(bm);
- switch (bm.colorType()) {
- case kN32_SkColorType: {
- SkAutoTMalloc<uint8_t> scanline(bm.width());
- for (int y = 0; y < bm.height(); ++y) {
- uint8_t* dst = scanline.get();
- const SkPMColor* src = bm.getAddr32(0, y);
- for (int x = 0; x < bm.width(); ++x) {
- *dst++ = SkGetPackedA32(*src++);
- }
- out->write(scanline.get(), bm.width());
- }
- return;
+ size_t scanlineLength = bm.width();
+ SkAutoTMalloc<uint8_t> scanline(scanlineLength);
+ for (int y = 0; y < bm.height(); ++y) {
+ uint8_t* dst = scanline.get();
+ const SkPMColor* src = bm.getAddr32(0, y);
+ for (int x = 0; x < bm.width(); ++x) {
+ *dst++ = SkGetPackedA32(*src++);
}
- case kAlpha_8_SkColorType:
- for (int y = 0; y < bm.height(); ++y) {
- out->write(bm.getAddr8(0, y), bm.width());
- }
- return;
- case kIndex_8_SkColorType: {
- SkColorTable* ct = bm.getColorTable();
- SkASSERT(ct);
- SkAutoTMalloc<uint8_t> scanline(bm.width());
- for (int y = 0; y < bm.height(); ++y) {
- uint8_t* dst = scanline.get();
- const uint8_t* src = bm.getAddr8(0, y);
- for (int x = 0; x < bm.width(); ++x) {
- *dst++ = SkGetPackedA32((*ct)[*src++]);
- }
- out->write(scanline.get(), bm.width());
- }
- return;
- }
- case kRGB_565_SkColorType:
- case kGray_8_SkColorType:
- SkDEBUGFAIL("color type has no alpha");
- return;
- case kARGB_4444_SkColorType:
- SkDEBUGFAIL("4444 color type should have been converted to N32");
- return;
- case kUnknown_SkColorType:
- default:
- SkDEBUGFAIL("unexpected color type");
+ out->write(scanline.get(), scanlineLength);
}
}
@@ -245,40 +145,49 @@
PDFAlphaBitmap(const SkBitmap& bm) : fBitmap(bm) {}
~PDFAlphaBitmap() {}
void emitObject(SkWStream*, SkPDFCatalog*) SK_OVERRIDE;
+ void addResources(SkTSet<SkPDFObject*>*, SkPDFCatalog*) const SK_OVERRIDE {}
private:
const SkBitmap fBitmap;
- void emitDict(SkWStream*, SkPDFCatalog*, size_t) const;
+ void emitDict(SkWStream*, SkPDFCatalog*, size_t, bool) const;
};
void PDFAlphaBitmap::emitObject(SkWStream* stream, SkPDFCatalog* catalog) {
SkAutoLockPixels autoLockPixels(fBitmap);
- SkASSERT(fBitmap.colorType() != kIndex_8_SkColorType ||
- fBitmap.getColorTable());
+#ifndef SK_NO_FLATE
// Write to a temporary buffer to get the compressed length.
SkDynamicMemoryWStream buffer;
SkDeflateWStream deflateWStream(&buffer);
- bitmap_alpha_to_a8(fBitmap, &deflateWStream);
+ pmcolor_alpha_to_a8(fBitmap, &deflateWStream);
deflateWStream.finalize(); // call before detachAsStream().
SkAutoTDelete<SkStreamAsset> asset(buffer.detachAsStream());
- this->emitDict(stream, catalog, asset->getLength());
+ this->emitDict(stream, catalog, asset->getLength(), /*deflate=*/true);
pdf_stream_begin(stream);
stream->writeStream(asset.get(), asset->getLength());
pdf_stream_end(stream);
+#else
+ this->emitDict(stream, catalog, pixel_count(fBitmap), /*deflate=*/false);
+ pdf_stream_begin(stream);
+ pmcolor_alpha_to_a8(fBitmap, stream);
+ pdf_stream_end(stream);
+#endif // SK_NO_FLATE
}
void PDFAlphaBitmap::emitDict(SkWStream* stream,
SkPDFCatalog* catalog,
- size_t length) const {
+ size_t length,
+ bool deflate) const {
SkPDFDict pdfDict("XObject");
pdfDict.insertName("Subtype", "Image");
pdfDict.insertInt("Width", fBitmap.width());
pdfDict.insertInt("Height", fBitmap.height());
pdfDict.insertName("ColorSpace", "DeviceGray");
pdfDict.insertInt("BitsPerComponent", 8);
- pdfDict.insertName("Filter", "FlateDecode");
+ if (deflate) {
+ pdfDict.insertName("Filter", "FlateDecode");
+ }
pdfDict.insertInt("Length", length);
pdfDict.emitObject(stream, catalog);
}
@@ -289,81 +198,50 @@
void SkPDFBitmap::addResources(SkTSet<SkPDFObject*>* resourceSet,
SkPDFCatalog* catalog) const {
if (fSMask.get()) {
- if (resourceSet->add(fSMask.get())) {
- fSMask->addResources(resourceSet, catalog);
- }
+ resourceSet->add(fSMask.get());
}
}
void SkPDFBitmap::emitObject(SkWStream* stream, SkPDFCatalog* catalog) {
SkAutoLockPixels autoLockPixels(fBitmap);
- SkASSERT(fBitmap.colorType() != kIndex_8_SkColorType ||
- fBitmap.getColorTable());
+#ifndef SK_NO_FLATE
// Write to a temporary buffer to get the compressed length.
SkDynamicMemoryWStream buffer;
SkDeflateWStream deflateWStream(&buffer);
- bitmap_to_pdf_pixels(fBitmap, &deflateWStream);
+ pmcolor_to_rgb24(fBitmap, &deflateWStream);
deflateWStream.finalize(); // call before detachAsStream().
SkAutoTDelete<SkStreamAsset> asset(buffer.detachAsStream());
- this->emitDict(stream, catalog, asset->getLength());
+ this->emitDict(stream, catalog, asset->getLength(), /*deflate=*/true);
pdf_stream_begin(stream);
stream->writeStream(asset.get(), asset->getLength());
pdf_stream_end(stream);
-}
-
-static SkPDFArray* make_indexed_color_space(const SkColorTable* table) {
- SkPDFArray* result = SkNEW(SkPDFArray);
- result->reserve(4);
- result->appendName("Indexed");
- result->appendName("DeviceRGB");
- SkASSERT(table);
- if (table->count() < 1) {
- result->appendInt(0);
- char shortTableArray[3] = {0, 0, 0};
- SkString tableString(shortTableArray, SK_ARRAY_COUNT(shortTableArray));
- result->append(new SkPDFString(tableString))->unref();
- return result;
- }
- result->appendInt(table->count() - 1); // maximum color index.
-
- // Potentially, this could be represented in fewer bytes with a stream.
- // Max size as a string is 1.5k.
- char tableArray[256 * 3];
- SkASSERT(3u * table->count() <= SK_ARRAY_COUNT(tableArray));
- uint8_t* tablePtr = reinterpret_cast<uint8_t*>(tableArray);
- const SkPMColor* colors = table->readColors();
- for (int i = 0; i < table->count(); i++) {
- pmcolor_to_rgb24(colors[i], tablePtr);
- tablePtr += 3;
- }
- SkString tableString(tableArray, 3 * table->count());
- result->append(new SkPDFString(tableString))->unref();
- return result;
+#else
+ this->emitDict(stream, catalog, 3 * pixel_count(fBitmap), /*deflate=*/false);
+ pdf_stream_begin(stream);
+ pmcolor_to_rgb24(fBitmap, stream);
+ pdf_stream_end(stream);
+ return;
+#endif // SK_NO_FLATE
}
void SkPDFBitmap::emitDict(SkWStream* stream,
SkPDFCatalog* catalog,
- size_t length) const {
+ size_t length,
+ bool deflate) const {
SkPDFDict pdfDict("XObject");
pdfDict.insertName("Subtype", "Image");
pdfDict.insertInt("Width", fBitmap.width());
pdfDict.insertInt("Height", fBitmap.height());
- if (fBitmap.colorType() == kIndex_8_SkColorType) {
- SkASSERT(1 == pdf_color_component_count(fBitmap.colorType()));
- pdfDict.insert("ColorSpace", make_indexed_color_space(
- fBitmap.getColorTable()))->unref();
- } else if (1 == pdf_color_component_count(fBitmap.colorType())) {
- pdfDict.insertName("ColorSpace", "DeviceGray");
- } else {
- pdfDict.insertName("ColorSpace", "DeviceRGB");
- }
+ pdfDict.insertName("ColorSpace", "DeviceRGB");
pdfDict.insertInt("BitsPerComponent", 8);
if (fSMask) {
pdfDict.insert("SMask", new SkPDFObjRef(fSMask))->unref();
}
- pdfDict.insertName("Filter", "FlateDecode");
+ if (deflate) {
+ pdfDict.insertName("Filter", "FlateDecode");
+ }
pdfDict.insertInt("Length", length);
pdfDict.emitObject(stream, catalog);
}
@@ -375,35 +253,64 @@
SkPDFBitmap::~SkPDFBitmap() {}
////////////////////////////////////////////////////////////////////////////////
-
-static const SkBitmap& immutable_bitmap(const SkBitmap& bm, SkBitmap* copy) {
- if (bm.isImmutable()) {
- return bm;
+static bool is_transparent(const SkBitmap& bm) {
+ SkAutoLockPixels autoLockPixels(bm);
+ if (NULL == bm.getPixels()) {
+ return true;
}
- bm.copyTo(copy);
- copy->setImmutable();
- return *copy;
+ SkASSERT(kN32_SkColorType == bm.colorType());
+ for (int y = 0; y < bm.height(); ++y) {
+ U8CPU alpha = 0;
+ const SkPMColor* src = bm.getAddr32(0, y);
+ for (int x = 0; x < bm.width(); ++x) {
+ alpha |= SkGetPackedA32(*src++);
+ }
+ if (alpha) {
+ return false;
+ }
+ }
+ return true;
}
-SkPDFBitmap* SkPDFBitmap::Create(SkPDFCanon* canon, const SkBitmap& bitmap) {
+SkPDFBitmap* SkPDFBitmap::Create(SkPDFCanon* canon,
+ const SkBitmap& bitmap,
+ const SkIRect& subset) {
SkASSERT(canon);
- if (!SkColorTypeIsValid(bitmap.colorType()) ||
- kUnknown_SkColorType == bitmap.colorType()) {
+ if (kN32_SkColorType != bitmap.colorType()) {
+ // TODO(halcanary): support other colortypes.
return NULL;
}
- SkBitmap copy;
- const SkBitmap& bm = immutable_bitmap(bitmap, ©);
+ SkBitmap bm;
+ // Should extractSubset be done by the SkPDFDevice?
+ if (!bitmap.extractSubset(&bm, subset)) {
+ return NULL;
+ }
if (bm.drawsNothing()) {
return NULL;
}
- if (SkPDFBitmap* canonBitmap = canon->findBitmap(bm)) {
- return SkRef(canonBitmap);
+ if (!bm.isImmutable()) {
+ SkBitmap copy;
+ if (!bm.copyTo(©)) {
+ return NULL;
+ }
+ copy.setImmutable();
+ bm = copy;
+ }
+
+ SkPDFBitmap* pdfBitmap = canon->findBitmap(bm);
+ if (pdfBitmap) {
+ return SkRef(pdfBitmap);
}
SkPDFObject* smask = NULL;
if (!bm.isOpaque() && !SkBitmap::ComputeIsOpaque(bm)) {
+ if (is_transparent(bm)) {
+ return NULL;
+ }
+ // PDFAlphaBitmaps do not get directly canonicalized (they
+ // are refed by the SkPDFBitmap).
smask = SkNEW_ARGS(PDFAlphaBitmap, (bm));
}
- SkPDFBitmap* pdfBitmap = SkNEW_ARGS(SkPDFBitmap, (bm, smask));
+ pdfBitmap = SkNEW_ARGS(SkPDFBitmap, (bm, smask));
canon->addBitmap(pdfBitmap);
return pdfBitmap;
}
diff --git a/src/pdf/SkPDFBitmap.h b/src/pdf/SkPDFBitmap.h
index 02a79df..45a8aa6 100644
--- a/src/pdf/SkPDFBitmap.h
+++ b/src/pdf/SkPDFBitmap.h
@@ -17,15 +17,18 @@
* It is designed to use a minimal amout of memory, aside from refing
* the bitmap's pixels, and its emitObject() does not cache any data.
*
- * If !bitmap.isImmutable(), then a copy of the bitmap must be made;
- * there is no way around this.
+ * As of now, it only supports 8888 bitmaps (the most common case).
*
* The SkPDFBitmap::Create function will check the canon for duplicates.
*/
class SkPDFBitmap : public SkPDFObject {
public:
// Returns NULL on unsupported bitmap;
- static SkPDFBitmap* Create(SkPDFCanon*, const SkBitmap&);
+ // TODO(halcanary): support other bitmap colortypes and replace
+ // SkPDFImage.
+ static SkPDFBitmap* Create(SkPDFCanon*,
+ const SkBitmap&,
+ const SkIRect& subset);
~SkPDFBitmap();
void emitObject(SkWStream*, SkPDFCatalog*) SK_OVERRIDE;
void addResources(SkTSet<SkPDFObject*>* resourceSet,
@@ -40,7 +43,7 @@
const SkBitmap fBitmap;
const SkAutoTUnref<SkPDFObject> fSMask;
SkPDFBitmap(const SkBitmap&, SkPDFObject*);
- void emitDict(SkWStream*, SkPDFCatalog*, size_t) const;
+ void emitDict(SkWStream*, SkPDFCatalog*, size_t, bool) const;
};
#endif // SkPDFBitmap_DEFINED
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index a0ad134..273b958 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -17,10 +17,10 @@
#include "SkPaint.h"
#include "SkPath.h"
#include "SkPathOps.h"
-#include "SkPDFBitmap.h"
#include "SkPDFFont.h"
#include "SkPDFFormXObject.h"
#include "SkPDFGraphicState.h"
+#include "SkPDFImage.h"
#include "SkPDFResourceDict.h"
#include "SkPDFShader.h"
#include "SkPDFStream.h"
@@ -2126,7 +2126,7 @@
if (content.needShape()) {
SkPath shape;
shape.addRect(SkRect::MakeWH(SkIntToScalar(subset.width()),
- SkIntToScalar(subset.height())));
+ SkIntToScalar( subset.height())));
shape.transform(matrix);
content.setShape(shape);
}
@@ -2134,12 +2134,8 @@
return;
}
- SkBitmap subsetBitmap;
- // Should extractSubset be done by the SkPDFDevice?
- if (!bitmap->extractSubset(&subsetBitmap, subset)) {
- return;
- }
- SkAutoTUnref<SkPDFObject> image(SkPDFBitmap::Create(fCanon, subsetBitmap));
+ SkAutoTUnref<SkPDFObject> image(
+ SkPDFCreateImageObject(fCanon, *bitmap, subset));
if (!image) {
return;
}
diff --git a/src/pdf/SkPDFImage.cpp b/src/pdf/SkPDFImage.cpp
new file mode 100644
index 0000000..e3971aa
--- /dev/null
+++ b/src/pdf/SkPDFImage.cpp
@@ -0,0 +1,727 @@
+/*
+ * Copyright 2010 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkPDFImage.h"
+
+#include "SkBitmap.h"
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkData.h"
+#include "SkFlate.h"
+#include "SkPDFBitmap.h"
+#include "SkPDFCatalog.h"
+#include "SkPixelRef.h"
+#include "SkRect.h"
+#include "SkStream.h"
+#include "SkString.h"
+#include "SkUnPreMultiply.h"
+
+static size_t get_uncompressed_size(const SkBitmap& bitmap,
+ const SkIRect& srcRect) {
+ switch (bitmap.colorType()) {
+ case kIndex_8_SkColorType:
+ return srcRect.width() * srcRect.height();
+ case kARGB_4444_SkColorType:
+ return ((srcRect.width() * 3 + 1) / 2) * srcRect.height();
+ case kRGB_565_SkColorType:
+ return srcRect.width() * 3 * srcRect.height();
+ case kRGBA_8888_SkColorType:
+ case kBGRA_8888_SkColorType:
+ case kGray_8_SkColorType:
+ return srcRect.width() * 3 * srcRect.height();
+ case kAlpha_8_SkColorType:
+ return 1;
+ default:
+ SkASSERT(false);
+ return 0;
+ }
+}
+
+static SkStream* extract_index8_image(const SkBitmap& bitmap,
+ const SkIRect& srcRect) {
+ const int rowBytes = srcRect.width();
+ SkStream* stream = SkNEW_ARGS(SkMemoryStream,
+ (get_uncompressed_size(bitmap, srcRect)));
+ uint8_t* dst = (uint8_t*)stream->getMemoryBase();
+
+ for (int y = srcRect.fTop; y < srcRect.fBottom; y++) {
+ memcpy(dst, bitmap.getAddr8(srcRect.fLeft, y), rowBytes);
+ dst += rowBytes;
+ }
+ return stream;
+}
+
+static SkStream* extract_argb4444_data(const SkBitmap& bitmap,
+ const SkIRect& srcRect,
+ bool extractAlpha,
+ bool* isOpaque,
+ bool* isTransparent) {
+ SkStream* stream;
+ uint8_t* dst = NULL;
+ if (extractAlpha) {
+ const int alphaRowBytes = (srcRect.width() + 1) / 2;
+ stream = SkNEW_ARGS(SkMemoryStream,
+ (alphaRowBytes * srcRect.height()));
+ } else {
+ stream = SkNEW_ARGS(SkMemoryStream,
+ (get_uncompressed_size(bitmap, srcRect)));
+ }
+ dst = (uint8_t*)stream->getMemoryBase();
+
+ for (int y = srcRect.fTop; y < srcRect.fBottom; y++) {
+ uint16_t* src = bitmap.getAddr16(0, y);
+ int x;
+ for (x = srcRect.fLeft; x + 1 < srcRect.fRight; x += 2) {
+ if (extractAlpha) {
+ dst[0] = (SkGetPackedA4444(src[x]) << 4) |
+ SkGetPackedA4444(src[x + 1]);
+ *isOpaque &= dst[0] == SK_AlphaOPAQUE;
+ *isTransparent &= dst[0] == SK_AlphaTRANSPARENT;
+ dst++;
+ } else {
+ dst[0] = (SkGetPackedR4444(src[x]) << 4) |
+ SkGetPackedG4444(src[x]);
+ dst[1] = (SkGetPackedB4444(src[x]) << 4) |
+ SkGetPackedR4444(src[x + 1]);
+ dst[2] = (SkGetPackedG4444(src[x + 1]) << 4) |
+ SkGetPackedB4444(src[x + 1]);
+ dst += 3;
+ }
+ }
+ if (srcRect.width() & 1) {
+ if (extractAlpha) {
+ dst[0] = (SkGetPackedA4444(src[x]) << 4);
+ *isOpaque &= dst[0] == (SK_AlphaOPAQUE & 0xF0);
+ *isTransparent &= dst[0] == (SK_AlphaTRANSPARENT & 0xF0);
+ dst++;
+
+ } else {
+ dst[0] = (SkGetPackedR4444(src[x]) << 4) |
+ SkGetPackedG4444(src[x]);
+ dst[1] = (SkGetPackedB4444(src[x]) << 4);
+ dst += 2;
+ }
+ }
+ }
+ return stream;
+}
+
+static SkStream* extract_rgb565_image(const SkBitmap& bitmap,
+ const SkIRect& srcRect) {
+ SkStream* stream = SkNEW_ARGS(SkMemoryStream,
+ (get_uncompressed_size(bitmap,
+ srcRect)));
+ uint8_t* dst = (uint8_t*)stream->getMemoryBase();
+ for (int y = srcRect.fTop; y < srcRect.fBottom; y++) {
+ uint16_t* src = bitmap.getAddr16(0, y);
+ for (int x = srcRect.fLeft; x < srcRect.fRight; x++) {
+ dst[0] = SkGetPackedR16(src[x]);
+ dst[1] = SkGetPackedG16(src[x]);
+ dst[2] = SkGetPackedB16(src[x]);
+ dst += 3;
+ }
+ }
+ return stream;
+}
+
+static SkStream* extract_gray8_image(const SkBitmap& bitmap, const SkIRect& srcRect) {
+ SkStream* stream = SkNEW_ARGS(SkMemoryStream,
+ (get_uncompressed_size(bitmap, srcRect)));
+ uint8_t* dst = (uint8_t*)stream->getMemoryBase();
+ for (int y = srcRect.fTop; y < srcRect.fBottom; y++) {
+ uint8_t* src = bitmap.getAddr8(0, y);
+ for (int x = srcRect.fLeft; x < srcRect.fRight; x++) {
+ dst[0] = dst[1] = dst[2] = src[x];
+ dst += 3;
+ }
+ }
+ return stream;
+}
+
+static uint32_t get_argb8888_neighbor_avg_color(const SkBitmap& bitmap,
+ int xOrig,
+ int yOrig);
+
+static SkStream* extract_argb8888_data(const SkBitmap& bitmap,
+ const SkIRect& srcRect,
+ bool extractAlpha,
+ bool* isOpaque,
+ bool* isTransparent) {
+ size_t streamSize = extractAlpha ? srcRect.width() * srcRect.height()
+ : get_uncompressed_size(bitmap, srcRect);
+ SkStream* stream = SkNEW_ARGS(SkMemoryStream, (streamSize));
+ uint8_t* dst = (uint8_t*)stream->getMemoryBase();
+
+ const SkUnPreMultiply::Scale* scaleTable = SkUnPreMultiply::GetScaleTable();
+
+ for (int y = srcRect.fTop; y < srcRect.fBottom; y++) {
+ uint32_t* src = bitmap.getAddr32(0, y);
+ for (int x = srcRect.fLeft; x < srcRect.fRight; x++) {
+ SkPMColor c = src[x];
+ U8CPU alpha = SkGetPackedA32(c);
+ if (extractAlpha) {
+ *isOpaque &= alpha == SK_AlphaOPAQUE;
+ *isTransparent &= alpha == SK_AlphaTRANSPARENT;
+ *dst++ = alpha;
+ } else {
+ if (SK_AlphaTRANSPARENT == alpha) {
+ // 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.
+ c = get_argb8888_neighbor_avg_color(bitmap, x, y);
+ *dst++ = SkGetPackedR32(c);
+ *dst++ = SkGetPackedG32(c);
+ *dst++ = SkGetPackedB32(c);
+ } else {
+ SkUnPreMultiply::Scale s = scaleTable[alpha];
+ *dst++ = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
+ *dst++ = SkUnPreMultiply::ApplyScale(s, SkGetPackedG32(c));
+ *dst++ = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
+ }
+ }
+ }
+ }
+ SkASSERT(dst == streamSize + (uint8_t*)stream->getMemoryBase());
+ return stream;
+}
+
+static SkStream* extract_a8_alpha(const SkBitmap& bitmap,
+ const SkIRect& srcRect,
+ bool* isOpaque,
+ bool* isTransparent) {
+ const int alphaRowBytes = srcRect.width();
+ SkStream* stream = SkNEW_ARGS(SkMemoryStream,
+ (alphaRowBytes * srcRect.height()));
+ uint8_t* alphaDst = (uint8_t*)stream->getMemoryBase();
+
+ for (int y = srcRect.fTop; y < srcRect.fBottom; y++) {
+ uint8_t* src = bitmap.getAddr8(0, y);
+ for (int x = srcRect.fLeft; x < srcRect.fRight; x++) {
+ alphaDst[0] = src[x];
+ *isOpaque &= alphaDst[0] == SK_AlphaOPAQUE;
+ *isTransparent &= alphaDst[0] == SK_AlphaTRANSPARENT;
+ alphaDst++;
+ }
+ }
+ return stream;
+}
+
+static SkStream* create_black_image() {
+ SkStream* stream = SkNEW_ARGS(SkMemoryStream, (1));
+ ((uint8_t*)stream->getMemoryBase())[0] = 0;
+ return stream;
+}
+
+/**
+ * Extract either the color or image data from a SkBitmap into a SkStream.
+ * @param bitmap Bitmap to extract data from.
+ * @param srcRect Region in the bitmap to extract.
+ * @param extractAlpha Set to true to extract the alpha data or false to
+ * extract the color data.
+ * @param isTransparent Pointer to a bool to output whether the alpha is
+ * completely transparent. May be NULL. Only valid when
+ * extractAlpha == true.
+ * @return Unencoded image data, or NULL if either data was not
+ * available or alpha data was requested but the image was
+ * entirely transparent or opaque.
+ */
+static SkStream* extract_image_data(const SkBitmap& bitmap,
+ const SkIRect& srcRect,
+ bool extractAlpha, bool* isTransparent) {
+ SkColorType colorType = bitmap.colorType();
+ if (extractAlpha && (kIndex_8_SkColorType == colorType ||
+ kRGB_565_SkColorType == colorType ||
+ kGray_8_SkColorType == colorType)) {
+ if (isTransparent != NULL) {
+ *isTransparent = false;
+ }
+ return NULL;
+ }
+
+ SkAutoLockPixels lock(bitmap);
+ if (NULL == bitmap.getPixels()) {
+ return NULL;
+ }
+
+ bool isOpaque = true;
+ bool transparent = extractAlpha;
+ SkAutoTDelete<SkStream> stream;
+
+ switch (colorType) {
+ case kIndex_8_SkColorType:
+ if (!extractAlpha) {
+ stream.reset(extract_index8_image(bitmap, srcRect));
+ }
+ break;
+ case kARGB_4444_SkColorType:
+ stream.reset(extract_argb4444_data(bitmap, srcRect, extractAlpha,
+ &isOpaque, &transparent));
+ break;
+ case kRGB_565_SkColorType:
+ if (!extractAlpha) {
+ stream.reset(extract_rgb565_image(bitmap, srcRect));
+ }
+ break;
+ case kGray_8_SkColorType:
+ if (!extractAlpha) {
+ stream.reset(extract_gray8_image(bitmap, srcRect));
+ }
+ break;
+ case kN32_SkColorType:
+ stream.reset(extract_argb8888_data(bitmap, srcRect, extractAlpha,
+ &isOpaque, &transparent));
+ break;
+ case kAlpha_8_SkColorType:
+ if (!extractAlpha) {
+ stream.reset(create_black_image());
+ } else {
+ stream.reset(extract_a8_alpha(bitmap, srcRect,
+ &isOpaque, &transparent));
+ }
+ break;
+ default:
+ SkASSERT(false);
+ }
+
+ if (isTransparent != NULL) {
+ *isTransparent = transparent;
+ }
+ if (extractAlpha && (transparent || isOpaque)) {
+ return NULL;
+ }
+ return stream.detach();
+}
+
+static SkPDFArray* make_indexed_color_space(SkColorTable* table) {
+ SkPDFArray* result = new SkPDFArray();
+ result->reserve(4);
+ result->appendName("Indexed");
+ result->appendName("DeviceRGB");
+ result->appendInt(table->count() - 1);
+
+ // Potentially, this could be represented in fewer bytes with a stream.
+ // Max size as a string is 1.5k.
+ SkString index;
+ for (int i = 0; i < table->count(); i++) {
+ char buf[3];
+ SkColor color = SkUnPreMultiply::PMColorToColor((*table)[i]);
+ buf[0] = SkGetPackedR32(color);
+ buf[1] = SkGetPackedG32(color);
+ buf[2] = SkGetPackedB32(color);
+ index.append(buf, 3);
+ }
+ result->append(new SkPDFString(index))->unref();
+ 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.allocPixels(bitmap.info().makeWH(srcRect.width(), srcRect.height()));
+ int dstRow = 0;
+
+ SkAutoLockPixels outBitmapPixelLock(outBitmap);
+ SkAutoLockPixels bitmapPixelLock(bitmap);
+ switch (bitmap.colorType()) {
+ case kARGB_4444_SkColorType: {
+ 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 kN32_SkColorType: {
+ 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);
+ }
+
+ outBitmap.setImmutable();
+
+ return outBitmap;
+}
+
+// static
+SkPDFImage* SkPDFImage::CreateImage(const SkBitmap& bitmap,
+ const SkIRect& srcRect) {
+ if (bitmap.colorType() == kUnknown_SkColorType) {
+ return NULL;
+ }
+
+ bool isTransparent = false;
+ SkAutoTDelete<SkStream> alphaData;
+ if (!bitmap.isOpaque()) {
+ // Note that isOpaque is not guaranteed to return false for bitmaps
+ // with alpha support but a completely opaque alpha channel,
+ // so alphaData may still be NULL if we have a completely opaque
+ // (or transparent) bitmap.
+ alphaData.reset(
+ extract_image_data(bitmap, srcRect, true, &isTransparent));
+ }
+ if (isTransparent) {
+ return NULL;
+ }
+
+ SkPDFImage* image;
+ SkColorType colorType = bitmap.colorType();
+ if (alphaData.get() != NULL && (kN32_SkColorType == colorType ||
+ kARGB_4444_SkColorType == colorType)) {
+ if (kN32_SkColorType == colorType) {
+ image = SkNEW_ARGS(SkPDFImage, (NULL, bitmap, false,
+ SkIRect::MakeWH(srcRect.width(),
+ srcRect.height())));
+ } else {
+ SkBitmap unpremulBitmap = unpremultiply_bitmap(bitmap, srcRect);
+ image = SkNEW_ARGS(SkPDFImage, (NULL, unpremulBitmap, false,
+ SkIRect::MakeWH(srcRect.width(),
+ srcRect.height())));
+ }
+ } else {
+ image = SkNEW_ARGS(SkPDFImage, (NULL, bitmap, false, srcRect));
+ }
+ if (alphaData.get() != NULL) {
+ SkAutoTUnref<SkPDFImage> mask(
+ SkNEW_ARGS(SkPDFImage, (alphaData.get(), bitmap, true, srcRect)));
+ image->insert("SMask", new SkPDFObjRef(mask))->unref();
+ }
+ return image;
+}
+
+SkPDFImage::~SkPDFImage() {}
+
+SkPDFImage::SkPDFImage(SkStream* stream,
+ const SkBitmap& bitmap,
+ bool isAlpha,
+ const SkIRect& srcRect)
+ : fIsAlpha(isAlpha),
+ fSrcRect(srcRect) {
+
+ if (bitmap.isImmutable()) {
+ fBitmap = bitmap;
+ } else {
+ bitmap.deepCopyTo(&fBitmap);
+ fBitmap.setImmutable();
+ }
+
+ if (stream != NULL) {
+ this->setData(stream);
+ fStreamValid = true;
+ } else {
+ fStreamValid = false;
+ }
+
+ SkColorType colorType = fBitmap.colorType();
+
+ insertName("Type", "XObject");
+ insertName("Subtype", "Image");
+
+ bool alphaOnly = (kAlpha_8_SkColorType == colorType);
+
+ if (!isAlpha && alphaOnly) {
+ // For alpha only images, we stretch a single pixel of black for
+ // the color/shape part.
+ SkAutoTUnref<SkPDFInt> one(new SkPDFInt(1));
+ insert("Width", one.get());
+ insert("Height", one.get());
+ } else {
+ insertInt("Width", fSrcRect.width());
+ insertInt("Height", fSrcRect.height());
+ }
+
+ if (isAlpha || alphaOnly) {
+ insertName("ColorSpace", "DeviceGray");
+ } else if (kIndex_8_SkColorType == colorType) {
+ SkAutoLockPixels alp(fBitmap);
+ insert("ColorSpace",
+ make_indexed_color_space(fBitmap.getColorTable()))->unref();
+ } else {
+ insertName("ColorSpace", "DeviceRGB");
+ }
+
+ int bitsPerComp = 8;
+ if (kARGB_4444_SkColorType == colorType) {
+ bitsPerComp = 4;
+ }
+ insertInt("BitsPerComponent", bitsPerComp);
+
+ if (kRGB_565_SkColorType == colorType) {
+ SkASSERT(!isAlpha);
+ SkAutoTUnref<SkPDFInt> zeroVal(new SkPDFInt(0));
+ SkAutoTUnref<SkPDFScalar> scale5Val(
+ new SkPDFScalar(8.2258f)); // 255/2^5-1
+ SkAutoTUnref<SkPDFScalar> scale6Val(
+ new SkPDFScalar(4.0476f)); // 255/2^6-1
+ SkAutoTUnref<SkPDFArray> decodeValue(new SkPDFArray());
+ decodeValue->reserve(6);
+ decodeValue->append(zeroVal.get());
+ decodeValue->append(scale5Val.get());
+ decodeValue->append(zeroVal.get());
+ decodeValue->append(scale6Val.get());
+ decodeValue->append(zeroVal.get());
+ decodeValue->append(scale5Val.get());
+ insert("Decode", decodeValue.get());
+ }
+}
+
+SkPDFImage::SkPDFImage(SkPDFImage& pdfImage)
+ : SkPDFStream(pdfImage),
+ fBitmap(pdfImage.fBitmap),
+ fIsAlpha(pdfImage.fIsAlpha),
+ fSrcRect(pdfImage.fSrcRect),
+ fStreamValid(pdfImage.fStreamValid) {
+ // Nothing to do here - the image params are already copied in SkPDFStream's
+ // constructor, and the bitmap will be regenerated and encoded in
+ // populate.
+}
+
+bool SkPDFImage::populate(SkPDFCatalog* catalog) {
+ if (getState() == kUnused_State) {
+ // Initializing image data for the first time.
+ // Fallback method
+ if (!fStreamValid) {
+ SkAutoTDelete<SkStream> stream(
+ extract_image_data(fBitmap, fSrcRect, fIsAlpha, NULL));
+ this->setData(stream);
+ fStreamValid = true;
+ }
+ return INHERITED::populate(catalog);
+ }
+#ifndef SK_NO_FLATE
+ else if (getState() == kNoCompression_State) {
+ // Compression has not been requested when the stream was first created,
+ // but the new catalog wants it compressed.
+ if (!getSubstitute()) {
+ SkPDFStream* substitute = SkNEW_ARGS(SkPDFImage, (*this));
+ setSubstitute(substitute);
+ catalog->setSubstitute(this, substitute);
+ }
+ return false;
+ }
+#endif // SK_NO_FLATE
+ return true;
+}
+
+#if 0 // reenable when we can figure out the JPEG colorspace
+namespace {
+/**
+ * This PDFObject assumes that its constructor was handed
+ * Jpeg-encoded data that can be directly embedded into a PDF.
+ */
+class PDFJPEGImage : public SkPDFObject {
+ SkAutoTUnref<SkData> fData;
+ int fWidth;
+ int fHeight;
+public:
+ PDFJPEGImage(SkData* data, int width, int height)
+ : fData(SkRef(data)), fWidth(width), fHeight(height) {}
+ virtual void emitObject(
+ SkWStream* stream,
+ SkPDFCatalog* catalog, bool indirect) SK_OVERRIDE {
+ if (indirect) {
+ this->emitIndirectObject(stream, catalog);
+ return;
+ }
+ SkASSERT(fData.get());
+ const char kPrefaceFormat[] =
+ "<<"
+ "/Type /XObject\n"
+ "/Subtype /Image\n"
+ "/Width %d\n"
+ "/Height %d\n"
+ "/ColorSpace /DeviceRGB\n" // or DeviceGray
+ "/BitsPerComponent 8\n"
+ "/Filter /DCTDecode\n"
+ "/ColorTransform 0\n"
+ "/Length " SK_SIZE_T_SPECIFIER "\n"
+ ">> stream\n";
+ SkString preface(
+ SkStringPrintf(kPrefaceFormat, fWidth, fHeight, fData->size()));
+ const char kPostface[] = "\nendstream";
+ stream->write(preface.c_str(), preface.size());
+ stream->write(fData->data(), fData->size());
+ stream->write(kPostface, sizeof(kPostface));
+ }
+};
+
+/**
+ * If the bitmap is not subsetted, return its encoded data, if
+ * availible.
+ */
+static inline SkData* ref_encoded_data(const SkBitmap& bm) {
+ if ((NULL == bm.pixelRef())
+ || !bm.pixelRefOrigin().isZero()
+ || (bm.info().dimensions() != bm.pixelRef()->info().dimensions())) {
+ return NULL;
+ }
+ return bm.pixelRef()->refEncodedData();
+}
+
+/*
+ * This functions may give false negatives but no false positives.
+ */
+static bool is_jfif_jpeg(SkData* data) {
+ if (!data || (data->size() < 11)) {
+ return false;
+ }
+ const uint8_t bytesZeroToThree[] = {0xFF, 0xD8, 0xFF, 0xE0};
+ const uint8_t bytesSixToTen[] = {'J', 'F', 'I', 'F', 0};
+ // 0 1 2 3 4 5 6 7 8 9 10
+ // FF D8 FF E0 ?? ?? 'J' 'F' 'I' 'F' 00 ...
+ return ((0 == memcmp(data->bytes(), bytesZeroToThree,
+ sizeof(bytesZeroToThree)))
+ && (0 == memcmp(data->bytes() + 6, bytesSixToTen,
+ sizeof(bytesSixToTen))));
+}
+} // namespace
+#endif
+
+SkPDFObject* SkPDFCreateImageObject(SkPDFCanon* canon,
+ const SkBitmap& bitmap,
+ const SkIRect& subset) {
+ if (SkPDFObject* pdfBitmap = SkPDFBitmap::Create(canon, bitmap, subset)) {
+ return pdfBitmap;
+ }
+#if 0 // reenable when we can figure out the JPEG colorspace
+ if (SkIRect::MakeWH(bitmap.width(), bitmap.height()) == subset) {
+ SkAutoTUnref<SkData> encodedData(ref_encoded_data(bitmap));
+ if (is_jfif_jpeg(encodedData)) {
+ return SkNEW_ARGS(PDFJPEGImage,
+ (encodedData, bitmap.width(), bitmap.height()));
+ }
+ }
+#endif
+ return SkPDFImage::CreateImage(bitmap, subset);
+}
diff --git a/src/pdf/SkPDFImage.h b/src/pdf/SkPDFImage.h
new file mode 100644
index 0000000..64be971
--- /dev/null
+++ b/src/pdf/SkPDFImage.h
@@ -0,0 +1,91 @@
+
+/*
+ * Copyright 2010 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#ifndef SkPDFImage_DEFINED
+#define SkPDFImage_DEFINED
+
+#include "SkPicture.h"
+#include "SkPDFDevice.h"
+#include "SkPDFStream.h"
+#include "SkPDFTypes.h"
+#include "SkRefCnt.h"
+
+class SkBitmap;
+class SkData;
+class SkPDFCatalog;
+struct SkIRect;
+
+/**
+ * Return the mose efficient availible encoding of the given bitmap.
+ */
+SkPDFObject* SkPDFCreateImageObject(SkPDFCanon* canon,
+ const SkBitmap&,
+ const SkIRect& subset);
+
+/** \class SkPDFImage
+
+ An image XObject.
+*/
+
+// We could play the same trick here as is done in SkPDFGraphicState, storing
+// a copy of the Bitmap object (not the pixels), the pixel generation number,
+// and settings used from the paint to canonicalize image objects.
+class SkPDFImage : public SkPDFStream {
+public:
+ /** Create a new Image XObject to represent the passed bitmap.
+ * @param bitmap The image to encode.
+ * @param srcRect The rectangle to cut out of bitmap.
+ * @param paint Used to calculate alpha, masks, etc.
+ * @return The image XObject or NUll if there is nothing to draw for
+ * the given parameters.
+ */
+ static SkPDFImage* CreateImage(const SkBitmap& bitmap,
+ const SkIRect& srcRect);
+
+ virtual ~SkPDFImage();
+
+ bool isEmpty() {
+ return fSrcRect.isEmpty();
+ }
+
+private:
+ SkBitmap fBitmap;
+ bool fIsAlpha;
+ SkIRect fSrcRect;
+ bool fStreamValid;
+
+ /** Create a PDF image XObject. Entries for the image properties are
+ * automatically added to the stream dictionary.
+ * @param stream The image stream. May be NULL. Otherwise, this
+ * (instead of the input bitmap) will be used as the
+ * PDF's content stream, possibly with lossless encoding.
+ * Will be duplicated, and left in indeterminate state.
+ * @param bitmap The image. If a stream is not given, its color data
+ * will be used as the image. If a stream is given, this
+ * is used for configuration only.
+ * @param isAlpha Whether or not this is the alpha of an image.
+ * @param srcRect The clipping applied to bitmap before generating
+ * imageData.
+ */
+ SkPDFImage(SkStream* stream, const SkBitmap& bitmap, bool isAlpha,
+ const SkIRect& srcRect);
+
+ /** Copy constructor, used to generate substitutes.
+ * @param image The SkPDFImage to copy.
+ */
+ SkPDFImage(SkPDFImage& pdfImage);
+
+ // Populate the stream dictionary. This method returns false if
+ // fSubstitute should be used.
+ virtual bool populate(SkPDFCatalog* catalog);
+
+ typedef SkPDFStream INHERITED;
+};
+
+#endif