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/gm/gmmain.cpp b/gm/gmmain.cpp
index 65058d6..d513a0e 100644
--- a/gm/gmmain.cpp
+++ b/gm/gmmain.cpp
@@ -174,6 +174,8 @@
         | SkGPipeWriter::kSharedAddressSpace_Flag }
 };
 
+static bool encode_to_dct_stream(SkWStream* stream, const SkBitmap& bitmap, const SkIRect& rect);
+
 const static ErrorCombination kDefaultIgnorableErrorTypes = ErrorCombination()
     .plus(kMissingExpectations_ErrorType)
     .plus(kIntentionallySkipped_ErrorType);
@@ -556,6 +558,7 @@
                               SkScalarRoundToInt(content.height()));
             dev = new SkPDFDevice(pageSize, contentSize, initialTransform);
         }
+        dev->setDCTEncoder(encode_to_dct_stream);
         SkAutoUnref aur(dev);
 
         SkCanvas c(dev);
@@ -1246,6 +1249,37 @@
              "each test).");
 DEFINE_string2(writePath, w, "",  "Write rendered images into this directory.");
 DEFINE_string2(writePicturePath, p, "", "Write .skp files into this directory.");
+DEFINE_int32(pdfJpegQuality, -1, "Encodes images in JPEG at quality level N, "
+             "which can be in range 0-100). N = -1 will disable JPEG compression. "
+             "Default is N = 100, maximum quality.");
+
+static bool encode_to_dct_stream(SkWStream* stream, const SkBitmap& bitmap, const SkIRect& rect) {
+    // Filter output of warnings that JPEG is not available for the image.
+    if (bitmap.width() >= 65500 || bitmap.height() >= 65500) return false;
+    if (FLAGS_pdfJpegQuality == -1) return false;
+
+    SkIRect bitmapBounds;
+    SkBitmap subset;
+    const SkBitmap* bitmapToUse = &bitmap;
+    bitmap.getBounds(&bitmapBounds);
+    if (rect != bitmapBounds) {
+        SkAssertResult(bitmap.extractSubset(&subset, rect));
+        bitmapToUse = ⊂
+    }
+
+#if defined(SK_BUILD_FOR_MAC)
+    // Workaround bug #1043 where bitmaps with referenced pixels cause
+    // CGImageDestinationFinalize to crash
+    SkBitmap copy;
+    bitmapToUse->deepCopyTo(&copy, bitmapToUse->config());
+    bitmapToUse = ©
+#endif
+
+    return SkImageEncoder::EncodeStream(stream,
+                                        *bitmapToUse,
+                                        SkImageEncoder::kJPEG_Type,
+                                        FLAGS_pdfJpegQuality);
+}
 
 static int findConfig(const char config[]) {
     for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); i++) {
@@ -1804,6 +1838,10 @@
         }
     }
 
