OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright 2015 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 | |
8 #include "Resources.h" | |
9 #include "SkAndroidCodec.h" | |
10 #include "SkBitmap.h" | |
11 #include "SkCodec.h" | |
12 #include "SkCodecImageGenerator.h" | |
13 #include "SkData.h" | |
14 #include "SkFrontBufferedStream.h" | |
15 #include "SkMD5.h" | |
16 #include "SkRandom.h" | |
17 #include "SkStream.h" | |
18 #include "SkStreamPriv.h" | |
19 #include "SkPngChunkReader.h" | |
20 #include "Test.h" | |
21 | |
22 #include "png.h" | |
23 | |
24 static SkStreamAsset* resource(const char path[]) { | |
25 SkString fullPath = GetResourcePath(path); | |
26 return SkStream::NewFromFile(fullPath.c_str()); | |
27 } | |
28 | |
29 static void md5(const SkBitmap& bm, SkMD5::Digest* digest) { | |
30 SkAutoLockPixels autoLockPixels(bm); | |
31 SkASSERT(bm.getPixels()); | |
32 SkMD5 md5; | |
33 size_t rowLen = bm.info().bytesPerPixel() * bm.width(); | |
34 for (int y = 0; y < bm.height(); ++y) { | |
35 md5.update(static_cast<uint8_t*>(bm.getAddr(0, y)), rowLen); | |
36 } | |
37 md5.finish(*digest); | |
38 } | |
39 | |
40 /** | |
41 * Compute the digest for bm and compare it to a known good digest. | |
42 * @param r Reporter to assert that bm's digest matches goodDigest. | |
43 * @param goodDigest The known good digest to compare to. | |
44 * @param bm The bitmap to test. | |
45 */ | |
46 static void compare_to_good_digest(skiatest::Reporter* r, const SkMD5::Digest& g
oodDigest, | |
47 const SkBitmap& bm) { | |
48 SkMD5::Digest digest; | |
49 md5(bm, &digest); | |
50 REPORTER_ASSERT(r, digest == goodDigest); | |
51 } | |
52 | |
53 /** | |
54 * Test decoding an SkCodec to a particular SkImageInfo. | |
55 * | |
56 * Calling getPixels(info) should return expectedResult, and if goodDigest is n
on nullptr, | |
57 * the resulting decode should match. | |
58 */ | |
59 template<typename Codec> | |
60 static void test_info(skiatest::Reporter* r, Codec* codec, const SkImageInfo& in
fo, | |
61 SkCodec::Result expectedResult, const SkMD5::Digest* goodD
igest) { | |
62 SkBitmap bm; | |
63 bm.allocPixels(info); | |
64 SkAutoLockPixels autoLockPixels(bm); | |
65 | |
66 SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes(
)); | |
67 REPORTER_ASSERT(r, result == expectedResult); | |
68 | |
69 if (goodDigest) { | |
70 compare_to_good_digest(r, *goodDigest, bm); | |
71 } | |
72 } | |
73 | |
74 SkIRect generate_random_subset(SkRandom* rand, int w, int h) { | |
75 SkIRect rect; | |
76 do { | |
77 rect.fLeft = rand->nextRangeU(0, w); | |
78 rect.fTop = rand->nextRangeU(0, h); | |
79 rect.fRight = rand->nextRangeU(0, w); | |
80 rect.fBottom = rand->nextRangeU(0, h); | |
81 rect.sort(); | |
82 } while (rect.isEmpty()); | |
83 return rect; | |
84 } | |
85 | |
86 template<typename Codec> | |
87 static void test_codec(skiatest::Reporter* r, Codec* codec, SkBitmap& bm, const
SkImageInfo& info, | |
88 const SkISize& size, SkCodec::Result expectedResult, SkMD5::Digest* dige
st, | |
89 const SkMD5::Digest* goodDigest) { | |
90 | |
91 REPORTER_ASSERT(r, info.dimensions() == size); | |
92 bm.allocPixels(info); | |
93 SkAutoLockPixels autoLockPixels(bm); | |
94 | |
95 SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes(
)); | |
96 REPORTER_ASSERT(r, result == expectedResult); | |
97 | |
98 md5(bm, digest); | |
99 if (goodDigest) { | |
100 REPORTER_ASSERT(r, *digest == *goodDigest); | |
101 } | |
102 | |
103 { | |
104 // Test decoding to 565 | |
105 SkImageInfo info565 = info.makeColorType(kRGB_565_SkColorType); | |
106 SkCodec::Result expected565 = info.alphaType() == kOpaque_SkAlphaType ? | |
107 expectedResult : SkCodec::kInvalidConversion; | |
108 test_info(r, codec, info565, expected565, nullptr); | |
109 } | |
110 | |
111 // Verify that re-decoding gives the same result. It is interesting to chec
k this after | |
112 // a decode to 565, since choosing to decode to 565 may result in some of th
e decode | |
113 // options being modified. These options should return to their defaults on
another | |
114 // decode to kN32, so the new digest should match the old digest. | |
115 test_info(r, codec, info, expectedResult, digest); | |
116 | |
117 { | |
118 // Check alpha type conversions | |
119 if (info.alphaType() == kOpaque_SkAlphaType) { | |
120 test_info(r, codec, info.makeAlphaType(kUnpremul_SkAlphaType), | |
121 expectedResult, digest); | |
122 test_info(r, codec, info.makeAlphaType(kPremul_SkAlphaType), | |
123 expectedResult, digest); | |
124 } else { | |
125 // Decoding to opaque should fail | |
126 test_info(r, codec, info.makeAlphaType(kOpaque_SkAlphaType), | |
127 SkCodec::kInvalidConversion, nullptr); | |
128 SkAlphaType otherAt = info.alphaType(); | |
129 if (kPremul_SkAlphaType == otherAt) { | |
130 otherAt = kUnpremul_SkAlphaType; | |
131 } else { | |
132 otherAt = kPremul_SkAlphaType; | |
133 } | |
134 // The other non-opaque alpha type should always succeed, but not ma
tch. | |
135 test_info(r, codec, info.makeAlphaType(otherAt), expectedResult, nul
lptr); | |
136 } | |
137 } | |
138 } | |
139 | |
140 static bool supports_partial_scanlines(const char path[]) { | |
141 static const char* const exts[] = { | |
142 "jpg", "jpeg", "png", "webp" | |
143 "JPG", "JPEG", "PNG", "WEBP" | |
144 }; | |
145 | |
146 for (uint32_t i = 0; i < SK_ARRAY_COUNT(exts); i++) { | |
147 if (SkStrEndsWith(path, exts[i])) { | |
148 return true; | |
149 } | |
150 } | |
151 return false; | |
152 } | |
153 | |
154 static void check(skiatest::Reporter* r, | |
155 const char path[], | |
156 SkISize size, | |
157 bool supportsScanlineDecoding, | |
158 bool supportsSubsetDecoding, | |
159 bool supportsIncomplete = true) { | |
160 | |
161 SkAutoTDelete<SkStream> stream(resource(path)); | |
162 if (!stream) { | |
163 SkDebugf("Missing resource '%s'\n", path); | |
164 return; | |
165 } | |
166 | |
167 SkAutoTDelete<SkCodec> codec(nullptr); | |
168 bool isIncomplete = supportsIncomplete; | |
169 if (isIncomplete) { | |
170 size_t size = stream->getLength(); | |
171 SkAutoTUnref<SkData> data((SkData::NewFromStream(stream, 2 * size / 3)))
; | |
172 codec.reset(SkCodec::NewFromData(data)); | |
173 } else { | |
174 codec.reset(SkCodec::NewFromStream(stream.release())); | |
175 } | |
176 if (!codec) { | |
177 ERRORF(r, "Unable to decode '%s'", path); | |
178 return; | |
179 } | |
180 | |
181 // Test full image decodes with SkCodec | |
182 SkMD5::Digest codecDigest; | |
183 const SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType); | |
184 SkBitmap bm; | |
185 SkCodec::Result expectedResult = isIncomplete ? SkCodec::kIncompleteInput :
SkCodec::kSuccess; | |
186 test_codec(r, codec.get(), bm, info, size, expectedResult, &codecDigest, nul
lptr); | |
187 | |
188 // Scanline decoding follows. | |
189 // Need to call startScanlineDecode() first. | |
190 REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0) | |
191 == 0); | |
192 REPORTER_ASSERT(r, codec->skipScanlines(1) | |
193 == 0); | |
194 | |
195 const SkCodec::Result startResult = codec->startScanlineDecode(info); | |
196 if (supportsScanlineDecoding) { | |
197 bm.eraseColor(SK_ColorYELLOW); | |
198 | |
199 REPORTER_ASSERT(r, startResult == SkCodec::kSuccess); | |
200 | |
201 for (int y = 0; y < info.height(); y++) { | |
202 const int lines = codec->getScanlines(bm.getAddr(0, y), 1, 0); | |
203 if (!isIncomplete) { | |
204 REPORTER_ASSERT(r, 1 == lines); | |
205 } | |
206 } | |
207 // verify that scanline decoding gives the same result. | |
208 if (SkCodec::kTopDown_SkScanlineOrder == codec->getScanlineOrder()) { | |
209 compare_to_good_digest(r, codecDigest, bm); | |
210 } | |
211 | |
212 // Cannot continue to decode scanlines beyond the end | |
213 REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0) | |
214 == 0); | |
215 | |
216 // Interrupting a scanline decode with a full decode starts from | |
217 // scratch | |
218 REPORTER_ASSERT(r, codec->startScanlineDecode(info) == SkCodec::kSuccess
); | |
219 const int lines = codec->getScanlines(bm.getAddr(0, 0), 1, 0); | |
220 if (!isIncomplete) { | |
221 REPORTER_ASSERT(r, lines == 1); | |
222 } | |
223 REPORTER_ASSERT(r, codec->getPixels(bm.info(), bm.getPixels(), bm.rowByt
es()) | |
224 == expectedResult); | |
225 REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0) | |
226 == 0); | |
227 REPORTER_ASSERT(r, codec->skipScanlines(1) | |
228 == 0); | |
229 | |
230 // Test partial scanline decodes | |
231 if (supports_partial_scanlines(path) && info.width() >= 3) { | |
232 SkCodec::Options options; | |
233 int width = info.width(); | |
234 int height = info.height(); | |
235 SkIRect subset = SkIRect::MakeXYWH(2 * (width / 3), 0, width / 3, he
ight); | |
236 options.fSubset = ⊂ | |
237 | |
238 const SkCodec::Result partialStartResult = codec->startScanlineDecod
e(info, &options, | |
239 nullptr, nullptr); | |
240 REPORTER_ASSERT(r, partialStartResult == SkCodec::kSuccess); | |
241 | |
242 for (int y = 0; y < height; y++) { | |
243 const int lines = codec->getScanlines(bm.getAddr(0, y), 1, 0); | |
244 if (!isIncomplete) { | |
245 REPORTER_ASSERT(r, 1 == lines); | |
246 } | |
247 } | |
248 } | |
249 } else { | |
250 REPORTER_ASSERT(r, startResult == SkCodec::kUnimplemented); | |
251 } | |
252 | |
253 // The rest of this function tests decoding subsets, and will decode an arbi
trary number of | |
254 // random subsets. | |
255 // Do not attempt to decode subsets of an image of only once pixel, since th
ere is no | |
256 // meaningful subset. | |
257 if (size.width() * size.height() == 1) { | |
258 return; | |
259 } | |
260 | |
261 SkRandom rand; | |
262 SkIRect subset; | |
263 SkCodec::Options opts; | |
264 opts.fSubset = ⊂ | |
265 for (int i = 0; i < 5; i++) { | |
266 subset = generate_random_subset(&rand, size.width(), size.height()); | |
267 SkASSERT(!subset.isEmpty()); | |
268 const bool supported = codec->getValidSubset(&subset); | |
269 REPORTER_ASSERT(r, supported == supportsSubsetDecoding); | |
270 | |
271 SkImageInfo subsetInfo = info.makeWH(subset.width(), subset.height()); | |
272 SkBitmap bm; | |
273 bm.allocPixels(subsetInfo); | |
274 const SkCodec::Result result = codec->getPixels(bm.info(), bm.getPixels(
), bm.rowBytes(), | |
275 &opts, nullptr, nullptr)
; | |
276 | |
277 if (supportsSubsetDecoding) { | |
278 REPORTER_ASSERT(r, result == expectedResult); | |
279 // Webp is the only codec that supports subsets, and it will have mo
dified the subset | |
280 // to have even left/top. | |
281 REPORTER_ASSERT(r, SkIsAlign2(subset.fLeft) && SkIsAlign2(subset.fTo
p)); | |
282 } else { | |
283 // No subsets will work. | |
284 REPORTER_ASSERT(r, result == SkCodec::kUnimplemented); | |
285 } | |
286 } | |
287 | |
288 // SkAndroidCodec tests | |
289 if (supportsScanlineDecoding || supportsSubsetDecoding) { | |
290 | |
291 SkAutoTDelete<SkStream> stream(resource(path)); | |
292 if (!stream) { | |
293 SkDebugf("Missing resource '%s'\n", path); | |
294 return; | |
295 } | |
296 | |
297 SkAutoTDelete<SkAndroidCodec> androidCodec(nullptr); | |
298 if (isIncomplete) { | |
299 size_t size = stream->getLength(); | |
300 SkAutoTUnref<SkData> data((SkData::NewFromStream(stream, 2 * size /
3))); | |
301 androidCodec.reset(SkAndroidCodec::NewFromData(data)); | |
302 } else { | |
303 androidCodec.reset(SkAndroidCodec::NewFromStream(stream.release())); | |
304 } | |
305 if (!androidCodec) { | |
306 ERRORF(r, "Unable to decode '%s'", path); | |
307 return; | |
308 } | |
309 | |
310 SkBitmap bm; | |
311 SkMD5::Digest androidCodecDigest; | |
312 test_codec(r, androidCodec.get(), bm, info, size, expectedResult, &andro
idCodecDigest, | |
313 &codecDigest); | |
314 } | |
315 | |
316 if (!isIncomplete) { | |
317 // Test SkCodecImageGenerator | |
318 SkAutoTDelete<SkStream> stream(resource(path)); | |
319 SkAutoTUnref<SkData> fullData(SkData::NewFromStream(stream, stream->getL
ength())); | |
320 SkAutoTDelete<SkImageGenerator> gen(SkCodecImageGenerator::NewFromEncode
dCodec(fullData)); | |
321 SkBitmap bm; | |
322 bm.allocPixels(info); | |
323 SkAutoLockPixels autoLockPixels(bm); | |
324 REPORTER_ASSERT(r, gen->getPixels(info, bm.getPixels(), bm.rowBytes())); | |
325 compare_to_good_digest(r, codecDigest, bm); | |
326 | |
327 // Test using SkFrontBufferedStream, as Android does | |
328 SkStream* bufferedStream = SkFrontBufferedStream::Create(new SkMemoryStr
eam(fullData), | |
329 SkCodec::MinBufferedBytesNeeded()); | |
330 REPORTER_ASSERT(r, bufferedStream); | |
331 codec.reset(SkCodec::NewFromStream(bufferedStream)); | |
332 REPORTER_ASSERT(r, codec); | |
333 if (codec) { | |
334 test_info(r, codec.get(), info, SkCodec::kSuccess, &codecDigest); | |
335 } | |
336 } | |
337 | |
338 // If we've just tested incomplete decodes, let's run the same test again on
full decodes. | |
339 if (isIncomplete) { | |
340 check(r, path, size, supportsScanlineDecoding, supportsSubsetDecoding, f
alse); | |
341 } | |
342 } | |
343 | |
344 DEF_TEST(Codec, r) { | |
345 // WBMP | |
346 check(r, "mandrill.wbmp", SkISize::Make(512, 512), true, false); | |
347 | |
348 // WEBP | |
349 check(r, "baby_tux.webp", SkISize::Make(386, 395), false, true); | |
350 check(r, "color_wheel.webp", SkISize::Make(128, 128), false, true); | |
351 check(r, "yellow_rose.webp", SkISize::Make(400, 301), false, true); | |
352 | |
353 // BMP | |
354 check(r, "randPixels.bmp", SkISize::Make(8, 8), true, false); | |
355 check(r, "rle.bmp", SkISize::Make(320, 240), true, false); | |
356 | |
357 // ICO | |
358 // FIXME: We are not ready to test incomplete ICOs | |
359 // These two tests examine interestingly different behavior: | |
360 // Decodes an embedded BMP image | |
361 check(r, "color_wheel.ico", SkISize::Make(128, 128), true, false, false); | |
362 // Decodes an embedded PNG image | |
363 check(r, "google_chrome.ico", SkISize::Make(256, 256), true, false, false); | |
364 | |
365 // GIF | |
366 // FIXME: We are not ready to test incomplete GIFs | |
367 check(r, "box.gif", SkISize::Make(200, 55), true, false, false); | |
368 check(r, "color_wheel.gif", SkISize::Make(128, 128), true, false, false); | |
369 // randPixels.gif is too small to test incomplete | |
370 check(r, "randPixels.gif", SkISize::Make(8, 8), true, false, false); | |
371 | |
372 // JPG | |
373 check(r, "CMYK.jpg", SkISize::Make(642, 516), true, false); | |
374 check(r, "color_wheel.jpg", SkISize::Make(128, 128), true, false); | |
375 // grayscale.jpg is too small to test incomplete | |
376 check(r, "grayscale.jpg", SkISize::Make(128, 128), true, false, false); | |
377 check(r, "mandrill_512_q075.jpg", SkISize::Make(512, 512), true, false); | |
378 // randPixels.jpg is too small to test incomplete | |
379 check(r, "randPixels.jpg", SkISize::Make(8, 8), true, false, false); | |
380 | |
381 // PNG | |
382 check(r, "arrow.png", SkISize::Make(187, 312), true, false, false); | |
383 check(r, "baby_tux.png", SkISize::Make(240, 246), true, false, false); | |
384 check(r, "color_wheel.png", SkISize::Make(128, 128), true, false, false); | |
385 check(r, "half-transparent-white-pixel.png", SkISize::Make(1, 1), true, fals
e, false); | |
386 check(r, "mandrill_128.png", SkISize::Make(128, 128), true, false, false); | |
387 check(r, "mandrill_16.png", SkISize::Make(16, 16), true, false, false); | |
388 check(r, "mandrill_256.png", SkISize::Make(256, 256), true, false, false); | |
389 check(r, "mandrill_32.png", SkISize::Make(32, 32), true, false, false); | |
390 check(r, "mandrill_512.png", SkISize::Make(512, 512), true, false, false); | |
391 check(r, "mandrill_64.png", SkISize::Make(64, 64), true, false, false); | |
392 check(r, "plane.png", SkISize::Make(250, 126), true, false, false); | |
393 // FIXME: We are not ready to test incomplete interlaced pngs | |
394 check(r, "plane_interlaced.png", SkISize::Make(250, 126), true, false, false
); | |
395 check(r, "randPixels.png", SkISize::Make(8, 8), true, false, false); | |
396 check(r, "yellow_rose.png", SkISize::Make(400, 301), true, false, false); | |
397 | |
398 // RAW | |
399 // Disable RAW tests for Win32. | |
400 #if defined(SK_CODEC_DECODES_RAW) && (!defined(_WIN32)) | |
401 check(r, "sample_1mp.dng", SkISize::Make(600, 338), false, false, false); | |
402 check(r, "sample_1mp_rotated.dng", SkISize::Make(600, 338), false, false, fa
lse); | |
403 check(r, "dng_with_preview.dng", SkISize::Make(600, 338), true, false, false
); | |
404 #endif | |
405 } | |
406 | |
407 // Test interlaced PNG in stripes, similar to DM's kStripe_Mode | |
408 DEF_TEST(Codec_stripes, r) { | |
409 const char * path = "plane_interlaced.png"; | |
410 SkAutoTDelete<SkStream> stream(resource(path)); | |
411 if (!stream) { | |
412 SkDebugf("Missing resource '%s'\n", path); | |
413 } | |
414 | |
415 SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream.release())); | |
416 REPORTER_ASSERT(r, codec); | |
417 | |
418 if (!codec) { | |
419 return; | |
420 } | |
421 | |
422 switch (codec->getScanlineOrder()) { | |
423 case SkCodec::kBottomUp_SkScanlineOrder: | |
424 case SkCodec::kOutOfOrder_SkScanlineOrder: | |
425 ERRORF(r, "This scanline order will not match the original."); | |
426 return; | |
427 default: | |
428 break; | |
429 } | |
430 | |
431 // Baseline for what the image should look like, using N32. | |
432 const SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType); | |
433 | |
434 SkBitmap bm; | |
435 bm.allocPixels(info); | |
436 SkAutoLockPixels autoLockPixels(bm); | |
437 SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes(
)); | |
438 REPORTER_ASSERT(r, result == SkCodec::kSuccess); | |
439 | |
440 SkMD5::Digest digest; | |
441 md5(bm, &digest); | |
442 | |
443 // Now decode in stripes | |
444 const int height = info.height(); | |
445 const int numStripes = 4; | |
446 int stripeHeight; | |
447 int remainingLines; | |
448 SkTDivMod(height, numStripes, &stripeHeight, &remainingLines); | |
449 | |
450 bm.eraseColor(SK_ColorYELLOW); | |
451 | |
452 result = codec->startScanlineDecode(info); | |
453 REPORTER_ASSERT(r, result == SkCodec::kSuccess); | |
454 | |
455 // Odd stripes | |
456 for (int i = 1; i < numStripes; i += 2) { | |
457 // Skip the even stripes | |
458 bool skipResult = codec->skipScanlines(stripeHeight); | |
459 REPORTER_ASSERT(r, skipResult); | |
460 | |
461 int linesDecoded = codec->getScanlines(bm.getAddr(0, i * stripeHeight),
stripeHeight, | |
462 bm.rowBytes()); | |
463 REPORTER_ASSERT(r, linesDecoded == stripeHeight); | |
464 } | |
465 | |
466 // Even stripes | |
467 result = codec->startScanlineDecode(info); | |
468 REPORTER_ASSERT(r, result == SkCodec::kSuccess); | |
469 | |
470 for (int i = 0; i < numStripes; i += 2) { | |
471 int linesDecoded = codec->getScanlines(bm.getAddr(0, i * stripeHeight),
stripeHeight, | |
472 bm.rowBytes()); | |
473 REPORTER_ASSERT(r, linesDecoded == stripeHeight); | |
474 | |
475 // Skip the odd stripes | |
476 if (i + 1 < numStripes) { | |
477 bool skipResult = codec->skipScanlines(stripeHeight); | |
478 REPORTER_ASSERT(r, skipResult); | |
479 } | |
480 } | |
481 | |
482 // Remainder at the end | |
483 if (remainingLines > 0) { | |
484 result = codec->startScanlineDecode(info); | |
485 REPORTER_ASSERT(r, result == SkCodec::kSuccess); | |
486 | |
487 bool skipResult = codec->skipScanlines(height - remainingLines); | |
488 REPORTER_ASSERT(r, skipResult); | |
489 | |
490 int linesDecoded = codec->getScanlines(bm.getAddr(0, height - remainingL
ines), | |
491 remainingLines, bm.rowBytes()); | |
492 REPORTER_ASSERT(r, linesDecoded == remainingLines); | |
493 } | |
494 | |
495 compare_to_good_digest(r, digest, bm); | |
496 } | |
497 | |
498 static void test_invalid_stream(skiatest::Reporter* r, const void* stream, size_
t len) { | |
499 // Neither of these calls should return a codec. Bots should catch us if we
leaked anything. | |
500 SkCodec* codec = SkCodec::NewFromStream(new SkMemoryStream(stream, len, fals
e)); | |
501 REPORTER_ASSERT(r, !codec); | |
502 | |
503 SkAndroidCodec* androidCodec = | |
504 SkAndroidCodec::NewFromStream(new SkMemoryStream(stream, len, false)
); | |
505 REPORTER_ASSERT(r, !androidCodec); | |
506 } | |
507 | |
508 // Ensure that SkCodec::NewFromStream handles freeing the passed in SkStream, | |
509 // even on failure. Test some bad streams. | |
510 DEF_TEST(Codec_leaks, r) { | |
511 // No codec should claim this as their format, so this tests SkCodec::NewFro
mStream. | |
512 const char nonSupportedStream[] = "hello world"; | |
513 // The other strings should look like the beginning of a file type, so we'll
call some | |
514 // internal version of NewFromStream, which must also delete the stream on f
ailure. | |
515 const unsigned char emptyPng[] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a,
0x0a }; | |
516 const unsigned char emptyJpeg[] = { 0xFF, 0xD8, 0xFF }; | |
517 const char emptyWebp[] = "RIFF1234WEBPVP"; | |
518 const char emptyBmp[] = { 'B', 'M' }; | |
519 const char emptyIco[] = { '\x00', '\x00', '\x01', '\x00' }; | |
520 const char emptyGif[] = "GIFVER"; | |
521 | |
522 test_invalid_stream(r, nonSupportedStream, sizeof(nonSupportedStream)); | |
523 test_invalid_stream(r, emptyPng, sizeof(emptyPng)); | |
524 test_invalid_stream(r, emptyJpeg, sizeof(emptyJpeg)); | |
525 test_invalid_stream(r, emptyWebp, sizeof(emptyWebp)); | |
526 test_invalid_stream(r, emptyBmp, sizeof(emptyBmp)); | |
527 test_invalid_stream(r, emptyIco, sizeof(emptyIco)); | |
528 test_invalid_stream(r, emptyGif, sizeof(emptyGif)); | |
529 } | |
530 | |
531 DEF_TEST(Codec_null, r) { | |
532 // Attempting to create an SkCodec or an SkAndroidCodec with null should not | |
533 // crash. | |
534 SkCodec* codec = SkCodec::NewFromStream(nullptr); | |
535 REPORTER_ASSERT(r, !codec); | |
536 | |
537 SkAndroidCodec* androidCodec = SkAndroidCodec::NewFromStream(nullptr); | |
538 REPORTER_ASSERT(r, !androidCodec); | |
539 } | |
540 | |
541 static void test_dimensions(skiatest::Reporter* r, const char path[]) { | |
542 // Create the codec from the resource file | |
543 SkAutoTDelete<SkStream> stream(resource(path)); | |
544 if (!stream) { | |
545 SkDebugf("Missing resource '%s'\n", path); | |
546 return; | |
547 } | |
548 SkAutoTDelete<SkAndroidCodec> codec(SkAndroidCodec::NewFromStream(stream.rel
ease())); | |
549 if (!codec) { | |
550 ERRORF(r, "Unable to create codec '%s'", path); | |
551 return; | |
552 } | |
553 | |
554 // Check that the decode is successful for a variety of scales | |
555 for (int sampleSize = 1; sampleSize < 32; sampleSize++) { | |
556 // Scale the output dimensions | |
557 SkISize scaledDims = codec->getSampledDimensions(sampleSize); | |
558 SkImageInfo scaledInfo = codec->getInfo() | |
559 .makeWH(scaledDims.width(), scaledDims.height()) | |
560 .makeColorType(kN32_SkColorType); | |
561 | |
562 // Set up for the decode | |
563 size_t rowBytes = scaledDims.width() * sizeof(SkPMColor); | |
564 size_t totalBytes = scaledInfo.getSafeSize(rowBytes); | |
565 SkAutoTMalloc<SkPMColor> pixels(totalBytes); | |
566 | |
567 SkAndroidCodec::AndroidOptions options; | |
568 options.fSampleSize = sampleSize; | |
569 SkCodec::Result result = | |
570 codec->getAndroidPixels(scaledInfo, pixels.get(), rowBytes, &opt
ions); | |
571 REPORTER_ASSERT(r, SkCodec::kSuccess == result); | |
572 } | |
573 } | |
574 | |
575 // Ensure that onGetScaledDimensions returns valid image dimensions to use for d
ecodes | |
576 DEF_TEST(Codec_Dimensions, r) { | |
577 // JPG | |
578 test_dimensions(r, "CMYK.jpg"); | |
579 test_dimensions(r, "color_wheel.jpg"); | |
580 test_dimensions(r, "grayscale.jpg"); | |
581 test_dimensions(r, "mandrill_512_q075.jpg"); | |
582 test_dimensions(r, "randPixels.jpg"); | |
583 | |
584 // Decoding small images with very large scaling factors is a potential | |
585 // source of bugs and crashes. We disable these tests in Gold because | |
586 // tiny images are not very useful to look at. | |
587 // Here we make sure that we do not crash or access illegal memory when | |
588 // performing scaled decodes on small images. | |
589 test_dimensions(r, "1x1.png"); | |
590 test_dimensions(r, "2x2.png"); | |
591 test_dimensions(r, "3x3.png"); | |
592 test_dimensions(r, "3x1.png"); | |
593 test_dimensions(r, "1x1.png"); | |
594 test_dimensions(r, "16x1.png"); | |
595 test_dimensions(r, "1x16.png"); | |
596 test_dimensions(r, "mandrill_16.png"); | |
597 | |
598 // RAW | |
599 // Disable RAW tests for Win32. | |
600 #if defined(SK_CODEC_DECODES_RAW) && (!defined(_WIN32)) | |
601 test_dimensions(r, "sample_1mp.dng"); | |
602 test_dimensions(r, "sample_1mp_rotated.dng"); | |
603 test_dimensions(r, "dng_with_preview.dng"); | |
604 #endif | |
605 } | |
606 | |
607 static void test_invalid(skiatest::Reporter* r, const char path[]) { | |
608 SkAutoTDelete<SkStream> stream(resource(path)); | |
609 if (!stream) { | |
610 SkDebugf("Missing resource '%s'\n", path); | |
611 return; | |
612 } | |
613 SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream.release())); | |
614 REPORTER_ASSERT(r, nullptr == codec); | |
615 } | |
616 | |
617 DEF_TEST(Codec_Empty, r) { | |
618 // Test images that should not be able to create a codec | |
619 test_invalid(r, "empty_images/zero-dims.gif"); | |
620 test_invalid(r, "empty_images/zero-embedded.ico"); | |
621 test_invalid(r, "empty_images/zero-width.bmp"); | |
622 test_invalid(r, "empty_images/zero-height.bmp"); | |
623 test_invalid(r, "empty_images/zero-width.jpg"); | |
624 test_invalid(r, "empty_images/zero-height.jpg"); | |
625 test_invalid(r, "empty_images/zero-width.png"); | |
626 test_invalid(r, "empty_images/zero-height.png"); | |
627 test_invalid(r, "empty_images/zero-width.wbmp"); | |
628 test_invalid(r, "empty_images/zero-height.wbmp"); | |
629 // This image is an ico with an embedded mask-bmp. This is illegal. | |
630 test_invalid(r, "invalid_images/mask-bmp-ico.ico"); | |
631 } | |
632 | |
633 static void test_invalid_parameters(skiatest::Reporter* r, const char path[]) { | |
634 SkAutoTDelete<SkStream> stream(resource(path)); | |
635 if (!stream) { | |
636 SkDebugf("Missing resource '%s'\n", path); | |
637 return; | |
638 } | |
639 SkAutoTDelete<SkCodec> decoder(SkCodec::NewFromStream(stream.release())); | |
640 | |
641 // This should return kSuccess because kIndex8 is supported. | |
642 SkPMColor colorStorage[256]; | |
643 int colorCount; | |
644 SkCodec::Result result = decoder->startScanlineDecode( | |
645 decoder->getInfo().makeColorType(kIndex_8_SkColorType), nullptr, colorSt
orage, &colorCount); | |
646 REPORTER_ASSERT(r, SkCodec::kSuccess == result); | |
647 // The rest of the test is uninteresting if kIndex8 is not supported | |
648 if (SkCodec::kSuccess != result) { | |
649 return; | |
650 } | |
651 | |
652 // This should return kInvalidParameters because, in kIndex_8 mode, we must
pass in a valid | |
653 // colorPtr and a valid colorCountPtr. | |
654 result = decoder->startScanlineDecode( | |
655 decoder->getInfo().makeColorType(kIndex_8_SkColorType), nullptr, nullptr
, nullptr); | |
656 REPORTER_ASSERT(r, SkCodec::kInvalidParameters == result); | |
657 result = decoder->startScanlineDecode( | |
658 decoder->getInfo().makeColorType(kIndex_8_SkColorType)); | |
659 REPORTER_ASSERT(r, SkCodec::kInvalidParameters == result); | |
660 } | |
661 | |
662 DEF_TEST(Codec_Params, r) { | |
663 test_invalid_parameters(r, "index8.png"); | |
664 test_invalid_parameters(r, "mandrill.wbmp"); | |
665 } | |
666 | |
667 static void codex_test_write_fn(png_structp png_ptr, png_bytep data, png_size_t
len) { | |
668 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr); | |
669 if (!sk_stream->write(data, len)) { | |
670 png_error(png_ptr, "sk_write_fn Error!"); | |
671 } | |
672 } | |
673 | |
674 #ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED | |
675 DEF_TEST(Codec_pngChunkReader, r) { | |
676 // Create a dummy bitmap. Use unpremul RGBA for libpng. | |
677 SkBitmap bm; | |
678 const int w = 1; | |
679 const int h = 1; | |
680 const SkImageInfo bmInfo = SkImageInfo::Make(w, h, kRGBA_8888_SkColorType, | |
681 kUnpremul_SkAlphaType); | |
682 bm.setInfo(bmInfo); | |
683 bm.allocPixels(); | |
684 bm.eraseColor(SK_ColorBLUE); | |
685 SkMD5::Digest goodDigest; | |
686 md5(bm, &goodDigest); | |
687 | |
688 // Write to a png file. | |
689 png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nu
llptr, nullptr); | |
690 REPORTER_ASSERT(r, png); | |
691 if (!png) { | |
692 return; | |
693 } | |
694 | |
695 png_infop info = png_create_info_struct(png); | |
696 REPORTER_ASSERT(r, info); | |
697 if (!info) { | |
698 png_destroy_write_struct(&png, nullptr); | |
699 return; | |
700 } | |
701 | |
702 if (setjmp(png_jmpbuf(png))) { | |
703 ERRORF(r, "failed writing png"); | |
704 png_destroy_write_struct(&png, &info); | |
705 return; | |
706 } | |
707 | |
708 SkDynamicMemoryWStream wStream; | |
709 png_set_write_fn(png, (void*) (&wStream), codex_test_write_fn, nullptr); | |
710 | |
711 png_set_IHDR(png, info, (png_uint_32)w, (png_uint_32)h, 8, | |
712 PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, | |
713 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); | |
714 | |
715 // Create some chunks that match the Android framework's use. | |
716 static png_unknown_chunk gUnknowns[] = { | |
717 { "npOl", (png_byte*)"outline", sizeof("outline"), PNG_HAVE_IHDR }, | |
718 { "npLb", (png_byte*)"layoutBounds", sizeof("layoutBounds"), PNG_HAVE_IH
DR }, | |
719 { "npTc", (png_byte*)"ninePatchData", sizeof("ninePatchData"), PNG_HAVE_
IHDR }, | |
720 }; | |
721 | |
722 png_set_keep_unknown_chunks(png, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"npOl\0
npLb\0npTc\0", 3); | |
723 png_set_unknown_chunks(png, info, gUnknowns, SK_ARRAY_COUNT(gUnknowns)); | |
724 #if PNG_LIBPNG_VER < 10600 | |
725 /* Deal with unknown chunk location bug in 1.5.x and earlier */ | |
726 png_set_unknown_chunk_location(png, info, 0, PNG_HAVE_IHDR); | |
727 png_set_unknown_chunk_location(png, info, 1, PNG_HAVE_IHDR); | |
728 #endif | |
729 | |
730 png_write_info(png, info); | |
731 | |
732 for (int j = 0; j < h; j++) { | |
733 png_bytep row = (png_bytep)(bm.getAddr(0, j)); | |
734 png_write_rows(png, &row, 1); | |
735 } | |
736 png_write_end(png, info); | |
737 png_destroy_write_struct(&png, &info); | |
738 | |
739 class ChunkReader : public SkPngChunkReader { | |
740 public: | |
741 ChunkReader(skiatest::Reporter* r) | |
742 : fReporter(r) | |
743 { | |
744 this->reset(); | |
745 } | |
746 | |
747 bool readChunk(const char tag[], const void* data, size_t length) overri
de { | |
748 for (size_t i = 0; i < SK_ARRAY_COUNT(gUnknowns); ++i) { | |
749 if (!strcmp(tag, (const char*) gUnknowns[i].name)) { | |
750 // Tag matches. This should have been the first time we see
it. | |
751 REPORTER_ASSERT(fReporter, !fSeen[i]); | |
752 fSeen[i] = true; | |
753 | |
754 // Data and length should match | |
755 REPORTER_ASSERT(fReporter, length == gUnknowns[i].size); | |
756 REPORTER_ASSERT(fReporter, !strcmp((const char*) data, | |
757 (const char*) gUnknowns[i
].data)); | |
758 return true; | |
759 } | |
760 } | |
761 ERRORF(fReporter, "Saw an unexpected unknown chunk."); | |
762 return true; | |
763 } | |
764 | |
765 bool allHaveBeenSeen() { | |
766 bool ret = true; | |
767 for (auto seen : fSeen) { | |
768 ret &= seen; | |
769 } | |
770 return ret; | |
771 } | |
772 | |
773 void reset() { | |
774 sk_bzero(fSeen, sizeof(fSeen)); | |
775 } | |
776 | |
777 private: | |
778 skiatest::Reporter* fReporter; // Unowned | |
779 bool fSeen[3]; | |
780 }; | |
781 | |
782 ChunkReader chunkReader(r); | |
783 | |
784 // Now read the file with SkCodec. | |
785 SkAutoTUnref<SkData> data(wStream.copyToData()); | |
786 SkAutoTDelete<SkCodec> codec(SkCodec::NewFromData(data, &chunkReader)); | |
787 REPORTER_ASSERT(r, codec); | |
788 if (!codec) { | |
789 return; | |
790 } | |
791 | |
792 // Now compare to the original. | |
793 SkBitmap decodedBm; | |
794 decodedBm.setInfo(codec->getInfo()); | |
795 decodedBm.allocPixels(); | |
796 SkCodec::Result result = codec->getPixels(codec->getInfo(), decodedBm.getPix
els(), | |
797 decodedBm.rowBytes()); | |
798 REPORTER_ASSERT(r, SkCodec::kSuccess == result); | |
799 | |
800 if (decodedBm.colorType() != bm.colorType()) { | |
801 SkBitmap tmp; | |
802 bool success = decodedBm.copyTo(&tmp, bm.colorType()); | |
803 REPORTER_ASSERT(r, success); | |
804 if (!success) { | |
805 return; | |
806 } | |
807 | |
808 tmp.swap(decodedBm); | |
809 } | |
810 | |
811 compare_to_good_digest(r, goodDigest, decodedBm); | |
812 REPORTER_ASSERT(r, chunkReader.allHaveBeenSeen()); | |
813 | |
814 // Decoding again will read the chunks again. | |
815 chunkReader.reset(); | |
816 REPORTER_ASSERT(r, !chunkReader.allHaveBeenSeen()); | |
817 result = codec->getPixels(codec->getInfo(), decodedBm.getPixels(), decodedBm
.rowBytes()); | |
818 REPORTER_ASSERT(r, SkCodec::kSuccess == result); | |
819 REPORTER_ASSERT(r, chunkReader.allHaveBeenSeen()); | |
820 } | |
821 #endif // PNG_READ_UNKNOWN_CHUNKS_SUPPORTED | |
822 | |
823 // Stream that can only peek up to a limit | |
824 class LimitedPeekingMemStream : public SkStream { | |
825 public: | |
826 LimitedPeekingMemStream(SkData* data, size_t limit) | |
827 : fStream(data) | |
828 , fLimit(limit) {} | |
829 | |
830 size_t peek(void* buf, size_t bytes) const override { | |
831 return fStream.peek(buf, SkTMin(bytes, fLimit)); | |
832 } | |
833 size_t read(void* buf, size_t bytes) override { | |
834 return fStream.read(buf, bytes); | |
835 } | |
836 bool rewind() override { | |
837 return fStream.rewind(); | |
838 } | |
839 bool isAtEnd() const override { | |
840 return false; | |
841 } | |
842 private: | |
843 SkMemoryStream fStream; | |
844 const size_t fLimit; | |
845 }; | |
846 | |
847 // Stream that is not an asset stream (!hasPosition() or !hasLength()) | |
848 class NotAssetMemStream : public SkStream { | |
849 public: | |
850 NotAssetMemStream(SkData* data) : fStream(data) {} | |
851 | |
852 bool hasPosition() const override { | |
853 return false; | |
854 } | |
855 | |
856 bool hasLength() const override { | |
857 return false; | |
858 } | |
859 | |
860 size_t peek(void* buf, size_t bytes) const override { | |
861 return fStream.peek(buf, bytes); | |
862 } | |
863 size_t read(void* buf, size_t bytes) override { | |
864 return fStream.read(buf, bytes); | |
865 } | |
866 bool rewind() override { | |
867 return fStream.rewind(); | |
868 } | |
869 bool isAtEnd() const override { | |
870 return fStream.isAtEnd(); | |
871 } | |
872 private: | |
873 SkMemoryStream fStream; | |
874 }; | |
875 | |
876 // Disable RAW tests for Win32. | |
877 #if defined(SK_CODEC_DECODES_RAW) && (!defined(_WIN32)) | |
878 // Test that the RawCodec works also for not asset stream. This will test the co
de path using | |
879 // SkRawBufferedStream instead of SkRawAssetStream. | |
880 DEF_TEST(Codec_raw_notseekable, r) { | |
881 const char* path = "dng_with_preview.dng"; | |
882 SkString fullPath(GetResourcePath(path)); | |
883 SkAutoTUnref<SkData> data(SkData::NewFromFileName(fullPath.c_str())); | |
884 if (!data) { | |
885 SkDebugf("Missing resource '%s'\n", path); | |
886 return; | |
887 } | |
888 | |
889 SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(new NotAssetMemStream(da
ta))); | |
890 REPORTER_ASSERT(r, codec); | |
891 | |
892 test_info(r, codec.get(), codec->getInfo(), SkCodec::kSuccess, nullptr); | |
893 } | |
894 #endif | |
895 | |
896 // Test that even if webp_parse_header fails to peek enough, it will fall back t
o read() | |
897 // + rewind() and succeed. | |
898 DEF_TEST(Codec_webp_peek, r) { | |
899 const char* path = "baby_tux.webp"; | |
900 SkString fullPath(GetResourcePath(path)); | |
901 auto data = SkData::MakeFromFileName(fullPath.c_str()); | |
902 if (!data) { | |
903 SkDebugf("Missing resource '%s'\n", path); | |
904 return; | |
905 } | |
906 | |
907 // The limit is less than webp needs to peek or read. | |
908 SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream( | |
909 new LimitedPeekingMemStream(data.get(), 25))); | |
910 REPORTER_ASSERT(r, codec); | |
911 | |
912 test_info(r, codec.get(), codec->getInfo(), SkCodec::kSuccess, nullptr); | |
913 | |
914 // Similarly, a stream which does not peek should still succeed. | |
915 codec.reset(SkCodec::NewFromStream(new LimitedPeekingMemStream(data.get(), 0
))); | |
916 REPORTER_ASSERT(r, codec); | |
917 | |
918 test_info(r, codec.get(), codec->getInfo(), SkCodec::kSuccess, nullptr); | |
919 } | |
920 | |
921 // SkCodec's wbmp decoder was initially unnecessarily restrictive. | |
922 // It required the second byte to be zero. The wbmp specification allows | |
923 // a couple of bits to be 1 (so long as they do not overlap with 0x9F). | |
924 // Test that SkCodec now supports an image with these bits set. | |
925 DEF_TEST(Codec_wbmp, r) { | |
926 const char* path = "mandrill.wbmp"; | |
927 SkAutoTDelete<SkStream> stream(resource(path)); | |
928 if (!stream) { | |
929 SkDebugf("Missing resource '%s'\n", path); | |
930 return; | |
931 } | |
932 | |
933 // Modify the stream to contain a second byte with some bits set. | |
934 auto data = SkCopyStreamToData(stream); | |
935 uint8_t* writeableData = static_cast<uint8_t*>(data->writable_data()); | |
936 writeableData[1] = static_cast<uint8_t>(~0x9F); | |
937 | |
938 // SkCodec should support this. | |
939 SkAutoTDelete<SkCodec> codec(SkCodec::NewFromData(data.get())); | |
940 REPORTER_ASSERT(r, codec); | |
941 if (!codec) { | |
942 return; | |
943 } | |
944 test_info(r, codec.get(), codec->getInfo(), SkCodec::kSuccess, nullptr); | |
945 } | |
946 | |
947 // wbmp images have a header that can be arbitrarily large, depending on the | |
948 // size of the image. We cap the size at 65535, meaning we only need to look at | |
949 // 8 bytes to determine whether we can read the image. This is important | |
950 // because SkCodec only passes 14 bytes to SkWbmpCodec to determine whether the | |
951 // image is a wbmp. | |
952 DEF_TEST(Codec_wbmp_max_size, r) { | |
953 const unsigned char maxSizeWbmp[] = { 0x00, 0x00, // Header | |
954 0x83, 0xFF, 0x7F, // W: 65535 | |
955 0x83, 0xFF, 0x7F }; // H: 65535 | |
956 SkAutoTDelete<SkStream> stream(new SkMemoryStream(maxSizeWbmp, sizeof(maxSiz
eWbmp), false)); | |
957 SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream.release())); | |
958 | |
959 REPORTER_ASSERT(r, codec); | |
960 if (!codec) return; | |
961 | |
962 REPORTER_ASSERT(r, codec->getInfo().width() == 65535); | |
963 REPORTER_ASSERT(r, codec->getInfo().height() == 65535); | |
964 | |
965 // Now test an image which is too big. Any image with a larger header (i.e. | |
966 // has bigger width/height) is also too big. | |
967 const unsigned char tooBigWbmp[] = { 0x00, 0x00, // Header | |
968 0x84, 0x80, 0x00, // W: 65536 | |
969 0x84, 0x80, 0x00 }; // H: 65536 | |
970 stream.reset(new SkMemoryStream(tooBigWbmp, sizeof(tooBigWbmp), false)); | |
971 codec.reset(SkCodec::NewFromStream(stream.release())); | |
972 | |
973 REPORTER_ASSERT(r, !codec); | |
974 } | |
OLD | NEW |