| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "platform/image-decoders/ImageDecoderTestHelpers.h" |
| |
| #include "platform/SharedBuffer.h" |
| #include "platform/image-decoders/ImageFrame.h" |
| #include "platform/testing/UnitTestHelpers.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "wtf/StringHasher.h" |
| #include "wtf/text/StringBuilder.h" |
| #include <memory> |
| |
| namespace blink { |
| |
| PassRefPtr<SharedBuffer> readFile(const char* fileName) { |
| String filePath = testing::blinkRootDir(); |
| filePath.append(fileName); |
| return testing::readFromFile(filePath); |
| } |
| |
| PassRefPtr<SharedBuffer> readFile(const char* dir, const char* fileName) { |
| StringBuilder filePath; |
| filePath.append(testing::blinkRootDir()); |
| filePath.append('/'); |
| filePath.append(dir); |
| filePath.append('/'); |
| filePath.append(fileName); |
| return testing::readFromFile(filePath.toString()); |
| } |
| |
| unsigned hashBitmap(const SkBitmap& bitmap) { |
| return StringHasher::hashMemory(bitmap.getPixels(), bitmap.getSize()); |
| } |
| |
| static unsigned createDecodingBaseline(DecoderCreator createDecoder, |
| SharedBuffer* data) { |
| std::unique_ptr<ImageDecoder> decoder = createDecoder(); |
| decoder->setData(data, true); |
| ImageFrame* frame = decoder->frameBufferAtIndex(0); |
| return hashBitmap(frame->bitmap()); |
| } |
| |
| void createDecodingBaseline(DecoderCreator createDecoder, |
| SharedBuffer* data, |
| Vector<unsigned>* baselineHashes) { |
| std::unique_ptr<ImageDecoder> decoder = createDecoder(); |
| decoder->setData(data, true); |
| size_t frameCount = decoder->frameCount(); |
| for (size_t i = 0; i < frameCount; ++i) { |
| ImageFrame* frame = decoder->frameBufferAtIndex(i); |
| baselineHashes->push_back(hashBitmap(frame->bitmap())); |
| } |
| } |
| |
| void testByteByByteDecode(DecoderCreator createDecoder, |
| SharedBuffer* data, |
| size_t expectedFrameCount, |
| int expectedRepetitionCount) { |
| ASSERT_TRUE(data->data()); |
| |
| Vector<unsigned> baselineHashes; |
| createDecodingBaseline(createDecoder, data, &baselineHashes); |
| |
| std::unique_ptr<ImageDecoder> decoder = createDecoder(); |
| |
| size_t frameCount = 0; |
| size_t framesDecoded = 0; |
| |
| // Pass data to decoder byte by byte. |
| RefPtr<SharedBuffer> sourceData[2] = {SharedBuffer::create(), |
| SharedBuffer::create()}; |
| const char* source = data->data(); |
| |
| for (size_t length = 1; length <= data->size() && !decoder->failed(); |
| ++length) { |
| sourceData[0]->append(source, 1u); |
| sourceData[1]->append(source++, 1u); |
| // Alternate the buffers to cover the JPEGImageDecoder::onSetData restart |
| // code. |
| decoder->setData(sourceData[length & 1].get(), length == data->size()); |
| |
| EXPECT_LE(frameCount, decoder->frameCount()); |
| frameCount = decoder->frameCount(); |
| |
| if (!decoder->isSizeAvailable()) |
| continue; |
| |
| for (size_t i = framesDecoded; i < frameCount; ++i) { |
| // In ICOImageDecoder memory layout could differ from frame order. |
| // E.g. memory layout could be |<frame1><frame0>| and frameCount |
| // would return 1 until receiving full file. |
| // When file is completely received frameCount would return 2 and |
| // only then both frames could be completely decoded. |
| ImageFrame* frame = decoder->frameBufferAtIndex(i); |
| if (frame && frame->getStatus() == ImageFrame::FrameComplete) |
| ++framesDecoded; |
| } |
| } |
| |
| EXPECT_FALSE(decoder->failed()); |
| EXPECT_EQ(expectedFrameCount, decoder->frameCount()); |
| EXPECT_EQ(expectedFrameCount, framesDecoded); |
| EXPECT_EQ(expectedRepetitionCount, decoder->repetitionCount()); |
| |
| ASSERT_EQ(expectedFrameCount, baselineHashes.size()); |
| for (size_t i = 0; i < decoder->frameCount(); i++) { |
| ImageFrame* frame = decoder->frameBufferAtIndex(i); |
| EXPECT_EQ(baselineHashes[i], hashBitmap(frame->bitmap())); |
| } |
| } |
| |
| // This test verifies that calling SharedBuffer::mergeSegmentsIntoBuffer() does |
| // not break decoding at a critical point: in between a call to decode the size |
| // (when the decoder stops while it may still have input data to read) and a |
| // call to do a full decode. |
| static void testMergeBuffer(DecoderCreator createDecoder, SharedBuffer* data) { |
| const unsigned hash = createDecodingBaseline(createDecoder, data); |
| |
| // In order to do any verification, this test needs to move the data owned |
| // by the SharedBuffer. A way to guarantee that is to create a new one, and |
| // then append a string of characters greater than kSegmentSize. This |
| // results in writing the data into a segment, skipping the internal |
| // contiguous buffer. |
| RefPtr<SharedBuffer> segmentedData = SharedBuffer::create(); |
| segmentedData->append(data->data(), data->size()); |
| |
| std::unique_ptr<ImageDecoder> decoder = createDecoder(); |
| decoder->setData(segmentedData.get(), true); |
| |
| ASSERT_TRUE(decoder->isSizeAvailable()); |
| |
| // This will call SharedBuffer::mergeSegmentsIntoBuffer, copying all |
| // segments into the contiguous buffer. If the ImageDecoder was pointing to |
| // data in a segment, its pointer would no longer be valid. |
| segmentedData->data(); |
| |
| ImageFrame* frame = decoder->frameBufferAtIndex(0); |
| ASSERT_FALSE(decoder->failed()); |
| EXPECT_EQ(frame->getStatus(), ImageFrame::FrameComplete); |
| EXPECT_EQ(hashBitmap(frame->bitmap()), hash); |
| } |
| |
| static void testRandomFrameDecode(DecoderCreator createDecoder, |
| SharedBuffer* fullData, |
| size_t skippingStep) { |
| Vector<unsigned> baselineHashes; |
| createDecodingBaseline(createDecoder, fullData, &baselineHashes); |
| size_t frameCount = baselineHashes.size(); |
| |
| // Random decoding should get the same results as sequential decoding. |
| std::unique_ptr<ImageDecoder> decoder = createDecoder(); |
| decoder->setData(fullData, true); |
| for (size_t i = 0; i < skippingStep; ++i) { |
| for (size_t j = i; j < frameCount; j += skippingStep) { |
| SCOPED_TRACE(::testing::Message() << "Random i:" << i << " j:" << j); |
| ImageFrame* frame = decoder->frameBufferAtIndex(j); |
| EXPECT_EQ(baselineHashes[j], hashBitmap(frame->bitmap())); |
| } |
| } |
| |
| // Decoding in reverse order. |
| decoder = createDecoder(); |
| decoder->setData(fullData, true); |
| for (size_t i = frameCount; i; --i) { |
| SCOPED_TRACE(::testing::Message() << "Reverse i:" << i); |
| ImageFrame* frame = decoder->frameBufferAtIndex(i - 1); |
| EXPECT_EQ(baselineHashes[i - 1], hashBitmap(frame->bitmap())); |
| } |
| } |
| |
| static void testRandomDecodeAfterClearFrameBufferCache( |
| DecoderCreator createDecoder, |
| SharedBuffer* data, |
| size_t skippingStep) { |
| Vector<unsigned> baselineHashes; |
| createDecodingBaseline(createDecoder, data, &baselineHashes); |
| size_t frameCount = baselineHashes.size(); |
| |
| std::unique_ptr<ImageDecoder> decoder = createDecoder(); |
| decoder->setData(data, true); |
| for (size_t clearExceptFrame = 0; clearExceptFrame < frameCount; |
| ++clearExceptFrame) { |
| decoder->clearCacheExceptFrame(clearExceptFrame); |
| for (size_t i = 0; i < skippingStep; ++i) { |
| for (size_t j = 0; j < frameCount; j += skippingStep) { |
| SCOPED_TRACE(::testing::Message() << "Random i:" << i << " j:" << j); |
| ImageFrame* frame = decoder->frameBufferAtIndex(j); |
| EXPECT_EQ(baselineHashes[j], hashBitmap(frame->bitmap())); |
| } |
| } |
| } |
| } |
| |
| static void testDecodeAfterReallocatingData(DecoderCreator createDecoder, |
| SharedBuffer* data) { |
| std::unique_ptr<ImageDecoder> decoder = createDecoder(); |
| |
| // Parse from 'data'. |
| decoder->setData(data, true); |
| size_t frameCount = decoder->frameCount(); |
| |
| // ... and then decode frames from 'reallocatedData'. |
| RefPtr<SharedBuffer> reallocatedData = data->copy(); |
| ASSERT_TRUE(reallocatedData.get()); |
| data->clear(); |
| decoder->setData(reallocatedData.get(), true); |
| |
| for (size_t i = 0; i < frameCount; ++i) { |
| const ImageFrame* const frame = decoder->frameBufferAtIndex(i); |
| EXPECT_EQ(ImageFrame::FrameComplete, frame->getStatus()); |
| } |
| } |
| |
| static void testByteByByteSizeAvailable(DecoderCreator createDecoder, |
| SharedBuffer* data, |
| size_t frameOffset, |
| bool hasColorSpace, |
| int expectedRepetitionCount) { |
| std::unique_ptr<ImageDecoder> decoder = createDecoder(); |
| EXPECT_LT(frameOffset, data->size()); |
| |
| // Send data to the decoder byte-by-byte and use the provided frame offset in |
| // the data to check that isSizeAvailable() changes state only when that |
| // offset is reached. Also check other decoder state. |
| RefPtr<SharedBuffer> tempData = SharedBuffer::create(); |
| const char* source = data->data(); |
| for (size_t length = 1; length <= frameOffset; ++length) { |
| tempData->append(source++, 1u); |
| decoder->setData(tempData.get(), false); |
| |
| if (length < frameOffset) { |
| EXPECT_FALSE(decoder->isSizeAvailable()); |
| EXPECT_TRUE(decoder->size().isEmpty()); |
| EXPECT_FALSE(decoder->hasEmbeddedColorSpace()); |
| EXPECT_EQ(0u, decoder->frameCount()); |
| EXPECT_EQ(cAnimationLoopOnce, decoder->repetitionCount()); |
| EXPECT_FALSE(decoder->frameBufferAtIndex(0)); |
| } else { |
| EXPECT_TRUE(decoder->isSizeAvailable()); |
| EXPECT_FALSE(decoder->size().isEmpty()); |
| EXPECT_EQ(decoder->hasEmbeddedColorSpace(), hasColorSpace); |
| EXPECT_EQ(1u, decoder->frameCount()); |
| EXPECT_EQ(expectedRepetitionCount, decoder->repetitionCount()); |
| } |
| |
| ASSERT_FALSE(decoder->failed()); |
| } |
| } |
| |
| static void testProgressiveDecoding(DecoderCreator createDecoder, |
| SharedBuffer* fullData, |
| size_t increment) { |
| const size_t fullLength = fullData->size(); |
| |
| std::unique_ptr<ImageDecoder> decoder; |
| |
| Vector<unsigned> truncatedHashes; |
| Vector<unsigned> progressiveHashes; |
| |
| // Compute hashes when the file is truncated. |
| RefPtr<SharedBuffer> data = SharedBuffer::create(); |
| const char* source = fullData->data(); |
| for (size_t i = 1; i <= fullLength; i += increment) { |
| decoder = createDecoder(); |
| data->append(source++, 1u); |
| decoder->setData(data.get(), i == fullLength); |
| ImageFrame* frame = decoder->frameBufferAtIndex(0); |
| if (!frame) { |
| truncatedHashes.push_back(0); |
| continue; |
| } |
| truncatedHashes.push_back(hashBitmap(frame->bitmap())); |
| } |
| |
| // Compute hashes when the file is progressively decoded. |
| decoder = createDecoder(); |
| data = SharedBuffer::create(); |
| source = fullData->data(); |
| for (size_t i = 1; i <= fullLength; i += increment) { |
| data->append(source++, 1u); |
| decoder->setData(data.get(), i == fullLength); |
| ImageFrame* frame = decoder->frameBufferAtIndex(0); |
| if (!frame) { |
| progressiveHashes.push_back(0); |
| continue; |
| } |
| progressiveHashes.push_back(hashBitmap(frame->bitmap())); |
| } |
| |
| for (size_t i = 0; i < truncatedHashes.size(); ++i) |
| ASSERT_EQ(truncatedHashes[i], progressiveHashes[i]); |
| } |
| |
| void testUpdateRequiredPreviousFrameAfterFirstDecode( |
| DecoderCreator createDecoder, |
| SharedBuffer* fullData) { |
| std::unique_ptr<ImageDecoder> decoder = createDecoder(); |
| |
| // Give it data that is enough to parse but not decode in order to check the |
| // status of requiredPreviousFrameIndex before decoding. |
| RefPtr<SharedBuffer> data = SharedBuffer::create(); |
| const char* source = fullData->data(); |
| do { |
| data->append(source++, 1u); |
| decoder->setData(data.get(), false); |
| } while (!decoder->frameCount() || |
| decoder->frameBufferAtIndex(0)->getStatus() == |
| ImageFrame::FrameEmpty); |
| |
| EXPECT_EQ(kNotFound, |
| decoder->frameBufferAtIndex(0)->requiredPreviousFrameIndex()); |
| unsigned frameCount = decoder->frameCount(); |
| for (size_t i = 1; i < frameCount; ++i) { |
| EXPECT_EQ(i - 1, |
| decoder->frameBufferAtIndex(i)->requiredPreviousFrameIndex()); |
| } |
| |
| decoder->setData(fullData, true); |
| for (size_t i = 0; i < frameCount; ++i) { |
| EXPECT_EQ(kNotFound, |
| decoder->frameBufferAtIndex(i)->requiredPreviousFrameIndex()); |
| } |
| } |
| |
| void testResumePartialDecodeAfterClearFrameBufferCache( |
| DecoderCreator createDecoder, |
| SharedBuffer* fullData) { |
| Vector<unsigned> baselineHashes; |
| createDecodingBaseline(createDecoder, fullData, &baselineHashes); |
| size_t frameCount = baselineHashes.size(); |
| |
| std::unique_ptr<ImageDecoder> decoder = createDecoder(); |
| |
| // Let frame 0 be partially decoded. |
| RefPtr<SharedBuffer> data = SharedBuffer::create(); |
| const char* source = fullData->data(); |
| do { |
| data->append(source++, 1u); |
| decoder->setData(data.get(), false); |
| } while (!decoder->frameCount() || |
| decoder->frameBufferAtIndex(0)->getStatus() == |
| ImageFrame::FrameEmpty); |
| |
| // Skip to the last frame and clear. |
| decoder->setData(fullData, true); |
| EXPECT_EQ(frameCount, decoder->frameCount()); |
| ImageFrame* lastFrame = decoder->frameBufferAtIndex(frameCount - 1); |
| EXPECT_EQ(baselineHashes[frameCount - 1], hashBitmap(lastFrame->bitmap())); |
| decoder->clearCacheExceptFrame(kNotFound); |
| |
| // Resume decoding of the first frame. |
| ImageFrame* firstFrame = decoder->frameBufferAtIndex(0); |
| EXPECT_EQ(ImageFrame::FrameComplete, firstFrame->getStatus()); |
| EXPECT_EQ(baselineHashes[0], hashBitmap(firstFrame->bitmap())); |
| } |
| |
| void testByteByByteDecode(DecoderCreator createDecoder, |
| const char* file, |
| size_t expectedFrameCount, |
| int expectedRepetitionCount) { |
| RefPtr<SharedBuffer> data = readFile(file); |
| ASSERT_TRUE(data.get()); |
| testByteByByteDecode(createDecoder, data.get(), expectedFrameCount, |
| expectedRepetitionCount); |
| } |
| void testByteByByteDecode(DecoderCreator createDecoder, |
| const char* dir, |
| const char* file, |
| size_t expectedFrameCount, |
| int expectedRepetitionCount) { |
| RefPtr<SharedBuffer> data = readFile(dir, file); |
| ASSERT_TRUE(data.get()); |
| testByteByByteDecode(createDecoder, data.get(), expectedFrameCount, |
| expectedRepetitionCount); |
| } |
| |
| void testMergeBuffer(DecoderCreator createDecoder, const char* file) { |
| RefPtr<SharedBuffer> data = readFile(file); |
| ASSERT_TRUE(data.get()); |
| testMergeBuffer(createDecoder, data.get()); |
| } |
| |
| void testMergeBuffer(DecoderCreator createDecoder, |
| const char* dir, |
| const char* file) { |
| RefPtr<SharedBuffer> data = readFile(dir, file); |
| ASSERT_TRUE(data.get()); |
| testMergeBuffer(createDecoder, data.get()); |
| } |
| |
| void testRandomFrameDecode(DecoderCreator createDecoder, |
| const char* file, |
| size_t skippingStep) { |
| RefPtr<SharedBuffer> data = readFile(file); |
| ASSERT_TRUE(data.get()); |
| SCOPED_TRACE(file); |
| testRandomFrameDecode(createDecoder, data.get(), skippingStep); |
| } |
| void testRandomFrameDecode(DecoderCreator createDecoder, |
| const char* dir, |
| const char* file, |
| size_t skippingStep) { |
| RefPtr<SharedBuffer> data = readFile(dir, file); |
| ASSERT_TRUE(data.get()); |
| SCOPED_TRACE(file); |
| testRandomFrameDecode(createDecoder, data.get(), skippingStep); |
| } |
| |
| void testRandomDecodeAfterClearFrameBufferCache(DecoderCreator createDecoder, |
| const char* file, |
| size_t skippingStep) { |
| RefPtr<SharedBuffer> data = readFile(file); |
| ASSERT_TRUE(data.get()); |
| SCOPED_TRACE(file); |
| testRandomDecodeAfterClearFrameBufferCache(createDecoder, data.get(), |
| skippingStep); |
| } |
| |
| void testRandomDecodeAfterClearFrameBufferCache(DecoderCreator createDecoder, |
| const char* dir, |
| const char* file, |
| size_t skippingStep) { |
| RefPtr<SharedBuffer> data = readFile(dir, file); |
| ASSERT_TRUE(data.get()); |
| SCOPED_TRACE(file); |
| testRandomDecodeAfterClearFrameBufferCache(createDecoder, data.get(), |
| skippingStep); |
| } |
| |
| void testDecodeAfterReallocatingData(DecoderCreator createDecoder, |
| const char* file) { |
| RefPtr<SharedBuffer> data = readFile(file); |
| ASSERT_TRUE(data.get()); |
| testDecodeAfterReallocatingData(createDecoder, data.get()); |
| } |
| |
| void testDecodeAfterReallocatingData(DecoderCreator createDecoder, |
| const char* dir, |
| const char* file) { |
| RefPtr<SharedBuffer> data = readFile(dir, file); |
| ASSERT_TRUE(data.get()); |
| testDecodeAfterReallocatingData(createDecoder, data.get()); |
| } |
| |
| void testByteByByteSizeAvailable(DecoderCreator createDecoder, |
| const char* file, |
| size_t frameOffset, |
| bool hasColorSpace, |
| int expectedRepetitionCount) { |
| RefPtr<SharedBuffer> data = readFile(file); |
| ASSERT_TRUE(data.get()); |
| testByteByByteSizeAvailable(createDecoder, data.get(), frameOffset, |
| hasColorSpace, expectedRepetitionCount); |
| } |
| |
| void testByteByByteSizeAvailable(DecoderCreator createDecoder, |
| const char* dir, |
| const char* file, |
| size_t frameOffset, |
| bool hasColorSpace, |
| int expectedRepetitionCount) { |
| RefPtr<SharedBuffer> data = readFile(dir, file); |
| ASSERT_TRUE(data.get()); |
| testByteByByteSizeAvailable(createDecoder, data.get(), frameOffset, |
| hasColorSpace, expectedRepetitionCount); |
| } |
| |
| void testProgressiveDecoding(DecoderCreator createDecoder, |
| const char* file, |
| size_t increment) { |
| RefPtr<SharedBuffer> data = readFile(file); |
| ASSERT_TRUE(data.get()); |
| testProgressiveDecoding(createDecoder, data.get(), increment); |
| } |
| |
| void testProgressiveDecoding(DecoderCreator createDecoder, |
| const char* dir, |
| const char* file, |
| size_t increment) { |
| RefPtr<SharedBuffer> data = readFile(dir, file); |
| ASSERT_TRUE(data.get()); |
| testProgressiveDecoding(createDecoder, data.get(), increment); |
| } |
| |
| void testUpdateRequiredPreviousFrameAfterFirstDecode( |
| DecoderCreator createDecoder, |
| const char* dir, |
| const char* file) { |
| RefPtr<SharedBuffer> data = readFile(dir, file); |
| ASSERT_TRUE(data.get()); |
| testUpdateRequiredPreviousFrameAfterFirstDecode(createDecoder, data.get()); |
| } |
| |
| void testUpdateRequiredPreviousFrameAfterFirstDecode( |
| DecoderCreator createDecoder, |
| const char* file) { |
| RefPtr<SharedBuffer> data = readFile(file); |
| ASSERT_TRUE(data.get()); |
| testUpdateRequiredPreviousFrameAfterFirstDecode(createDecoder, data.get()); |
| } |
| |
| void testResumePartialDecodeAfterClearFrameBufferCache( |
| DecoderCreator createDecoder, |
| const char* dir, |
| const char* file) { |
| RefPtr<SharedBuffer> data = readFile(dir, file); |
| ASSERT_TRUE(data.get()); |
| testResumePartialDecodeAfterClearFrameBufferCache(createDecoder, data.get()); |
| } |
| |
| void testResumePartialDecodeAfterClearFrameBufferCache( |
| DecoderCreator createDecoder, |
| const char* file) { |
| RefPtr<SharedBuffer> data = readFile(file); |
| ASSERT_TRUE(data.get()); |
| testResumePartialDecodeAfterClearFrameBufferCache(createDecoder, data.get()); |
| } |
| |
| static uint32_t premultiplyColor(uint32_t c) { |
| return SkPremultiplyARGBInline(SkGetPackedA32(c), SkGetPackedR32(c), |
| SkGetPackedG32(c), SkGetPackedB32(c)); |
| } |
| |
| static void verifyFramesMatch(const char* file, |
| const ImageFrame* const a, |
| const ImageFrame* const b) { |
| const SkBitmap& bitmapA = a->bitmap(); |
| const SkBitmap& bitmapB = b->bitmap(); |
| ASSERT_EQ(bitmapA.width(), bitmapB.width()); |
| ASSERT_EQ(bitmapA.height(), bitmapB.height()); |
| |
| int maxDifference = 0; |
| for (int y = 0; y < bitmapA.height(); ++y) { |
| for (int x = 0; x < bitmapA.width(); ++x) { |
| uint32_t colorA = *bitmapA.getAddr32(x, y); |
| if (!a->premultiplyAlpha()) |
| colorA = premultiplyColor(colorA); |
| uint32_t colorB = *bitmapB.getAddr32(x, y); |
| if (!b->premultiplyAlpha()) |
| colorB = premultiplyColor(colorB); |
| uint8_t* pixelA = reinterpret_cast<uint8_t*>(&colorA); |
| uint8_t* pixelB = reinterpret_cast<uint8_t*>(&colorB); |
| for (int channel = 0; channel < 4; ++channel) { |
| const int difference = abs(pixelA[channel] - pixelB[channel]); |
| if (difference > maxDifference) |
| maxDifference = difference; |
| } |
| } |
| } |
| |
| // Pre-multiplication could round the RGBA channel values. So, we declare |
| // that the frames match if the RGBA channel values differ by at most 2. |
| EXPECT_GE(2, maxDifference) << file; |
| } |
| |
| // Verifies that result of alpha blending is similar for AlphaPremultiplied and |
| // AlphaNotPremultiplied cases. |
| void testAlphaBlending(DecoderCreatorWithAlpha createDecoder, |
| const char* file) { |
| RefPtr<SharedBuffer> data = readFile(file); |
| ASSERT_TRUE(data.get()); |
| |
| std::unique_ptr<ImageDecoder> decoderA = |
| createDecoder(ImageDecoder::AlphaPremultiplied); |
| decoderA->setData(data.get(), true); |
| |
| std::unique_ptr<ImageDecoder> decoderB = |
| createDecoder(ImageDecoder::AlphaNotPremultiplied); |
| decoderB->setData(data.get(), true); |
| |
| size_t frameCount = decoderA->frameCount(); |
| ASSERT_EQ(frameCount, decoderB->frameCount()); |
| |
| for (size_t i = 0; i < frameCount; ++i) { |
| verifyFramesMatch(file, decoderA->frameBufferAtIndex(i), |
| decoderB->frameBufferAtIndex(i)); |
| } |
| } |
| |
| } // namespace blink |