[PDF] Support image alpha channel plus a couple small fixes.

Fix bug in rendering paths with cubic segments.
Only compress data if the compressed size is smaller than the uncompressed size.

Review URL: http://codereview.appspot.com/4079048

git-svn-id: http://skia.googlecode.com/svn/trunk@747 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/pdf/SkPDFImage.h b/include/pdf/SkPDFImage.h
index 48d6398..9b4e4f9 100644
--- a/include/pdf/SkPDFImage.h
+++ b/include/pdf/SkPDFImage.h
@@ -36,19 +36,45 @@
 // and settings used from the paint to canonicalize image objects.
 class SkPDFImage : public SkPDFObject {
 public:
-    /** Create a PDF image XObject. Entries for the image properties are
-     *  automatically added to the stream dictionary.
-     *  @param bitmap  The image to use.
-     *  @param paint   Used to calculate alpha, masks, etc.
+    /** 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.
      */
-    SkPDFImage(const SkBitmap& bitmap, const SkIRect& srcRect,
-               const SkPaint& paint);
+    static SkPDFImage* CreateImage(const SkBitmap& bitmap,
+                                   const SkIRect& srcRect,
+                                   const SkPaint& paint);
+
     virtual ~SkPDFImage();
 
+    /** Add a Soft Mask (alpha or shape channel) to the image.
+     *  @param mask A gray scale image representing the mask.
+     */
+    void addSMask(SkPDFImage* mask);
+
     // The SkPDFObject interface.
     virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
                             bool indirect);
     virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+    virtual void getResources(SkTDArray<SkPDFObject*>* resourceList);
+
+private:
+    SkRefPtr<SkPDFStream> fStream;
+    SkTDArray<SkPDFObject*> fResources;
+
+    /** Create a PDF image XObject. Entries for the image properties are
+     *  automatically added to the stream dictionary.
+     *  @param imageData  The final raw bits representing the image.
+     *  @param bitmap     The image parameters to use (Config, etc).
+     *  @param srcRect    The clipping applied to bitmap before generating
+     *                    imageData.
+     *  @param alpha      Is this the alpha channel of the bitmap.
+     *  @param paint      Used to calculate alpha, masks, etc.
+     */
+    SkPDFImage(SkStream* imageData, const SkBitmap& bitmap,
+               const SkIRect& srcRect, bool alpha, const SkPaint& paint);
 
     /** Add the value to the stream dictionary with the given key.
      *  @param key   The key for this dictionary entry.
@@ -61,9 +87,6 @@
      *  @param value The value for this dictionary entry.
      */
     void insert(const char key[], SkPDFObject* value);
-
-private:
-    SkRefPtr<SkPDFStream> fStream;
 };
 
 #endif
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index b98e4af..d3a15a8 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -702,6 +702,7 @@
     fContent.appendScalar(dstX);
     fContent.append(" ");
     fContent.appendScalar(dstY);
+    fContent.append(" ");
     fContent.append(cmd);
 }
 
@@ -817,6 +818,10 @@
     if (srcRect && !subset.intersect(*srcRect))
         return;
 
+    SkPDFImage* image = SkPDFImage::CreateImage(bitmap, subset, paint);
+    if (!image)
+        return;
+
     SkMatrix scaled;
     // Adjust for origin flip.
     scaled.setScale(1, -1);
@@ -826,7 +831,6 @@
     scaled.postConcat(matrix);
     SkMatrix curTransform = setTransform(scaled);
 
-    SkPDFImage* image = new SkPDFImage(bitmap, subset, paint);
     fXObjectResources.push(image);  // Transfer reference.
     fContent.append("/X");
     fContent.appendS32(fXObjectResources.count() - 1);
diff --git a/src/pdf/SkPDFGraphicState.cpp b/src/pdf/SkPDFGraphicState.cpp
index 286468b..8cc8314 100644
--- a/src/pdf/SkPDFGraphicState.cpp
+++ b/src/pdf/SkPDFGraphicState.cpp
@@ -83,9 +83,8 @@
         typeName->unref();  // SkRefPtr and new both took a reference.
         insert("Type", typeName.get());
 
-        SkScalar maxAlpha = SkIntToScalar(0xFF);
         SkRefPtr<SkPDFScalar> alpha =
