blob: 1fa0244ace10f71bdee71b0c2b7bbe918314d144 [file] [log] [blame]
/*
* 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 "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_source(this)
{
}
BitmapImage::BitmapImage(NativeImagePtr&& image, ImageObserver* observer)
: Image(observer)
, m_source(WTFMove(image))
{
}
BitmapImage::~BitmapImage()
{
invalidatePlatformData();
stopAnimation();
}
void BitmapImage::destroyDecodedData(bool destroyAll)
{
m_source.destroyDecodedData(data(), destroyAll, m_currentFrame);
invalidatePlatformData();
}
void BitmapImage::destroyDecodedDataIfNecessary(bool destroyAll)
{
m_source.destroyDecodedDataIfNecessary(data(), destroyAll, m_currentFrame);
}
bool BitmapImage::dataChanged(bool allDataReceived)
{
return m_source.dataChanged(data(), allDataReceived);
}
NativeImagePtr BitmapImage::frameImageAtIndex(size_t index, SubsamplingLevel subsamplingLevel, const GraphicsContext* targetContext)
{
if (!frameHasValidNativeImageAtIndex(index, subsamplingLevel)) {
LOG(Images, "BitmapImage %p frameImageAtIndex - subsamplingLevel was %d, resampling", this, static_cast<int>(frameSubsamplingLevelAtIndex(index)));
invalidatePlatformData();
}
return m_source.frameImageAtIndex(index, subsamplingLevel, targetContext);
}
NativeImagePtr BitmapImage::nativeImage(const GraphicsContext* targetContext)
{
return frameImageAtIndex(0, SubsamplingLevel::Default, targetContext);
}
NativeImagePtr BitmapImage::nativeImageForCurrentFrame(const GraphicsContext* targetContext)
{
return frameImageAtIndex(m_currentFrame, SubsamplingLevel::Default, targetContext);
}
#if USE(CG)
NativeImagePtr BitmapImage::nativeImageOfSize(const IntSize& size, const GraphicsContext* targetContext)
{
size_t count = frameCount();
for (size_t i = 0; i < count; ++i) {
auto image = frameImageAtIndex(i, SubsamplingLevel::Default, targetContext);
if (image && nativeImageSize(image) == size)
return image;
}
// Fallback to the first frame image if we can't find the right size
return frameImageAtIndex(0, SubsamplingLevel::Default, targetContext);
}
Vector<NativeImagePtr> BitmapImage::framesNativeImages()
{
Vector<NativeImagePtr> images;
size_t count = frameCount();
for (size_t i = 0; i < count; ++i) {
if (auto image = frameImageAtIndex(i))
images.append(image);
}
return images;
}
#endif
#if !ASSERT_DISABLED
bool BitmapImage::notSolidColor()
{
return size().width() != 1 || size().height() != 1 || frameCount() > 1;
}
#endif
void BitmapImage::draw(GraphicsContext& context, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, BlendMode mode, ImageOrientationDescription description)
{
if (destRect.isEmpty() || srcRect.isEmpty())
return;
startAnimation();
Color color = singlePixelSolidColor();
if (color.isValid()) {
fillWithSolidColor(context, destRect, color, op);
return;
}
float scale = subsamplingScale(context, destRect, srcRect);
SubsamplingLevel subsamplingLevel = m_source.subsamplingLevelForScale(scale);
LOG(Images, "BitmapImage %p draw - subsamplingLevel %d at scale %.4f", this, static_cast<int>(subsamplingLevel), scale);
auto image = frameImageAtIndex(m_currentFrame, subsamplingLevel, &context);
if (!image) // If it's too early we won't have an image yet.
return;
ImageOrientation orientation(description.imageOrientation());
if (description.respectImageOrientation() == RespectImageOrientation)
orientation = frameOrientationAtIndex(m_currentFrame);
drawNativeImage(image, context, destRect, srcRect, IntSize(size()), op, mode, orientation);
if (imageObserver())
imageObserver()->didDraw(this);
}
void BitmapImage::drawPattern(GraphicsContext& ctxt, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& transform, const FloatPoint& phase, const FloatSize& spacing, CompositeOperator op, BlendMode blendMode)
{
if (tileRect.isEmpty())
return;
if (!ctxt.drawLuminanceMask()) {
Image::drawPattern(ctxt, destRect, tileRect, transform, phase, spacing, op, blendMode);
return;
}
if (!m_cachedImage) {
auto buffer = ImageBuffer::createCompatibleBuffer(expandedIntSize(tileRect.size()), ColorSpaceSRGB, ctxt);
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, op, blendMode, ImageOrientationDescription());
setImageObserver(observer);
buffer->convertToLuminanceMask();
m_cachedImage = buffer->copyImage(DontCopyBackingStore, Unscaled);
if (!m_cachedImage)
return;
}
ctxt.setDrawLuminanceMask(false);
m_cachedImage->drawPattern(ctxt, destRect, tileRect, transform, phase, spacing, op, blendMode);
}
bool BitmapImage::shouldAnimate()
{
return repetitionCount() && !m_animationFinished && imageObserver();
}
bool BitmapImage::canAnimate()
{
return shouldAnimate() && frameCount() > 1;
}
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);
}
void BitmapImage::startAnimation()
{
if (m_frameTimer || !shouldAnimate() || frameCount() <= 1)
return;
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;
++m_repetitionsComplete;
// Check for the end of animation.
if (repetitionCount() != RepetitionCountInfinite && m_repetitionsComplete > repetitionCount()) {
m_animationFinished = true;
destroyDecodedDataIfNecessary(false);
return;
}
destroyDecodedDataIfNecessary(true);
}
// Don't advance the animation to an incomplete frame.
size_t nextFrame = (m_currentFrame + 1) % frameCount();
if (!m_source.isAllDataReceived() && !frameIsCompleteAtIndex(nextFrame))
return;
double time = monotonicallyIncreasingTime();
// 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 + frameDurationAtIndex(m_currentFrame));
ASSERT(!m_frameTimer);
startTimer(m_desiredFrameStartTime - time);
}
void BitmapImage::advanceAnimation()
{
clearTimer();
m_currentFrame = (m_currentFrame + 1) % frameCount();
destroyDecodedDataIfNecessary(false);
if (imageObserver())
imageObserver()->animationAdvanced(this);
}
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();
}
void BitmapImage::resetAnimation()
{
stopAnimation();
m_currentFrame = 0;
m_repetitionsComplete = RepetitionCountNone;
m_desiredFrameStartTime = 0;
m_animationFinished = false;
// For extremely large animations, when the animation is reset, we just throw everything away.
destroyDecodedDataIfNecessary(true);
}
void BitmapImage::dump(TextStream& ts) const
{
Image::dump(ts);
if (isAnimated())
ts.dumpProperty("current-frame", m_currentFrame);
m_source.dump(ts);
}
}