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()); }
// Reproduce a crash that used to happen for a specific file with specific sequence of method calls. TEST(AnimatedWebPTests, reproCrash) { OwnPtr<WEBPImageDecoder> decoder = createDecoder(); RefPtr<SharedBuffer> fullData = readFile("/LayoutTests/fast/images/resources/invalid_vp8_vp8x.webp"); ASSERT_TRUE(fullData.get()); // Parse partial data up to which error in bitstream is not detected. const size_t partialSize = 32768; ASSERT_GT(fullData->size(), partialSize); RefPtr<SharedBuffer> data = SharedBuffer::create(fullData->data(), partialSize); decoder->setData(data.get(), false); EXPECT_EQ(1u, decoder->frameCount()); ImageFrame* frame = decoder->frameBufferAtIndex(0); ASSERT_TRUE(frame); EXPECT_EQ(ImageFrame::FramePartial, frame->status()); EXPECT_FALSE(decoder->failed()); // Parse full data now. The error in bitstream should now be detected. decoder->setData(fullData.get(), true); EXPECT_EQ(1u, decoder->frameCount()); frame = decoder->frameBufferAtIndex(0); ASSERT_TRUE(frame); EXPECT_EQ(ImageFrame::FramePartial, frame->status()); EXPECT_EQ(cAnimationLoopOnce, decoder->repetitionCount()); EXPECT_TRUE(decoder->failed()); }
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); }
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, 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())); }
bool ImageSource::frameHasAlphaAtIndex(size_t index) { #ifdef ANDROID_ANIMATED_GIF if (m_decoder.m_gifDecoder) { ImageFrame* buffer = m_decoder.m_gifDecoder->frameBufferAtIndex(index); if (!buffer || buffer->status() == ImageFrame::FrameEmpty) return false; return buffer->hasAlpha(); } #else SkASSERT(0 == index); #endif if (NULL == m_decoder.m_image) return true; // if we're not sure, assume the worse-case const PrivateAndroidImageSourceRec& decoder = *m_decoder.m_image; // if we're 16bit, we know even without all the data available if (decoder.bitmap().getConfig() == SkBitmap::kRGB_565_Config) return false; if (!decoder.fAllDataReceived) return true; // if we're not sure, assume the worse-case return !decoder.bitmap().isOpaque(); }
bool DeferredImageDecoder::createFrameAtIndex(size_t index, SkBitmap* bitmap) { prepareLazyDecodedFrames(); if (index < m_frameData.size()) { // ImageFrameGenerator has the latest known alpha state. There will // be a performance boost if this frame is opaque. *bitmap = createBitmap(index); if (m_frameGenerator->hasAlpha(index)) { m_frameData[index].m_hasAlpha = true; bitmap->setAlphaType(kPremul_SkAlphaType); } else { m_frameData[index].m_hasAlpha = false; bitmap->setAlphaType(kOpaque_SkAlphaType); } m_frameData[index].m_frameBytes = m_size.area() * sizeof(ImageFrame::PixelData); return true; } if (m_actualDecoder) { ImageFrame* buffer = m_actualDecoder->frameBufferAtIndex(index); if (!buffer || buffer->status() == ImageFrame::FrameEmpty) return false; *bitmap = buffer->bitmap(); return true; } return false; }
TEST(GIFImageDecoderTest, parseAndDecodeByteByByte) { OwnPtr<GIFImageDecoder> decoder = createDecoder(); RefPtr<SharedBuffer> data = readFile("/LayoutTests/fast/images/resources/animated-gif-with-offsets.gif"); ASSERT_TRUE(data.get()); size_t frameCount = 0; size_t framesDecoded = 0; // Pass data to decoder byte by byte. for (size_t length = 1; length <= data->size(); ++length) { RefPtr<SharedBuffer> tempData = SharedBuffer::create(data->data(), length); decoder->setData(tempData.get(), length == data->size()); EXPECT_LE(frameCount, decoder->frameCount()); frameCount = decoder->frameCount(); ImageFrame* frame = decoder->frameBufferAtIndex(frameCount - 1); if (frame && frame->status() == ImageFrame::FrameComplete && framesDecoded < frameCount) ++framesDecoded; } EXPECT_EQ(5u, decoder->frameCount()); EXPECT_EQ(5u, framesDecoded); EXPECT_EQ(cAnimationLoopInfinite, decoder->repetitionCount()); }
TEST(AnimatedWebPTests, truncatedInBetweenFrame) { OwnPtr<WEBPImageDecoder> decoder = createDecoder(); RefPtr<SharedBuffer> fullData = readFile("/LayoutTests/fast/images/resources/invalid-animated-webp4.webp"); ASSERT_TRUE(fullData.get()); RefPtr<SharedBuffer> data = SharedBuffer::create(fullData->data(), fullData->size() - 1); decoder->setData(data.get(), false); ImageFrame* frame = decoder->frameBufferAtIndex(1); ASSERT_TRUE(frame); EXPECT_EQ(ImageFrame::FrameComplete, frame->status()); frame = decoder->frameBufferAtIndex(2); ASSERT_TRUE(frame); EXPECT_EQ(ImageFrame::FramePartial, frame->status()); EXPECT_TRUE(decoder->failed()); }
bool ImageSource::frameIsCompleteAtIndex(size_t index) { if (!m_decoder) return false; ImageFrame* buffer = m_decoder->frameBufferAtIndex(index); return buffer && buffer->status() == ImageFrame::FrameComplete; }
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); }
TEST(AnimatedWebPTests, truncatedLastFrame) { OwnPtr<WEBPImageDecoder> decoder = createDecoder(); RefPtr<SharedBuffer> data = readFile("/LayoutTests/fast/images/resources/invalid-animated-webp2.webp"); ASSERT_TRUE(data.get()); decoder->setData(data.get(), true); size_t frameCount = 8; EXPECT_EQ(frameCount, decoder->frameCount()); ImageFrame* frame = decoder->frameBufferAtIndex(0); ASSERT_TRUE(frame); EXPECT_EQ(ImageFrame::FrameComplete, frame->status()); EXPECT_FALSE(decoder->failed()); frame = decoder->frameBufferAtIndex(frameCount - 1); ASSERT_TRUE(frame); EXPECT_EQ(ImageFrame::FramePartial, frame->status()); EXPECT_TRUE(decoder->failed()); frame = decoder->frameBufferAtIndex(0); ASSERT_TRUE(frame); EXPECT_EQ(ImageFrame::FrameComplete, frame->status()); }
bool ImageSource::frameIsCompleteAtIndex(size_t index) { #ifdef ANDROID_ANIMATED_GIF if (m_decoder.m_gifDecoder) { ImageFrame* buffer = m_decoder.m_gifDecoder->frameBufferAtIndex(index); return buffer && buffer->status() == ImageFrame::FrameComplete; } #else SkASSERT(0 == index); #endif return m_decoder.m_image && m_decoder.m_image->fAllDataReceived; }
bool GraphicsContext3D::ImageExtractor::extractImage(bool premultiplyAlpha, bool ignoreGammaAndColorProfile) { if (!m_image) return false; m_skiaImage = m_image->nativeImageForCurrentFrame(); m_alphaOp = AlphaDoNothing; bool hasAlpha = m_skiaImage ? !m_skiaImage->bitmap().isOpaque() : true; if ((!m_skiaImage || ignoreGammaAndColorProfile || (hasAlpha && !premultiplyAlpha)) && m_image->data()) { // Attempt to get raw unpremultiplied image data. OwnPtr<ImageDecoder> decoder(ImageDecoder::create( *(m_image->data()), ImageSource::AlphaNotPremultiplied, ignoreGammaAndColorProfile ? ImageSource::GammaAndColorProfileIgnored : ImageSource::GammaAndColorProfileApplied)); if (!decoder) return false; decoder->setData(m_image->data(), true); if (!decoder->frameCount()) return false; ImageFrame* frame = decoder->frameBufferAtIndex(0); if (!frame || frame->status() != ImageFrame::FrameComplete) return false; hasAlpha = frame->hasAlpha(); m_nativeImage = adoptPtr(frame->asNewNativeImage()); if (!m_nativeImage.get() || !m_nativeImage->isDataComplete() || !m_nativeImage->bitmap().width() || !m_nativeImage->bitmap().height()) return false; SkBitmap::Config skiaConfig = m_nativeImage->bitmap().config(); if (skiaConfig != SkBitmap::kARGB_8888_Config) return false; m_skiaImage = m_nativeImage.get(); if (hasAlpha && premultiplyAlpha) m_alphaOp = AlphaDoPremultiply; } else if (!premultiplyAlpha && hasAlpha) { // 1. For texImage2D with HTMLVideoElment input, assume no PremultiplyAlpha had been applied and the alpha value for each pixel is 0xFF // which is true at present and may be changed in the future and needs adjustment accordingly. // 2. For texImage2D with HTMLCanvasElement input in which Alpha is already Premultiplied in this port, // do AlphaDoUnmultiply if UNPACK_PREMULTIPLY_ALPHA_WEBGL is set to false. if (m_imageHtmlDomSource != HtmlDomVideo) m_alphaOp = AlphaDoUnmultiply; } if (!m_skiaImage) return false; m_imageSourceFormat = SK_B32_SHIFT ? DataFormatRGBA8 : DataFormatBGRA8; m_imageWidth = m_skiaImage->bitmap().width(); m_imageHeight = m_skiaImage->bitmap().height(); if (!m_imageWidth || !m_imageHeight) return false; m_imageSourceUnpackAlignment = 0; m_skiaImage->bitmap().lockPixels(); m_imagePixelData = m_skiaImage->bitmap().getPixels(); return true; }
// Test if a BMP decoder returns a proper error while decoding an empty image. TEST(BMPImageDecoderTest, emptyImage) { const char* bmpFile = "/LayoutTests/fast/images/resources/0x0.bmp"; // 0x0 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::FrameEmpty, frame->status()); EXPECT_TRUE(decoder->failed()); }
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()); }
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()); }
ImageFrame* BMPImageDecoder::frameBufferAtIndex(size_t index) { if (index) return 0; if (m_frameBufferCache.isEmpty()) { m_frameBufferCache.resize(1); m_frameBufferCache.first().setPremultiplyAlpha(m_premultiplyAlpha); } ImageFrame* buffer = &m_frameBufferCache.first(); if (buffer->status() != ImageFrame::FrameComplete) decode(false); return buffer; }
ImageFrame* ImageDecoder::frameBufferAtIndex(size_t index) { if (index >= frameCount()) return 0; ImageFrame* frame = &m_frameBufferCache[index]; if (frame->status() != ImageFrame::FrameComplete) { PlatformInstrumentation::willDecodeImage(filenameExtension()); decode(index); PlatformInstrumentation::didDecodeImage(); } frame->notifyBitmapIfPixelsChanged(); return frame; }
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()); }
ImageFrame* BMPImageDecoder::frameBufferAtIndex(size_t index) { if (index) return 0; if (m_frameBufferCache.isEmpty()) { m_frameBufferCache.resize(1); m_frameBufferCache.first().setPremultiplyAlpha(m_premultiplyAlpha); } ImageFrame* buffer = &m_frameBufferCache.first(); if (buffer->status() != ImageFrame::FrameComplete) { PlatformInstrumentation::willDecodeImage("BMP"); decode(false); PlatformInstrumentation::didDecodeImage(); } return buffer; }
float ImageSource::frameDurationAtIndex(size_t index) { if (!m_decoder) return 0; ImageFrame* buffer = m_decoder->frameBufferAtIndex(index); if (!buffer || buffer->status() == ImageFrame::FrameEmpty) return 0; // Many annoying ads specify a 0 duration to make an image flash as quickly as possible. // We follow Firefox's behavior and use a duration of 100 ms for any frames that specify // a duration of <= 10 ms. See <rdar://problem/7689300> and <http://webkit.org/b/36082> // for more information. const float duration = buffer->duration() / 1000.0f; if (duration < 0.011f) return 0.100f; return duration; }
NativeImagePtr ImageSource::createFrameAtIndex(size_t index) { if (!m_decoder) return 0; ImageFrame* buffer = m_decoder->frameBufferAtIndex(index); if (!buffer || buffer->status() == ImageFrame::FrameEmpty) return 0; // Zero-height images can cause problems for some ports. If we have an // empty image dimension, just bail. if (size().isEmpty()) return 0; // Return the buffer contents as a native image. For some ports, the data // is already in a native container, and this just increments its refcount. return buffer->asNewNativeImage(); }
PassRefPtr<SkImage> DeferredImageDecoder::createFrameAtIndex(size_t index) { prepareLazyDecodedFrames(); if (index < m_frameData.size()) { // ImageFrameGenerator has the latest known alpha state. There will be a // performance boost if this frame is opaque. FrameData* frameData = &m_frameData[index]; frameData->m_hasAlpha = m_frameGenerator->hasAlpha(index); frameData->m_frameBytes = m_size.area() * sizeof(ImageFrame::PixelData); return createImage(index, !frameData->m_hasAlpha); } if (!m_actualDecoder) return nullptr; ImageFrame* buffer = m_actualDecoder->frameBufferAtIndex(index); if (!buffer || buffer->status() == ImageFrame::FrameEmpty) return nullptr; return adoptRef(SkImage::NewFromBitmap(buffer->bitmap())); }
SkBitmapRef* ImageSource::createFrameAtIndex(size_t index) { #ifdef ANDROID_ANIMATED_GIF if (m_decoder.m_gifDecoder) { ImageFrame* buffer = m_decoder.m_gifDecoder->frameBufferAtIndex(index); if (!buffer || buffer->status() == ImageFrame::FrameEmpty) return 0; SkBitmap& bitmap = buffer->bitmap(); SkPixelRef* pixelRef = bitmap.pixelRef(); if (pixelRef) pixelRef->setURI(m_decoder.m_url); return new SkBitmapRef(bitmap); } #else SkASSERT(index == 0); #endif SkASSERT(m_decoder.m_image != NULL); m_decoder.m_image->ref(); return m_decoder.m_image; }
float ImageSource::frameDurationAtIndex(size_t index) { float duration = 0; #ifdef ANDROID_ANIMATED_GIF if (m_decoder.m_gifDecoder) { ImageFrame* buffer = m_decoder.m_gifDecoder->frameBufferAtIndex(index); if (!buffer || buffer->status() == ImageFrame::FrameEmpty) return 0; duration = buffer->duration() / 1000.0f; } #else SkASSERT(index == 0); #endif // Many annoying ads specify a 0 duration to make an image flash as quickly as possible. // We follow Firefox's behavior and use a duration of 100 ms for any frames that specify // a duration of <= 10 ms. See gfxImageFrame::GetTimeout in Gecko or Radar 4051389 for more. if (duration <= 0.010f) duration = 0.100f; return duration; }
TEST_F(AnimatedWebPTests, parseAndDecodeByteByByte) { const struct TestImage { const char* filename; unsigned frameCount; int repetitionCount; } testImages[] = { { "/LayoutTests/fast/images/resources/webp-animated.webp", 3u, cAnimationLoopInfinite }, { "/LayoutTests/fast/images/resources/webp-animated-icc-xmp.webp", 13u, 32000 }, }; for (size_t i = 0; i < ARRAY_SIZE(testImages); ++i) { OwnPtr<WEBPImageDecoder> decoder = createDecoder(); RefPtr<SharedBuffer> data = readFile(testImages[i].filename); ASSERT_TRUE(data.get()); size_t frameCount = 0; size_t framesDecoded = 0; // Pass data to decoder byte by byte. for (size_t length = 1; length <= data->size(); ++length) { RefPtr<SharedBuffer> tempData = SharedBuffer::create(data->data(), length); decoder->setData(tempData.get(), length == data->size()); EXPECT_LE(frameCount, decoder->frameCount()); frameCount = decoder->frameCount(); ImageFrame* frame = decoder->frameBufferAtIndex(frameCount - 1); if (frame && frame->status() == ImageFrame::FrameComplete && framesDecoded < frameCount) ++framesDecoded; } EXPECT_EQ(testImages[i].frameCount, decoder->frameCount()); EXPECT_EQ(testImages[i].frameCount, framesDecoded); EXPECT_EQ(testImages[i].repetitionCount, decoder->repetitionCount()); } }
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); }