-            new SkPDFScalar(SkColorGetA(fPaint.getColor())/maxAlpha);
+            new SkPDFScalar(fPaint.getAlpha() * SkScalarInvert(0xFF));
         alpha->unref();  // SkRefPtr and new both took a reference.
         insert("CA", alpha.get());
         insert("ca", alpha.get());
diff --git a/src/pdf/SkPDFImage.cpp b/src/pdf/SkPDFImage.cpp
index 51bf8ae..6c35f5a 100644
--- a/src/pdf/SkPDFImage.cpp
+++ b/src/pdf/SkPDFImage.cpp
@@ -29,16 +29,19 @@
 
 namespace {
 
-SkMemoryStream* extractImageData(const SkBitmap& bitmap,
-                                 const SkIRect& srcRect) {
-    SkMemoryStream* result = NULL;
+void extractImageData(const SkBitmap& bitmap, const SkIRect& srcRect,
+                      SkStream** imageData, SkStream** alphaData) {
+    SkMemoryStream* image = NULL;
+    SkMemoryStream* alpha = NULL;
+    bool hasAlpha = false;
+    bool isTransparent = false;
 
     bitmap.lockPixels();
     switch (bitmap.getConfig()) {
         case SkBitmap::kIndex8_Config: {
             const int rowBytes = srcRect.width();
-            result = new SkMemoryStream(rowBytes * srcRect.height());
-            uint8_t* dst = (uint8_t*)result->getMemoryBase();
+            image = new SkMemoryStream(rowBytes * srcRect.height());
+            uint8_t* dst = (uint8_t*)image->getMemoryBase();
             for (int y = srcRect.fTop; y < srcRect.fBottom; y++) {
                 memcpy(dst, bitmap.getAddr8(srcRect.fLeft, y), rowBytes);
                 dst += rowBytes;
@@ -47,8 +50,8 @@
         }
         case SkBitmap::kRLE_Index8_Config: {
             const int rowBytes = srcRect.width();
-            result = new SkMemoryStream(rowBytes * srcRect.height());
-            uint8_t* dst = (uint8_t*)result->getMemoryBase();
+            image = new SkMemoryStream(rowBytes * srcRect.height());
+            uint8_t* dst = (uint8_t*)image->getMemoryBase();
             const SkBitmap::RLEPixels* rle =
                 (const SkBitmap::RLEPixels*)bitmap.getPixels();
             for (int y = srcRect.fTop; y < srcRect.fBottom; y++) {
@@ -59,9 +62,13 @@
             break;
         }
         case SkBitmap::kARGB_4444_Config: {
+            isTransparent = true;
             const int rowBytes = (srcRect.width() * 3 + 1) / 2;
-            result = new SkMemoryStream(rowBytes * srcRect.height());
-            uint8_t* dst = (uint8_t*)result->getMemoryBase();
+            const int alphaRowBytes = (srcRect.width() + 1) / 2;
+            image = new SkMemoryStream(rowBytes * srcRect.height());
+            alpha = new SkMemoryStream(alphaRowBytes * srcRect.height());
+            uint8_t* dst = (uint8_t*)image->getMemoryBase();
+            uint8_t* alphaDst = (uint8_t*)alpha->getMemoryBase();
             for (int y = srcRect.fTop; y < srcRect.fBottom; y++) {
                 uint16_t* src = bitmap.getAddr16(0, y);
                 int x;
@@ -73,19 +80,31 @@
                     dst[2] = (SkGetPackedG4444(src[x + 1]) << 4) |
                         SkGetPackedB4444(src[x + 1]);
                     dst += 3;
+                    alphaDst[0] = (SkGetPackedA4444(src[x]) << 4) |
+                        SkGetPackedA4444(src[x + 1]);
+                    if (alphaDst[0] != 0xFF)
+                        hasAlpha = true;
+                    if (alphaDst[0])
+                        isTransparent = false;
+                    alphaDst++;
                 }
                 if (srcRect.width() & 1) {
                     dst[0] = (SkGetPackedR4444(src[x]) << 4) |
                         SkGetPackedG4444(src[x]);
                     dst[1] = (SkGetPackedB4444(src[x]) << 4);
+                    alphaDst[0] = (SkGetPackedA4444(src[x]) << 4);
+                    if (alphaDst[0] != 0xF0)
+                        hasAlpha = true;
+                    if (alphaDst[0] & 0xF0)
+                        isTransparent = false;
                 }
             }
             break;
         }
         case SkBitmap::kRGB_565_Config: {
             const int rowBytes = srcRect.width() * 3;
-            result = new SkMemoryStream(rowBytes * srcRect.height());
-            uint8_t* dst = (uint8_t*)result->getMemoryBase();
+            image = new SkMemoryStream(rowBytes * srcRect.height());
+            uint8_t* dst = (uint8_t*)image->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++) {
@@ -98,9 +117,12 @@
             break;
         }
         case SkBitmap::kARGB_8888_Config: {
+            isTransparent = true;
             const int rowBytes = srcRect.width() * 3;
-            result = new SkMemoryStream(rowBytes * srcRect.height());
-            uint8_t* dst = (uint8_t*)result->getMemoryBase();
+            image = new SkMemoryStream(rowBytes * srcRect.height());
+            alpha = new SkMemoryStream(srcRect.width() * srcRect.height());
+            uint8_t* dst = (uint8_t*)image->getMemoryBase();
+            uint8_t* alphaDst = (uint8_t*)alpha->getMemoryBase();
             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++) {
@@ -108,6 +130,71 @@
                     dst[1] = SkGetPackedG32(src[x]);
                     dst[2] = SkGetPackedB32(src[x]);
                     dst += 3;
+                    alphaDst[0] = SkGetPackedA32(src[x]);
+                    if (alphaDst[0] != 0xFF)
+                        hasAlpha = true;
+                    if (alphaDst[0])
+                        isTransparent = false;
+                    alphaDst++;
+                }
+            }
+            break;
+        }
+        case SkBitmap::kA1_Config: {
+            isTransparent = true;
+            image = new SkMemoryStream(1);
+            ((uint8_t*)image->getMemoryBase())[0] = 0;
+
+            const int alphaRowBytes = (srcRect.width() + 7) / 8;
+            alpha = new SkMemoryStream(alphaRowBytes * srcRect.height());
+            uint8_t* alphaDst = (uint8_t*)alpha->getMemoryBase();
+            int offset1 = srcRect.fLeft % 8;
+            int offset2 = 8 - offset1;
+            for (int y = srcRect.fTop; y < srcRect.fBottom; y++) {
+                uint8_t* src = bitmap.getAddr1(0, y);
+                // This may read up to one byte after src, but the potentially 
+                // invalid bits are never used for computation.
+                for (int x = srcRect.fLeft; x < srcRect.fRight; x += 8)  {
+                    if (offset1) {
+                        alphaDst[0] = src[x / 8] << offset1 |
+                            src[x / 8 + 1] >> offset2;
+                    } else {
+                        alphaDst[0] = src[x / 8];
+                    }
+                    if (x + 7 < srcRect.fRight && alphaDst[0] != 0xFF)
+                        hasAlpha = true;
+                    if (x + 7 < srcRect.fRight && alphaDst[0])
+                        isTransparent = false;
+                    alphaDst++;
+                }
+                // Calculate the mask of bits we're interested in within the
+                // last byte of alphaDst.
+                // width mod 8  == 1 -> 0x80 ... width mod 8 == 7 -> 0xFE
+                uint8_t mask = ~((1 << (8 - (srcRect.width() % 8))) - 1);
+                if (srcRect.width() % 8 && (alphaDst[-1] & mask) != mask)
+                    hasAlpha = true;
+                if (srcRect.width() % 8 && (alphaDst[-1] & mask))
+                    isTransparent = false;
+            }
+            break;
+        }
+        case SkBitmap::kA8_Config: {
+            isTransparent = true;
+            image = new SkMemoryStream(1);
+            ((uint8_t*)image->getMemoryBase())[0] = 0;
+
+            const int alphaRowBytes = srcRect.width();
+            alpha = new SkMemoryStream(alphaRowBytes * srcRect.height());
+            uint8_t* alphaDst = (uint8_t*)alpha->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];
+                    if (alphaDst[0] != 0xFF)
+                        hasAlpha = true;
+                    if (alphaDst[0])
+                        isTransparent = false;
+                    alphaDst++;
                 }
             }
             break;
@@ -116,7 +203,18 @@
             SkASSERT(false);
     }
     bitmap.unlockPixels();