+    if (FLAGS_pdfJpegQuality < -1 || FLAGS_pdfJpegQuality > 100) {
+        gm_fprintf(stderr, "%s\n", "pdfJpegQuality must be in [-1 .. 100] range.");
+    }
+
     Iter iter;
     GM* gm;
     while ((gm = iter.next()) != NULL) {
diff --git a/gyp/pdf.gyp b/gyp/pdf.gyp
index 081df01..0e75914 100644
--- a/gyp/pdf.gyp
+++ b/gyp/pdf.gyp
@@ -34,6 +34,8 @@
         '../src/pdf/SkPDFGraphicState.h',
         '../src/pdf/SkPDFImage.cpp',
         '../src/pdf/SkPDFImage.h',
+        '../src/pdf/SkPDFImageStream.cpp',
+        '../src/pdf/SkPDFImageStream.h',
         '../src/pdf/SkPDFPage.cpp',
         '../src/pdf/SkPDFPage.h',
         '../src/pdf/SkPDFShader.cpp',
diff --git a/include/pdf/SkPDFDevice.h b/include/pdf/SkPDFDevice.h
index f8261b5..7731d3f 100644
--- a/include/pdf/SkPDFDevice.h
+++ b/include/pdf/SkPDFDevice.h
@@ -37,6 +37,8 @@
 struct GraphicStateEntry;
 struct NamedDestination;
 
+typedef bool (*EncodeToDCTStream)(SkWStream* stream, const SkBitmap& bitmap, const SkIRect& rect);
+
 /** \class SkPDFDevice
 
     The drawing context for the PDF backend.
@@ -126,6 +128,21 @@
      */
     SK_API void setDrawingArea(DrawingArea drawingArea);
 
+    /** Sets the DCTEncoder for images.
+     *  @param encoder The encoder to encode a bitmap as JPEG (DCT).
+     *         Result of encodings are cached, if the encoder changes the
+     *         behaivor dynamically and an image is added to a second catalog,
+     *         we will likely use the result of the first encoding call.
+     *         By returning false from the encoder function, the encoder result
+     *         is not used.
+     *         Callers might not want to encode small images, as the time spent
+     *         encoding and decoding might not be worth the space savings,
+     *         if any at all.
+     */
+    void setDCTEncoder(EncodeToDCTStream encoder) {
+        fEncoder = encoder;
+    }
+
     // PDF specific methods.
 
     /** Returns the resource dictionary for this device.
@@ -230,6 +247,8 @@
     // Glyph ids used for each font on this device.
     SkTScopedPtr<SkPDFGlyphSetMap> fFontGlyphUsage;
 
+    EncodeToDCTStream fEncoder;
+
     SkPDFDevice(const SkISize& layerSize, const SkClipStack& existingClipStack,
                 const SkRegion& existingClipRegion);
 
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
diff --git a/tests/PDFPrimitivesTest.cpp b/tests/PDFPrimitivesTest.cpp
index 3b54d17..cf929b4 100644
--- a/tests/PDFPrimitivesTest.cpp
+++ b/tests/PDFPrimitivesTest.cpp
@@ -6,11 +6,13 @@
  * found in the LICENSE file.
  */
 
-
 #include "Test.h"
+#include "SkBitmap.h"
 #include "SkCanvas.h"
 #include "SkData.h"
 #include "SkFlate.h"
+#include "SkImageEncoder.h"
+#include "SkMatrix.h"
 #include "SkPDFCatalog.h"
 #include "SkPDFDevice.h"
 #include "SkPDFStream.h"
@@ -37,6 +39,11 @@
     SkTDArray<SkPDFObject*> fResources;
 };
 
+static bool encode_to_dct_stream(SkWStream* stream, const SkBitmap& bitmap, const SkIRect& rect) {
+    stream->writeText("DCT compessed stream.");
+    return true;
+}
+
 static bool stream_equals(const SkDynamicMemoryWStream& stream, size_t offset,
                           const void* buffer, size_t len) {
     SkAutoDataUnref data(stream.copyToData());
@@ -46,6 +53,20 @@
     return memcmp(data->bytes() + offset, buffer, len) == 0;
 }
 
+static bool stream_contains(const SkDynamicMemoryWStream& stream,
+                            const char* buffer) {
+    SkAutoDataUnref data(stream.copyToData());
+    int len = strlen(buffer);  // our buffer does not have EOSs.
+
+    for (int offset = 0 ; offset < (int)data->size() - len; offset++) {
+        if (memcmp(data->bytes() + offset, buffer, len) == 0) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 static void CheckObjectOutput(skiatest::Reporter* reporter, SkPDFObject* obj,
                               const char* expectedData, size_t expectedSize,
                               bool indirect, bool compression) {
@@ -219,6 +240,102 @@
                                             buffer.getOffset()));
 }
 
