Add support for webp lossless compression
authorMatt Sarett <msarett@google.com>
Tue, 16 May 2017 21:06:52 +0000 (17:06 -0400)
committerSkia Commit-Bot <skia-commit-bot@chromium.org>
Wed, 17 May 2017 02:24:29 +0000 (02:24 +0000)
Bug: 713862
Change-Id: I8dcc6506338f3c54fb14a78620e7daaadadfedde
Reviewed-on: https://skia-review.googlesource.com/17073
Reviewed-by: Mike Reed <reed@google.com>
Reviewed-by: Leon Scroggins <scroggo@google.com>
Commit-Queue: Matt Sarett <msarett@google.com>

include/core/SkImageEncoder.h
include/encode/SkWebpEncoder.h
src/images/SkImageEncoder.cpp
src/images/SkWebpEncoder.cpp
tests/EncodeTest.cpp

index 5cd5f4a..87d5170 100644 (file)
  * @param  dst     results are written to this stream.
  * @param  src     source pixels.
  * @param  format  image format, not all formats are supported.
- * @param  quality range from 0-100, not all formats respect quality.
+ * @param  quality range from 0-100, this is supported by jpeg and webp.
+ *                 higher values correspond to improved visual quality, but less compression.
  *
  * @return false iff input is bad or format is unsupported.
  *
  * Will always return false if Skia is compiled without image
  * encoders.
  *
+ * Note that webp encodes will use webp lossy compression.
+ *
  * For examples of encoding an image to a file or to a block of memory,
  * see tools/sk_tool_utils.h.
  */
