Skip to content

Commit e7f165b

Browse files
LeonScrogginsSkia Commit-Bot
authored andcommitted
Treat kWEBP encode with quality=100 as lossless
In SkEncodeImage and friends, treat quality of 100 as a lossless encode when using kWEBP. This seems a good fit for the intent - which is presumably to save the highest quality image. This also matches Chromium's blink::ImageEncoder::ComputeWebpOptions, which treats a quality of 1 (on a float scale from 0 to 1) as a lossless encode. FWIW, Chromium has had this behavior since https://codereview.chromium.org/1937433002, in response to crbug.com/523098. The goal is to "maintain sharpness to match the JPEG encoder behavior (use WEBP lossless encoding)". Add a test to verify the new behavior. This requires making tests depend on libwebp to use WebPGetFeatures, since the Skia API does not provide a way to determine whether an encoded webp file was encoded lossless-ly or lossily. Bug: skia:8586 Change-Id: Ie9e09c2f7414ab701d696c4ad9edf405868a716f Reviewed-on: https://skia-review.googlesource.com/c/175823 Commit-Queue: Leon Scroggins <scroggo@google.com> Reviewed-by: Derek Sollenberger <djsollen@google.com> Reviewed-by: Mike Reed <reed@google.com>
1 parent db80cbe commit e7f165b

File tree

4 files changed

+72
-4
lines changed

4 files changed

+72
-4
lines changed

BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,6 +1560,7 @@ if (skia_enable_tools) {
15601560
"modules/skottie:tests",
15611561
"modules/sksg:tests",
15621562
"//third_party/libpng",
1563+
"//third_party/libwebp",
15631564
"//third_party/zlib",
15641565
]
15651566
public_deps = [

include/core/SkImageEncoder.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
* Will always return false if Skia is compiled without image
2828
* encoders.
2929
*
30-
* Note that webp encodes will use webp lossy compression.
30+
* For SkEncodedImageFormat::kWEBP, if quality is 100, it will use lossless compression. Otherwise
31+
* it will use lossy.
3132
*
3233
* For examples of encoding an image to a file or to a block of memory,
3334
* see tools/sk_tool_utils.h.
@@ -56,7 +57,8 @@ inline bool SkEncodeImage(SkWStream* dst, const SkBitmap& src, SkEncodedImageFor
5657
* Will always return nullptr if Skia is compiled without image
5758
* encoders.
5859
*
59-
* Note that webp encodes will use webp lossy compression.
60+
* For SkEncodedImageFormat::kWEBP, if quality is 100, it will use lossless compression. Otherwise
61+
* it will use lossy.
6062
*/
6163
SK_API sk_sp<SkData> SkEncodePixmap(const SkPixmap& src, SkEncodedImageFormat format, int quality);
6264

src/images/SkImageEncoder.cpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,25 @@ bool SkEncodeImage(SkWStream* dst, const SkPixmap& src,
4848
}
4949
case SkEncodedImageFormat::kWEBP: {
5050
SkWebpEncoder::Options opts;
51-
opts.fCompression = SkWebpEncoder::Compression::kLossy;
52-
opts.fQuality = quality;
51+
if (quality == 100) {
52+
opts.fCompression = SkWebpEncoder::Compression::kLossless;
53+
// Note: SkEncodeImage treats 0 quality as the lowest quality
54+
// (greatest compression) and 100 as the highest quality (least
55+
// compression). For kLossy, this matches libwebp's
56+
// interpretation, so it is passed directly to libwebp. But
57+
// with kLossless, libwebp always creates the highest quality
58+
// image. In this case, fQuality is reinterpreted as how much
59+
// effort (time) to put into making a smaller file. This API
60+
// does not provide a way to specify this value (though it can
61+
// be specified by using SkWebpEncoder::Encode) so we have to
62+
// pick one arbitrarily. This value matches that chosen by
63+
// blink::ImageEncoder::ComputeWebpOptions as well
64+
// WebPConfigInit.
65+
opts.fQuality = 75;
66+
} else {
67+
opts.fCompression = SkWebpEncoder::Compression::kLossy;
68+
opts.fQuality = quality;
69+
}
5370
return SkWebpEncoder::Encode(dst, src, opts);
5471
}
5572
default:

tests/EncodeTest.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#include <string>
2424
#include <vector>
2525

26+
#include "webp/decode.h"
27+
2628
static bool encode(SkEncodedImageFormat format, SkWStream* dst, const SkPixmap& src) {
2729
switch (format) {
2830
case SkEncodedImageFormat::kJPEG:
@@ -286,6 +288,52 @@ DEF_TEST(Encode_PngOptions, r) {
286288
REPORTER_ASSERT(r, almost_equals(bm0, bm2, 0));
287289
}
288290

291+
DEF_TEST(Encode_WebpQuality, r) {
292+
SkBitmap bm;
293+
bm.allocN32Pixels(100, 100);
294+
bm.eraseColor(SK_ColorBLUE);
295+
296+
auto dataLossy = SkEncodeBitmap(bm, SkEncodedImageFormat::kWEBP, 99);
297+
auto dataLossLess = SkEncodeBitmap(bm, SkEncodedImageFormat::kWEBP, 100);
298+
299+
enum Format {
300+
kMixed = 0,
301+
kLossy = 1,
302+
kLossless = 2,
303+
};
304+
305+
auto test = [&r](const sk_sp<SkData>& data, Format expected) {
306+
auto printFormat = [](int f) {
307+
switch (f) {
308+
case kMixed: return "mixed";
309+
case kLossy: return "lossy";
310+
case kLossless: return "lossless";
311+
default: return "unknown";
312+
}
313+
};
314+
315+
if (!data) {
316+
ERRORF(r, "Failed to encode. Expected %s", printFormat(expected));
317+
return;
318+
}
319+
320+
WebPBitstreamFeatures features;
321+
auto status = WebPGetFeatures(data->bytes(), data->size(), &features);
322+
if (status != VP8_STATUS_OK) {
323+
ERRORF(r, "Encode had an error %i. Expected %s", status, printFormat(expected));
324+
return;
325+
}
326+
327+
if (expected != features.format) {
328+
ERRORF(r, "Expected %s encode, but got format %s", printFormat(expected),
329+
printFormat(features.format));
330+
}
331+
};
332+
333+
test(dataLossy, kLossy);
334+
test(dataLossLess, kLossless);
335+
}
336+
289337
DEF_TEST(Encode_WebpOptions, r) {
290338
SkBitmap bitmap;
291339
bool success = GetResourceAsBitmap("images/google_chrome.ico", &bitmap);

0 commit comments

Comments
 (0)