| /* |
| * Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) |
| * Copyright (C) 2004, 2005, 2006, 2008, 2015 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "BitmapImage.h" |
| |
| #include "FloatRect.h" |
| #include "GeometryUtilities.h" |
| #include "GraphicsContext.h" |
| #include "ImageBuffer.h" |
| #include "ImageObserver.h" |
| #include "IntRect.h" |
| #include "Logging.h" |
| #include "Settings.h" |
| #include "Timer.h" |
| #include <wtf/Vector.h> |
| #include <wtf/text/TextStream.h> |
| #include <wtf/text/WTFString.h> |
| |
| #if USE(CG) |
| #include <pal/spi/cg/CoreGraphicsSPI.h> |
| #endif |
| |
| namespace WebCore { |
| |
| BitmapImage::BitmapImage(ImageObserver* observer) |
| : Image(observer) |
| , m_source(ImageSource::create(this)) |
| { |
| } |
| |
| BitmapImage::BitmapImage(Ref<NativeImage>&& image) |
| : m_source(ImageSource::create(WTFMove(image))) |
| { |
| } |
| |
| BitmapImage::~BitmapImage() |
| { |
| invalidatePlatformData(); |
| clearTimer(); |
| m_source->clearImage(); |
| m_source->stopAsyncDecodingQueue(); |
| } |
| |
| void BitmapImage::updateFromSettings(const Settings& settings) |
| { |
| m_allowSubsampling = settings.imageSubsamplingEnabled(); |
| m_allowAnimatedImageAsyncDecoding = settings.animatedImageAsyncDecodingEnabled(); |
| m_showDebugBackground = settings.showDebugBorders(); |
| } |
| |
| void BitmapImage::destroyDecodedData(bool destroyAll) |
| { |
| LOG(Images, "BitmapImage::%s - %p - url: %s", __FUNCTION__, this, sourceURL().string().utf8().data()); |
| |
| if (!destroyAll) { |
| // Destroy all the frames between frame0 and m_currentFrame. |
| m_source->destroyDecodedData(1, m_currentFrame); |
| } else if (!canDestroyDecodedData()) { |
| // Destroy all the frames except frame0 and m_currentFrame. |
| m_source->destroyDecodedData(1, m_currentFrame); |
| m_source->destroyDecodedData(m_currentFrame + 1, frameCount()); |
| } else { |
| m_source->destroyDecodedData(0, frameCount()); |
| m_currentFrameDecodingStatus = DecodingStatus::Invalid; |
| } |
| |
| // There's no need to throw away the decoder unless we're explicitly asked |
| // to destroy all of the frames. |
| if (!destroyAll || m_source->hasAsyncDecodingQueue()) |
| m_source->clearFrameBufferCache(m_currentFrame); |
| else |
| m_source->resetData(data()); |
| |
| invalidatePlatformData(); |
| } |
| |
| void BitmapImage::destroyDecodedDataIfNecessary(bool destroyAll) |
| { |
| // If we have decoded frames but there is no encoded data, we shouldn't destroy |
| // the decoded image since we won't be able to reconstruct it later. |
| if (!data() && frameCount()) |
| return; |
| |
| if (m_source->decodedSize() < LargeAnimationCutoff) |
| return; |
| |
| destroyDecodedData(destroyAll); |
| } |
| |
| EncodedDataStatus BitmapImage::dataChanged(bool allDataReceived) |
| { |
| if (m_source->decodedSize() && !canUseAsyncDecodingForLargeImages()) |
| m_source->destroyIncompleteDecodedData(); |
| |
| m_currentFrameDecodingStatus = DecodingStatus::Invalid; |
| return m_source->dataChanged(data(), allDataReceived); |
| } |
| |
| void BitmapImage::setCurrentFrameDecodingStatusIfNecessary(DecodingStatus decodingStatus) |
| { |
| // When new data is received, m_currentFrameDecodingStatus is set to DecodingStatus::Invalid |
| // to force decoding the frame when it's drawn. m_currentFrameDecodingStatus should not be |
| // changed in this case till draw() is called and sets its value to DecodingStatus::Decoding. |
| if (m_currentFrameDecodingStatus != DecodingStatus::Decoding) |
| return; |
| m_currentFrameDecodingStatus = decodingStatus; |
| } |
| |
| RefPtr<NativeImage> BitmapImage::frameImageAtIndexCacheIfNeeded(size_t index, SubsamplingLevel subsamplingLevel) |
| { |
| if (!frameHasFullSizeNativeImageAtIndex(index, subsamplingLevel)) { |
| LOG(Images, "BitmapImage::%s - %p - url: %s [subsamplingLevel was %d, resampling]", __FUNCTION__, this, sourceURL().string().utf8().data(), static_cast<int>(frameSubsamplingLevelAtIndex(index))); |
| invalidatePlatformData(); |
| } |
| return m_source->frameImageAtIndexCacheIfNeeded(index, subsamplingLevel); |
| } |
| |
| RefPtr<NativeImage> BitmapImage::nativeImage(const DestinationColorSpace&) |
| { |
| // FIXME: Handle the case when the requested colorSpace is not equal to BitmapImage::colorSpace(). |
| return frameImageAtIndexCacheIfNeeded(0, SubsamplingLevel::Default); |
| } |
| |
| RefPtr<NativeImage> BitmapImage::nativeImageForCurrentFrame() |
| { |
| return frameImageAtIndexCacheIfNeeded(m_currentFrame, SubsamplingLevel::Default); |
| } |
| |
| RefPtr<NativeImage> BitmapImage::preTransformedNativeImageForCurrentFrame(bool respectOrientation) |
| { |
| auto image = nativeImageForCurrentFrame(); |
| if (!image) |
| return image; |
| |
| auto orientation = respectOrientation ? orientationForCurrentFrame() : ImageOrientation(ImageOrientation::None); |
| auto correctedSize = m_source->densityCorrectedSize(orientation); |
| if (orientation == ImageOrientation::None && !correctedSize) |
| return image; |
| |
| auto correctedSizeFloat = correctedSize ? FloatSize(correctedSize.value()) : size(); |
| auto buffer = ImageBuffer::create(correctedSizeFloat, RenderingPurpose::Unspecified, 1, DestinationColorSpace::SRGB(), PixelFormat::BGRA8); |
| if (!buffer) |
| return image; |
| |
| auto sourceSize = this->sourceSize(); |
| |
| FloatRect destRect(FloatPoint(), correctedSizeFloat); |
| FloatRect sourceRect(FloatPoint(), sourceSize); |
| |
| buffer->context().drawNativeImage(*image, sourceSize, destRect, sourceRect, { orientation }); |
| return ImageBuffer::sinkIntoNativeImage(WTFMove(buffer)); |
| } |
| |
| #if USE(CG) |
| RefPtr<NativeImage> BitmapImage::nativeImageOfSize(const IntSize& size) |
| { |
| size_t count = frameCount(); |
| |
| for (size_t i = 0; i < count; ++i) { |
| auto image = frameImageAtIndexCacheIfNeeded(i, SubsamplingLevel::Default); |
| if (image && image->size() == size) |
| return image; |
| } |
| |
| // Fallback to the first frame image if we can't find the right size |
| return frameImageAtIndexCacheIfNeeded(0, SubsamplingLevel::Default); |
| } |
| |
| Vector<Ref<NativeImage>> BitmapImage::framesNativeImages() |
| { |
| Vector<Ref<NativeImage>> images; |
| size_t count = frameCount(); |
| |
| for (size_t i = 0; i < count; ++i) { |
| if (auto image = frameImageAtIndexCacheIfNeeded(i)) |
| images.append(*image); |
| } |
| |
| return images; |
| } |
| #endif |
| |
| #if ASSERT_ENABLED |
| bool BitmapImage::notSolidColor() |
| { |
| return size().width() != 1 || size().height() != 1 || frameCount() > 1; |
| } |
| #endif // ASSERT_ENABLED |
| |
| static inline void drawNativeImage(NativeImage& image, GraphicsContext& context, const FloatRect& destRect, const FloatRect& srcRect, const IntSize& srcSize, const ImagePaintingOptions& options) |
| { |
| // Subsampling may have given us an image that is smaller than size(). |
| IntSize subsampledImageSize = image.size(); |
| if (options.orientation().usesWidthAsHeight()) |
| subsampledImageSize = subsampledImageSize.transposedSize(); |
| |
| // srcRect is in the coordinates of the unsubsampled image, so we have to map it to the subsampled image. |
| FloatRect adjustedSrcRect = srcRect; |
| if (subsampledImageSize != srcSize) |
| adjustedSrcRect = mapRect(srcRect, FloatRect({ }, srcSize), FloatRect({ }, subsampledImageSize)); |
| |
| context.drawNativeImage(image, subsampledImageSize, destRect, adjustedSrcRect, options); |
| } |
| |
| ImageDrawResult BitmapImage::draw(GraphicsContext& context, const FloatRect& destRect, const FloatRect& requestedSrcRect, const ImagePaintingOptions& options) |
| { |
| if (destRect.isEmpty() || requestedSrcRect.isEmpty()) |
| return ImageDrawResult::DidNothing; |
| |
| auto srcRect = requestedSrcRect; |
| auto preferredSize = size(); |
| auto srcSize = sourceSize(); |
| |
| if (preferredSize != srcSize) |
| srcRect.scale(srcSize.width() / preferredSize.width(), srcSize.height() / preferredSize.height()); |
| |
| auto scaleFactorForDrawing = context.scaleFactorForDrawing(destRect, srcRect); |
| auto sizeForDrawing = expandedIntSize(srcSize * scaleFactorForDrawing); |
| ImageDrawResult result = ImageDrawResult::DidDraw; |
| |
| m_currentSubsamplingLevel = m_allowSubsampling ? subsamplingLevelForScaleFactor(context, scaleFactorForDrawing) : SubsamplingLevel::Default; |
| LOG(Images, "BitmapImage::%s - %p - url: %s [subsamplingLevel = %d scaleFactorForDrawing = (%.4f, %.4f)]", __FUNCTION__, this, sourceURL().string().utf8().data(), static_cast<int>(m_currentSubsamplingLevel), scaleFactorForDrawing.width(), scaleFactorForDrawing.height()); |
| |
| RefPtr<NativeImage> image; |
| if (options.decodingMode() == DecodingMode::Asynchronous) { |
| ASSERT(!canAnimate()); |
| ASSERT(!m_currentFrame || m_animationFinished); |
| |
| bool frameIsCompatible = frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(m_currentFrame, m_currentSubsamplingLevel, DecodingOptions(sizeForDrawing)); |
| bool frameIsBeingDecoded = frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(m_currentFrame, DecodingOptions(sizeForDrawing)); |
| |
| // If the current frame is incomplete, a new request for decoding this frame has to be made even if |
| // it is currently being decoded. New data may have been received since the previous request was made. |
| if ((!frameIsCompatible && !frameIsBeingDecoded) || m_currentFrameDecodingStatus == DecodingStatus::Invalid) { |
| LOG(Images, "BitmapImage::%s - %p - url: %s [requesting large async decoding]", __FUNCTION__, this, sourceURL().string().utf8().data()); |
| m_source->requestFrameAsyncDecodingAtIndex(m_currentFrame, m_currentSubsamplingLevel, sizeForDrawing); |
| m_currentFrameDecodingStatus = DecodingStatus::Decoding; |
| } |
| |
| if (m_currentFrameDecodingStatus == DecodingStatus::Decoding) |
| result = ImageDrawResult::DidRequestDecoding; |
| |
| if (!frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(m_currentFrame, m_currentSubsamplingLevel, DecodingOptions(DecodingMode::Asynchronous))) { |
| if (m_showDebugBackground) |
| fillWithSolidColor(context, destRect, Color::yellow.colorWithAlphaByte(128), options.compositeOperator()); |
| return result; |
| } |
| |
| image = frameImageAtIndex(m_currentFrame); |
| LOG(Images, "BitmapImage::%s - %p - url: %s [a decoded frame will be used for asynchronous drawing]", __FUNCTION__, this, sourceURL().string().utf8().data()); |
| } else { |
| StartAnimationStatus status = internalStartAnimation(); |
| ASSERT_IMPLIES(status == StartAnimationStatus::DecodingActive, (!m_currentFrame && !m_repetitionsComplete) || frameHasFullSizeNativeImageAtIndex(m_currentFrame, m_currentSubsamplingLevel)); |
| |
| if (status == StartAnimationStatus::DecodingActive && m_showDebugBackground) { |
| fillWithSolidColor(context, destRect, Color::yellow.colorWithAlphaByte(128), options.compositeOperator()); |
| return result; |
| } |
| |
| // If the decodingMode changes from asynchronous to synchronous and new data is received, |
| // the current incomplete decoded frame has to be destroyed. |
| if (m_currentFrameDecodingStatus == DecodingStatus::Invalid) |
| m_source->destroyIncompleteDecodedData(); |
| |
| bool frameIsCompatible = frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(m_currentFrame, m_currentSubsamplingLevel, DecodingOptions(sizeForDrawing)); |
| bool frameIsBeingDecoded = frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(m_currentFrame, DecodingOptions(DecodingMode::Asynchronous)); |
| |
| if (frameIsCompatible) { |
| image = frameImageAtIndex(m_currentFrame); |
| LOG(Images, "BitmapImage::%s - %p - url: %s [a decoded frame will reused for synchronous drawing]", __FUNCTION__, this, sourceURL().string().utf8().data()); |
| } else if (frameIsBeingDecoded) { |
| // FIXME: instead of showing the yellow rectangle and returning we need to wait for this frame to finish decoding. |
| if (m_showDebugBackground) { |
| fillWithSolidColor(context, destRect, Color::yellow.colorWithAlphaByte(128), options.compositeOperator()); |
| LOG(Images, "BitmapImage::%s - %p - url: %s [waiting for async decoding to finish]", __FUNCTION__, this, sourceURL().string().utf8().data()); |
| } |
| return ImageDrawResult::DidRequestDecoding; |
| } else { |
| image = frameImageAtIndexCacheIfNeeded(m_currentFrame, m_currentSubsamplingLevel); |
| LOG(Images, "BitmapImage::%s - %p - url: %s [an image frame will be decoded synchronously]", __FUNCTION__, this, sourceURL().string().utf8().data()); |
| } |
| |
| if (!image) // If it's too early we won't have an image yet. |
| return ImageDrawResult::DidNothing; |
| |
| if (m_currentFrameDecodingStatus != DecodingStatus::Complete) |
| ++m_decodeCountForTesting; |
| } |
| |
| ASSERT(image); |
| Color color = singlePixelSolidColor(); |
| if (color.isValid()) { |
| fillWithSolidColor(context, destRect, color, options.compositeOperator()); |
| return result; |
| } |
| |
| auto orientation = options.orientation(); |
| if (orientation == ImageOrientation::FromImage) { |
| orientation = frameOrientationAtIndex(m_currentFrame); |
| drawNativeImage(*image, context, destRect, srcRect, IntSize(sourceSize(orientation)), { options, orientation }); |
| } else |
| drawNativeImage(*image, context, destRect, srcRect, IntSize(sourceSize(orientation)), options); |
| |
| m_currentFrameDecodingStatus = frameDecodingStatusAtIndex(m_currentFrame); |
| |
| if (imageObserver()) |
| imageObserver()->didDraw(*this); |
| |
| return result; |
| } |
| |
| void BitmapImage::drawPattern(GraphicsContext& ctxt, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& transform, const FloatPoint& phase, const FloatSize& spacing, const ImagePaintingOptions& options) |
| { |
| if (tileRect.isEmpty()) |
| return; |
| |
| if (!ctxt.drawLuminanceMask()) { |
| // If new data is received, the current incomplete decoded frame has to be destroyed. |
| if (m_currentFrameDecodingStatus == DecodingStatus::Invalid) |
| m_source->destroyIncompleteDecodedData(); |
| |
| Image::drawPattern(ctxt, destRect, tileRect, transform, phase, spacing, { options, ImageOrientation::FromImage }); |
| m_currentFrameDecodingStatus = frameDecodingStatusAtIndex(m_currentFrame); |
| return; |
| } |
| |
| if (!m_cachedImage) { |
| auto buffer = ctxt.createAlignedImageBuffer(expandedIntSize(tileRect.size())); |
| if (!buffer) |
| return; |
| |
| ImageObserver* observer = imageObserver(); |
| |
| // Temporarily reset image observer, we don't want to receive any changeInRect() calls due to this relayout. |
| setImageObserver(nullptr); |
| |
| draw(buffer->context(), tileRect, tileRect, { options, DecodingMode::Synchronous, ImageOrientation::FromImage }); |
| |
| setImageObserver(observer); |
| buffer->convertToLuminanceMask(); |
| |
| m_cachedImage = ImageBuffer::sinkIntoImage(WTFMove(buffer), PreserveResolution::Yes); |
| if (!m_cachedImage) |
| return; |
| } |
| |
| ctxt.setDrawLuminanceMask(false); |
| m_cachedImage->drawPattern(ctxt, destRect, tileRect, transform, phase, spacing, { options, ImageOrientation::FromImage }); |
| } |
| |
| bool BitmapImage::shouldAnimate() const |
| { |
| return repetitionCount() && !m_animationFinished && imageObserver(); |
| } |
| |
| bool BitmapImage::canAnimate() const |
| { |
| return shouldAnimate() && frameCount() > 1; |
| } |
| |
| bool BitmapImage::canUseAsyncDecodingForLargeImages() const |
| { |
| return !canAnimate() && m_source->canUseAsyncDecoding(); |
| } |
| |
| bool BitmapImage::shouldUseAsyncDecodingForAnimatedImages() const |
| { |
| return canAnimate() && m_allowAnimatedImageAsyncDecoding && (shouldUseAsyncDecodingForTesting() || m_source->canUseAsyncDecoding()); |
| } |
| |
| void BitmapImage::clearTimer() |
| { |
| m_frameTimer = nullptr; |
| } |
| |
| void BitmapImage::startTimer(Seconds delay) |
| { |
| ASSERT(!m_frameTimer); |
| m_frameTimer = makeUnique<Timer>(*this, &BitmapImage::advanceAnimation); |
| m_frameTimer->startOneShot(delay); |
| } |
| |
| SubsamplingLevel BitmapImage::subsamplingLevelForScaleFactor(GraphicsContext& context, const FloatSize& scaleFactor) |
| { |
| #if USE(CG) |
| // Never use subsampled images for drawing into PDF contexts. |
| if (context.hasPlatformContext() && CGContextGetType(context.platformContext()) == kCGContextTypePDF) |
| return SubsamplingLevel::Default; |
| |
| float scale = std::min(float(1), std::max(scaleFactor.width(), scaleFactor.height())); |
| if (!(scale > 0 && scale <= 1)) |
| return SubsamplingLevel::Default; |
| |
| int result = std::ceil(std::log2(1 / scale)); |
| return static_cast<SubsamplingLevel>(std::min(result, static_cast<int>(m_source->maximumSubsamplingLevel()))); |
| #else |
| UNUSED_PARAM(context); |
| UNUSED_PARAM(scaleFactor); |
| return SubsamplingLevel::Default; |
| #endif |
| } |
| |
| bool BitmapImage::canDestroyDecodedData() |
| { |
| // Animated images should preserve the current frame till the next one finishes decoding. |
| if (m_source->hasAsyncDecodingQueue()) |
| return false; |
| |
| // Small image should be decoded synchronously. Deleting its decoded frame is fine. |
| if (!canUseAsyncDecodingForLargeImages()) |
| return true; |
| |
| return !imageObserver() || imageObserver()->canDestroyDecodedData(*this); |
| } |
| |
| BitmapImage::StartAnimationStatus BitmapImage::internalStartAnimation() |
| { |
| LOG_WITH_STREAM(Images, stream << "BitmapImage " << this << " internalStartAnimation"); |
| |
| if (!canAnimate()) |
| return StartAnimationStatus::CannotStart; |
| |
| if (m_frameTimer) |
| return StartAnimationStatus::TimerActive; |
| |
| // Don't start a new animation until we draw the frame that is currently being decoded. |
| size_t nextFrame = (m_currentFrame + 1) % frameCount(); |
| if (frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(nextFrame, DecodingOptions(DecodingMode::Asynchronous))) { |
| LOG(Images, "BitmapImage::%s - %p - url: %s [nextFrame = %ld is being decoded]", __FUNCTION__, this, sourceURL().string().utf8().data(), nextFrame); |
| return StartAnimationStatus::DecodingActive; |
| } |
| |
| if (m_currentFrame >= frameCount() - 1) { |
| // Don't advance past the last frame if we haven't decoded the whole image |
| // yet and our repetition count is potentially unset. The repetition count |
| // in a GIF can potentially come after all the rest of the image data, so |
| // wait on it. |
| if (!m_source->isAllDataReceived() && repetitionCount() == RepetitionCountOnce) |
| return StartAnimationStatus::IncompleteData; |
| |
| ++m_repetitionsComplete; |
| |
| // Check for the end of animation. |
| if (repetitionCount() != RepetitionCountInfinite && m_repetitionsComplete >= repetitionCount()) { |
| m_animationFinished = true; |
| destroyDecodedDataIfNecessary(false); |
| return StartAnimationStatus::CannotStart; |
| } |
| |
| destroyDecodedDataIfNecessary(true); |
| } |
| |
| // Don't advance the animation to an incomplete frame. |
| if (!m_source->isAllDataReceived() && !frameIsCompleteAtIndex(nextFrame)) |
| return StartAnimationStatus::IncompleteData; |
| |
| MonotonicTime time = MonotonicTime::now(); |
| |
| // Handle initial state. |
| if (!m_desiredFrameStartTime) |
| m_desiredFrameStartTime = time; |
| |
| // Setting 'm_desiredFrameStartTime' to 'time' means we are late; otherwise we are early. |
| m_desiredFrameStartTime = std::max(time, m_desiredFrameStartTime + Seconds { frameDurationAtIndex(m_currentFrame) }); |
| |
| // Request async decoding for nextFrame only if this is required. If nextFrame is not in the frameCache, |
| // it will be decoded on a separate work queue. When decoding nextFrame finishes, we will be notified |
| // through the callback newFrameNativeImageAvailableAtIndex(). Otherwise, advanceAnimation() will be called |
| // when the timer fires and m_currentFrame will be advanced to nextFrame since it is not being decoded. |
| if (shouldUseAsyncDecodingForAnimatedImages()) { |
| if (frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(nextFrame, m_currentSubsamplingLevel, DecodingOptions(std::optional<IntSize>()))) |
| LOG(Images, "BitmapImage::%s - %p - url: %s [cachedFrameCount = %ld nextFrame = %ld]", __FUNCTION__, this, sourceURL().string().utf8().data(), ++m_cachedFrameCount, nextFrame); |
| else { |
| m_source->requestFrameAsyncDecodingAtIndex(nextFrame, m_currentSubsamplingLevel); |
| m_currentFrameDecodingStatus = DecodingStatus::Decoding; |
| LOG(Images, "BitmapImage::%s - %p - url: %s [requesting async decoding for nextFrame = %ld]", __FUNCTION__, this, sourceURL().string().utf8().data(), nextFrame); |
| } |
| |
| if (m_clearDecoderAfterAsyncFrameRequestForTesting) |
| m_source->resetData(data()); |
| } |
| |
| ASSERT(!m_frameTimer); |
| startTimer(m_desiredFrameStartTime - time); |
| return StartAnimationStatus::Started; |
| } |
| |
| void BitmapImage::advanceAnimation() |
| { |
| clearTimer(); |
| |
| // Don't advance to nextFrame unless its decoding has finished or was not required. |
| size_t nextFrame = (m_currentFrame + 1) % frameCount(); |
| if (!frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(nextFrame, DecodingOptions(DecodingMode::Asynchronous))) |
| internalAdvanceAnimation(); |
| else { |
| // Force repaint if showDebugBackground() is on. |
| if (m_showDebugBackground) |
| imageObserver()->changedInRect(*this); |
| LOG(Images, "BitmapImage::%s - %p - url: %s [lateFrameCount = %ld nextFrame = %ld]", __FUNCTION__, this, sourceURL().string().utf8().data(), ++m_lateFrameCount, nextFrame); |
| } |
| } |
| |
| void BitmapImage::internalAdvanceAnimation() |
| { |
| m_currentFrame = (m_currentFrame + 1) % frameCount(); |
| ASSERT(!frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(m_currentFrame, DecodingOptions(DecodingMode::Asynchronous))); |
| |
| destroyDecodedDataIfNecessary(false); |
| |
| DecodingStatus decodingStatus = frameDecodingStatusAtIndex(m_currentFrame); |
| setCurrentFrameDecodingStatusIfNecessary(decodingStatus); |
| |
| callDecodingCallbacks(); |
| |
| if (imageObserver()) |
| imageObserver()->imageFrameAvailable(*this, ImageAnimatingState::Yes, nullptr, decodingStatus); |
| |
| LOG(Images, "BitmapImage::%s - %p - url: %s [m_currentFrame = %ld]", __FUNCTION__, this, sourceURL().string().utf8().data(), m_currentFrame); |
| } |
| |
| bool BitmapImage::isAnimating() const |
| { |
| return !!m_frameTimer; |
| } |
| |
| void BitmapImage::stopAnimation() |
| { |
| // This timer is used to animate all occurrences of this image. Don't invalidate |
| // the timer unless all renderers have stopped drawing. |
| clearTimer(); |
| if (canAnimate()) |
| m_source->stopAsyncDecodingQueue(); |
| } |
| |
| void BitmapImage::resetAnimation() |
| { |
| stopAnimation(); |
| m_currentFrame = 0; |
| m_repetitionsComplete = RepetitionCountNone; |
| m_desiredFrameStartTime = { }; |
| m_animationFinished = false; |
| |
| // For extremely large animations, when the animation is reset, we just throw everything away. |
| destroyDecodedDataIfNecessary(true); |
| } |
| |
| void BitmapImage::decode(Function<void()>&& callback) |
| { |
| if (!m_decodingCallbacks) |
| m_decodingCallbacks = makeUnique<Vector<Function<void()>, 1>>(); |
| |
| m_decodingCallbacks->append(WTFMove(callback)); |
| |
| if (canAnimate()) { |
| if (m_desiredFrameStartTime) { |
| internalStartAnimation(); |
| return; |
| } |
| |
| // The animated image has not been displayed. In this case, either the first frame has not been decoded yet or the animation has not started yet. |
| bool frameIsCompatible = frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(m_currentFrame, m_currentSubsamplingLevel, std::optional<IntSize>()); |
| bool frameIsBeingDecoded = frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(m_currentFrame, std::optional<IntSize>()); |
| |
| if (frameIsCompatible) |
| internalStartAnimation(); |
| else if (!frameIsBeingDecoded) { |
| m_source->requestFrameAsyncDecodingAtIndex(m_currentFrame, m_currentSubsamplingLevel, std::optional<IntSize>()); |
| m_currentFrameDecodingStatus = DecodingStatus::Decoding; |
| } |
| return; |
| } |
| |
| bool frameIsCompatible = frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(m_currentFrame, m_currentSubsamplingLevel, std::optional<IntSize>()); |
| bool frameIsBeingDecoded = frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(m_currentFrame, std::optional<IntSize>()); |
| |
| if (frameIsCompatible) |
| callDecodingCallbacks(); |
| else if (!frameIsBeingDecoded) { |
| m_source->requestFrameAsyncDecodingAtIndex(m_currentFrame, m_currentSubsamplingLevel, std::optional<IntSize>()); |
| m_currentFrameDecodingStatus = DecodingStatus::Decoding; |
| } |
| } |
| |
| void BitmapImage::callDecodingCallbacks() |
| { |
| if (!m_decodingCallbacks) |
| return; |
| for (auto& decodingCallback : *m_decodingCallbacks) |
| decodingCallback(); |
| m_decodingCallbacks = nullptr; |
| } |
| |
| void BitmapImage::imageFrameAvailableAtIndex(size_t index) |
| { |
| LOG(Images, "BitmapImage::%s - %p - url: %s [requested frame %ld is now available]", __FUNCTION__, this, sourceURL().string().utf8().data(), index); |
| |
| if (canAnimate()) { |
| if (index == (m_currentFrame + 1) % frameCount()) { |
| // Don't advance to nextFrame unless the timer was fired before its decoding finishes. |
| if (!m_frameTimer) |
| internalAdvanceAnimation(); |
| else |
| LOG(Images, "BitmapImage::%s - %p - url: %s [earlyFrameCount = %ld nextFrame = %ld]", __FUNCTION__, this, sourceURL().string().utf8().data(), ++m_earlyFrameCount, index); |
| return; |
| } |
| |
| // Because of image partial loading, an image may start decoding as a large static image. But |
| // when more data is received, frameCount() changes to be > 1 so the image starts animating. |
| // The animation may even start before finishing the decoding of the first frame. |
| ASSERT(!m_repetitionsComplete); |
| LOG(Images, "BitmapImage::%s - %p - url: %s [More data makes frameCount() > 1]", __FUNCTION__, this, sourceURL().string().utf8().data()); |
| } |
| |
| ASSERT(index == m_currentFrame && !m_currentFrame); |
| if (m_source->isAsyncDecodingQueueIdle()) |
| m_source->stopAsyncDecodingQueue(); |
| |
| DecodingStatus decodingStatus = frameDecodingStatusAtIndex(m_currentFrame); |
| setCurrentFrameDecodingStatusIfNecessary(decodingStatus); |
| |
| if (m_currentFrameDecodingStatus == DecodingStatus::Complete) |
| ++m_decodeCountForTesting; |
| |
| // Call m_decodingCallbacks only if the image frame was decoded with the native size. |
| if (frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(m_currentFrame, m_currentSubsamplingLevel, std::optional<IntSize>())) |
| callDecodingCallbacks(); |
| |
| if (imageObserver()) |
| imageObserver()->imageFrameAvailable(*this, ImageAnimatingState::No, nullptr, decodingStatus); |
| } |
| |
| DestinationColorSpace BitmapImage::colorSpace() |
| { |
| if (auto nativeImage = this->nativeImage()) |
| return nativeImage->colorSpace(); |
| return DestinationColorSpace::SRGB(); |
| } |
| |
| unsigned BitmapImage::decodeCountForTesting() const |
| { |
| return m_decodeCountForTesting; |
| } |
| |
| void BitmapImage::dump(TextStream& ts) const |
| { |
| Image::dump(ts); |
| |
| if (isAnimated()) |
| ts.dumpProperty("current-frame", m_currentFrame); |
| |
| m_source->dump(ts); |
| } |
| |
| } |