Revert of PDF: remove last use of SkPDFImage (patchset #5 id:120001 of https://codere...
authorreed <reed@google.com>
Fri, 20 Mar 2015 17:03:36 +0000 (10:03 -0700)
committerCommit bot <commit-bot@chromium.org>
Fri, 20 Mar 2015 17:03:36 +0000 (10:03 -0700)
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

gm/all_bitmap_configs.cpp [deleted file]
gyp/gmslides.gypi
gyp/pdf.gypi
src/pdf/SkPDFBitmap.cpp
src/pdf/SkPDFBitmap.h
src/pdf/SkPDFDevice.cpp
src/pdf/SkPDFImage.cpp [new file with mode: 0644]
src/pdf/SkPDFImage.h [new file with mode: 0644]

diff --git a/gm/all_bitmap_configs.cpp b/gm/all_bitmap_configs.cpp
deleted file mode 100644 (file)
index 95568f2..0000000
+++ /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(&copy, 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");
-}
index ab0a2b3..fdcac44 100644 (file)
@@ -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',
index 8b56495..6d68439 100644 (file)
@@ -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',
index 486dac4..668f7de 100644 (file)
@@ -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, &copy);
-    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, &copy);
-    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, &copy);
     if (bm.drawsNothing()) {
         return NULL;
     }
-    if (SkPDFBitmap* canonBitmap = canon->findBitmap(bm)) {
-        return SkRef(canonBitmap);
+    if (!bm.isImmutable()) {
+        SkBitmap copy;
+        if (!bm.copyTo(&copy)) {
+            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;
 }
index 02a79df..45a8aa6 100644 (file)
@@ -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
index a0ad134..273b958 100644 (file)
 #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 (file)
index 0000000..e3971aa
--- /dev/null
@@ -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 (file)
index 0000000..64be971
--- /dev/null
@@ -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