Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "platform/image-decoders/png/PNGImageDecoder.h" | |
| 6 | |
| 7 #include "png.h" | |
| 8 #include "platform/image-decoders/ImageDecoderTestHelpers.h" | |
| 9 #include "testing/gtest/include/gtest/gtest.h" | |
| 10 #include <memory> | |
| 11 | |
| 12 // The image /LT/f/i/r/png-animated-idat-part-of-animation.png is modified in | |
|
scroggo_chromium
2016/11/29 16:30:52
Is this abbreviation standard? If not, I think it
joostouwerling
2016/12/02 16:08:42
Vim uses this way of abbreviation, but besides tha
| |
| 13 // multiple tests to simulate erroneous PNGs. As a reference, the table below | |
| 14 // shows how the file is structured. | |
| 15 // | |
| 16 // Offset | 8 33 95 133 172 210 241 279 314 352 422 | |
| 17 // ------------------------------------------------------------------------- | |
| 18 // Chunk | IHDR acTL fcTL IDAT fcTL fdAT fcTL fdAT fcTL fdAT IEND | |
| 19 // | |
| 20 // In between the acTL and fcTL there are two other chunks, PLTE and tRNS, but | |
| 21 // those are not specifically used in this test suite. The same holds for a | |
| 22 // tEXT chunk in between the last fdAT and IEND. | |
| 23 // | |
| 24 // In the current behavior of PNG image decoders, the 4 frames are detected when | |
| 25 // respectively 141, 249, 322 and 430 bytes are received. The first frame should | |
| 26 // be detected when the IDAT has been received, and non-first frames when the | |
| 27 // next fcTL or IEND chunk has been received. Note that all offsets are +8, | |
| 28 // because a chunk is identified by byte 4-7. | |
| 29 | |
| 30 // @TODO(joostouwerling) verify IEND before IDAT fails the decode. | |
| 31 // @TODO(joostouwerling) extend test image set with other image encodings, such | |
| 32 // as first frame fcTL and multiple chunks per frame. | |
| 33 | |
| 34 namespace blink { | |
| 35 | |
| 36 namespace { | |
| 37 | |
| 38 std::unique_ptr<ImageDecoder> createDecoder(ImageDecoder::AlphaOption alphaOptio n) | |
| 39 { | |
| 40 return wrapUnique(new PNGImageDecoder(alphaOption, | |
| 41 ImageDecoder::ColorSpaceApplied, | |
| 42 ImageDecoder::noDecodedImageByteLimit)); | |
| 43 } | |
| 44 | |
| 45 std::unique_ptr<ImageDecoder> createDecoder() | |
| 46 { | |
| 47 return createDecoder(ImageDecoder::AlphaNotPremultiplied); | |
| 48 } | |
| 49 | |
| 50 std::unique_ptr<ImageDecoder> createDecoderWithPngData(const char* pngFile) | |
| 51 { | |
| 52 auto decoder = createDecoder(); | |
| 53 auto data = readFile(pngFile); | |
| 54 EXPECT_FALSE(data->isEmpty()); | |
| 55 decoder->setData(data.get(), true); | |
| 56 return decoder; | |
| 57 } | |
| 58 | |
| 59 void testSize(const char* pngFile, IntSize expectedSize) | |
| 60 { | |
| 61 auto decoder = createDecoderWithPngData(pngFile); | |
| 62 EXPECT_TRUE(decoder->isSizeAvailable()); | |
| 63 EXPECT_EQ(expectedSize, decoder->size()); | |
| 64 } | |
| 65 | |
| 66 void writeUint32(uint32_t val, png_byte* data) | |
| 67 { | |
| 68 data[0] = val >> 24; | |
| 69 data[1] = val >> 16; | |
| 70 data[2] = val >> 8; | |
| 71 data[3] = val; | |
| 72 } | |
| 73 | |
| 74 void testRepetitionCount(const char* pngFile, int expectedRepetitionCount) | |
| 75 { | |
| 76 auto decoder = createDecoderWithPngData(pngFile); | |
| 77 // Decode frame count should see the number of repetitions as well. | |
| 78 decoder->frameCount(); | |
| 79 EXPECT_FALSE(decoder->failed()); | |
| 80 EXPECT_EQ(expectedRepetitionCount, decoder->repetitionCount()); | |
| 81 } | |
| 82 | |
| 83 // Test whether querying for the size of the image works if we present the | |
| 84 // data byte by byte. | |
| 85 void testSizeByteByByte(const char *pngFile, size_t bytesNeededToDecodeSize, | |
| 86 IntSize expectedSize) | |
| 87 { | |
| 88 auto decoder = createDecoder(); | |
| 89 auto data = readFile(pngFile); | |
| 90 ASSERT_FALSE(data->isEmpty()); | |
| 91 ASSERT_LT(bytesNeededToDecodeSize, data->size()); | |
| 92 | |
| 93 for (size_t length = 1; length <= bytesNeededToDecodeSize; length++) { | |
| 94 RefPtr<SharedBuffer> tempData = SharedBuffer::create(data->data(), | |
| 95 length); | |
| 96 decoder->setData(tempData.get(), false); | |
| 97 | |
| 98 if (length < bytesNeededToDecodeSize) { | |
| 99 EXPECT_FALSE(decoder->isSizeAvailable()); | |
| 100 EXPECT_TRUE(decoder->size().isEmpty()); | |
| 101 EXPECT_FALSE(decoder->failed()); | |
| 102 } else { | |
| 103 EXPECT_TRUE(decoder->isSizeAvailable()); | |
| 104 EXPECT_EQ(expectedSize, decoder->size()); | |
| 105 } | |
| 106 } | |
| 107 EXPECT_FALSE(decoder->failed()); | |
| 108 } | |
| 109 | |
| 110 struct PublicFrameInfo { | |
| 111 size_t duration; | |
| 112 IntRect frameRect; | |
| 113 ImageFrame::AlphaBlendSource alphaBlend; | |
| 114 ImageFrame::DisposalMethod disposalMethod; | |
| 115 }; | |
| 116 | |
| 117 // This is the frame data for the following PNG image: | |
| 118 // /LayoutTests/fast/images/resources/png-animated-idat-part-of-animation.png | |
| 119 static PublicFrameInfo pngAnimatedFrameInfo[] = { | |
| 120 {500, {IntPoint(0, 0), IntSize(5, 5)}, ImageFrame::BlendAtopBgcolor, | |
| 121 ImageFrame::DisposeKeep}, | |
| 122 {900, {IntPoint(1, 1), IntSize(3, 1)}, ImageFrame::BlendAtopBgcolor, | |
| 123 ImageFrame::DisposeOverwriteBgcolor}, | |
| 124 {2000, {IntPoint(1, 2), IntSize(3, 2)}, ImageFrame::BlendAtopPreviousFrame, | |
| 125 ImageFrame::DisposeKeep}, | |
| 126 {1500, {IntPoint(1, 2), IntSize(3, 1)}, ImageFrame::BlendAtopBgcolor, | |
| 127 ImageFrame::DisposeKeep}, | |
| 128 }; | |
| 129 | |
| 130 void compareFrameWithExpectation(const PublicFrameInfo& expected, | |
| 131 const ImageFrame* frame) | |
| 132 { | |
| 133 EXPECT_EQ(expected.duration, frame->duration()); | |
| 134 EXPECT_EQ(expected.frameRect, frame->originalFrameRect()); | |
| 135 EXPECT_EQ(expected.disposalMethod, frame->getDisposalMethod()); | |
| 136 EXPECT_EQ(expected.alphaBlend, frame->getAlphaBlendSource()); | |
| 137 } | |
| 138 | |
| 139 // This function removes |length| bytes at |offset|, and then calls frameCount. | |
| 140 // It assumes the missing bytes should result in a failed decode because the | |
| 141 // parser jumps |length| bytes too far in the next chunk. | |
| 142 void testMissingDataBreaksDecoding(const char* pngFile, size_t offset, | |
| 143 size_t length) | |
| 144 { | |
| 145 auto decoder = createDecoder(); | |
| 146 auto data = readFile(pngFile); | |
| 147 ASSERT_FALSE(data->isEmpty()); | |
| 148 | |
| 149 RefPtr<SharedBuffer> invalidData = SharedBuffer::create(data->data(), | |
| 150 offset); | |
| 151 invalidData->append(SharedBuffer::create(data->data() + offset + length, | |
| 152 data->size() - offset - length)); | |
| 153 ASSERT_EQ(data->size() - length, invalidData->size()); | |
| 154 | |
| 155 decoder->setData(invalidData, true); | |
| 156 decoder->frameCount(); | |
| 157 EXPECT_TRUE(decoder->failed()); | |
| 158 } | |
| 159 | |
| 160 // Decoding up to the indicated fcTL offset and then provide an fcTL with | |
| 161 // the wrong chunk size (20 instead of 26). It should break the decoder. | |
| 162 void testInvalidFctlSize(const char* pngFile, | |
| 163 size_t offsetFctl, | |
| 164 size_t expectedFrameCountBeforeFail, | |
| 165 bool expectedDecoderFailure) | |
| 166 { | |
| 167 auto data = readFile(pngFile); | |
| 168 ASSERT_FALSE(data->isEmpty()); | |
| 169 | |
| 170 auto decoder = createDecoder(); | |
| 171 RefPtr<SharedBuffer> invalidData = SharedBuffer::create(data->data(), | |
| 172 offsetFctl); | |
| 173 | |
| 174 // Test if this gives the correct frame count, before the fcTL is parsed. | |
| 175 decoder->setData(invalidData, false); | |
| 176 EXPECT_EQ(expectedFrameCountBeforeFail, decoder->frameCount()); | |
| 177 ASSERT_FALSE(decoder->failed()); | |
| 178 | |
| 179 // Append the wrong size to the data stream | |
| 180 png_byte sizeChunk[4]; | |
| 181 writeUint32(20, sizeChunk); | |
| 182 invalidData->append(reinterpret_cast<char*>(sizeChunk), 4u); | |
| 183 | |
| 184 // Skip the size in the original data, but provide the rest of the fcTL, | |
| 185 // which is 4B of tag, 26B of data and 4B of CRC, totalling 34B. | |
| 186 invalidData->append(data->data() + offsetFctl + 4, 34u); | |
| 187 | |
| 188 decoder->setData(invalidData, false); | |
| 189 decoder->frameCount(); | |
| 190 EXPECT_EQ(expectedDecoderFailure, decoder->failed()); | |
| 191 } | |
| 192 | |
| 193 void testDifferentActlFrameCountIsIgnored(const char* pngFile, | |
| 194 size_t offsetActl, | |
| 195 size_t injectedFrameCount, | |
| 196 size_t expectedFrameCount) | |
| 197 { | |
| 198 // First make sure that this tests makes sense. | |
| 199 ASSERT_NE(injectedFrameCount, expectedFrameCount); | |
| 200 | |
| 201 auto data = readFile(pngFile); | |
| 202 auto decoder = createDecoder(); | |
| 203 ASSERT_FALSE(data->isEmpty()); | |
| 204 | |
| 205 RefPtr<SharedBuffer> diffActlData = SharedBuffer::create(data->data(), | |
| 206 offsetActl + 8); | |
| 207 // Write the injectedFrameCount to the stream | |
| 208 png_byte sizeChunk[4]; | |
| 209 writeUint32(injectedFrameCount, sizeChunk); | |
| 210 diffActlData->append(reinterpret_cast<char*>(sizeChunk), 4u); | |
| 211 // Append the rest of the data. The first |offsetActl + 12| bytes that are | |
| 212 // already in diffActlData should not be appended again. | |
| 213 diffActlData->append(data->data() + offsetActl + 12, | |
| 214 data->size() - offsetActl - 12); | |
| 215 | |
| 216 decoder->setData(diffActlData, true); | |
| 217 EXPECT_EQ(expectedFrameCount, decoder->frameCount()); | |
| 218 } | |
| 219 | |
| 220 // Test if the frame bitmap hashes of truncated decoding are equal to the | |
| 221 // hashes found by incremental decoding. | |
| 222 void testProgressiveDecoding(const char *pngFile) | |
| 223 { | |
| 224 RefPtr<SharedBuffer> fullData = readFile(pngFile); | |
| 225 ASSERT_TRUE(fullData.get()); | |
| 226 const size_t fullLength = fullData->size(); | |
| 227 | |
| 228 std::unique_ptr<ImageDecoder> decoder; | |
| 229 ImageFrame* frame; | |
| 230 | |
| 231 Vector<unsigned> truncatedHashes; | |
| 232 Vector<unsigned> progressiveHashes; | |
| 233 | |
| 234 // Compute hashes when the file is truncated. | |
| 235 const size_t increment = 13; | |
| 236 for (size_t i = 1; i <= fullLength; i += increment) { | |
| 237 decoder = createDecoder(); | |
| 238 RefPtr<SharedBuffer> data = SharedBuffer::create(fullData->data(), i); | |
| 239 decoder->setData(data.get(), i == fullLength); | |
| 240 size_t frameCount = decoder->frameCount(); | |
| 241 ASSERT_FALSE(decoder->failed()); | |
| 242 if (frameCount == 0) { | |
| 243 truncatedHashes.append(0); | |
| 244 continue; | |
| 245 } | |
| 246 frame = decoder->frameBufferAtIndex(frameCount - 1); | |
| 247 if (!frame) { | |
| 248 truncatedHashes.append(0); | |
| 249 continue; | |
| 250 } | |
| 251 truncatedHashes.append(hashBitmap(frame->bitmap())); | |
| 252 } | |
| 253 | |
| 254 // Compute hashes when the file is progressively decoded. | |
| 255 decoder = createDecoder(); | |
| 256 for (size_t i = 1; i <= fullLength; i += increment) { | |
| 257 RefPtr<SharedBuffer> data = SharedBuffer::create(fullData->data(), i); | |
| 258 decoder->setData(data.get(), i == fullLength); | |
| 259 ASSERT_FALSE(decoder->failed()); | |
| 260 size_t frameCount = decoder->frameCount(); | |
| 261 if (frameCount == 0) { | |
| 262 progressiveHashes.append(0); | |
| 263 continue; | |
| 264 } | |
| 265 frame = decoder->frameBufferAtIndex(frameCount - 1); | |
| 266 if (!frame) { | |
| 267 progressiveHashes.append(0); | |
| 268 continue; | |
| 269 } | |
| 270 progressiveHashes.append(hashBitmap(frame->bitmap())); | |
| 271 } | |
| 272 | |
| 273 for (size_t i = 0; i < truncatedHashes.size(); ++i) | |
| 274 ASSERT_EQ(truncatedHashes[i], progressiveHashes[i]); | |
| 275 } | |
| 276 | |
| 277 // Check that providing the full data after the first frame was partially | |
| 278 // decoded properly continues where it left off. | |
| 279 void testProgressiveDecodingContinuesAfterFullData(const char* pngFile, | |
| 280 size_t offsetMidFirstFrame) | |
| 281 { | |
| 282 auto fullData = readFile(pngFile); | |
| 283 auto decoder = createDecoder(); | |
| 284 ASSERT_FALSE(fullData->isEmpty()); | |
| 285 | |
| 286 RefPtr<SharedBuffer> partialData = | |
| 287 SharedBuffer::create(fullData->data(), offsetMidFirstFrame); | |
| 288 decoder->setData(partialData, false); | |
| 289 | |
| 290 EXPECT_EQ(1u, decoder->frameCount()); | |
| 291 ImageFrame* frame = decoder->frameBufferAtIndex(0); | |
| 292 EXPECT_EQ(frame->getStatus(), ImageFrame::FramePartial); | |
| 293 unsigned hashPartial = hashBitmap(frame->bitmap()); | |
| 294 | |
| 295 decoder->setData(fullData.get(), true); | |
| 296 frame = decoder->frameBufferAtIndex(0); | |
| 297 EXPECT_EQ(frame->getStatus(), ImageFrame::FrameComplete); | |
| 298 unsigned hashFull = hashBitmap(frame->bitmap()); | |
| 299 | |
| 300 EXPECT_FALSE(decoder->failed()); | |
| 301 EXPECT_NE(hashFull, hashPartial); | |
| 302 } | |
| 303 | |
| 304 // This test verifies that the frame buffer contents change when progressively | |
| 305 // decoding the first frame. It should change more than 1 time: once for the | |
| 306 // first data, and at least once more thereafter. If |offsetFirstFrameEnd| == 0, | |
| 307 // the test uses the full data size of the image for its value. | |
| 308 void testProgressiveDecodingChangesFrameBuffer(const char* pngFile, | |
| 309 size_t offsetFirstFrameEnd, | |
| 310 size_t step = 1u) | |
| 311 { | |
| 312 auto fullData = readFile(pngFile); | |
| 313 auto decoder = createDecoder(); | |
| 314 ASSERT_FALSE(fullData->isEmpty()); | |
| 315 | |
| 316 if (offsetFirstFrameEnd == 0) | |
| 317 offsetFirstFrameEnd = fullData->size(); | |
| 318 | |
| 319 size_t numTimesBufferChanged = 0; | |
| 320 unsigned lastHash; | |
| 321 | |
| 322 for (size_t length = 1; length <= offsetFirstFrameEnd; length += step) { | |
| 323 RefPtr<SharedBuffer> data = SharedBuffer::create(fullData->data(), | |
| 324 length); | |
| 325 decoder->setData(data, false); | |
| 326 ImageFrame* frame = decoder->frameBufferAtIndex(0); | |
| 327 if (!frame) | |
| 328 continue; | |
| 329 unsigned newHash = hashBitmap(frame->bitmap()); | |
| 330 if (newHash != lastHash) { | |
| 331 lastHash = newHash; | |
| 332 numTimesBufferChanged++; | |
| 333 } | |
| 334 } | |
| 335 EXPECT_GT(numTimesBufferChanged, 1u); | |
| 336 } | |
| 337 | |
| 338 // @TODO(joostouwerling) Pull this up to ImageDecoderTestHelper since it is also | |
| 339 // used in WEBPImageDecoderTest | |
| 340 void testRandomDecodeAfterClearFrameBufferCache(const char* pngFile) { | |
| 341 SCOPED_TRACE(pngFile); | |
| 342 | |
| 343 RefPtr<SharedBuffer> data = readFile(pngFile); | |
| 344 ASSERT_TRUE(data.get()); | |
| 345 Vector<unsigned> baselineHashes; | |
| 346 createDecodingBaseline(&createDecoder, data.get(), &baselineHashes); | |
| 347 size_t frameCount = baselineHashes.size(); | |
| 348 | |
| 349 std::unique_ptr<ImageDecoder> decoder = createDecoder(); | |
| 350 decoder->setData(data.get(), true); | |
| 351 for (size_t clearExceptFrame = 0; clearExceptFrame < frameCount; | |
| 352 ++clearExceptFrame) { | |
| 353 decoder->clearCacheExceptFrame(clearExceptFrame); | |
| 354 const size_t skippingStep = 2; | |
| 355 for (size_t i = 0; i < skippingStep; ++i) { | |
| 356 for (size_t j = 0; j < frameCount; j += skippingStep) { | |
| 357 SCOPED_TRACE(testing::Message() << "Random i:" << i << " j:" << j); | |
| 358 ImageFrame* frame = decoder->frameBufferAtIndex(j); | |
| 359 EXPECT_EQ(baselineHashes[j], hashBitmap(frame->bitmap())); | |
| 360 } | |
| 361 } | |
| 362 } | |
| 363 } | |
| 364 | |
| 365 uint32_t premultiplyColor(uint32_t c) { | |
| 366 return SkPremultiplyARGBInline(SkGetPackedA32(c), SkGetPackedR32(c), | |
| 367 SkGetPackedG32(c), SkGetPackedB32(c)); | |
| 368 } | |
| 369 | |
| 370 void verifyFramesMatch(const char* pngFile, | |
| 371 const ImageFrame* const a, | |
| 372 ImageFrame* const b) { | |
| 373 const SkBitmap& bitmapA = a->bitmap(); | |
| 374 const SkBitmap& bitmapB = b->bitmap(); | |
| 375 ASSERT_EQ(bitmapA.width(), bitmapB.width()); | |
| 376 ASSERT_EQ(bitmapA.height(), bitmapB.height()); | |
| 377 | |
| 378 int maxDifference = 0; | |
| 379 for (int y = 0; y < bitmapA.height(); ++y) { | |
| 380 for (int x = 0; x < bitmapA.width(); ++x) { | |
| 381 uint32_t colorA = *bitmapA.getAddr32(x, y); | |
| 382 if (!a->premultiplyAlpha()) | |
| 383 colorA = premultiplyColor(colorA); | |
| 384 uint32_t colorB = *bitmapB.getAddr32(x, y); | |
| 385 if (!b->premultiplyAlpha()) | |
| 386 colorB = premultiplyColor(colorB); | |
| 387 uint8_t* pixelA = reinterpret_cast<uint8_t*>(&colorA); | |
| 388 uint8_t* pixelB = reinterpret_cast<uint8_t*>(&colorB); | |
| 389 for (int channel = 0; channel < 4; ++channel) { | |
| 390 const int difference = abs(pixelA[channel] - pixelB[channel]); | |
| 391 if (difference > maxDifference) | |
| 392 maxDifference = difference; | |
| 393 } | |
| 394 } | |
| 395 } | |
| 396 | |
| 397 // Pre-multiplication could round the RGBA channel values. So, we declare | |
| 398 // that the frames match if the RGBA channel values differ by at most 2. | |
| 399 EXPECT_GE(2, maxDifference) << pngFile; | |
| 400 } | |
| 401 | |
| 402 // Verifies that result of alpha blending is similar for AlphaPremultiplied and | |
| 403 // AlphaNotPremultiplied cases. | |
| 404 void testAlphaBlending(const char* pngFile) { | |
| 405 RefPtr<SharedBuffer> data = readFile(pngFile); | |
| 406 ASSERT_TRUE(data.get()); | |
| 407 | |
| 408 std::unique_ptr<ImageDecoder> decoderA = | |
| 409 createDecoder(ImageDecoder::AlphaPremultiplied); | |
| 410 decoderA->setData(data.get(), true); | |
| 411 | |
| 412 std::unique_ptr<ImageDecoder> decoderB = | |
| 413 createDecoder(ImageDecoder::AlphaNotPremultiplied); | |
| 414 decoderB->setData(data.get(), true); | |
| 415 | |
| 416 size_t frameCount = decoderA->frameCount(); | |
| 417 ASSERT_EQ(frameCount, decoderB->frameCount()); | |
| 418 | |
| 419 for (size_t i = 0; i < frameCount; ++i) | |
| 420 verifyFramesMatch(pngFile, decoderA->frameBufferAtIndex(i), | |
| 421 decoderB->frameBufferAtIndex(i)); | |
| 422 } | |
| 423 | |
| 424 // Modify the frame data bytes for frame |frameIndex| so that decoding fails. | |
| 425 // Parsing should work fine, and is checked with |expectedFrameCountBefore|. If | |
| 426 // the failure should invalidate the decoder, |expectFailure| should be set to | |
| 427 // true. If not, |expectedFrameCountAfter| should indicate the new frame count | |
| 428 // after the failure. | |
| 429 void testFailureDuringDecode(const char *file, | |
| 430 size_t idatOffset, | |
| 431 size_t frameIndex, | |
| 432 bool expectFailure, | |
| 433 size_t expectedFrameCountBefore, | |
| 434 size_t expectedFrameCountAfter = 0u) | |
| 435 { | |
| 436 RefPtr<SharedBuffer> fullData = readFile(file); | |
| 437 ASSERT_FALSE(fullData->isEmpty()); | |
| 438 | |
| 439 // This is the offset where the frame data chunk frame |frameIndex| starts. | |
| 440 RefPtr<SharedBuffer> data = SharedBuffer::create(fullData->data(), | |
| 441 idatOffset + 8u); | |
| 442 // Repeat the first 8 bytes of the frame data. This should result in a | |
| 443 // succesfull parse, since frame data is not analyzed in that step, but | |
|
scroggo_chromium
2016/11/29 16:30:52
successful* (two "s"s, one "l")
joostouwerling
2016/12/02 16:08:42
Done.
| |
| 444 // should give an error during decoding. | |
| 445 data->append(fullData->data() + idatOffset, 8u); | |
| 446 data->append(fullData->data() + idatOffset + 16u, | |
| 447 fullData->size() - idatOffset - 16u); | |
| 448 | |
| 449 auto decoder = createDecoder(); | |
| 450 decoder->setData(data.get(), true); | |
| 451 | |
| 452 EXPECT_EQ(expectedFrameCountBefore, decoder->frameCount()); | |
| 453 | |
| 454 const ImageFrame* const frame = decoder->frameBufferAtIndex(frameIndex); | |
| 455 EXPECT_EQ(expectFailure, decoder->failed()); | |
| 456 if (!expectFailure) { | |
| 457 EXPECT_EQ(expectedFrameCountAfter, decoder->frameCount()); | |
| 458 EXPECT_EQ(ImageFrame::FrameEmpty, frame->getStatus()); | |
| 459 } | |
| 460 } | |
| 461 | |
| 462 } // Anonymous namespace | |
| 463 | |
| 464 // Animated PNG Tests | |
| 465 | |
| 466 TEST(AnimatedPNGTests, sizeTest) | |
| 467 { | |
| 468 testSize("/LayoutTests/fast/images/resources/png-animated-idat-part-of-animati on.png", IntSize(5, 5)); | |
| 469 testSize("/LayoutTests/fast/images/resources/png-animated-idat-not-part-of-ani mation.png", IntSize(227, 35)); | |
| 470 } | |
| 471 | |
| 472 TEST(AnimatedPNGTests, repetitionCountTest) | |
| 473 { | |
| 474 testRepetitionCount("/LayoutTests/fast/images/resources/png-animated-idat-part -of-animation.png", 7u); | |
| 475 // This is an "animated" image with only one frame, that is, the IDAT is | |
| 476 // ignored and there is one fdAT frame. so it should be considered | |
| 477 // non-animated. | |
| 478 testRepetitionCount("/LayoutTests/fast/images/resources/png-animated-idat-not- part-of-animation.png", cAnimationNone); | |
| 479 } | |
| 480 | |
| 481 // Test if the decoded metdata corresponds to the defined expectations | |
| 482 TEST(AnimatedPNGTests, MetaDataTest) | |
| 483 { | |
| 484 const char* pngFile = "/LayoutTests/fast/images/resources/png-animated-idat-pa rt-of-animation.png"; | |
| 485 constexpr size_t expectedFrameCount = 4; | |
| 486 | |
| 487 auto decoder = createDecoderWithPngData(pngFile); | |
| 488 ASSERT_EQ(expectedFrameCount, decoder->frameCount()); | |
| 489 for (size_t i = 0; i < expectedFrameCount; i++) | |
| 490 compareFrameWithExpectation(pngAnimatedFrameInfo[i], | |
| 491 decoder->frameBufferAtIndex(i)); | |
| 492 } | |
| 493 | |
| 494 TEST(AnimatedPNGTests, ByteByByteSizeAvailable) | |
| 495 { | |
| 496 testSizeByteByByte("/LayoutTests/fast/images/resources/png-animated-idat-part- of-animation.png", | |
| 497 141u, IntSize(5, 5)); | |
| 498 testSizeByteByByte("/LayoutTests/fast/images/resources/png-animated-idat-not-p art-of-animation.png", | |
| 499 79u, IntSize(227, 35)); | |
| 500 } | |
| 501 | |
| 502 // Test whether the frame metadata decoding also works when we provide the data | |
| 503 // byte by byte. This should cover the case when the client does not provide | |
| 504 // all data at once. At given offsets, we expect frames to become available. | |
| 505 // This test checks whether that is the case, and if so, if the frame data is | |
| 506 // equal to what we expected. | |
| 507 TEST(AnimatedPNGTests, ByteByByteMetaData) | |
| 508 { | |
| 509 const char* pngFile = "/LayoutTests/fast/images/resources/png-animated-idat-pa rt-of-animation.png"; | |
| 510 constexpr size_t expectedFrameCount = 4; | |
| 511 | |
| 512 // These are the byte offsets where each frame should have been parsed. | |
| 513 // It boils down to the offset of the first fcTL / IEND after the last | |
| 514 // frame data chunk, plus 8 bytes for recognition. The exception on this is | |
| 515 // the first frame, which is reported when its first framedata is seen. | |
| 516 size_t frameOffsets[expectedFrameCount] = {141, 249, 322, 430}; | |
| 517 | |
| 518 auto decoder = createDecoder(); | |
| 519 auto data = readFile(pngFile); | |
| 520 ASSERT_FALSE(data->isEmpty()); | |
| 521 size_t framesParsed = 0; | |
| 522 | |
| 523 for (size_t length = 1; length <= frameOffsets[expectedFrameCount - 1]; length ++) { | |
| 524 RefPtr<SharedBuffer> tempData = SharedBuffer::create(data->data(), | |
| 525 length); | |
| 526 decoder->setData(tempData.get(), false); | |
| 527 EXPECT_FALSE(decoder->failed()); | |
| 528 if (length < frameOffsets[framesParsed]) { | |
| 529 EXPECT_EQ(framesParsed, decoder->frameCount()); | |
| 530 } else { | |
| 531 ASSERT_EQ(framesParsed + 1, decoder->frameCount()); | |
| 532 compareFrameWithExpectation(pngAnimatedFrameInfo[framesParsed], | |
| 533 decoder->frameBufferAtIndex(framesParsed)); | |
| 534 framesParsed++; | |
| 535 } | |
| 536 } | |
| 537 EXPECT_EQ(expectedFrameCount, decoder->frameCount()); | |
| 538 EXPECT_FALSE(decoder->failed()); | |
| 539 } | |
| 540 | |
| 541 TEST(AnimatedPNGTests, TestRandomFrameDecode) | |
| 542 { | |
| 543 RefPtr<SharedBuffer> fullData = readFile("/LayoutTests/fast/images/resources/p ng-animated-idat-part-of-animation.png"); | |
| 544 ASSERT_TRUE(fullData.get()); | |
| 545 Vector<unsigned> baselineHashes; | |
| 546 createDecodingBaseline(&createDecoder, fullData.get(), &baselineHashes); | |
| 547 size_t frameCount = baselineHashes.size(); | |
| 548 | |
| 549 // Random decoding should get the same results as sequential decoding. | |
| 550 std::unique_ptr<ImageDecoder> decoder = createDecoder(); | |
| 551 decoder->setData(fullData.get(), true); | |
| 552 const size_t skippingStep = 2; | |
| 553 for (size_t i = 0; i < skippingStep; ++i) { | |
| 554 for (size_t j = i; j < frameCount; j += skippingStep) { | |
| 555 SCOPED_TRACE(testing::Message() << "Random i:" << i << " j:" << j); | |
| 556 ImageFrame* frame = decoder->frameBufferAtIndex(j); | |
| 557 EXPECT_EQ(baselineHashes[j], hashBitmap(frame->bitmap())); | |
| 558 } | |
| 559 } | |
| 560 | |
| 561 // Decoding in reverse order. | |
| 562 decoder = createDecoder(); | |
| 563 decoder->setData(fullData.get(), true); | |
| 564 for (size_t i = frameCount; i; --i) { | |
| 565 SCOPED_TRACE(testing::Message() << "Reverse i:" << i); | |
| 566 ImageFrame* frame = decoder->frameBufferAtIndex(i - 1); | |
| 567 EXPECT_EQ(baselineHashes[i - 1], hashBitmap(frame->bitmap())); | |
| 568 } | |
| 569 | |
| 570 } | |
| 571 | |
| 572 TEST(AnimatedPNGTests, TestDecodeAfterReallocation) | |
| 573 { | |
| 574 std::unique_ptr<ImageDecoder> decoder = createDecoder(); | |
| 575 RefPtr<SharedBuffer> data = readFile("/LayoutTests/fast/images/resources/png-a nimated-idat-part-of-animation.png"); | |
| 576 ASSERT_TRUE(data.get()); | |
| 577 | |
| 578 // Parse from |data|. | |
| 579 decoder->setData(data.get(), true); | |
| 580 size_t frameCount = decoder->frameCount(); | |
| 581 | |
| 582 // ... and then decode frames from |reallocatedData|. | |
| 583 RefPtr<SharedBuffer> reallocatedData = data.get()->copy(); | |
| 584 ASSERT_TRUE(reallocatedData.get()); | |
| 585 data.clear(); | |
| 586 decoder->setData(reallocatedData.get(), true); | |
| 587 | |
| 588 for (size_t i = 0; i < frameCount; ++i) { | |
| 589 const ImageFrame* const frame = decoder->frameBufferAtIndex(i); | |
| 590 EXPECT_EQ(ImageFrame::FrameComplete, frame->getStatus()); | |
| 591 } | |
| 592 } | |
| 593 | |
| 594 TEST(AnimatedPNGTests, ProgressiveDecode) | |
| 595 { | |
| 596 testProgressiveDecoding("/LayoutTests/fast/images/resources/png-animated-idat- part-of-animation.png"); | |
| 597 } | |
| 598 | |
| 599 TEST(AnimatedPNGTests, ParseAndDecodeByteByByte) | |
| 600 { | |
| 601 testByteByByteDecode(&createDecoder, | |
| 602 "/LayoutTests/fast/images/resources/png-animated-idat-part-of-anima tion.png", | |
| 603 4u, 7u); | |
| 604 } | |
| 605 | |
| 606 TEST(AnimatedPNGTests, FailureDuringParsing) | |
| 607 { | |
| 608 const char* pngFile = "/LayoutTests/fast/images/resources/png-animated-idat-pa rt-of-animation.png"; | |
| 609 auto data = readFile(pngFile); | |
| 610 ASSERT_FALSE(data->isEmpty()); | |
| 611 | |
| 612 // Test the first fcTL in the stream. Because no frame data has been set at | |
| 613 // this point, the expected frame count is zero. 95 bytes is just before the | |
| 614 // first fcTL chunk, at which the first frame is detected. The decoder should | |
| 615 // be in the failed state since no frames were parsed before this error. | |
| 616 testInvalidFctlSize("/LayoutTests/fast/images/resources/png-animated-idat-part -of-animation.png", | |
| 617 95u, 0u, true); | |
| 618 | |
| 619 // Test for the third fcTL in the stream. This should be tested as well, | |
| 620 // since the first fcTL is parsed in PNGImageReader::parseSize() whereas | |
| 621 // later fcTLs are parsed in PNGImageReader::parse() as part of framecount. | |
| 622 // The expected frame count before the fcTL chunk is 1u, since the second | |
| 623 // frame is registered when the third fcTL (or IEND) is seen. | |
| 624 // | |
| 625 // This should *not* set the decoder to the failed state since the first | |
| 626 // frame was succesfully parsed, and we still want to show that frame. | |
| 627 testInvalidFctlSize("/LayoutTests/fast/images/resources/png-animated-idat-part -of-animation.png", | |
| 628 241u, 1u, false); | |
| 629 | |
| 630 } | |
| 631 | |
| 632 TEST(AnimatedPNGTests, FailureDuringParsingDueToMissingFctlData) | |
| 633 { | |
| 634 // The fcTL chunk starts at 95u, add 10u to get to the content data, and | |
| 635 // remove 5 bytes of data. This sets the decoder to the failed state since | |
| 636 // no frames were parsed yet. | |
| 637 testMissingDataBreaksDecoding( | |
| 638 "/LayoutTests/fast/images/resources/png-animated-idat-part-of-animation.png" , | |
| 639 105u, 5u); | |
| 640 } | |
| 641 | |
| 642 TEST(AnimatedPNGTests, MissingActlResultsInNonAnimated) | |
| 643 { | |
| 644 const char* pngFile = "/LayoutTests/fast/images/resources/png-animated-idat-pa rt-of-animation.png"; | |
| 645 auto data = readFile(pngFile); | |
| 646 auto decoder = createDecoder(); | |
| 647 ASSERT_FALSE(data->isEmpty()); | |
| 648 | |
| 649 // Remove the acTL chunk from the stream. | |
| 650 size_t offsetActl = 33; | |
| 651 RefPtr<SharedBuffer> noActlData = SharedBuffer::create(data->data(), | |
| 652 offsetActl); | |
| 653 noActlData->append(data->data() + offsetActl + 20, | |
| 654 data->size() - offsetActl - 20); | |
| 655 | |
| 656 decoder->setData(noActlData, true); | |
| 657 EXPECT_EQ(1u, decoder->frameCount()); | |
| 658 EXPECT_FALSE(decoder->failed()); | |
| 659 } | |
| 660 | |
| 661 // Test if the indicated frame count by the acTL is ignored if the actual | |
| 662 // number of frames is different. Test for higher and lower indicated number. | |
| 663 TEST(AnimatedPNGTests, differentActlFrameCountIsIgnored) | |
| 664 { | |
| 665 const char* pngFile = "/LayoutTests/fast/images/resources/png-animated-idat-pa rt-of-animation.png"; | |
| 666 testDifferentActlFrameCountIsIgnored(pngFile, 33u, 2u, 4u); | |
| 667 testDifferentActlFrameCountIsIgnored(pngFile, 33u, 8u, 4u); | |
| 668 } | |
| 669 | |
| 670 // Check if a frame rectangle, that is larger than the image width, gets | |
| 671 // clipped correctly. | |
| 672 // @TODO(joostouwerling) use an image that also has data that needs to be | |
| 673 // clipped, not just report larger dimensions. | |
| 674 // @TODO(joostouwerling) determine how libpng handles larger / smaller images | |
| 675 // than indicated. | |
| 676 TEST(AnimatedPNGTests, frameRectIsClipped) | |
| 677 { | |
| 678 const char* pngFile = "/LayoutTests/fast/images/resources/png-animated-idat-pa rt-of-animation.png"; | |
| 679 auto data = readFile(pngFile); | |
| 680 auto decoder = createDecoder(); | |
| 681 ASSERT_FALSE(data->isEmpty()); | |
| 682 | |
| 683 // Change the width and height of the frame so it falls outside the image. | |
| 684 size_t offsetThirdFctl = 241 + 12; | |
| 685 RefPtr<SharedBuffer> modifiedData = SharedBuffer::create(data->data(), | |
| 686 offsetThirdFctl); | |
| 687 png_byte sizeChunk[8]; | |
| 688 writeUint32(10, sizeChunk); | |
| 689 writeUint32(15, sizeChunk + 4); | |
| 690 modifiedData->append(const_cast<const char*>( | |
| 691 reinterpret_cast<char*>(sizeChunk)), 8u); | |
| 692 modifiedData->append(data->data() + offsetThirdFctl + 8, | |
| 693 data->size() - offsetThirdFctl - 8); | |
| 694 | |
| 695 decoder->setData(modifiedData, true); | |
| 696 | |
| 697 IntSize expectedSize(5, 5); | |
| 698 size_t expectedFrameCount = 4; | |
| 699 IntRect expectedFrameRect(IntPoint(1, 2), IntSize(4, 3)); | |
| 700 | |
| 701 EXPECT_TRUE(decoder->isSizeAvailable()); | |
| 702 EXPECT_EQ(expectedSize, decoder->size()); | |
| 703 EXPECT_EQ(expectedFrameCount, decoder->frameCount()); | |
| 704 ASSERT_FALSE(decoder->failed()); | |
| 705 EXPECT_EQ(expectedFrameRect, | |
| 706 decoder->frameBufferAtIndex(2)->originalFrameRect()); | |
| 707 } | |
| 708 | |
| 709 TEST(AnimatedPNGTests, ProgressiveDecodingChangesFrameBuffer) | |
| 710 { | |
| 711 testProgressiveDecodingChangesFrameBuffer( | |
| 712 "/LayoutTests/fast/images/resources/png-animated-idat-part-of-animation.png" , 249u); | |
| 713 } | |
| 714 | |
| 715 TEST(AnimatedPNGTests, ProgressiveDecodingContinuesAfterFullData) | |
| 716 { | |
| 717 // 160u is a randomly chosen offset in the IDAT chunk of the first frame. | |
| 718 testProgressiveDecodingContinuesAfterFullData( | |
| 719 "/LayoutTests/fast/images/resources/png-animated-idat-part-of-animation.png" , 160u); | |
| 720 } | |
| 721 | |
| 722 TEST(AnimatedPNGTests, RandomDecodeAfterClearFrameBufferCache) | |
| 723 { | |
| 724 testRandomDecodeAfterClearFrameBufferCache( | |
| 725 "/LayoutTests/fast/images/resources/png-animated-idat-part-of-animation.png" ); | |
| 726 } | |
| 727 | |
| 728 TEST(AnimatedPNGTests, VerifyAlphaBlending) { | |
| 729 testAlphaBlending( | |
| 730 "/LayoutTests/fast/images/resources/png-animated-idat-part-of-animation.png" ); | |
| 731 } | |
| 732 | |
| 733 // This tests if the frame count gets set correctly when parsing frameCount | |
| 734 // fails in one of the parsing queries. | |
| 735 // | |
| 736 // First, enough data is provided such that two frames should be registered. | |
| 737 // The decoder should at this point not be in the failed status. | |
| 738 // | |
| 739 // Then, we provide the rest of the data except for the last IEND chunk, but | |
| 740 // tell the decoder that this is all the data we have. The frame count should | |
| 741 // be three, since one extra frame should be discovered. The fourth frame | |
| 742 // should *not* be registered since the reader should not be able to determine | |
| 743 // where the frame ends. The decoder should *not* be in the failed state since | |
| 744 // there are three frames which can be shown. | |
| 745 TEST(AnimatedPNGTests, FailureMissingIendChunk) | |
| 746 { | |
| 747 RefPtr<SharedBuffer> fullData = readFile("/LayoutTests/fast/images/resources/p ng-animated-idat-part-of-animation.png"); | |
| 748 ASSERT_FALSE(fullData->isEmpty()); | |
| 749 auto decoder = createDecoder(); | |
| 750 | |
| 751 const size_t offsetTwoFrames = 249; | |
| 752 const size_t expectedFramesAfter249Bytes = 2; | |
| 753 RefPtr<SharedBuffer> tempData = SharedBuffer::create(fullData->data(), | |
| 754 offsetTwoFrames); | |
| 755 decoder->setData(tempData.get(), false); | |
| 756 EXPECT_EQ(expectedFramesAfter249Bytes, decoder->frameCount()); | |
| 757 EXPECT_FALSE(decoder->failed()); | |
| 758 | |
| 759 // Provide the rest of the data except for the last IEND chunk. | |
| 760 const size_t expectedFramesAfterAllExpect12Bytes = 3; | |
| 761 tempData = SharedBuffer::create(fullData->data(), fullData->size() - 12); | |
| 762 decoder->setData(tempData.get(), true); | |
| 763 EXPECT_EQ(expectedFramesAfterAllExpect12Bytes, decoder->frameCount()); | |
| 764 EXPECT_FALSE(decoder->failed()); | |
| 765 } | |
| 766 | |
| 767 TEST(AnimatedPNGTests, VerifyFrameCountChangesOnDecodingFailure) | |
| 768 { | |
| 769 testFailureDuringDecode( | |
| 770 "/LayoutTests/fast/images/resources/png-animated-idat-part-of-animation.pn g", | |
| 771 279u, // idat offset for frame index 2 | |
| 772 2u, // try to decode frame index 2 | |
| 773 false, // expect the decoder to *not* be invalidated after the failure | |
| 774 4u, // expected frame count before failure | |
| 775 2u); // expected frame count after failure | |
| 776 | |
| 777 testFailureDuringDecode( | |
| 778 "/LayoutTests/fast/images/resources/png-animated-idat-part-of-animation.pn g", | |
| 779 133u, // idat offset for frame index 0 | |
| 780 0u, // try to decode frame index 0 | |
| 781 true, // expect the decoder to be invalidated after the failure | |
| 782 4u); // expected frame count before failure | |
| 783 } | |
| 784 | |
| 785 // Static PNG tests | |
| 786 | |
| 787 TEST(StaticPNGTests, repetitionCountTest) | |
| 788 { | |
| 789 testRepetitionCount("/LayoutTests/fast/images/resources/png-simple.png", | |
| 790 cAnimationNone); | |
| 791 } | |
| 792 | |
| 793 TEST(StaticPNGTests, sizeTest) | |
| 794 { | |
| 795 testSize("/LayoutTests/fast/images/resources/png-simple.png", | |
| 796 IntSize(111, 29)); | |
| 797 } | |
| 798 | |
| 799 TEST(StaticPNGTests, MetaDataTest) | |
| 800 { | |
| 801 const size_t expectedFrameCount = 1; | |
| 802 const size_t expectedDuration = 0; | |
| 803 auto decoder = createDecoderWithPngData("/LayoutTests/fast/images/resources/pn g-simple.png"); | |
| 804 EXPECT_EQ(expectedFrameCount, decoder->frameCount()); | |
| 805 EXPECT_EQ(expectedDuration, decoder->frameDurationAtIndex(0)); | |
| 806 } | |
| 807 | |
| 808 TEST(StaticPNGTests, InvalidIHDRChunk) | |
| 809 { | |
| 810 testMissingDataBreaksDecoding( | |
| 811 "/LayoutTests/fast/images/resources/png-simple.png", 20u, 2u); | |
| 812 } | |
| 813 | |
| 814 TEST(StaticPNGTests, ProgressiveDecoding) | |
| 815 { | |
| 816 testProgressiveDecoding("/LayoutTests/fast/images/resources/png-simple.png"); | |
| 817 } | |
| 818 | |
| 819 TEST(StaticPNGTests, ProgressiveDecodingChangesFrameBuffer) | |
| 820 { | |
| 821 testProgressiveDecodingChangesFrameBuffer( | |
| 822 "/LayoutTests/fast/images/resources/png-simple.png", 0u, 5u); | |
| 823 } | |
| 824 | |
| 825 TEST(StaticPNGTests, ProgressiveDecodingContinuesAfterFullData) | |
| 826 { | |
| 827 testProgressiveDecodingContinuesAfterFullData( | |
| 828 "/LayoutTests/fast/images/resources/png-simple.png", 1000u); | |
| 829 } | |
| 830 | |
| 831 TEST(StaticPNGTests, FailureDuringDecodingInvalidatesDecoder) | |
| 832 { | |
| 833 testFailureDuringDecode( | |
| 834 "/LayoutTests/fast/images/resources/png-simple.png", | |
| 835 85u, // idat offset for frame index 0 | |
| 836 0u, // try to decode frame index 0 | |
| 837 true, // expect the decoder to be invalidated after the failure | |
| 838 1u); // expected frame count before failure | |
| 839 } | |
| 840 | |
| 841 }; // namespace blink | |
| OLD | NEW |