+// Create a bitmap that would be easier to be compressed in a JPEG than ZIP.
+static void setup_jpegBitmap(SkBitmap* bitmap, int width, int height) {
+    bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
+    bitmap->allocPixels();
+    for (int y = 0;  y < bitmap->height(); y++) {
+        for (int x = 0; x < bitmap->width(); x++) {
+            *bitmap->getAddr32(x, y) =
+                SkColorSetRGB(0 + y % 20 + 128 + 100 * cos(x * 0.01F),
+                              1 + y % 20 + 128 + 100 * cos(x * 0.1F),
+                              2 + y % 20 + 128 + 100 * cos(x * 1.0F));
+        }
+    }
+}
+
+// Create a bitmap that would be very eficiently compressed in a ZIP.
+static void setup_solidBitmap(SkBitmap* bitmap, int width, int height) {
+    bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
+    bitmap->allocPixels();
+    bitmap->eraseColor(SK_ColorWHITE);
+}
+
+static void TestImage(skiatest::Reporter* reporter, const SkBitmap& bitmap,
+                      const char* expected, bool useDCTEncoder) {
+    SkISize pageSize = SkISize::Make(bitmap.width(), bitmap.height());
+    SkPDFDevice* dev = new SkPDFDevice(pageSize, pageSize, SkMatrix::I());
+
+    if (useDCTEncoder) {
+        dev->setDCTEncoder(encode_to_dct_stream);
+    }
+
+    SkCanvas c(dev);
+    c.drawBitmap(bitmap, 0, 0, NULL);
+
+    SkPDFDocument doc;
+    doc.appendPage(dev);
+
+    SkDynamicMemoryWStream stream;
+    doc.emitPDF(&stream);
+
+    REPORTER_ASSERT(reporter, stream_contains(stream, expected));
+}
+
+static void TestUncompressed(skiatest::Reporter* reporter) {
+    SkBitmap bitmap;
+    setup_solidBitmap(&bitmap, 1, 1);
+    TestImage(reporter, bitmap,
+              "/Subtype /Image\n"
+              "/Width 1\n"
+              "/Height 1\n"
+              "/ColorSpace /DeviceRGB\n"
+              "/BitsPerComponent 8\n"
+              "/Length 3\n"
+              ">> stream",
+              true);
+}
+
+static void TestFlateDecode(skiatest::Reporter* reporter) {
+    if (!SkFlate::HaveFlate()) {
+        return;
+    }
+    SkBitmap bitmap;   
+    setup_solidBitmap(&bitmap, 10, 10);
+    TestImage(reporter, bitmap,
+              "/Subtype /Image\n"
+              "/Width 10\n"
+              "/Height 10\n"
+              "/ColorSpace /DeviceRGB\n"
+              "/BitsPerComponent 8\n"
+              "/Filter /FlateDecode\n"
+              "/Length 13\n"
+              ">> stream",
+              false);
+}
+
+static void TestDCTDecode(skiatest::Reporter* reporter) {
+    SkBitmap bitmap;
+    setup_jpegBitmap(&bitmap, 32, 32);
+    TestImage(reporter, bitmap,
+              "/Subtype /Image\n"
+              "/Width 32\n"
+              "/Height 32\n"
+              "/ColorSpace /DeviceRGB\n"
+              "/BitsPerComponent 8\n"
+              "/Filter /DCTDecode\n"
+              "/ColorTransform 0\n"
+              "/Length 21\n"
+              ">> stream",
+              true);
+}
+
+static void TestImages(skiatest::Reporter* reporter) {
+    TestUncompressed(reporter);
+    TestFlateDecode(reporter);
+    TestDCTDecode(reporter);
+}
+
 // This test used to assert without the fix submitted for
 // http://code.google.com/p/skia/issues/detail?id=1083.
 // SKP files might have invalid glyph ids. This test ensures they are ignored,
@@ -324,6 +441,8 @@
     TestSubstitute(reporter);
 
     test_issue1083();
+
+    TestImages(reporter);    
 }
 
 #include "TestClassDef.h"
diff --git a/tools/PdfRenderer.cpp b/tools/PdfRenderer.cpp
index 9a4bd38..704cbea 100644
--- a/tools/PdfRenderer.cpp
+++ b/tools/PdfRenderer.cpp
@@ -36,6 +36,7 @@
 SkCanvas* PdfRenderer::setupCanvas(int width, int height) {
     SkISize pageSize = SkISize::Make(width, height);
     fPDFDevice = SkNEW_ARGS(SkPDFDevice, (pageSize, pageSize, SkMatrix::I()));
+    fPDFDevice->setDCTEncoder(fEncoder);
     return SkNEW_ARGS(SkCanvas, (fPDFDevice));
 }
 
diff --git a/tools/PdfRenderer.h b/tools/PdfRenderer.h
index d2d6637..d2d1a5c 100644
--- a/tools/PdfRenderer.h
+++ b/tools/PdfRenderer.h
@@ -14,6 +14,7 @@
 //
 
 #include "SkMath.h"
+#include "SkPDFDevice.h"
 #include "SkPicture.h"
 #include "SkTypes.h"
 #include "SkTDArray.h"
@@ -22,7 +23,6 @@
 
 class SkBitmap;
 class SkCanvas;
