Encode images with DCTDecode (JPEG) in PDFs if it makes sense. Fallback to FlateDecode (zip) if it makes sense. Otherewise include uncompressed stream.
This change will reduce the size of PDFs to 50% (in the case of the existing SKPs, we reduce the total size of PDFs from 105MB to 50MB) 
Review URL: https://codereview.appspot.com/7068055

git-svn-id: http://skia.googlecode.com/svn/trunk@8835 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index 6298f7b..15da5a5 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -591,7 +591,8 @@
       fContentSize(contentSize),
       fLastContentEntry(NULL),
       fLastMarginContentEntry(NULL),
-      fClipStack(NULL) {
+      fClipStack(NULL),
+      fEncoder(NULL) {
     // Skia generally uses the top left as the origin but PDF natively has the
     // origin at the bottom left. This matrix corrects for that.  But that only
     // needs to be done once, we don't do it when layering.
@@ -1822,7 +1823,7 @@
         return;
     }
 
-    SkPDFImage* image = SkPDFImage::CreateImage(bitmap, subset);
+    SkPDFImage* image = SkPDFImage::CreateImage(bitmap, subset, fEncoder);
     if (!image) {
         return;
     }
diff --git a/src/pdf/SkPDFImage.cpp b/src/pdf/SkPDFImage.cpp
index f7889f1..04307be 100644
--- a/src/pdf/SkPDFImage.cpp
+++ b/src/pdf/SkPDFImage.cpp
@@ -249,7 +249,8 @@
 
 // static
 SkPDFImage* SkPDFImage::CreateImage(const SkBitmap& bitmap,
-                                    const SkIRect& srcRect) {
+                                    const SkIRect& srcRect,
+                                    EncodeToDCTStream encoder) {
     if (bitmap.getConfig() == SkBitmap::kNo_Config) {
         return NULL;
     }
@@ -265,10 +266,12 @@
     }
 
     SkPDFImage* image =
-        new SkPDFImage(imageData, bitmap, srcRect, false);
+        SkNEW_ARGS(SkPDFImage, (imageData, bitmap, srcRect, false, encoder));
 
     if (alphaData != NULL) {
-        image->addSMask(new SkPDFImage(alphaData, bitmap, srcRect, true))->unref();
+        // Don't try to use DCT compression with alpha because alpha is small
+        // anyway and it could lead to artifacts.
+        image->addSMask(SkNEW_ARGS(SkPDFImage, (alphaData, bitmap, srcRect, true, NULL)))->unref();
     }
     return image;
 }
@@ -289,9 +292,12 @@
     GetResourcesHelper(&fResources, knownResourceObjects, newResourceObjects);
 }
 
-SkPDFImage::SkPDFImage(SkStream* imageData, const SkBitmap& bitmap,
-                       const SkIRect& srcRect, bool doingAlpha) {
-    this->setData(imageData);
+SkPDFImage::SkPDFImage(SkStream* imageData,
+                       const SkBitmap& bitmap,
+                       const SkIRect& srcRect,
+                       bool doingAlpha,
+                       EncodeToDCTStream encoder)
+        : SkPDFImageStream(imageData, bitmap, srcRect, encoder) {
     SkBitmap::Config config = bitmap.getConfig();
     bool alphaOnly = (config == SkBitmap::kA1_Config ||
                       config == SkBitmap::kA8_Config);
diff --git a/src/pdf/SkPDFImage.h b/src/pdf/SkPDFImage.h
index d41466f..31f8940 100644
--- a/src/pdf/SkPDFImage.h
+++ b/src/pdf/SkPDFImage.h
@@ -10,7 +10,8 @@
 #ifndef SkPDFImage_DEFINED
 #define SkPDFImage_DEFINED
 
-#include "SkPDFStream.h"
+#include "SkPDFDevice.h"
+#include "SkPDFImageStream.h"
 #include "SkPDFTypes.h"
 #include "SkRefCnt.h"
 
@@ -26,7 +27,7 @@
 // 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 {
+class SkPDFImage : public SkPDFImageStream {
 public:
     /** Create a new Image XObject to represent the passed bitmap.
      *  @param bitmap   The image to encode.
@@ -36,7 +37,8 @@
      *           the given parameters.
      */
     static SkPDFImage* CreateImage(const SkBitmap& bitmap,
-                                   const SkIRect& srcRect);
+                                   const SkIRect& srcRect,
+                                   EncodeToDCTStream encoder);
 
     virtual ~SkPDFImage();
 
@@ -63,7 +65,7 @@
      *  @param paint      Used to calculate alpha, masks, etc.
      */
     SkPDFImage(SkStream* imageData, const SkBitmap& bitmap,
-               const SkIRect& srcRect, bool alpha);
+               const SkIRect& srcRect, bool alpha, EncodeToDCTStream encoder);
 };
 
 #endif
