Encode images with DCTDecode (JPEG) in PDFs if it makes sense. Fallback to FlateDecod...
authoredisonn@google.com <edisonn@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Wed, 24 Apr 2013 13:01:01 +0000 (13:01 +0000)
committeredisonn@google.com <edisonn@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Wed, 24 Apr 2013 13:01:01 +0000 (13:01 +0000)
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

13 files changed:
gm/gmmain.cpp
gyp/pdf.gyp
include/pdf/SkPDFDevice.h
src/pdf/SkPDFDevice.cpp
src/pdf/SkPDFImage.cpp
src/pdf/SkPDFImage.h
src/pdf/SkPDFImageStream.cpp [new file with mode: 0644]
src/pdf/SkPDFImageStream.h [new file with mode: 0644]
src/pdf/SkPDFStream.h
tests/PDFPrimitivesTest.cpp
tools/PdfRenderer.cpp
tools/PdfRenderer.h
tools/render_pdfs_main.cpp

index 65058d6..d513a0e 100644 (file)
@@ -174,6 +174,8 @@ static PipeFlagComboData gPipeWritingFlagCombos[] = {
         | 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 @@ public:
                               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 @@ DEFINE_bool2(verbose, v, false, "Give more detail (e.g. list all GMs run, more i
              "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 = &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,
+                                        FLAGS_pdfJpegQuality);
+}
 
 static int findConfig(const char config[]) {
     for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); i++) {
@@ -1804,6 +1838,10 @@ int tool_main(int argc, char** argv) {
         }
     }
 
+    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) {
index 081df01..0e75914 100644 (file)
@@ -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',
index f8261b5..7731d3f 100644 (file)
@@ -37,6 +37,8 @@ struct ContentEntry;
 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 @@ public:
      */
     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 @@ private:
     // Glyph ids used for each font on this device.
     SkTScopedPtr<SkPDFGlyphSetMap> fFontGlyphUsage;
 
+    EncodeToDCTStream fEncoder;
+
     SkPDFDevice(const SkISize& layerSize, const SkClipStack& existingClipStack,
                 const SkRegion& existingClipRegion);
 
index 6298f7b..15da5a5 100644 (file)
@@ -591,7 +591,8 @@ SkPDFDevice::SkPDFDevice(const SkISize& pageSize, const SkISize& contentSize,
       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 @@ void SkPDFDevice::internalDrawBitmap(const SkMatrix& matrix,
         return;
     }
 
-    SkPDFImage* image = SkPDFImage::CreateImage(bitmap, subset);
+    SkPDFImage* image = SkPDFImage::CreateImage(bitmap, subset, fEncoder);
     if (!image) {
         return;
     }
index f7889f1..04307be 100644 (file)
@@ -249,7 +249,8 @@ SkPDFArray* makeIndexedColorSpace(SkColorTable* table) {
 
 // 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* SkPDFImage::CreateImage(const SkBitmap& bitmap,
     }
 
     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 @@ void SkPDFImage::getResources(const SkTSet<SkPDFObject*>& knownResourceObjects,
     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);
index d41466f..31f8940 100644 (file)
@@ -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 @@ struct SkIRect;
 // 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 @@ public:
      *           the given parameters.
      */
     static SkPDFImage* CreateImage(const SkBitmap& bitmap,
-                                   const SkIRect& srcRect);
+                                   const SkIRect& srcRect,
+                                   EncodeToDCTStream encoder);
 
     virtual ~SkPDFImage();
 
@@ -63,7 +65,7 @@ private:
      *  @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 (file)
index 0000000..7130f2b
--- /dev/null
@@ -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 (file)
index 0000000..c518081
--- /dev/null
@@ -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
index 6f7a08e..4b8153f 100644 (file)
@@ -44,32 +44,53 @@ public:
     virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
 
 protected:
+    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:
-    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.
-    };
     // 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
index 3b54d17..cf929b4 100644 (file)
@@ -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 @@ private:
     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 @@ static bool stream_equals(const SkDynamicMemoryWStream& stream, size_t offset,
     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 @@ static void TestSubstitute(skiatest::Reporter* reporter) {
                                             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 @@ static void TestPDFPrimitives(skiatest::Reporter* reporter) {
     TestSubstitute(reporter);
 
     test_issue1083();
+
+    TestImages(reporter);    
 }
 
 #include "TestClassDef.h"
index 9a4bd38..704cbea 100644 (file)
@@ -36,6 +36,7 @@ SkCanvas* PdfRenderer::setupCanvas() {
 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));
 }
 
index d2d6637..d2d1a5c 100644 (file)
@@ -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 @@ public:
     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 @@ protected:
     SkAutoTUnref<SkCanvas> fCanvas;
     SkPicture* fPicture;
     SkPDFDevice* fPDFDevice;
-
+    EncodeToDCTStream fEncoder;
 
 private:
     typedef SkRefCnt INHERITED;
@@ -55,6 +56,8 @@ private:
 
 class SimplePdfRenderer : public PdfRenderer {
 public:
+    SimplePdfRenderer(EncodeToDCTStream encoder)
+        : PdfRenderer(encoder) {}
     virtual void render() SK_OVERRIDE;
 
 private:
index 1ac02d3..96666ac 100644 (file)
@@ -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 @@ static void usage(const char* argv0) {
     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 @@ static void usage(const char* argv0) {
     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 @@ static bool replace_filename_extension(SkString* path,
     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 @@ static void parse_commandline(int argc, char* const argv[],
                 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 @@ int tool_main_core(int argc, char** argv) {
     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;