-class SkPDFDevice;
 
 namespace sk_tools {
 
@@ -33,9 +33,10 @@
     virtual void render() = 0;
     virtual void end();
 
-    PdfRenderer()
+    PdfRenderer(EncodeToDCTStream encoder)
         : fPicture(NULL)
         , fPDFDevice(NULL)
+        , fEncoder(encoder)
         {}
 
     void write(SkWStream* stream) const;
@@ -47,7 +48,7 @@
     SkAutoTUnref<SkCanvas> fCanvas;
     SkPicture* fPicture;
     SkPDFDevice* fPDFDevice;
-
+    EncodeToDCTStream fEncoder;
 
 private:
     typedef SkRefCnt INHERITED;
@@ -55,6 +56,8 @@
 
 class SimplePdfRenderer : public PdfRenderer {
 public:
+    SimplePdfRenderer(EncodeToDCTStream encoder)
+        : PdfRenderer(encoder) {}
     virtual void render() SK_OVERRIDE;
 
 private:
diff --git a/tools/render_pdfs_main.cpp b/tools/render_pdfs_main.cpp
index 1ac02d3..96666ac 100644
--- a/tools/render_pdfs_main.cpp
+++ b/tools/render_pdfs_main.cpp
@@ -9,6 +9,7 @@
 #include "SkDevice.h"
 #include "SkGraphics.h"
 #include "SkImageDecoder.h"
+#include "SkImageEncoder.h"
 #include "SkOSFile.h"
 #include "SkPicture.h"
 #include "SkStream.h"
@@ -38,7 +39,7 @@
     SkDebugf("SKP to PDF rendering tool\n");
     SkDebugf("\n"
 "Usage: \n"
-"     %s <input>... -w <outputDir> \n"
+"     %s <input>... [-w <outputDir>] [--jpegQuality N] \n"
 , argv0);
     SkDebugf("\n\n");
     SkDebugf(
@@ -47,6 +48,12 @@
     SkDebugf(
 "     outputDir: directory to write the rendered pdfs.\n\n");
     SkDebugf("\n");
+        SkDebugf(
+"     jpegQuality N: encodes images in JPEG at quality level N, which can\n"
+"                    be in range 0-100).\n"
+"                    N = -1 will disable JPEG compression.\n"
+"                    Default is N = 100, maximum quality.\n\n");
+    SkDebugf("\n");
 }
 
 /** Replaces the extension of a file.
@@ -71,6 +78,33 @@
     return false;
 }
 
+int gJpegQuality = 100;
+static bool encode_to_dct_stream(SkWStream* stream, const SkBitmap& bitmap, const SkIRect& rect) {
+    if (gJpegQuality == -1) return false;
+
+        SkIRect bitmapBounds;
+        SkBitmap subset;
+        const SkBitmap* bitmapToUse = &bitmap;
+        bitmap.getBounds(&bitmapBounds);
+        if (rect != bitmapBounds) {
+            SkAssertResult(bitmap.extractSubset(&subset, rect));
+            bitmapToUse = &subset;
+        }
+    
+#if defined(SK_BUILD_FOR_MAC)
+        // Workaround bug #1043 where bitmaps with referenced pixels cause
+        // CGImageDestinationFinalize to crash
+        SkBitmap copy;
+        bitmapToUse->deepCopyTo(&copy, bitmapToUse->config());
+        bitmapToUse = &copy;
+#endif
+
+    return SkImageEncoder::EncodeStream(stream,
+                                        *bitmapToUse,
+                                        SkImageEncoder::kJPEG_Type,
+                                        gJpegQuality);
+}
+
 /** Builds the output filename. path = dir/name, and it replaces expected
  * .skp extension with .pdf extention.
  * @param path Output filename.
@@ -200,6 +234,19 @@
                 exit(-1);
             }
             *outputDir = SkString(*argv);
+        } else if (0 == strcmp(*argv, "--jpegQuality")) {
+            ++argv;
+            if (argv >= stop) {
+                SkDebugf("Missing argument for --jpegQuality\n");
+                usage(argv0);
+                exit(-1);
+            }
+            gJpegQuality = atoi(*argv);
+            if (gJpegQuality < -1 || gJpegQuality > 100) {
+                SkDebugf("Invalid argument for --jpegQuality\n");
+                usage(argv0);
+                exit(-1);            
+            }
         } else {
             inputs->push_back(SkString(*argv));
         }
@@ -217,7 +264,7 @@
     SkTArray<SkString> inputs;
 
     SkAutoTUnref<sk_tools::PdfRenderer>
-        renderer(SkNEW(sk_tools::SimplePdfRenderer));
+        renderer(SkNEW_ARGS(sk_tools::SimplePdfRenderer, (encode_to_dct_stream)));
     SkASSERT(renderer.get());
 
     SkString outputDir;