TEST(GIFImageDecoderTest, resumePartialDecodeAfterClearFrameBufferCache) { RefPtr<SharedBuffer> fullData = readFile("/LayoutTests/fast/images/resources/animated-10color.gif"); ASSERT_TRUE(fullData.get()); Vector<unsigned> baselineHashes; createDecodingBaseline(fullData.get(), &baselineHashes); size_t frameCount = baselineHashes.size(); OwnPtr<GIFImageDecoder> decoder = createDecoder(); // Let frame 0 be partially decoded. size_t partialSize = 1; do { RefPtr<SharedBuffer> data = SharedBuffer::create(fullData->data(), partialSize); decoder->setData(data.get(), false); ++partialSize; } while (!decoder->frameCount() || decoder->frameBufferAtIndex(0)->status() == ImageFrame::FrameEmpty); // Skip to the last frame and clear. decoder->setData(fullData.get(), true); EXPECT_EQ(frameCount, decoder->frameCount()); ImageFrame* lastFrame = decoder->frameBufferAtIndex(frameCount - 1); EXPECT_EQ(baselineHashes[frameCount - 1], hashSkBitmap(lastFrame->getSkBitmap())); decoder->clearCacheExceptFrame(kNotFound); // Resume decoding of the first frame. ImageFrame* firstFrame = decoder->frameBufferAtIndex(0); EXPECT_EQ(ImageFrame::FrameComplete, firstFrame->status()); EXPECT_EQ(baselineHashes[0], hashSkBitmap(firstFrame->getSkBitmap())); }
TEST(GIFImageDecoderTest, progressiveDecode) { RefPtr<SharedBuffer> fullData = readFile("/Source/web/tests/data/radient.gif"); ASSERT_TRUE(fullData.get()); const size_t fullLength = fullData->size(); OwnPtr<GIFImageDecoder> decoder; ImageFrame* frame; Vector<unsigned> truncatedHashes; Vector<unsigned> progressiveHashes; // Compute hashes when the file is truncated. const size_t increment = 1; for (size_t i = 1; i <= fullLength; i += increment) { decoder = createDecoder(); RefPtr<SharedBuffer> data = SharedBuffer::create(fullData->data(), i); decoder->setData(data.get(), i == fullLength); frame = decoder->frameBufferAtIndex(0); if (!frame) { truncatedHashes.append(0); continue; } truncatedHashes.append(hashSkBitmap(frame->getSkBitmap())); } // Compute hashes when the file is progressively decoded. decoder = createDecoder(); EXPECT_EQ(cAnimationLoopOnce, decoder->repetitionCount()); for (size_t i = 1; i <= fullLength; i += increment) { RefPtr<SharedBuffer> data = SharedBuffer::create(fullData->data(), i); decoder->setData(data.get(), i == fullLength); frame = decoder->frameBufferAtIndex(0); if (!frame) { progressiveHashes.append(0); continue; } progressiveHashes.append(hashSkBitmap(frame->getSkBitmap())); } EXPECT_EQ(cAnimationNone, decoder->repetitionCount()); bool match = true; for (size_t i = 0; i < truncatedHashes.size(); ++i) { if (truncatedHashes[i] != progressiveHashes[i]) { match = false; break; } } EXPECT_TRUE(match); }
void downsample(size_t maxDecodedBytes, unsigned* outputWidth, unsigned* outputHeight, const char* imageFilePath) { RefPtr<SharedBuffer> data = readFile(imageFilePath); ASSERT_TRUE(data.get()); OwnPtr<JPEGImageDecoder> decoder = createDecoder(maxDecodedBytes); decoder->setData(data.get(), true); ImageFrame* frame = decoder->frameBufferAtIndex(0); ASSERT_TRUE(frame); *outputWidth = frame->getSkBitmap().width(); *outputHeight = frame->getSkBitmap().height(); EXPECT_EQ(IntSize(*outputWidth, *outputHeight), decoder->decodedSize()); }
TEST(AnimatedWebPTests, uniqueGenerationIDs) { OwnPtr<WEBPImageDecoder> decoder = createDecoder(); RefPtr<SharedBuffer> data = readFile("/LayoutTests/fast/images/resources/webp-animated.webp"); ASSERT_TRUE(data.get()); decoder->setData(data.get(), true); ImageFrame* frame = decoder->frameBufferAtIndex(0); uint32_t generationID0 = frame->getSkBitmap().getGenerationID(); frame = decoder->frameBufferAtIndex(1); uint32_t generationID1 = frame->getSkBitmap().getGenerationID(); EXPECT_TRUE(generationID0 != generationID1); }
TEST(AnimatedWebPTests, progressiveDecode) { RefPtr<SharedBuffer> fullData = readFile("/LayoutTests/fast/images/resources/webp-animated.webp"); ASSERT_TRUE(fullData.get()); const size_t fullLength = fullData->size(); OwnPtr<WEBPImageDecoder> decoder; ImageFrame* frame; Vector<unsigned> truncatedHashes; Vector<unsigned> progressiveHashes; // Compute hashes when the file is truncated. const size_t increment = 1; for (size_t i = 1; i <= fullLength; i += increment) { decoder = createDecoder(); RefPtr<SharedBuffer> data = SharedBuffer::create(fullData->data(), i); decoder->setData(data.get(), i == fullLength); frame = decoder->frameBufferAtIndex(0); if (!frame) { truncatedHashes.append(0); continue; } truncatedHashes.append(hashSkBitmap(frame->getSkBitmap())); } // Compute hashes when the file is progressively decoded. decoder = createDecoder(); for (size_t i = 1; i <= fullLength; i += increment) { RefPtr<SharedBuffer> data = SharedBuffer::create(fullData->data(), i); decoder->setData(data.get(), i == fullLength); frame = decoder->frameBufferAtIndex(0); if (!frame) { progressiveHashes.append(0); continue; } progressiveHashes.append(hashSkBitmap(frame->getSkBitmap())); } bool match = true; for (size_t i = 0; i < truncatedHashes.size(); ++i) { if (truncatedHashes[i] != progressiveHashes[i]) { match = false; break; } } EXPECT_TRUE(match); }
const ScaledImageFragment* ImageFrameGenerator::tryToDecodeAndScale(const SkISize& scaledSize) { RefPtr<SharedBuffer> data; bool allDataReceived = false; { MutexLocker lock(m_dataMutex); // FIXME: We should do a shallow copy instead. Now we're restricted by the API of SharedBuffer. data = m_data->copy(); allDataReceived = m_allDataReceived; } OwnPtr<ImageDecoder> decoder(adoptPtr(ImageDecoder::create(*data.get(), ImageSource::AlphaPremultiplied, ImageSource::GammaAndColorProfileApplied))); if (!decoder && m_imageDecoderFactory) decoder = m_imageDecoderFactory->create(); if (!decoder) return 0; decoder->setData(data.get(), allDataReceived); ImageFrame* frame = decoder->frameBufferAtIndex(0); if (!frame || frame->status() == ImageFrame::FrameEmpty) return 0; bool isComplete = frame->status() == ImageFrame::FrameComplete; SkBitmap fullSizeBitmap = frame->getSkBitmap(); ASSERT(fullSizeBitmap.width() == m_fullSize.width() && fullSizeBitmap.height() == m_fullSize.height()); const ScaledImageFragment* fullSizeImage = ImageDecodingStore::instance()->insertAndLockCache( this, ScaledImageFragment::create(m_fullSize, fullSizeBitmap, isComplete)); if (m_fullSize == scaledSize) return fullSizeImage; return tryToScale(fullSizeImage, scaledSize); }
TEST(BMPImageDecoderTest, parseAndDecode) { const char* bmpFile = "/LayoutTests/fast/images/resources/lenna.bmp"; // 256x256 RefPtr<SharedBuffer> data = readFile(bmpFile); ASSERT_TRUE(data.get()); OwnPtr<ImageDecoder> decoder = createDecoder(); decoder->setData(data.get(), true); ImageFrame* frame = decoder->frameBufferAtIndex(0); ASSERT_TRUE(frame); EXPECT_EQ(ImageFrame::FrameComplete, frame->status()); EXPECT_EQ(256, frame->getSkBitmap().width()); EXPECT_EQ(256, frame->getSkBitmap().height()); EXPECT_FALSE(decoder->failed()); }
bool ImageFrameGenerator::decode(size_t index, ImageDecoder** decoder, SkBitmap* bitmap) { TRACE_EVENT2("blink", "ImageFrameGenerator::decode", "width", m_fullSize.width(), "height", m_fullSize.height()); ASSERT(decoder); SharedBuffer* data = 0; bool allDataReceived = false; bool newDecoder = false; m_data.data(&data, &allDataReceived); // Try to create an ImageDecoder if we are not given one. if (!*decoder) { newDecoder = true; if (m_imageDecoderFactory) *decoder = m_imageDecoderFactory->create().leakPtr(); if (!*decoder) *decoder = ImageDecoder::create(*data, ImageSource::AlphaPremultiplied, ImageSource::GammaAndColorProfileApplied).leakPtr(); if (!*decoder) return false; } if (!m_isMultiFrame && newDecoder && allDataReceived) { // If we're using an external memory allocator that means we're decoding // directly into the output memory and we can save one memcpy. ASSERT(m_externalAllocator.get()); (*decoder)->setMemoryAllocator(m_externalAllocator.get()); } (*decoder)->setData(data, allDataReceived); ImageFrame* frame = (*decoder)->frameBufferAtIndex(index); // For multi-frame image decoders, we need to know how many frames are // in that image in order to release the decoder when all frames are // decoded. frameCount() is reliable only if all data is received and set in // decoder, particularly with GIF. if (allDataReceived) m_frameCount = (*decoder)->frameCount(); (*decoder)->setData(0, false); // Unref SharedBuffer from ImageDecoder. (*decoder)->clearCacheExceptFrame(index); (*decoder)->setMemoryAllocator(0); if (!frame || frame->status() == ImageFrame::FrameEmpty) return false; // A cache object is considered complete if we can decode a complete frame. // Or we have received all data. The image might not be fully decoded in // the latter case. const bool isDecodeComplete = frame->status() == ImageFrame::FrameComplete || allDataReceived; SkBitmap fullSizeBitmap = frame->getSkBitmap(); if (!fullSizeBitmap.isNull()) { ASSERT(fullSizeBitmap.width() == m_fullSize.width() && fullSizeBitmap.height() == m_fullSize.height()); setHasAlpha(index, !fullSizeBitmap.isOpaque()); } *bitmap = fullSizeBitmap; return isDecodeComplete; }
TEST(GIFImageDecoderTest, badTerminator) { RefPtr<SharedBuffer> referenceData = readFile("/Source/web/tests/data/radient.gif"); RefPtr<SharedBuffer> testData = readFile("/Source/web/tests/data/radient-bad-terminator.gif"); ASSERT_TRUE(referenceData.get()); ASSERT_TRUE(testData.get()); OwnPtr<GIFImageDecoder> referenceDecoder(createDecoder()); referenceDecoder->setData(referenceData.get(), true); EXPECT_EQ(1u, referenceDecoder->frameCount()); ImageFrame* referenceFrame = referenceDecoder->frameBufferAtIndex(0); ASSERT(referenceFrame); OwnPtr<GIFImageDecoder> testDecoder(createDecoder()); testDecoder->setData(testData.get(), true); EXPECT_EQ(1u, testDecoder->frameCount()); ImageFrame* testFrame = testDecoder->frameBufferAtIndex(0); ASSERT(testFrame); EXPECT_EQ(hashSkBitmap(referenceFrame->getSkBitmap()), hashSkBitmap(testFrame->getSkBitmap())); }
TEST(GIFImageDecoderTest, decodeTwoFrames) { OwnPtr<GIFImageDecoder> decoder = createDecoder(); RefPtr<SharedBuffer> data = readFile("/LayoutTests/fast/images/resources/animated.gif"); ASSERT_TRUE(data.get()); decoder->setData(data.get(), true); EXPECT_EQ(cAnimationLoopOnce, decoder->repetitionCount()); ImageFrame* frame = decoder->frameBufferAtIndex(0); uint32_t generationID0 = frame->getSkBitmap().getGenerationID(); EXPECT_EQ(ImageFrame::FrameComplete, frame->status()); EXPECT_EQ(16, frame->getSkBitmap().width()); EXPECT_EQ(16, frame->getSkBitmap().height()); frame = decoder->frameBufferAtIndex(1); uint32_t generationID1 = frame->getSkBitmap().getGenerationID(); EXPECT_EQ(ImageFrame::FrameComplete, frame->status()); EXPECT_EQ(16, frame->getSkBitmap().width()); EXPECT_EQ(16, frame->getSkBitmap().height()); EXPECT_TRUE(generationID0 != generationID1); EXPECT_EQ(2u, decoder->frameCount()); EXPECT_EQ(cAnimationLoopInfinite, decoder->repetitionCount()); }
static bool decodeBitmap(const void* data, size_t length, SkBitmap* result) { RefPtr<SharedBuffer> buffer = SharedBuffer::create(static_cast<const char*>(data), length); OwnPtr<ImageDecoder> imageDecoder = ImageDecoder::create(*buffer, ImageDecoder::AlphaPremultiplied, ImageDecoder::GammaAndColorProfileIgnored); if (!imageDecoder) return false; imageDecoder->setData(buffer.get(), true); ImageFrame* frame = imageDecoder->frameBufferAtIndex(0); if (!frame) return true; *result = frame->getSkBitmap(); return true; }
TEST_F(DeferredImageDecoderTest, singleFrameImageLoading) { m_status = ImageFrame::FramePartial; m_lazyDecoder->setData(*m_data, false); EXPECT_FALSE(m_lazyDecoder->frameIsCompleteAtIndex(0)); ImageFrame* frame = m_lazyDecoder->frameBufferAtIndex(0); unsigned firstId = frame->getSkBitmap().getGenerationID(); EXPECT_EQ(ImageFrame::FramePartial, frame->status()); EXPECT_TRUE(m_actualDecoder); m_status = ImageFrame::FrameComplete; m_data->append(" ", 1); m_lazyDecoder->setData(*m_data, true); EXPECT_FALSE(m_actualDecoder); EXPECT_TRUE(m_lazyDecoder->frameIsCompleteAtIndex(0)); frame = m_lazyDecoder->frameBufferAtIndex(0); unsigned secondId = frame->getSkBitmap().getGenerationID(); EXPECT_EQ(ImageFrame::FrameComplete, frame->status()); EXPECT_FALSE(m_frameBufferRequestCount); EXPECT_NE(firstId, secondId); EXPECT_EQ(secondId, m_lazyDecoder->frameBufferAtIndex(0)->getSkBitmap().getGenerationID()); }
TEST(GIFImageDecoderTest, parseAndDecode) { OwnPtr<GIFImageDecoder> decoder = createDecoder(); RefPtr<SharedBuffer> data = readFile("/LayoutTests/fast/images/resources/animated.gif"); ASSERT_TRUE(data.get()); decoder->setData(data.get(), true); EXPECT_EQ(cAnimationLoopOnce, decoder->repetitionCount()); // This call will parse the entire file. EXPECT_EQ(2u, decoder->frameCount()); ImageFrame* frame = decoder->frameBufferAtIndex(0); EXPECT_EQ(ImageFrame::FrameComplete, frame->status()); EXPECT_EQ(16, frame->getSkBitmap().width()); EXPECT_EQ(16, frame->getSkBitmap().height()); frame = decoder->frameBufferAtIndex(1); EXPECT_EQ(ImageFrame::FrameComplete, frame->status()); EXPECT_EQ(16, frame->getSkBitmap().width()); EXPECT_EQ(16, frame->getSkBitmap().height()); EXPECT_EQ(cAnimationLoopInfinite, decoder->repetitionCount()); }
PassOwnPtr<ScaledImageFragment> ImageFrameGenerator::decode(ImageDecoder** decoder) { TRACE_EVENT2("webkit", "ImageFrameGenerator::decode", "width", m_fullSize.width(), "height", m_fullSize.height()); ASSERT(decoder); SharedBuffer* data = 0; bool allDataReceived = false; m_data.data(&data, &allDataReceived); // Try to create an ImageDecoder if we are not given one. if (!*decoder) { *decoder = ImageDecoder::create(*data, ImageSource::AlphaPremultiplied, ImageSource::GammaAndColorProfileApplied).leakPtr(); if (!*decoder && m_imageDecoderFactory) *decoder = m_imageDecoderFactory->create().leakPtr(); if (!*decoder) return nullptr; } // TODO: this is very ugly. We need to refactor the way how we can pass a // memory allocator to image decoders. (*decoder)->setMemoryAllocator(&m_allocator); (*decoder)->setData(data, allDataReceived); // If this call returns a newly allocated DiscardablePixelRef, then // ImageFrame::m_bitmap and the contained DiscardablePixelRef are locked. // They will be unlocked when ImageDecoder is destroyed since ImageDecoder // owns the ImageFrame. Partially decoded SkBitmap is thus inserted into the // ImageDecodingStore while locked. ImageFrame* frame = (*decoder)->frameBufferAtIndex(0); (*decoder)->setData(0, false); // Unref SharedBuffer from ImageDecoder. if (!frame || frame->status() == ImageFrame::FrameEmpty) return nullptr; bool isComplete = frame->status() == ImageFrame::FrameComplete; SkBitmap fullSizeBitmap = frame->getSkBitmap(); { MutexLocker lock(m_alphaMutex); m_hasAlpha = !fullSizeBitmap.isOpaque(); } ASSERT(fullSizeBitmap.width() == m_fullSize.width() && fullSizeBitmap.height() == m_fullSize.height()); return ScaledImageFragment::create(m_fullSize, fullSizeBitmap, isComplete); }
void CanvasImageDecoder::OnDataComplete() { OwnPtr<ImageDecoder> decoder = ImageDecoder::create(*buffer_.get(), ImageSource::AlphaPremultiplied, ImageSource::GammaAndColorProfileIgnored); // decoder can be null if the buffer we was empty and we couldn't even guess // what type of image to decode. if (!decoder) { callback_->handleEvent(nullptr); return; } decoder->setData(buffer_.get(), true); if (decoder->failed() || decoder->frameCount() == 0) { callback_->handleEvent(nullptr); return; } RefPtr<CanvasImage> resultImage = CanvasImage::create(); ImageFrame* imageFrame = decoder->frameBufferAtIndex(0); RefPtr<SkImage> skImage = adoptRef(SkImage::NewFromBitmap(imageFrame->getSkBitmap())); resultImage->setImage(skImage.release()); callback_->handleEvent(resultImage.get()); }
PassOwnPtr<ScaledImageFragment> ImageFrameGenerator::decode(size_t index, ImageDecoder** decoder) { TRACE_EVENT2("webkit", "ImageFrameGenerator::decode", "width", m_fullSize.width(), "height", m_fullSize.height()); ASSERT(decoder); SharedBuffer* data = 0; bool allDataReceived = false; m_data.data(&data, &allDataReceived); // Try to create an ImageDecoder if we are not given one. if (!*decoder) { if (m_imageDecoderFactory) *decoder = m_imageDecoderFactory->create().leakPtr(); if (!*decoder) *decoder = ImageDecoder::create(*data, ImageSource::AlphaPremultiplied, ImageSource::GammaAndColorProfileApplied).leakPtr(); if (!*decoder) return nullptr; } // TODO: this is very ugly. We need to refactor the way how we can pass a // memory allocator to image decoders. if (!m_isMultiFrame) (*decoder)->setMemoryAllocator(&m_allocator); (*decoder)->setData(data, allDataReceived); // If this call returns a newly allocated DiscardablePixelRef, then // ImageFrame::m_bitmap and the contained DiscardablePixelRef are locked. // They will be unlocked when ImageDecoder is destroyed since ImageDecoder // owns the ImageFrame. Partially decoded SkBitmap is thus inserted into the // ImageDecodingStore while locked. ImageFrame* frame = (*decoder)->frameBufferAtIndex(index); (*decoder)->setData(0, false); // Unref SharedBuffer from ImageDecoder. (*decoder)->clearCacheExceptFrame(index); if (!frame || frame->status() == ImageFrame::FrameEmpty) return nullptr; const bool isComplete = frame->status() == ImageFrame::FrameComplete; SkBitmap fullSizeBitmap = frame->getSkBitmap(); { MutexLocker lock(m_alphaMutex); if (index >= m_hasAlpha.size()) { const size_t oldSize = m_hasAlpha.size(); m_hasAlpha.resize(index + 1); for (size_t i = oldSize; i < m_hasAlpha.size(); ++i) m_hasAlpha[i] = true; } m_hasAlpha[index] = !fullSizeBitmap.isOpaque(); } ASSERT(fullSizeBitmap.width() == m_fullSize.width() && fullSizeBitmap.height() == m_fullSize.height()); if (isComplete) return ScaledImageFragment::createComplete(m_fullSize, index, fullSizeBitmap); // If the image is partial we need to return a copy. This is to avoid future // decode operations writing to the same bitmap. SkBitmap copyBitmap; fullSizeBitmap.copyTo(©Bitmap, fullSizeBitmap.config(), &m_allocator); return ScaledImageFragment::createPartial(m_fullSize, index, nextGenerationId(), copyBitmap); }