blob: 0193bfb0f96c7d34053e768a0c258fc125780fd3 [file] [log] [blame]
/*
* Copyright (C) 2009 Dirk Schulze <krit@webkit.org>
* Copyright (C) Research In Motion Limited 2011. All rights reserved.
* Copyright (C) 2016-2020 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 "ImageBuffer.h"
#include "GraphicsContext.h"
#include "HostWindow.h"
#include "PlatformImageBuffer.h"
namespace WebCore {
static const float MaxClampedLength = 4096;
static const float MaxClampedArea = MaxClampedLength * MaxClampedLength;
RefPtr<ImageBuffer> ImageBuffer::create(const FloatSize& size, RenderingMode renderingMode, ShouldUseDisplayList shouldUseDisplayList, RenderingPurpose purpose, float resolutionScale, const DestinationColorSpace& colorSpace, PixelFormat pixelFormat, const HostWindow* hostWindow)
{
RefPtr<ImageBuffer> imageBuffer;
// Give ShouldUseDisplayList a higher precedence since it is a debug option.
if (shouldUseDisplayList == ShouldUseDisplayList::Yes) {
if (renderingMode == RenderingMode::Accelerated)
imageBuffer = DisplayListAcceleratedImageBuffer::create(size, resolutionScale, colorSpace, pixelFormat, hostWindow);
if (!imageBuffer)
imageBuffer = DisplayListUnacceleratedImageBuffer::create(size, resolutionScale, colorSpace, pixelFormat, hostWindow);
}
if (hostWindow && !imageBuffer)
imageBuffer = hostWindow->createImageBuffer(size, renderingMode, purpose, resolutionScale, colorSpace, pixelFormat);
if (!imageBuffer)
imageBuffer = ImageBuffer::create(size, renderingMode, resolutionScale, colorSpace, pixelFormat, hostWindow);
return imageBuffer;
}
RefPtr<ImageBuffer> ImageBuffer::create(const FloatSize& size, RenderingMode renderingMode, float resolutionScale, const DestinationColorSpace& colorSpace, PixelFormat pixelFormat, const HostWindow* hostWindow)
{
RefPtr<ImageBuffer> imageBuffer;
if (renderingMode == RenderingMode::Accelerated)
imageBuffer = AcceleratedImageBuffer::create(size, resolutionScale, colorSpace, pixelFormat, hostWindow);
if (!imageBuffer)
imageBuffer = UnacceleratedImageBuffer::create(size, resolutionScale, colorSpace, pixelFormat, hostWindow);
return imageBuffer;
}
RefPtr<ImageBuffer> ImageBuffer::createCompatibleBuffer(const FloatSize& size, const GraphicsContext& context)
{
if (size.isEmpty())
return nullptr;
IntSize scaledSize = ImageBuffer::compatibleBufferSize(size, context);
RefPtr<ImageBuffer> imageBuffer;
if (context.renderingMode() == RenderingMode::Accelerated)
imageBuffer = AcceleratedImageBuffer::create(scaledSize, context);
if (!imageBuffer)
imageBuffer = UnacceleratedImageBuffer::create(scaledSize, context);
if (!imageBuffer)
return nullptr;
imageBuffer->context().scale(scaledSize / size);
return imageBuffer;
}
// This is useful when you need to make sure the pixel grid of the ImageBuffer aligns with the pixel grid of the context.
// Simply saying createCompatibleBuffer(rect.size(), context) isn't sufficient in this situation, because rect.location() may not lie on a pixel boundary.
// In this situation, we have to inflate both the left side and the right side, which can lead to different results than
// createCompatibleBuffer(rect.size(), context) would have produced.
auto ImageBuffer::createCompatibleBuffer(const FloatRect& rect, const GraphicsContext& context) -> std::optional<CompatibleBufferDescription>
{
if (rect.isEmpty())
return std::nullopt;
auto info = ImageBuffer::compatibleBufferInfo(rect, context);
RefPtr<ImageBuffer> imageBuffer;
if (context.renderingMode() == RenderingMode::Accelerated)
imageBuffer = AcceleratedImageBuffer::create(info.physicalSizeInDeviceCoordinates, context);
if (!imageBuffer)
imageBuffer = UnacceleratedImageBuffer::create(info.physicalSizeInDeviceCoordinates, context);
if (!imageBuffer)
return std::nullopt;
imageBuffer->context().scale(info.scale);
imageBuffer->context().translate(-info.inflatedRectInUserCoordinates.location());
return { { imageBuffer.releaseNonNull(), info.inflatedRectInUserCoordinates } };
}
RefPtr<ImageBuffer> ImageBuffer::createCompatibleBuffer(const FloatSize& size, const DestinationColorSpace& colorSpace, const GraphicsContext& context)
{
if (size.isEmpty())
return nullptr;
IntSize scaledSize = ImageBuffer::compatibleBufferSize(size, context);
auto imageBuffer = ImageBuffer::createCompatibleBuffer(scaledSize, 1, colorSpace, context);
if (!imageBuffer)
return nullptr;
imageBuffer->context().scale(scaledSize / size);
return imageBuffer;
}
RefPtr<ImageBuffer> ImageBuffer::createCompatibleBuffer(const FloatSize& size, float resolutionScale, const DestinationColorSpace& colorSpace, const GraphicsContext& context)
{
return ImageBuffer::create(size, context.renderingMode(), resolutionScale, colorSpace, PixelFormat::BGRA8);
}
bool ImageBuffer::sizeNeedsClamping(const FloatSize& size)
{
if (size.isEmpty())
return false;
return floorf(size.height()) * floorf(size.width()) > MaxClampedArea;
}
bool ImageBuffer::sizeNeedsClamping(const FloatSize& size, FloatSize& scale)
{
FloatSize scaledSize(size);
scaledSize.scale(scale.width(), scale.height());
if (!sizeNeedsClamping(scaledSize))
return false;
// The area of scaled size is bigger than the upper limit, adjust the scale to fit.
scale.scale(sqrtf(MaxClampedArea / (scaledSize.width() * scaledSize.height())));
ASSERT(!sizeNeedsClamping(size, scale));
return true;
}
FloatSize ImageBuffer::clampedSize(const FloatSize& size)
{
return size.shrunkTo(FloatSize(MaxClampedLength, MaxClampedLength));
}
FloatSize ImageBuffer::clampedSize(const FloatSize& size, FloatSize& scale)
{
if (size.isEmpty())
return size;
FloatSize clampedSize = ImageBuffer::clampedSize(size);
scale = clampedSize / size;
ASSERT(!sizeNeedsClamping(clampedSize));
ASSERT(!sizeNeedsClamping(size, scale));
return clampedSize;
}
FloatRect ImageBuffer::clampedRect(const FloatRect& rect)
{
return FloatRect(rect.location(), clampedSize(rect.size()));
}
IntSize ImageBuffer::compatibleBufferSize(const FloatSize& size, const GraphicsContext& context)
{
// Enlarge the buffer size if the context's transform is scaling it so we need a higher
// resolution than one pixel per unit.
return expandedIntSize(size * context.scaleFactor());
}
auto ImageBuffer::compatibleBufferInfo(const FloatRect& rect, const GraphicsContext& context) -> CompatibleBufferInfo
{
auto scaleFactor = context.scaleFactor();
auto scaledRect = rect;
scaledRect.scale(scaleFactor);
auto inflatedScaledRect = enclosingIntRect(scaledRect);
auto inflatedRectInUserCoordinates = FloatRect(inflatedScaledRect);
// We don't want to allocate huge ImageBuffers because they can take a lot of memory,
// so if the size would have been too big, decrease resolution and scale the resulting context.
// This will mean that the ImageBuffer will lose quality, but will still generally look correct.
constexpr int maxDimension = 4096;
if (inflatedScaledRect.width() > maxDimension) {
// The pixel grids won't align any more, so there's no need to try to handle fractions of pixels.
inflatedScaledRect.setWidth(maxDimension);
inflatedRectInUserCoordinates.scale(maxDimension / inflatedRectInUserCoordinates.width(), 1);
scaleFactor.setWidth(maxDimension / rect.width());
}
if (inflatedScaledRect.height() > maxDimension) {
// The pixel grids won't align any more, so there's no need to try to handle fractions of pixels.
inflatedScaledRect.setHeight(maxDimension);
inflatedRectInUserCoordinates.scale(1, maxDimension / inflatedRectInUserCoordinates.height());
scaleFactor.setHeight(maxDimension / rect.height());
}
auto physicalSizeInDeviceCoordinates = inflatedScaledRect.size();
inflatedRectInUserCoordinates.scale(1.0f / scaleFactor);
return { WTFMove(physicalSizeInDeviceCoordinates), WTFMove(inflatedRectInUserCoordinates), scaleFactor };
}
RefPtr<ImageBuffer> ImageBuffer::copyRectToBuffer(const FloatRect& rect, const DestinationColorSpace& colorSpace, const GraphicsContext& context)
{
if (rect.isEmpty())
return nullptr;
IntSize scaledSize = ImageBuffer::compatibleBufferSize(rect.size(), context);
auto buffer = ImageBuffer::createCompatibleBuffer(scaledSize, 1, colorSpace, context);
if (!buffer)
return nullptr;
buffer->context().drawImageBuffer(*this, -rect.location());
return buffer;
}
RefPtr<NativeImage> ImageBuffer::sinkIntoNativeImage(RefPtr<ImageBuffer> imageBuffer)
{
return imageBuffer->sinkIntoNativeImage();
}
RefPtr<Image> ImageBuffer::sinkIntoImage(RefPtr<ImageBuffer> imageBuffer, PreserveResolution preserveResolution)
{
return imageBuffer->sinkIntoImage(preserveResolution);
}
void ImageBuffer::drawConsuming(RefPtr<ImageBuffer> imageBuffer, GraphicsContext& context, const FloatRect& destRect, const FloatRect& srcRect, const ImagePaintingOptions& options)
{
imageBuffer->drawConsuming(context, destRect, srcRect, options);
}
} // namespace WebCore