diff --git a/src/pdf/SkPDFImageStream.cpp b/src/pdf/SkPDFImageStream.cpp
new file mode 100644
index 0000000..7130f2b
--- /dev/null
+++ b/src/pdf/SkPDFImageStream.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkData.h"
+#include "SkFlate.h"
+#include "SkPDFCatalog.h"
+#include "SkPDFImageStream.h"
+#include "SkStream.h"
+
+#define kNoColorTransform 0
+
+static bool skip_compression(SkPDFCatalog* catalog) {
+    return SkToBool(catalog->getDocumentFlags() &
+                    SkPDFDocument::kFavorSpeedOverSize_Flags);
+}
+
+// TODO(edisonn): Use SkData (after removing deprecated constructor in SkPDFStream).
+SkPDFImageStream::SkPDFImageStream(SkStream* stream,
+                                   const SkBitmap& bitmap,
+                                   const SkIRect& srcRect,
+                                   EncodeToDCTStream encoder)
+    : SkPDFStream(stream),
+      fBitmap(bitmap),
+      fSrcRect(srcRect),
+      fEncoder(encoder) {
+}
+
+SkPDFImageStream::SkPDFImageStream(const SkPDFImageStream& pdfStream)
+        : SkPDFStream(pdfStream),
+          fBitmap(pdfStream.fBitmap),
+          fSrcRect(pdfStream.fSrcRect),
+          fEncoder(pdfStream.fEncoder) {
+}
+
+SkPDFImageStream::~SkPDFImageStream() {}
+
+bool SkPDFImageStream::populate(SkPDFCatalog* catalog) {
+    if (getState() == kUnused_State) {
+        if (!skip_compression(catalog)) {
+            SkDynamicMemoryWStream dctCompressedWStream;
+            if (!fEncoder || !fEncoder(&dctCompressedWStream, fBitmap, fSrcRect)) {
+                return INHERITED::populate(catalog);
+            }
+
+            if (dctCompressedWStream.getOffset() < getData()->getLength()) {
+                SkData* data = dctCompressedWStream.copyToData();
+                SkMemoryStream* stream = SkNEW_ARGS(SkMemoryStream, (data));
+                setData(stream);
+                stream->unref();
+                if (data) {
+                    // copyToData and new SkMemoryStream both call ref(), supress one.
+                    data->unref();
+                }
+
+                insertName("Filter", "DCTDecode");
+                insertInt("ColorTransform", kNoColorTransform);
+                setState(kCompressed_State);
+            }
+        }
+        setState(kNoCompression_State);
+        insertInt("Length", getData()->getLength());
+    } else if (getState() == kNoCompression_State && !skip_compression(catalog) &&
+               (SkFlate::HaveFlate() || fEncoder)) {
+        // Compression has not been requested when the stream was first created.
+        // But a new Catalog would want it compressed.
+        if (!getSubstitute()) {
+            SkPDFImageStream* substitute = SkNEW_ARGS(SkPDFImageStream, (*this));
+            setSubstitute(substitute);
+            catalog->setSubstitute(this, substitute);
+        }
+        return false;
+    }
+    return true;
+}
diff --git a/src/pdf/SkPDFImageStream.h b/src/pdf/SkPDFImageStream.h
new file mode 100644
index 0000000..c518081
--- /dev/null
+++ b/src/pdf/SkPDFImageStream.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkPDFImageStream_DEFINED
+#define SkPDFImageStream_DEFINED
+
+#include "SkBitmap.h"
+#include "SkPDFDevice.h"
+#include "SkPDFStream.h"
+#include "SkPDFTypes.h"
+#include "SkRect.h"
+#include "SkRefCnt.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+
+class SkPDFCatalog;
+
+/** \class SkPDFImageStream
+
+    An image stream object in a PDF.  Note, all streams must be indirect objects
+    (via SkObjRef).
+    This class is similar to SkPDFStream, but it is also able to use image
+    specific compression. Currently we support DCT(jpeg) and flate(zip).
+*/
+class SkPDFImageStream : public SkPDFStream {
+public:
+    /** Create a PDF stream with the same content and dictionary entries
+     *  as the passed one.
+     */
+    explicit SkPDFImageStream(const SkPDFImageStream& pdfStream);
+    virtual ~SkPDFImageStream();
+
+protected:
+    SkPDFImageStream(SkStream* stream, const SkBitmap& bitmap,
+                     const SkIRect& srcRect, EncodeToDCTStream encoder);
+
+    // Populate the stream dictionary.  This method returns false if
+    // fSubstitute should be used.
+    virtual bool populate(SkPDFCatalog* catalog);
+
+private:
+    const SkBitmap fBitmap;
+    const SkIRect fSrcRect;
+    EncodeToDCTStream fEncoder;
+
+    typedef SkPDFStream INHERITED;
+};
+
+#endif
diff --git a/src/pdf/SkPDFStream.h b/src/pdf/SkPDFStream.h
index 6f7a08e..4b8153f 100644
--- a/src/pdf/SkPDFStream.h
+++ b/src/pdf/SkPDFStream.h
@@ -44,32 +44,53 @@
     virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
 
 protected:
-    /* Create a PDF stream with no data.  The setData method must be called to
-     * set the data.
-     */
-    SkPDFStream();
-
-    void setData(SkStream* stream);
-
-private:
     enum State {
         kUnused_State,         //!< The stream hasn't been requested yet.
         kNoCompression_State,  //!< The stream's been requested in an
                                //   uncompressed form.
         kCompressed_State,     //!< The stream's already been compressed.
     };
+
+    /* Create a PDF stream with no data.  The setData method must be called to
+     * set the data.
+     */
+    SkPDFStream();
+
+    // Populate the stream dictionary.  This method returns false if
+    // fSubstitute should be used.
+    virtual bool populate(SkPDFCatalog* catalog);
+
+    void setSubstitute(SkPDFStream* stream) {
+        fSubstitute.reset(stream);
+    }
+
+    SkPDFStream* getSubstitute() {
+        return fSubstitute.get();
+    }
+
+    void setData(SkStream* stream);
+
+    SkStream* getData() {
+        return fData.get();
+    }
+
+    void setState(State state) {
+        fState = state;
+    }
+
+    State getState() {
+        return fState;
+    }
+
+private:
     // Indicates what form (or if) the stream has been requested.
     State fState;
-
+    
     // TODO(vandebo): Use SkData (after removing deprecated constructor).
     SkAutoTUnref<SkStream> fData;
     SkAutoTUnref<SkPDFStream> fSubstitute;
 
     typedef SkPDFDict INHERITED;
-
-    // Populate the stream dictionary.  This method returns false if
-    // fSubstitute should be used.
-    bool populate(SkPDFCatalog* catalog);
 };
 
 #endif