blob: f4c3b7758afbc6ec363f89cd3f840c94876ddf58 [file] [log] [blame]
Matt Sarett26b44df2017-05-02 16:04:56 -04001/*
2 * Copyright 2017 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "tests/Test.h"
9#include "tools/Resources.h"
Matt Sarett26b44df2017-05-02 16:04:56 -040010
Mike Kleinc0bd9f92019-04-23 12:05:21 -050011#include "include/core/SkBitmap.h"
Brian Osman01e6d172020-03-30 15:57:14 -040012#include "include/core/SkCanvas.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050013#include "include/core/SkColorPriv.h"
14#include "include/core/SkEncodedImageFormat.h"
15#include "include/core/SkImage.h"
16#include "include/core/SkStream.h"
Leon Scroggins III112ac7f2019-07-08 14:08:50 -040017#include "include/core/SkSurface.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050018#include "include/encode/SkJpegEncoder.h"
19#include "include/encode/SkPngEncoder.h"
20#include "include/encode/SkWebpEncoder.h"
Matt Sarett26b44df2017-05-02 16:04:56 -040021
Yuqian Lid0dbee62017-06-09 11:35:58 -040022#include "png.h"
23
24#include <algorithm>
25#include <string>
26#include <vector>
27
Leon Scroggins III194580d2019-02-22 12:32:16 -050028// FIXME: Update the Google3 build's dependencies so it can run this test.
29#ifndef SK_BUILD_FOR_GOOGLE3
30#include "webp/decode.h"
31#endif
32
Matt Sarettc367d032017-05-05 11:13:26 -040033static bool encode(SkEncodedImageFormat format, SkWStream* dst, const SkPixmap& src) {
34 switch (format) {
35 case SkEncodedImageFormat::kJPEG:
36 return SkJpegEncoder::Encode(dst, src, SkJpegEncoder::Options());
37 case SkEncodedImageFormat::kPNG:
38 return SkPngEncoder::Encode(dst, src, SkPngEncoder::Options());
39 default:
Matt Sarett41a8ec02017-05-05 15:07:41 -040040 return false;
Matt Sarettc367d032017-05-05 11:13:26 -040041 }
42}
43
44static std::unique_ptr<SkEncoder> make(SkEncodedImageFormat format, SkWStream* dst,
45 const SkPixmap& src) {
46 switch (format) {
47 case SkEncodedImageFormat::kJPEG:
48 return SkJpegEncoder::Make(dst, src, SkJpegEncoder::Options());
49 case SkEncodedImageFormat::kPNG:
50 return SkPngEncoder::Make(dst, src, SkPngEncoder::Options());
51 default:
52 return nullptr;
53 }
54}
55
56static void test_encode(skiatest::Reporter* r, SkEncodedImageFormat format) {
Matt Sarett26b44df2017-05-02 16:04:56 -040057 SkBitmap bitmap;
Hal Canaryc465d132017-12-08 10:21:31 -050058 bool success = GetResourceAsBitmap("images/mandrill_128.png", &bitmap);
Matt Sarett26b44df2017-05-02 16:04:56 -040059 if (!success) {
60 return;
61 }
62
63 SkPixmap src;
64 success = bitmap.peekPixels(&src);
65 REPORTER_ASSERT(r, success);
66 if (!success) {
67 return;
68 }
69
70 SkDynamicMemoryWStream dst0, dst1, dst2, dst3;
Matt Sarettc367d032017-05-05 11:13:26 -040071 success = encode(format, &dst0, src);
Matt Sarett26b44df2017-05-02 16:04:56 -040072 REPORTER_ASSERT(r, success);
73
Matt Sarettc367d032017-05-05 11:13:26 -040074 auto encoder1 = make(format, &dst1, src);
Matt Sarett26b44df2017-05-02 16:04:56 -040075 for (int i = 0; i < src.height(); i++) {
76 success = encoder1->encodeRows(1);
77 REPORTER_ASSERT(r, success);
78 }
79
Matt Sarettc367d032017-05-05 11:13:26 -040080 auto encoder2 = make(format, &dst2, src);
Matt Sarett26b44df2017-05-02 16:04:56 -040081 for (int i = 0; i < src.height(); i+=3) {
82 success = encoder2->encodeRows(3);
83 REPORTER_ASSERT(r, success);
84 }
85
Matt Sarettc367d032017-05-05 11:13:26 -040086 auto encoder3 = make(format, &dst3, src);
Matt Sarett26b44df2017-05-02 16:04:56 -040087 success = encoder3->encodeRows(200);
88 REPORTER_ASSERT(r, success);
89
90 sk_sp<SkData> data0 = dst0.detachAsData();
91 sk_sp<SkData> data1 = dst1.detachAsData();
92 sk_sp<SkData> data2 = dst2.detachAsData();
93 sk_sp<SkData> data3 = dst3.detachAsData();
94 REPORTER_ASSERT(r, data0->equals(data1.get()));
95 REPORTER_ASSERT(r, data0->equals(data2.get()));
96 REPORTER_ASSERT(r, data0->equals(data3.get()));
97}
Matt Sarettc367d032017-05-05 11:13:26 -040098
Matt Sarettbe4c9b02017-05-08 12:11:44 -040099DEF_TEST(Encode, r) {
Matt Sarettc367d032017-05-05 11:13:26 -0400100 test_encode(r, SkEncodedImageFormat::kJPEG);
101 test_encode(r, SkEncodedImageFormat::kPNG);
102}
Matt Sarettbe4c9b02017-05-08 12:11:44 -0400103
Matt Sarettfe319082017-05-09 14:02:10 -0400104static inline bool almost_equals(SkPMColor a, SkPMColor b, int tolerance) {
105 if (SkTAbs((int)SkGetPackedR32(a) - (int)SkGetPackedR32(b)) > tolerance) {
106 return false;
107 }
108
109 if (SkTAbs((int)SkGetPackedG32(a) - (int)SkGetPackedG32(b)) > tolerance) {
110 return false;
111 }
112
113 if (SkTAbs((int)SkGetPackedB32(a) - (int)SkGetPackedB32(b)) > tolerance) {
114 return false;
115 }
116
117 if (SkTAbs((int)SkGetPackedA32(a) - (int)SkGetPackedA32(b)) > tolerance) {
118 return false;
119 }
120
121 return true;
122}
123
124static inline bool almost_equals(const SkBitmap& a, const SkBitmap& b, int tolerance) {
Matt Sarettbe4c9b02017-05-08 12:11:44 -0400125 if (a.info() != b.info()) {
126 return false;
127 }
128
129 SkASSERT(kN32_SkColorType == a.colorType());
130 for (int y = 0; y < a.height(); y++) {
131 for (int x = 0; x < a.width(); x++) {
Matt Sarettfe319082017-05-09 14:02:10 -0400132 if (!almost_equals(*a.getAddr32(x, y), *b.getAddr32(x, y), tolerance)) {
Matt Sarettbe4c9b02017-05-08 12:11:44 -0400133 return false;
134 }
135 }
136 }
137
138 return true;
139}
140
Leon Scroggins III112ac7f2019-07-08 14:08:50 -0400141DEF_TEST(Encode_JPG, r) {
142 auto image = GetResourceAsImage("images/mandrill_128.png");
143 if (!image) {
144 return;
145 }
146
147 for (auto ct : { kRGBA_8888_SkColorType,
148 kBGRA_8888_SkColorType,
149 kRGB_565_SkColorType,
150 kARGB_4444_SkColorType,
151 kGray_8_SkColorType,
152 kRGBA_F16_SkColorType }) {
153 for (auto at : { kPremul_SkAlphaType, kUnpremul_SkAlphaType, kOpaque_SkAlphaType }) {
154 auto info = SkImageInfo::Make(image->width(), image->height(), ct, at);
155 auto surface = SkSurface::MakeRaster(info);
156 auto canvas = surface->getCanvas();
157 canvas->drawImage(image, 0, 0);
158
159 SkBitmap bm;
160 bm.allocPixels(info);
161 if (!surface->makeImageSnapshot()->readPixels(bm.pixmap(), 0, 0)) {
162 ERRORF(r, "failed to readPixels! ct: %i\tat: %i\n", ct, at);
163 continue;
164 }
165 for (auto alphaOption : { SkJpegEncoder::AlphaOption::kIgnore,
166 SkJpegEncoder::AlphaOption::kBlendOnBlack }) {
167 SkJpegEncoder::Options opts;
168 opts.fAlphaOption = alphaOption;
169 SkNullWStream dummy;
170 if (!SkJpegEncoder::Encode(&dummy, bm.pixmap(), opts)) {
171 REPORTER_ASSERT(r, ct == kARGB_4444_SkColorType
172 && alphaOption == SkJpegEncoder::AlphaOption::kBlendOnBlack);
173 }
174 }
175 }
176 }
177}
178
Matt Sarettfe319082017-05-09 14:02:10 -0400179DEF_TEST(Encode_JpegDownsample, r) {
180 SkBitmap bitmap;
Hal Canaryc465d132017-12-08 10:21:31 -0500181 bool success = GetResourceAsBitmap("images/mandrill_128.png", &bitmap);
Matt Sarettfe319082017-05-09 14:02:10 -0400182 if (!success) {
183 return;
184 }
185
186 SkPixmap src;
187 success = bitmap.peekPixels(&src);
188 REPORTER_ASSERT(r, success);
189 if (!success) {
190 return;
191 }
192
193 SkDynamicMemoryWStream dst0, dst1, dst2;
194 SkJpegEncoder::Options options;
195 success = SkJpegEncoder::Encode(&dst0, src, options);
196 REPORTER_ASSERT(r, success);
197
198 options.fDownsample = SkJpegEncoder::Downsample::k422;
199 success = SkJpegEncoder::Encode(&dst1, src, options);
200 REPORTER_ASSERT(r, success);
201
202 options.fDownsample = SkJpegEncoder::Downsample::k444;
203 success = SkJpegEncoder::Encode(&dst2, src, options);
204 REPORTER_ASSERT(r, success);
205
206 sk_sp<SkData> data0 = dst0.detachAsData();
207 sk_sp<SkData> data1 = dst1.detachAsData();
208 sk_sp<SkData> data2 = dst2.detachAsData();
209 REPORTER_ASSERT(r, data0->size() < data1->size());
210 REPORTER_ASSERT(r, data1->size() < data2->size());
211
212 SkBitmap bm0, bm1, bm2;
Cary Clark4f5a79c2018-02-07 15:51:00 -0500213 SkImage::MakeFromEncoded(data0)->asLegacyBitmap(&bm0);
214 SkImage::MakeFromEncoded(data1)->asLegacyBitmap(&bm1);
215 SkImage::MakeFromEncoded(data2)->asLegacyBitmap(&bm2);
Matt Sarettfe319082017-05-09 14:02:10 -0400216 REPORTER_ASSERT(r, almost_equals(bm0, bm1, 60));
217 REPORTER_ASSERT(r, almost_equals(bm1, bm2, 60));
218}
219
Yuqian Lid0dbee62017-06-09 11:35:58 -0400220static inline void pushComment(
221 std::vector<std::string>& comments, const char* keyword, const char* text) {
222 comments.push_back(keyword);
223 comments.push_back(text);
224}
225
226static void testPngComments(const SkPixmap& src, SkPngEncoder::Options& options,
227 skiatest::Reporter* r) {
228 std::vector<std::string> commentStrings;
229 pushComment(commentStrings, "key", "text");
230 pushComment(commentStrings, "test", "something");
231 pushComment(commentStrings, "have some", "spaces in both");
232
233 std::string longKey(PNG_KEYWORD_MAX_LENGTH, 'x');
234#ifdef SK_DEBUG
235 commentStrings.push_back(longKey);
236#else
237 // We call SkDEBUGFAILF it the key is too long so we'll only test this in release mode.
238 commentStrings.push_back(longKey + "x");
239#endif
240 commentStrings.push_back("");
241
242 std::vector<const char*> commentPointers;
243 std::vector<size_t> commentSizes;
244 for(auto& str : commentStrings) {
245 commentPointers.push_back(str.c_str());
246 commentSizes.push_back(str.length() + 1);
247 }
248
249 options.fComments = SkDataTable::MakeCopyArrays((void const *const *)commentPointers.data(),
250 commentSizes.data(), commentStrings.size());
251
252
253 SkDynamicMemoryWStream dst;
254 bool success = SkPngEncoder::Encode(&dst, src, options);
255 REPORTER_ASSERT(r, success);
256
257 std::vector<char> output(dst.bytesWritten());
258 dst.copyTo(output.data());
259
260 // Each chunk is of the form length (4 bytes), chunk type (tEXt), data,
261 // checksum (4 bytes). Make sure we find all of them in the encoded
262 // results.
263 const char kExpected1[] =
264 "\x00\x00\x00\x08tEXtkey\x00text\x9e\xe7\x66\x51";
265 const char kExpected2[] =
266 "\x00\x00\x00\x0etEXttest\x00something\x29\xba\xef\xac";
267 const char kExpected3[] =
268 "\x00\x00\x00\x18tEXthave some\x00spaces in both\x8d\x69\x34\x2d";
269 std::string longKeyRecord = "tEXt" + longKey; // A snippet of our long key comment
270 std::string tooLongRecord = "tExt" + longKey + "x"; // A snippet whose key is too long
271
272 auto search1 = std::search(output.begin(), output.end(),
273 kExpected1, kExpected1 + sizeof(kExpected1));
274 auto search2 = std::search(output.begin(), output.end(),
275 kExpected2, kExpected2 + sizeof(kExpected2));
276 auto search3 = std::search(output.begin(), output.end(),
277 kExpected3, kExpected3 + sizeof(kExpected3));
278 auto search4 = std::search(output.begin(), output.end(),
279 longKeyRecord.begin(), longKeyRecord.end());
280 auto search5 = std::search(output.begin(), output.end(),
281 tooLongRecord.begin(), tooLongRecord.end());
282
283 REPORTER_ASSERT(r, search1 != output.end());
284 REPORTER_ASSERT(r, search2 != output.end());
285 REPORTER_ASSERT(r, search3 != output.end());
286 REPORTER_ASSERT(r, search4 != output.end());
287 REPORTER_ASSERT(r, search5 == output.end());
288 // Comments test ends
289}
290
Matt Sarettbe4c9b02017-05-08 12:11:44 -0400291DEF_TEST(Encode_PngOptions, r) {
292 SkBitmap bitmap;
Hal Canaryc465d132017-12-08 10:21:31 -0500293 bool success = GetResourceAsBitmap("images/mandrill_128.png", &bitmap);
Matt Sarettbe4c9b02017-05-08 12:11:44 -0400294 if (!success) {
295 return;
296 }
297
298 SkPixmap src;
299 success = bitmap.peekPixels(&src);
300 REPORTER_ASSERT(r, success);
301 if (!success) {
302 return;
303 }
304
305 SkDynamicMemoryWStream dst0, dst1, dst2;
306 SkPngEncoder::Options options;
307 success = SkPngEncoder::Encode(&dst0, src, options);
308 REPORTER_ASSERT(r, success);
309
310 options.fFilterFlags = SkPngEncoder::FilterFlag::kUp;
311 success = SkPngEncoder::Encode(&dst1, src, options);
312 REPORTER_ASSERT(r, success);
313
314 options.fZLibLevel = 3;
315 success = SkPngEncoder::Encode(&dst2, src, options);
316 REPORTER_ASSERT(r, success);
317
Yuqian Lid0dbee62017-06-09 11:35:58 -0400318 testPngComments(src, options, r);
319
Matt Sarettbe4c9b02017-05-08 12:11:44 -0400320 sk_sp<SkData> data0 = dst0.detachAsData();
321 sk_sp<SkData> data1 = dst1.detachAsData();
322 sk_sp<SkData> data2 = dst2.detachAsData();
323 REPORTER_ASSERT(r, data0->size() < data1->size());
324 REPORTER_ASSERT(r, data1->size() < data2->size());
325
326 SkBitmap bm0, bm1, bm2;
Cary Clark4f5a79c2018-02-07 15:51:00 -0500327 SkImage::MakeFromEncoded(data0)->asLegacyBitmap(&bm0);
328 SkImage::MakeFromEncoded(data1)->asLegacyBitmap(&bm1);
329 SkImage::MakeFromEncoded(data2)->asLegacyBitmap(&bm2);
Matt Sarettfe319082017-05-09 14:02:10 -0400330 REPORTER_ASSERT(r, almost_equals(bm0, bm1, 0));
331 REPORTER_ASSERT(r, almost_equals(bm0, bm2, 0));
Matt Sarettbe4c9b02017-05-08 12:11:44 -0400332}
Matt Sarettd5a16912017-05-16 17:06:52 -0400333
Leon Scroggins III194580d2019-02-22 12:32:16 -0500334#ifndef SK_BUILD_FOR_GOOGLE3
335DEF_TEST(Encode_WebpQuality, r) {
336 SkBitmap bm;
337 bm.allocN32Pixels(100, 100);
338 bm.eraseColor(SK_ColorBLUE);
339
340 auto dataLossy = SkEncodeBitmap(bm, SkEncodedImageFormat::kWEBP, 99);
341 auto dataLossLess = SkEncodeBitmap(bm, SkEncodedImageFormat::kWEBP, 100);
342
343 enum Format {
344 kMixed = 0,
345 kLossy = 1,
346 kLossless = 2,
347 };
348
349 auto test = [&r](const sk_sp<SkData>& data, Format expected) {
350 auto printFormat = [](int f) {
351 switch (f) {
352 case kMixed: return "mixed";
353 case kLossy: return "lossy";
354 case kLossless: return "lossless";
355 default: return "unknown";
356 }
357 };
358
359 if (!data) {
360 ERRORF(r, "Failed to encode. Expected %s", printFormat(expected));
361 return;
362 }
363
364 WebPBitstreamFeatures features;
365 auto status = WebPGetFeatures(data->bytes(), data->size(), &features);
366 if (status != VP8_STATUS_OK) {
367 ERRORF(r, "Encode had an error %i. Expected %s", status, printFormat(expected));
368 return;
369 }
370
371 if (expected != features.format) {
372 ERRORF(r, "Expected %s encode, but got format %s", printFormat(expected),
373 printFormat(features.format));
374 }
375 };
376
377 test(dataLossy, kLossy);
378 test(dataLossLess, kLossless);
379}
380#endif
381
Matt Sarettd5a16912017-05-16 17:06:52 -0400382DEF_TEST(Encode_WebpOptions, r) {
383 SkBitmap bitmap;
Hal Canaryc465d132017-12-08 10:21:31 -0500384 bool success = GetResourceAsBitmap("images/google_chrome.ico", &bitmap);
Matt Sarettd5a16912017-05-16 17:06:52 -0400385 if (!success) {
386 return;
387 }
388
389 SkPixmap src;
390 success = bitmap.peekPixels(&src);
391 REPORTER_ASSERT(r, success);
392 if (!success) {
393 return;
394 }
395
396 SkDynamicMemoryWStream dst0, dst1, dst2, dst3;
397 SkWebpEncoder::Options options;
398 options.fCompression = SkWebpEncoder::Compression::kLossless;
399 options.fQuality = 0.0f;
400 success = SkWebpEncoder::Encode(&dst0, src, options);
401 REPORTER_ASSERT(r, success);
402
403 options.fQuality = 100.0f;
404 success = SkWebpEncoder::Encode(&dst1, src, options);
405 REPORTER_ASSERT(r, success);
406
407 options.fCompression = SkWebpEncoder::Compression::kLossy;
408 options.fQuality = 100.0f;
409 success = SkWebpEncoder::Encode(&dst2, src, options);
410 REPORTER_ASSERT(r, success);
411
412 options.fCompression = SkWebpEncoder::Compression::kLossy;
413 options.fQuality = 50.0f;
414 success = SkWebpEncoder::Encode(&dst3, src, options);
415 REPORTER_ASSERT(r, success);
416
417 sk_sp<SkData> data0 = dst0.detachAsData();
418 sk_sp<SkData> data1 = dst1.detachAsData();
419 sk_sp<SkData> data2 = dst2.detachAsData();
420 sk_sp<SkData> data3 = dst3.detachAsData();
421 REPORTER_ASSERT(r, data0->size() > data1->size());
422 REPORTER_ASSERT(r, data1->size() > data2->size());
423 REPORTER_ASSERT(r, data2->size() > data3->size());
424
425 SkBitmap bm0, bm1, bm2, bm3;
Cary Clark4f5a79c2018-02-07 15:51:00 -0500426 SkImage::MakeFromEncoded(data0)->asLegacyBitmap(&bm0);
427 SkImage::MakeFromEncoded(data1)->asLegacyBitmap(&bm1);
428 SkImage::MakeFromEncoded(data2)->asLegacyBitmap(&bm2);
429 SkImage::MakeFromEncoded(data3)->asLegacyBitmap(&bm3);
Matt Sarettd5a16912017-05-16 17:06:52 -0400430 REPORTER_ASSERT(r, almost_equals(bm0, bm1, 0));
Matt Sarett2f687872017-05-19 19:12:54 -0400431 REPORTER_ASSERT(r, almost_equals(bm0, bm2, 90));
Brian Osmanb62f50c2018-07-12 14:44:27 -0400432 REPORTER_ASSERT(r, almost_equals(bm2, bm3, 50));
Matt Sarettd5a16912017-05-16 17:06:52 -0400433}