#include "config.h"
#include "BitmapImage.h"
#include "FloatRect.h"
#include "GraphicsContext.h"
#include "ImageBuffer.h"
#include "ImageObserver.h"
#include "IntRect.h"
#include "Logging.h"
#include "MIMETypeRegistry.h"
#include "TextStream.h"
#include "Timer.h"
#include <wtf/CurrentTime.h>
#include <wtf/Vector.h>
#include <wtf/text/WTFString.h>
#if PLATFORM(IOS)
#include <limits>
#endif
namespace WebCore {
BitmapImage::BitmapImage(ImageObserver* observer)
: Image(observer)
, m_animationFinished(false)
, m_allDataReceived(false)
, m_haveSize(false)
, m_sizeAvailable(false)
, m_haveFrameCount(false)
, m_animationFinishedWhenCatchingUp(false)
{
}
BitmapImage::BitmapImage(NativeImagePtr&& image, ImageObserver* observer)
: Image(observer)
, m_source(image)
, m_frameCount(1)
, m_animationFinished(true)
, m_allDataReceived(true)
, m_haveSize(true)
, m_sizeAvailable(true)
, m_haveFrameCount(true)
, m_animationFinishedWhenCatchingUp(false)
{
m_sizeRespectingOrientation = m_size = NativeImage::size(image);
m_decodedSize = m_size.area() * 4;
m_frames.grow(1);
m_frames[0].m_hasAlpha = NativeImage::hasAlpha(image);
m_frames[0].m_haveMetadata = true;
m_frames[0].m_image = WTFMove(image);
}
BitmapImage::~BitmapImage()
{
invalidatePlatformData();
stopAnimation();
}
void BitmapImage::clearTimer()
{
m_frameTimer = nullptr;
}
void BitmapImage::startTimer(double delay)
{
ASSERT(!m_frameTimer);
m_frameTimer = std::make_unique<Timer>(*this, &BitmapImage::advanceAnimation);
m_frameTimer->startOneShot(delay);
}
bool BitmapImage::haveFrameImageAtIndex(size_t index)
{
if (index >= frameCount())
return false;
if (index >= m_frames.size())
return false;
return m_frames[index].m_image;
}
bool BitmapImage::hasSingleSecurityOrigin() const
{
return true;
}
void BitmapImage::destroyDecodedData(bool destroyAll)
{
unsigned frameBytesCleared = 0;
const size_t clearBeforeFrame = destroyAll ? m_frames.size() : m_currentFrame;
for (size_t i = 0; i < std::min(clearBeforeFrame, m_frames.size()); ++i) {
unsigned frameBytes = m_frames[i].m_frameBytes;
if (m_frames[i].clear(false))
frameBytesCleared += frameBytes;
}
m_source.clear(destroyAll, clearBeforeFrame, data(), m_allDataReceived);
destroyMetadataAndNotify(frameBytesCleared, ClearedSource::Yes);
}
void BitmapImage::destroyDecodedDataIfNecessary(bool destroyAll)
{
#if PLATFORM(IOS)
const unsigned largeAnimationCutoff = 2097152;
#else
const unsigned largeAnimationCutoff = 5242880;
#endif
if (!data() && m_frames.size())
return;
unsigned allFrameBytes = 0;
for (size_t i = 0; i < m_frames.size(); ++i)
allFrameBytes += m_frames[i].usedFrameBytes();
if (allFrameBytes > largeAnimationCutoff) {
LOG(Images, "BitmapImage %p destroyDecodedDataIfNecessary destroyingData: allFrameBytes=%u cutoff=%u", this, allFrameBytes, largeAnimationCutoff);
destroyDecodedData(destroyAll);
}
}
void BitmapImage::destroyMetadataAndNotify(unsigned frameBytesCleared, ClearedSource clearedSource)
{
m_solidColor = Nullopt;
invalidatePlatformData();
ASSERT(m_decodedSize >= frameBytesCleared);
m_decodedSize -= frameBytesCleared;
if (clearedSource == ClearedSource::Yes) {
frameBytesCleared += m_decodedPropertiesSize;
m_decodedPropertiesSize = 0;
}
if (frameBytesCleared && imageObserver())
imageObserver()->decodedSizeChanged(this, -safeCast<int>(frameBytesCleared));
}
void BitmapImage::cacheFrame(size_t index, SubsamplingLevel subsamplingLevel, ImageFrameCaching frameCaching)
{
size_t numFrames = frameCount();
ASSERT(m_decodedSize == 0 || numFrames > 1);
if (m_frames.size() < numFrames)
m_frames.grow(numFrames);
if (frameCaching == CacheMetadataAndFrame) {
m_frames[index].m_image = m_source.createFrameImageAtIndex(index, subsamplingLevel);
m_frames[index].m_subsamplingLevel = subsamplingLevel;
}
m_frames[index].m_orientation = m_source.orientationAtIndex(index);
m_frames[index].m_haveMetadata = true;
m_frames[index].m_isComplete = m_source.frameIsCompleteAtIndex(index);
if (repetitionCount(false) != cAnimationNone)
m_frames[index].m_duration = m_source.frameDurationAtIndex(index);
m_frames[index].m_hasAlpha = m_source.frameHasAlphaAtIndex(index);
m_frames[index].m_frameBytes = m_source.frameBytesAtIndex(index, subsamplingLevel);
LOG(Images, "BitmapImage %p cacheFrame %lu (%s%u bytes, complete %d)", this, index, frameCaching == CacheMetadataOnly ? "metadata only, " : "", m_frames[index].m_frameBytes, m_frames[index].m_isComplete);
if (m_frames[index].m_image) {
int deltaBytes = safeCast<int>(m_frames[index].m_frameBytes);
m_decodedSize += deltaBytes;
deltaBytes -= m_decodedPropertiesSize;
m_decodedPropertiesSize = 0;
if (imageObserver())
imageObserver()->decodedSizeChanged(this, deltaBytes);
}
}
void BitmapImage::didDecodeProperties() const
{
if (m_decodedSize)
return;
size_t updatedSize = m_source.bytesDecodedToDetermineProperties();
if (m_decodedPropertiesSize == updatedSize)
return;
int deltaBytes = updatedSize - m_decodedPropertiesSize;
#if !ASSERT_DISABLED
bool overflow = updatedSize > m_decodedPropertiesSize && deltaBytes < 0;
bool underflow = updatedSize < m_decodedPropertiesSize && deltaBytes > 0;
ASSERT(!overflow && !underflow);
#endif
m_decodedPropertiesSize = updatedSize;
if (imageObserver())
imageObserver()->decodedSizeChanged(this, deltaBytes);
}
void BitmapImage::updateSize() const
{
if (!m_sizeAvailable || m_haveSize)
return;
m_size = m_source.size();
m_sizeRespectingOrientation = m_source.sizeRespectingOrientation();
m_haveSize = true;
didDecodeProperties();
}
FloatSize BitmapImage::size() const
{
updateSize();
return m_size;
}
IntSize BitmapImage::sizeRespectingOrientation() const
{
updateSize();
return m_sizeRespectingOrientation;
}
Optional<IntPoint> BitmapImage::hotSpot() const
{
auto result = m_source.hotSpot();
didDecodeProperties();
return result;
}
bool BitmapImage::dataChanged(bool allDataReceived)
{
#if !PLATFORM(IOS)
unsigned frameBytesCleared = 0;
for (size_t i = 0; i < m_frames.size(); ++i) {
unsigned frameBytes = m_frames[i].m_frameBytes;
if (m_frames[i].m_haveMetadata && !m_frames[i].m_isComplete)
frameBytesCleared += (m_frames[i].clear(true) ? frameBytes : 0);
}
destroyMetadataAndNotify(frameBytesCleared, ClearedSource::No);
#else
int deltaBytes = 0;
if (!m_frames.isEmpty()) {
int bytes = m_frames[m_frames.size() - 1].m_frameBytes;
if (m_frames[m_frames.size() - 1].clear(true)) {
deltaBytes += bytes;
deltaBytes += m_decodedPropertiesSize;
m_decodedPropertiesSize = 0;
}
}
destroyMetadataAndNotify(deltaBytes, ClearedSource::No);
#endif
m_allDataReceived = allDataReceived;
#if PLATFORM(IOS)
static const double chunkLoadIntervals[] = {0, 1, 3, 6, 15};
double interval = chunkLoadIntervals[std::min(m_progressiveLoadChunkCount, static_cast<uint16_t>(4))];
bool needsUpdate = false;
if (currentTime() - m_progressiveLoadChunkTime > interval) { needsUpdate = true;
m_progressiveLoadChunkTime = currentTime();
ASSERT(m_progressiveLoadChunkCount <= std::numeric_limits<uint16_t>::max());
++m_progressiveLoadChunkCount;
}
if (needsUpdate || allDataReceived)
m_source.setData(data(), allDataReceived);
#else
m_source.setData(data(), allDataReceived);
#endif
m_haveFrameCount = false;
m_source.setNeedsUpdateMetadata();
return isSizeAvailable();
}
String BitmapImage::filenameExtension() const
{
return m_source.filenameExtension();
}
size_t BitmapImage::frameCount()
{
if (!m_haveFrameCount) {
m_frameCount = m_source.frameCount();
if (m_frameCount) {
didDecodeProperties();
m_haveFrameCount = true;
}
}
return m_frameCount;
}
bool BitmapImage::isSizeAvailable()
{
if (m_sizeAvailable)
return true;
m_sizeAvailable = m_source.isSizeAvailable();
didDecodeProperties();
return m_sizeAvailable;
}
bool BitmapImage::ensureFrameIsCached(size_t index, ImageFrameCaching frameCaching)
{
if (index >= frameCount())
return false;
if (index >= m_frames.size()
|| (frameCaching == CacheMetadataAndFrame && !m_frames[index].m_image)
|| (frameCaching == CacheMetadataOnly && !m_frames[index].m_haveMetadata))
cacheFrame(index, 0, frameCaching);
return true;
}
NativeImagePtr BitmapImage::frameImageAtIndex(size_t index, float presentationScaleHint)
{
if (index >= frameCount())
return nullptr;
SubsamplingLevel subsamplingLevel = m_source.subsamplingLevelForScale(presentationScaleHint);
if (index < m_frames.size() && m_frames[index].m_image && subsamplingLevel < m_frames[index].m_subsamplingLevel) {
int sizeChange = -m_frames[index].m_frameBytes;
m_frames[index].clear(true);
invalidatePlatformData();
m_decodedSize += sizeChange;
if (imageObserver())
imageObserver()->decodedSizeChanged(this, sizeChange);
}
if (index >= m_frames.size() || !m_frames[index].m_image)
cacheFrame(index, subsamplingLevel, CacheMetadataAndFrame);
return m_frames[index].m_image;
}
bool BitmapImage::frameIsCompleteAtIndex(size_t index)
{
if (!ensureFrameIsCached(index, CacheMetadataOnly))
return false;
return m_frames[index].m_isComplete;
}
float BitmapImage::frameDurationAtIndex(size_t index)
{
if (!ensureFrameIsCached(index, CacheMetadataOnly))
return 0;
return m_frames[index].m_duration;
}
NativeImagePtr BitmapImage::nativeImageForCurrentFrame()
{
return frameImageAtIndex(currentFrame());
}
bool BitmapImage::frameHasAlphaAtIndex(size_t index)
{
if (!ensureFrameIsCached(index, CacheMetadataOnly))
return true;
if (m_frames[index].m_haveMetadata)
return m_frames[index].m_hasAlpha;
return m_source.frameHasAlphaAtIndex(index);
}
bool BitmapImage::currentFrameKnownToBeOpaque()
{
return !frameHasAlphaAtIndex(currentFrame());
}
ImageOrientation BitmapImage::frameOrientationAtIndex(size_t index)
{
if (!ensureFrameIsCached(index, CacheMetadataOnly))
return ImageOrientation();
if (m_frames[index].m_haveMetadata)
return m_frames[index].m_orientation;
return m_source.orientationAtIndex(index);
}
#if !ASSERT_DISABLED
bool BitmapImage::notSolidColor()
{
return size().width() != 1 || size().height() != 1 || frameCount() > 1;
}
#endif
int BitmapImage::repetitionCount(bool imageKnownToBeComplete)
{
if ((m_repetitionCountStatus == Unknown) || ((m_repetitionCountStatus == Uncertain) && imageKnownToBeComplete)) {
m_repetitionCount = m_source.repetitionCount();
didDecodeProperties();
m_repetitionCountStatus = (imageKnownToBeComplete || m_repetitionCount == cAnimationNone) ? Certain : Uncertain;
}
return m_repetitionCount;
}
bool BitmapImage::shouldAnimate()
{
return (repetitionCount(false) != cAnimationNone && !m_animationFinished && imageObserver());
}
void BitmapImage::startAnimation(CatchUpAnimation catchUpIfNecessary)
{
if (m_frameTimer || !shouldAnimate() || frameCount() <= 1)
return;
const double time = monotonicallyIncreasingTime();
if (!m_desiredFrameStartTime)
m_desiredFrameStartTime = time;
size_t nextFrame = (m_currentFrame + 1) % frameCount();
if (!m_allDataReceived && !frameIsCompleteAtIndex(nextFrame))
return;
if (!m_allDataReceived && repetitionCount(false) == cAnimationLoopOnce && m_currentFrame >= (frameCount() - 1))
return;
const double currentDuration = frameDurationAtIndex(m_currentFrame);
m_desiredFrameStartTime += currentDuration;
#if !PLATFORM(IOS)
const double cAnimationResyncCutoff = 5 * 60;
if ((time - m_desiredFrameStartTime) > cAnimationResyncCutoff)
m_desiredFrameStartTime = time + currentDuration;
#else
m_desiredFrameStartTime = time + currentDuration;
#endif
if (nextFrame == 0 && m_repetitionsComplete == 0 && m_desiredFrameStartTime < time)
m_desiredFrameStartTime = time;
if (catchUpIfNecessary == DoNotCatchUp || time < m_desiredFrameStartTime) {
startTimer(std::max<double>(m_desiredFrameStartTime - time, 0));
return;
}
ASSERT(!m_frameTimer);
#if !LOG_DISABLED
size_t startCatchupFrameIndex = nextFrame;
#endif
for (size_t frameAfterNext = (nextFrame + 1) % frameCount(); frameIsCompleteAtIndex(frameAfterNext); frameAfterNext = (nextFrame + 1) % frameCount()) {
double frameAfterNextStartTime = m_desiredFrameStartTime + frameDurationAtIndex(nextFrame);
if (time < frameAfterNextStartTime)
break;
if (!internalAdvanceAnimation(SkippingFramesToCatchUp)) {
m_animationFinishedWhenCatchingUp = true;
startTimer(0);
LOG(Images, "BitmapImage %p startAnimation catching up from frame %lu, ended", this, startCatchupFrameIndex);
return;
}
m_desiredFrameStartTime = frameAfterNextStartTime;
nextFrame = frameAfterNext;
}
LOG(Images, "BitmapImage %p startAnimation catching up jumped from from frame %lu to %d", this, startCatchupFrameIndex, (int)nextFrame - 1);
startTimer(0);
}
void BitmapImage::stopAnimation()
{
clearTimer();
}
void BitmapImage::resetAnimation()
{
stopAnimation();
m_currentFrame = 0;
m_repetitionsComplete = 0;
m_desiredFrameStartTime = 0;
m_animationFinished = false;
destroyDecodedDataIfNecessary(true);
}
void BitmapImage::drawPattern(GraphicsContext& ctxt, const FloatRect& tileRect, const AffineTransform& transform,
const FloatPoint& phase, const FloatSize& spacing, CompositeOperator op, const FloatRect& destRect, BlendMode blendMode)
{
if (tileRect.isEmpty())
return;
if (!ctxt.drawLuminanceMask()) {
Image::drawPattern(ctxt, tileRect, transform, phase, spacing, op, destRect, blendMode);
return;
}
if (!m_cachedImage) {
auto buffer = ImageBuffer::createCompatibleBuffer(expandedIntSize(tileRect.size()), ColorSpaceSRGB, ctxt);
if (!buffer)
return;
ImageObserver* observer = imageObserver();
ASSERT(observer);
setImageObserver(nullptr);
draw(buffer->context(), tileRect, tileRect, op, blendMode, ImageOrientationDescription());
setImageObserver(observer);
buffer->convertToLuminanceMask();
m_cachedImage = buffer->copyImage(DontCopyBackingStore, Unscaled);
if (!m_cachedImage)
return;
}
ctxt.setDrawLuminanceMask(false);
m_cachedImage->drawPattern(ctxt, tileRect, transform, phase, spacing, op, destRect, blendMode);
}
void BitmapImage::advanceAnimation()
{
internalAdvanceAnimation();
}
bool BitmapImage::internalAdvanceAnimation(AnimationAdvancement advancement)
{
clearTimer();
if (m_animationFinishedWhenCatchingUp) {
imageObserver()->animationAdvanced(this);
m_animationFinishedWhenCatchingUp = false;
return false;
}
++m_currentFrame;
bool advancedAnimation = true;
bool destroyAll = false;
if (m_currentFrame >= frameCount()) {
++m_repetitionsComplete;
if (repetitionCount(true) != cAnimationLoopInfinite && m_repetitionsComplete > m_repetitionCount) {
m_animationFinished = true;
m_desiredFrameStartTime = 0;
--m_currentFrame;
advancedAnimation = false;
} else {
m_currentFrame = 0;
destroyAll = true;
}
}
destroyDecodedDataIfNecessary(destroyAll);
if (advancement == Normal && advancedAnimation)
imageObserver()->animationAdvanced(this);
return advancedAnimation;
}
Color BitmapImage::singlePixelSolidColor()
{
if (frameCount() != 1)
return Color();
if (m_solidColor)
return m_solidColor.value();
if (!haveFrameImageAtIndex(0) && m_source.frameSizeAtIndex(0, 0) != IntSize(1, 1)) {
m_solidColor = Color();
return m_solidColor.value();
}
if (!ensureFrameIsCached(0))
return Color();
ASSERT(m_frames.size());
m_solidColor = NativeImage::singlePixelSolidColor(m_frames[0].m_image.get());
ASSERT(m_solidColor);
return m_solidColor.value();
}
bool BitmapImage::canAnimate()
{
return shouldAnimate() && frameCount() > 1;
}
void BitmapImage::dump(TextStream& ts) const
{
Image::dump(ts);
ts.dumpProperty("type", m_source.filenameExtension());
if (isAnimated()) {
ts.dumpProperty("frame-count", m_frameCount);
ts.dumpProperty("repetitions", m_repetitionCount);
ts.dumpProperty("current-frame", m_currentFrame);
}
if (m_solidColor)
ts.dumpProperty("solid-color", m_solidColor.value());
m_source.dump(ts);
}
}