index 8d894a4..97d2742 100644 (file)
@@ -14,10 +14,25 @@ class SkWStream;
 
 namespace SkWebpEncoder {
 
+    enum class SK_API Compression {
+        kLossy,
+        kLossless,
+    };
+
     struct SK_API Options {
         /**
-         *  |fQuality| must be in [0.0f, 100.0f] where 0.0f corresponds to the lowest quality.
+         *  |fCompression| determines whether we will use webp lossy or lossless compression.
+         *
+         *  |fQuality| must be in [0.0f, 100.0f].
+         *  If |fCompression| is kLossy, |fQuality| corresponds to the visual quality of the
+         *  encoding.  Decreasing the quality will result in a smaller encoded image.
+         *  If |fCompression| is kLossless, |fQuality| corresponds to the amount of effort
+         *  put into the encoding.  Lower values will compress faster into larger files,
+         *  while larger values will compress slower into smaller files.
+         *
+         *  This scheme is designed to match the libwebp API.
          */
+        Compression fCompression = Compression::kLossy;
         float fQuality = 100.0f;
 
         /**
index 30493cf..d79741d 100644 (file)
@@ -49,6 +49,7 @@ bool SkEncodeImage(SkWStream* dst, const SkPixmap& src,
             }
             case SkEncodedImageFormat::kWEBP: {
                 SkWebpEncoder::Options opts;
+                opts.fCompression = SkWebpEncoder::Compression::kLossy;
                 opts.fQuality = quality;
                 opts.fUnpremulBehavior = SkTransferFunctionBehavior::kIgnore;
                 return SkWebpEncoder::Encode(dst, src, opts);
index 5c26111..b6c6212 100644 (file)
@@ -124,8 +124,7 @@ static int stream_writer(const uint8_t* data, size_t data_size,
   return stream->write(data, data_size) ? 1 : 0;
 }
 
-static bool do_encode(SkWStream* stream, const SkPixmap& pixmap, const SkWebpEncoder::Options& opts)
-{
+bool SkWebpEncoder::Encode(SkWStream* stream, const SkPixmap& pixmap, const Options& opts) {
     if (!SkPixmapIsValid(pixmap, opts.fUnpremulBehavior)) {
         return false;
     }
@@ -168,6 +167,16 @@ static bool do_encode(SkWStream* stream, const SkPixmap& pixmap, const SkWebpEnc
         return false;
     }
 
+    // The choice of |webp_config.method| currently just match Chrome's defaults.  We
+    // could potentially expose this to the client.
+    if (Compression::kLossy == opts.fCompression) {
+        webp_config.lossless = 0;
+        webp_config.method = 3;
+    } else {
+        webp_config.lossless = 1;
+        webp_config.method = 0;
+    }
+
     WebPPicture pic;
     WebPPictureInit(&pic);
     SkAutoTCallVProc<WebPPicture, WebPPictureFree> autoPic(&pic);
@@ -236,8 +245,4 @@ static bool do_encode(SkWStream* stream, const SkPixmap& pixmap, const SkWebpEnc
     return true;
 }
 
-bool SkWebpEncoder::Encode(SkWStream* dst, const SkPixmap& src, const Options& opts) {
-    return do_encode(dst, src, opts);
-}
-
 #endif
index 7046e67..102b4cd 100644 (file)
@@ -14,6 +14,7 @@
 #include "SkJpegEncoder.h"
 #include "SkPngEncoder.h"
 #include "SkStream.h"
+#include "SkWebpEncoder.h"
 
 static bool encode(SkEncodedImageFormat format, SkWStream* dst, const SkPixmap& src) {
     switch (format) {
@@ -204,3 +205,56 @@ DEF_TEST(Encode_PngOptions, r) {
     REPORTER_ASSERT(r, almost_equals(bm0, bm1, 0));
     REPORTER_ASSERT(r, almost_equals(bm0, bm2, 0));
 }
+
+DEF_TEST(Encode_WebpOptions, r) {
+    SkBitmap bitmap;
+    bool success = GetResourceAsBitmap("google_chrome.ico", &bitmap);
+    if (!success) {
+        return;
+    }
+
+    SkPixmap src;
+    success = bitmap.peekPixels(&src);
+    REPORTER_ASSERT(r, success);
+    if (!success) {
+        return;
+    }
+
+    SkDynamicMemoryWStream dst0, dst1, dst2, dst3;
+    SkWebpEncoder::Options options;
+    options.fCompression = SkWebpEncoder::Compression::kLossless;
+    options.fQuality = 0.0f;
+    success = SkWebpEncoder::Encode(&dst0, src, options);
+    REPORTER_ASSERT(r, success);
+
+    options.fQuality = 100.0f;
+    success = SkWebpEncoder::Encode(&dst1, src, options);
+    REPORTER_ASSERT(r, success);
+
+    options.fCompression = SkWebpEncoder::Compression::kLossy;
+    options.fQuality = 100.0f;
+    success = SkWebpEncoder::Encode(&dst2, src, options);
+    REPORTER_ASSERT(r, success);
+
+    options.fCompression = SkWebpEncoder::Compression::kLossy;
+    options.fQuality = 50.0f;
+    success = SkWebpEncoder::Encode(&dst3, src, options);
+    REPORTER_ASSERT(r, success);
+
+    sk_sp<SkData> data0 = dst0.detachAsData();
+    sk_sp<SkData> data1 = dst1.detachAsData();
+    sk_sp<SkData> data2 = dst2.detachAsData();
+    sk_sp<SkData> data3 = dst3.detachAsData();
+    REPORTER_ASSERT(r, data0->size() > data1->size());
+    REPORTER_ASSERT(r, data1->size() > data2->size());
+    REPORTER_ASSERT(r, data2->size() > data3->size());
+
+    SkBitmap bm0, bm1, bm2, bm3;
+    SkImage::MakeFromEncoded(data0)->asLegacyBitmap(&bm0, SkImage::kRO_LegacyBitmapMode);
+    SkImage::MakeFromEncoded(data1)->asLegacyBitmap(&bm1, SkImage::kRO_LegacyBitmapMode);
+    SkImage::MakeFromEncoded(data2)->asLegacyBitmap(&bm2, SkImage::kRO_LegacyBitmapMode);
+    SkImage::MakeFromEncoded(data3)->asLegacyBitmap(&bm3, SkImage::kRO_LegacyBitmapMode);
+    REPORTER_ASSERT(r, almost_equals(bm0, bm1, 0));
+    REPORTER_ASSERT(r, almost_equals(bm0, bm2, 6));
+    REPORTER_ASSERT(r, almost_equals(bm2, bm3, 45));
+}