diff options
author | reed <reed@google.com> | 2015-03-20 10:03:36 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-03-20 10:03:36 -0700 |
commit | 1b600d3446b3d236bfa06cf116ec41960bea6ac8 (patch) | |
tree | cbc41e783250b80358ac40c5af3406a8cf13098e /src/pdf | |
parent | b79ff56de23fef680ae7187040f2d6a9516b553d (diff) |
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
Diffstat (limited to 'src/pdf')
-rw-r--r-- | src/pdf/SkPDFBitmap.cpp | 415 | ||||
-rw-r--r-- | src/pdf/SkPDFBitmap.h | 11 | ||||
-rw-r--r-- | src/pdf/SkPDFDevice.cpp | 12 | ||||
-rw-r--r-- | src/pdf/SkPDFImage.cpp | 727 | ||||
-rw-r--r-- | src/pdf/SkPDFImage.h | 91 |
5 files changed, 990 insertions, 266 deletions
diff --git a/src/pdf/SkPDFBitmap.cpp b/src/pdf/SkPDFBitmap.cpp index 486dac44aa..668f7dede0 100644 --- a/src/pdf/SkPDFBitmap.cpp +++ b/src/pdf/SkPDFBitmap.cpp @@ -25,214 +25,114 @@ static void pdf_stream_end(SkWStream* stream) { 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; - } -} - -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; + return SkPackARGB32NoCheck( + SK_AlphaOPAQUE, r / count, g / count, b / count); } } -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 @@ public: 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 PDFAlphaBitmap::emitDict(SkWStream* stream, 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(const SkBitmap& bm, 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; + } + 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; + } } - bm.copyTo(copy); - copy->setImmutable(); - return *copy; + 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 bm; + // Should extractSubset be done by the SkPDFDevice? + if (!bitmap.extractSubset(&bm, subset)) { return NULL; } - SkBitmap copy; - const SkBitmap& bm = immutable_bitmap(bitmap, ©); 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 02a79df24e..45a8aa6350 100644 --- a/src/pdf/SkPDFBitmap.h +++ b/src/pdf/SkPDFBitmap.h @@ -17,15 +17,18 @@ class SkPDFCanon; * 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 @@ private: 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 a0ad1343af..273b958a8c 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 @@ void SkPDFDevice::internalDrawBitmap(const SkMatrix& origMatrix, 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 @@ void SkPDFDevice::internalDrawBitmap(const SkMatrix& origMatrix, 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 0000000000..e3971aa57a --- /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 0000000000..64be971bfc --- /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 |