-    return result;
+
+    if (isTransparent) {
+        SkSafeUnref(image);
+    } else {
+        *imageData = image;
+    }
+
+    if (isTransparent || !hasAlpha) {
+        SkSafeUnref(alpha);
+    } else {
+        *alphaData = alpha;
+    }
 }
 
 SkPDFArray* makeIndexedColorSpace(SkColorTable* table) {
@@ -153,20 +251,78 @@
 
 };  // namespace
 
-SkPDFImage::SkPDFImage(const SkBitmap& bitmap, const SkIRect& srcRect,
+// static
+SkPDFImage* SkPDFImage::CreateImage(const SkBitmap& bitmap,
+                                    const SkIRect& srcRect,
+                                    const SkPaint& paint) {
+    if (bitmap.getConfig() == SkBitmap::kNo_Config)
+        return NULL;
+
+    SkStream* imageData = NULL;
+    SkStream* alphaData = NULL;
+    extractImageData(bitmap, srcRect, &imageData, &alphaData);
+    SkAutoUnref unrefImageData(imageData);
+    SkAutoUnref unrefAlphaData(alphaData);
+    if (!imageData) {
+        SkASSERT(!alphaData);
+        return NULL;
+    }
+
+    SkPDFImage* image =
+        new SkPDFImage(imageData, bitmap, srcRect, false, paint);
+
+    if (alphaData != NULL) {
+        SkRefPtr<SkPDFImage> alphaImage =
+            new SkPDFImage(alphaData, bitmap, srcRect, true, paint);
+        alphaImage->unref();  // SkRefPtr and new both took a reference.
+        image->addSMask(alphaImage.get());
+    }
+    return image;
+}
+
+SkPDFImage::~SkPDFImage() {
+    fResources.unrefAll();
+}
+
+void SkPDFImage::addSMask(SkPDFImage* mask) {
+    fResources.push(mask);
+    mask->ref();
+
+    SkRefPtr<SkPDFObjRef> maskRef = new SkPDFObjRef(mask);
+    maskRef->unref();  // SkRefPtr and new both took a reference.
+    insert("SMask", maskRef.get());
+}
+
+void SkPDFImage::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                             bool indirect) {
+    if (indirect)
+        return emitIndirectObject(stream, catalog);
+
+    fStream->emitObject(stream, catalog, indirect);
+}
+
+size_t SkPDFImage::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+    if (indirect)
+        return getIndirectOutputSize(catalog);
+
+    return fStream->getOutputSize(catalog, indirect);
+}
+
+void SkPDFImage::getResources(SkTDArray<SkPDFObject*>* resourceList) {
+    if (fResources.count()) {
+        resourceList->setReserve(resourceList->count() + fResources.count());
+        for (int i = 0; i < fResources.count(); i++) {
+            resourceList->push(fResources[i]);
+            fResources[i]->ref();
+            fResources[i]->getResources(resourceList);
+        }
+    }
+}
+
+SkPDFImage::SkPDFImage(SkStream* imageData, const SkBitmap& bitmap,
+                       const SkIRect& srcRect, bool doingAlpha,
                        const SkPaint& paint) {
-    SkBitmap::Config config = bitmap.getConfig();
-
-    // TODO(vandebo) Handle alpha and alpha only images correctly.
-    SkASSERT(config == SkBitmap::kRGB_565_Config ||
-             config == SkBitmap::kARGB_4444_Config ||
-             config == SkBitmap::kARGB_8888_Config ||
-             config == SkBitmap::kIndex8_Config ||
-             config == SkBitmap::kRLE_Index8_Config);
-
-    SkMemoryStream* image_data = extractImageData(bitmap, srcRect);
-    SkAutoUnref image_data_unref(image_data);
-    fStream = new SkPDFStream(image_data);
+    fStream = new SkPDFStream(imageData);
     fStream->unref();  // SkRefPtr and new both took a reference.
 
     SkRefPtr<SkPDFName> typeValue = new SkPDFName("XObject");
@@ -177,17 +333,31 @@
     subTypeValue->unref();  // SkRefPtr and new both took a reference.
     insert("Subtype", subTypeValue.get());
 
-    SkRefPtr<SkPDFInt> widthValue = new SkPDFInt(srcRect.width());
+    SkBitmap::Config config = bitmap.getConfig();
+    bool alphaOnly = (config == SkBitmap::kA1_Config ||
+                      config == SkBitmap::kA8_Config);
+
+    SkRefPtr<SkPDFInt> widthValue;
+    SkRefPtr<SkPDFInt> heightValue;
+    if (!doingAlpha && alphaOnly) {
+        // For alpha only images, we stretch a single pixel of black for
+        // the color/shape part.
+        widthValue = new SkPDFInt(1);
+        heightValue = widthValue.get();
+    } else {
+        widthValue = new SkPDFInt(srcRect.width());
+        heightValue = new SkPDFInt(srcRect.height());
+        heightValue->unref();  // SkRefPtr and new both took a reference.
+    }
     widthValue->unref();  // SkRefPtr and new both took a reference.
     insert("Width", widthValue.get());
-
-    SkRefPtr<SkPDFInt> heightValue = new SkPDFInt(srcRect.height());
-    heightValue->unref();  // SkRefPtr and new both took a reference.
     insert("Height", heightValue.get());
 
     // if (!image mask) {
     SkRefPtr<SkPDFObject> colorSpaceValue;
-    if (config == SkBitmap::kIndex8_Config ||
+    if (doingAlpha || alphaOnly) {
+        colorSpaceValue = new SkPDFName("DeviceGray");
+    } else if (config == SkBitmap::kIndex8_Config ||
         config == SkBitmap::kRLE_Index8_Config) {
         colorSpaceValue = makeIndexedColorSpace(bitmap.getColorTable());
     } else {
@@ -197,14 +367,11 @@
     insert("ColorSpace", colorSpaceValue.get());
     // }
 
-    int bitsPerComp = bitmap.bytesPerPixel() * 2;
-    if (bitsPerComp == 0) {
-        SkASSERT(config == SkBitmap::kA1_Config);
+    int bitsPerComp = 8;
+    if (config == SkBitmap::kARGB_4444_Config)
+        bitsPerComp = 4;
+    else if (doingAlpha && config == SkBitmap::kA1_Config)
         bitsPerComp = 1;
-    } else if (bitsPerComp == 2 ||
-               (bitsPerComp == 4 && config == SkBitmap::kRGB_565_Config)) {
-        bitsPerComp = 8;
-    }
     SkRefPtr<SkPDFInt> bitsPerCompValue = new SkPDFInt(bitsPerComp);
     bitsPerCompValue->unref();  // SkRefPtr and new both took a reference.
     insert("BitsPerComponent", bitsPerCompValue.get());
@@ -229,23 +396,6 @@
     }
 }
 
-SkPDFImage::~SkPDFImage() {}
-
-void SkPDFImage::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
-                             bool indirect) {
-    if (indirect)
-        return emitIndirectObject(stream, catalog);
-
-    fStream->emitObject(stream, catalog, indirect);
-}
-
-size_t SkPDFImage::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
-    if (indirect)
-        return getIndirectOutputSize(catalog);
-
-    return fStream->getOutputSize(catalog, indirect);
-}
-
 void SkPDFImage::insert(SkPDFName* key, SkPDFObject* value) {
     fStream->insert(key, value);
 }
diff --git a/src/pdf/SkPDFStream.cpp b/src/pdf/SkPDFStream.cpp
index 6947ae4..cbba068 100644
--- a/src/pdf/SkPDFStream.cpp
+++ b/src/pdf/SkPDFStream.cpp
@@ -20,14 +20,18 @@
 #include "SkStream.h"
 
 SkPDFStream::SkPDFStream(SkStream* stream) {
-    if (SkFlate::HaveFlate()) {
+    if (SkFlate::HaveFlate())
         SkAssertResult(SkFlate::Deflate(stream, &fCompressedData));
+
+    if (SkFlate::HaveFlate() &&
+            fCompressedData.getOffset() < stream->getLength()) {
         fLength = fCompressedData.getOffset();
 
         SkRefPtr<SkPDFName> flateFilter = new SkPDFName("FlateDecode");
         flateFilter->unref();  // SkRefPtr and new both took a reference.
         fDict.insert("Filter", flateFilter.get());
     } else {
+        fCompressedData.reset();
         fPlainData = stream;
         fLength = fPlainData->getLength();
     }