| 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 |