blob: 768f617bd170cb8bd179ad1ba2e92953c958510d [file] [log] [blame]
/*
* Copyright (C) 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 "ImageBufferDirect2DBackend.h"
#if USE(DIRECT2D)
#include "BitmapImage.h"
#include "COMPtr.h"
#include "Direct2DUtilities.h"
#include "GraphicsContext.h"
#include "ImageDecoderDirect2D.h"
#include "IntRect.h"
#include "MIMETypeRegistry.h"
#include "NotImplemented.h"
#include "PixelBuffer.h"
#include "PlatformContextDirect2D.h"
#include <d2d1_1.h>
#include <math.h>
#include <wincodec.h>
#include <wtf/Assertions.h>
#include <wtf/IsoMallocInlines.h>
namespace WebCore {
WTF_MAKE_ISO_ALLOCATED_IMPL(ImageBufferDirect2DBackend);
std::unique_ptr<ImageBufferDirect2DBackend> ImageBufferDirect2DBackend::create(const FloatSize& size, float resolutionScale, DestinationColorSpace colorSpace, const HostWindow* hostWindow)
{
IntSize backendSize = calculateBackendSize(size, resolutionScale);
if (backendSize.isEmpty())
return nullptr;
auto* platformContext = targetContext ? targetContext->platformContext() : nullptr;
auto* renderTarget = platformContext ? platformContext->renderTarget() : nullptr;
if (!renderTarget)
renderTarget = GraphicsContext::defaultRenderTarget();
auto bitmapContext = Direct2D::createBitmapRenderTargetOfSize(size, renderTarget, resolutionScale);
if (!bitmapContext)
return;
PlatformImagePtr bitmap;
HRESULT hr = bitmapContext->GetBitmap(&bitmap);
if (!SUCCEEDED(hr))
return;
auto platformContext = makeUnique<PlatformContextDirect2D>(bitmapContext.get(), [this]() {
m_data.loadDataToBitmapIfNeeded();
}, [this]() {
m_data.markBufferOutOfSync();
});
if (!platformContext)
return nullptr;
auto context = makeUnique<GraphicsContextDirect2D>(platformContext.get(), GraphicsContext::BitmapRenderingContextType::GPUMemory);
if (!context)
return nullptr;
return std::unique_ptr<ImageBufferDirect2DBackend>(new ImageBufferDirect2DBackend(size, backendSize, resolutionScale, colorSpace, WTFMove(platformContext), WTFMove(context), WTFMove(bitmap)));
}
std::unique_ptr<ImageBufferDirect2DBackend> ImageBufferDirect2DBackend::create(const FloatSize& size, const GraphicsContext& context)
{
return ImageBufferDirect2DBackend::create(size, 1, DestinationColorSpace::SRGB, nullptr);
}
ImageBufferDirect2DBackend::ImageBufferDirect2DBackend(const FloatSize& logicalSize, const IntSize& backendSize, float resolutionScale, DestinationColorSpace colorSpace, std::unique_ptr<PlatformContextDirect2D>&& platformContext, std::unique_ptr<GraphicsContext>&& context, PlatformImagePtr&& bitmap)
: ImageBufferCGBackend(logicalSize, backendSize, resolutionScale, colorSpace)
, m_platformContext(WTFMove(platformContext))
, m_context(WTFMove(context))
, m_bitmap(WTFMove(bitmap))
{
}
GraphicsContext& ImageBufferDirect2DBackend::context() const
{
return *m_context;
}
void ImageBufferDirect2DBackend::flushContext()
{
context().flush();
}
RefPtr<NativeImage> ImageBufferDirect2DBackend::copyNativeImage(BackingStoreCopy copyBehavior) const
{
COMPtr<ID2D1BitmapRenderTarget> bitmapTarget;
HRESULT hr = context().platformContext()->renderTarget()->QueryInterface(&bitmapTarget);
if (!SUCCEEDED(hr))
return nullptr;
COMPtr<ID2D1Bitmap> image;
hr = bitmapTarget->GetBitmap(&image);
ASSERT(SUCCEEDED(hr));
// FIXME: m_data.data is nullptr even when asking to copy backing store leading to test failures.
if (copyBehavior == CopyBackingStore && m_data.data.isEmpty())
copyBehavior = DontCopyBackingStore;
CheckedSize numBytes = CheckedUint32(m_data.backingStoreSize.height()) * m_data.bytesPerRow;
if (numBytes.hasOverflowed())
return nullptr;
if (copyBehavior == CopyBackingStore) {
D2D1_RECT_U backingStoreDimenstions = IntRect(IntPoint(), m_data.backingStoreSize);
image->CopyFromMemory(&backingStoreDimenstions, m_data.data.data(), 32);
}
return NativeImage::create(WTFMove(image));
}
static PlatformImagePtr createCroppedImageIfNecessary(ID2D1BitmapRenderTarget* bitmapTarget, ID2D1Bitmap* image, const IntSize& bounds)
{
FloatSize imageSize = image ? nativeImageSize(image) : FloatSize();
if (image && (static_cast<size_t>(imageSize.width()) != static_cast<size_t>(bounds.width()) || static_cast<size_t>(imageSize.height()) != static_cast<size_t>(bounds.height()))) {
COMPtr<ID2D1Bitmap> croppedBitmap = Direct2D::createBitmap(bitmapTarget, bounds);
if (croppedBitmap) {
auto sourceRect = D2D1::RectU(0, 0, bounds.width(), bounds.height());
HRESULT hr = croppedBitmap->CopyFromBitmap(nullptr, image, &sourceRect);
if (SUCCEEDED(hr))
return croppedBitmap;
}
}
return image;
}
static RefPtr<Image> createBitmapImageAfterScalingIfNeeded(ID2D1BitmapRenderTarget* bitmapTarget, COMPtr<ID2D1Bitmap>&& image, IntSize internalSize, float resolutionScale, PreserveResolution preserveResolution)
{
if (resolutionScale == 1 || preserveResolution == PreserveResolution::Yes)
image = createCroppedImageIfNecessary(bitmapTarget, image.get(), internalSize);
else {
// FIXME: Need to implement scaled version
notImplemented();
}
if (!image)
return nullptr;
return BitmapImage::create(WTFMove(image));
}
RefPtr<Image> ImageBufferDirect2DBackend::copyImage(BackingStoreCopy copyBehavior, PreserveResolution preserveResolution) const
{
PlatformImagePtr image;
if (resolutionScale() == 1 || preserveResolution == PreserveResolution::Yes)
image = copyNativeImage(copyBehavior)->platformImage();
else
image = copyNativeImage(DontCopyBackingStore)->platformImage();
auto bitmapTarget = reinterpret_cast<ID2D1BitmapRenderTarget*>(context().platformContext());
return createBitmapImageAfterScalingIfNeeded(bitmapTarget, WTFMove(image), internalSize(), resolutionScale(), preserveResolution);
}
RefPtr<Image> ImageBufferDirect2DBackend::sinkIntoImage(PreserveResolution preserveResolution)
{
COMPtr<ID2D1BitmapRenderTarget> bitmapTarget;
HRESULT hr = context().platformContext()->renderTarget()->QueryInterface(&bitmapTarget);
if (!SUCCEEDED(hr))
return nullptr;
PlatformImagePtr image = copyNativeImage(DontCopyBackingStore)->platformImage();
return createBitmapImageAfterScalingIfNeeded(bitmapTarget.get(), WTFMove(image), internalSize(), resolutionScale(), preserveResolution);
}
PlatformImagePtr ImageBufferDirect2DBackend::compatibleBitmap(ID2D1RenderTarget* renderTarget)
{
loadDataToBitmapIfNeeded();
if (!renderTarget)
return bitmap;
if (m_platformContext->renderTarget() == renderTarget) {
COMPtr<ID2D1BitmapRenderTarget> bitmapTarget(Query, renderTarget);
if (bitmapTarget) {
COMPtr<ID2D1Bitmap> backingBitmap;
if (SUCCEEDED(bitmapTarget->GetBitmap(&backingBitmap))) {
if (backingBitmap != m_bitmap)
return bitmap;
// We can't draw an ID2D1Bitmap to itself. Must return a copy.
COMPtr<ID2D1Bitmap> copiedBitmap;
if (SUCCEEDED(renderTarget->CreateBitmap(bitmap->GetPixelSize(), Direct2D::bitmapProperties(), &copiedBitmap))) {
if (SUCCEEDED(copiedBitmap->CopyFromBitmap(nullptr, m_bitmap.get(), nullptr)))
return copiedBitmap;
}
}
}
}
auto size = m_bitmap->GetPixelSize();
ASSERT(size.height && size.width);
CheckedUint32 numBytes = size.width * size.height * 4;
if (numBytes.hasOverflowed())
return nullptr;
// Copy the bits from current renderTarget to the output target.
// We cannot access the data backing an IWICBitmap while an active draw session is open.
context->endDraw();
COMPtr<ID2D1DeviceContext> sourceDeviceContext = m_platformContext->deviceContext();
if (!sourceDeviceContext)
return nullptr;
COMPtr<ID2D1Bitmap1> sourceCPUBitmap;
D2D1_BITMAP_PROPERTIES1 bitmapProperties = D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_CPU_READ | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, Direct2D::pixelFormat());
HRESULT hr = sourceDeviceContext->CreateBitmap(m_bitmap->GetPixelSize(), nullptr, bytesPerRow, bitmapProperties, &sourceCPUBitmap);
if (!SUCCEEDED(hr))
return nullptr;
if (!sourceCPUBitmap)
return nullptr;
hr = sourceCPUBitmap->CopyFromBitmap(nullptr, m_bitmap.get(), nullptr);
if (!SUCCEEDED(hr))
return nullptr;
D2D1_MAPPED_RECT mappedSourceData;
hr = sourceCPUBitmap->Map(D2D1_MAP_OPTIONS_READ, &mappedSourceData);
if (!SUCCEEDED(hr))
return nullptr;
COMPtr<ID2D1DeviceContext> targetDeviceContext;
hr = renderTarget->QueryInterface(&targetDeviceContext);
ASSERT(SUCCEEDED(hr));
COMPtr<ID2D1Bitmap> compatibleBitmap;
hr = targetDeviceContext->CreateBitmap(bitmap->GetPixelSize(), mappedSourceData.bits, mappedSourceData.pitch, Direct2D::bitmapProperties(), &compatibleBitmap);
if (!SUCCEEDED(hr))
return nullptr;
hr = sourceCPUBitmap->Unmap();
ASSERT(SUCCEEDED(hr));
context->beginDraw();
return compatibleBitmap;
}
void ImageBufferDirect2DBackend::draw(GraphicsContext& destContext, const FloatRect& destRect, const FloatRect& srcRect, const ImagePaintingOptions& options)
{
FloatRect adjustedSrcRect = srcRect;
adjustedSrcRect.scale(resolutionScale());
auto compatibleBitmap = compatibleBitmap(destContext.platformContext()->renderTarget());
FloatSize currentImageSize = nativeImageSize(compatibleBitmap);
if (currentImageSize.isZero())
return;
destContext.drawNativeImage(compatibleBitmap, currentImageSize, destRect, adjustedSrcRect, options);
}
void ImageBufferDirect2DBackend::drawPattern(GraphicsContext& destContext, const FloatRect& destRect, const FloatRect& srcRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, const ImagePaintingOptions& options)
{
FloatRect adjustedSrcRect = srcRect;
adjustedSrcRect.scale(resolutionScale());
if (auto image = copyImage(&destContext == &context() ? CopyBackingStore : DontCopyBackingStore))
image->drawPattern(destContext, destRect, adjustedSrcRect, patternTransform, phase, spacing, options);
}
void ImageBufferDirect2DBackend::clipToMask(GraphicsContext&, const FloatRect&)
{
notImplemented();
}
String ImageBufferDirect2DBackend::toDataURL(const String&, std::optional<double>, PreserveResolution) const
{
notImplemented();
return "data:,"_s;
}
Vector<uint8_t> ImageBufferDirect2DBackend::toData(const String& mimeType, std::optional<double> quality) const
{
notImplemented();
return { };
}
std::optional<PixelBuffer> ImageBufferDirect2DBackend::getPixelBuffer(const PixelBufferFormat& outputFormat, const IntRect& srcRect) const
{
notImplemented();
return ImageBufferBackend::getPixelBuffer(outputFormat, srcRect, nullptr);
}
void ImageBufferDirect2DBackend::putPixelBuffer(const PixelBuffer& pixelBuffer, const IntRect& srcRect, const IntPoint& destPoint, AlphaPremultiplication destFormat)
{
notImplemented();
ImageBufferBackend::putPixelBuffer(pixelBuffer, srcRect, destPoint, destFormat, nullptr);
}
} // namespace WebCore
#endif // USE(DIRECT2D)