blob: f0feafb80d14b738ba510563a7cbe053c422c888 [file] [log] [blame]
/*
* Copyright (C) 2016-2019 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 "ImageBufferData.h"
#if USE(DIRECT2D)
#include "BitmapInfo.h"
#include "Direct2DUtilities.h"
#include "GraphicsContext.h"
#include "HWndDC.h"
#include "IntRect.h"
#include "NotImplemented.h"
#include "PlatformContextDirect2D.h"
#include <JavaScriptCore/JSCInlines.h>
#include <JavaScriptCore/TypedArrayInlines.h>
#include <JavaScriptCore/Uint8ClampedArray.h>
#include <d2d1.h>
#include <wtf/Assertions.h>
#include <wtf/UniqueArray.h>
namespace WebCore {
// Swizzle the red and blue bytes of the pixels in a buffer
template <AlphaPremultiplication desiredFormat>
void swizzleAndPremultiply(const uint8_t* srcRows, unsigned rowCount, unsigned colCount, unsigned srcStride, unsigned destStride, uint8_t* destRows)
{
for (unsigned y = 0; y < rowCount; ++y) {
// Source data may be power-of-two sized, so we need to only copy the bits that
// correspond to the rectangle supplied by the caller.
const uint32_t* srcRow = reinterpret_cast<const uint32_t*>(srcRows + srcStride * y);
uint8_t* destRow = destRows + destStride * y;
for (unsigned x = 0; x < colCount; ++x) {
unsigned bytePosition = x * 4;
const uint32_t* srcPixel = srcRow + x;
// Software filters expect (P)RGBA bytes. We need to swizzle from Direct2D's PBGRA to be compatible.
uint32_t alpha = (*srcPixel & 0xFF000000) >> 24;
uint32_t red = (*srcPixel & 0x00FF0000) >> 16;
uint32_t green = (*srcPixel & 0x0000FF00) >> 8;
uint32_t blue = (*srcPixel & 0x000000FF);
if (desiredFormat == AlphaPremultiplication::Unpremultiplied) {
if (alpha && alpha != 255) {
red = red * 255 / alpha;
green = green * 255 / alpha;
blue = blue * 255 / alpha;
}
}
destRow[bytePosition] = red;
destRow[bytePosition + 1] = green;
destRow[bytePosition + 2] = blue;
destRow[bytePosition + 3] = alpha;
}
}
}
static bool copyRectFromSourceToDest(const IntRect& sourceRect, const IntSize& sourceBufferSize, const uint8_t* source, const IntSize& destBufferSize, uint8_t* dest, const IntPoint& destBufferPosition)
{
if (!IntRect({ }, destBufferSize).contains(IntRect(destBufferPosition, sourceRect.size())))
return false;
if (!IntRect({ }, sourceBufferSize).contains(sourceRect))
return false;
unsigned srcStride = sourceBufferSize.width() * 4;
unsigned destStride = destBufferSize.width() * 4;
unsigned srcRowOffset = sourceRect.location().x() * 4;
unsigned destRowOffset = destBufferPosition.x() * 4;
const uint8_t* srcRows = source + srcStride * sourceRect.location().y();
uint8_t* destRows = dest + destStride * destBufferPosition.y();
const unsigned bytesPerRow = sourceRect.width() * 4;
for (unsigned y = 0; y < sourceRect.height(); ++y) {
const uint8_t* srcRow = srcRows + srcStride * y + srcRowOffset;
uint8_t* destRow = destRows + destStride * y + destRowOffset;
memcpy(destRow, srcRow, bytesPerRow);
}
return true;
}
// Note: Assumes that bytes are already in RGBA format
template <AlphaPremultiplication desiredFormat>
bool copyRectFromSourceToDestAndSetPremultiplication(const IntRect& sourceRect, const IntSize& sourceBufferSize, const uint8_t* source, const IntSize& destBufferSize, uint8_t* dest, const IntPoint& destBufferPosition)
{
if (!IntRect({ }, destBufferSize).contains(IntRect(destBufferPosition, sourceRect.size())))
return false;
if (!IntRect({ }, sourceBufferSize).contains(sourceRect))
return false;
unsigned srcStride = sourceBufferSize.width() * 4;
unsigned destStride = destBufferSize.width() * 4;
unsigned srcRowOffset = sourceRect.location().x() * 4;
unsigned destRowOffset = destBufferPosition.x() * 4;
const uint8_t* srcRows = source + srcStride * sourceRect.location().y();
uint8_t* destRows = dest + destStride * destBufferPosition.y();
const unsigned bytesPerRow = sourceRect.width() * 4;
for (unsigned y = 0; y < sourceRect.height(); ++y) {
const uint8_t* srcRow = srcRows + srcStride * y + srcRowOffset;
uint8_t* destRow = destRows + destStride * y + destRowOffset;
for (size_t x = 0; x < sourceRect.width(); ++x) {
unsigned pos = x * 4;
unsigned red = srcRow[x];
unsigned green = srcRow[x + 1];
unsigned blue = srcRow[x + 2];
unsigned alpha = srcRow[x + 3];
if (desiredFormat == AlphaPremultiplication::Premultiplied) {
if (alpha != 255) {
red = (red * alpha + 254) / 255;
green = (green * alpha + 254) / 255;
blue = (blue * alpha + 254) / 255;
}
} else {
if (alpha && alpha != 255) {
red = red * 255 / alpha;
green = green * 255 / alpha;
blue = blue * 255 / alpha;
}
}
uint32_t* pixel = reinterpret_cast<uint32_t*>(destRow) + x;
*pixel = (alpha << 24) | red << 16 | green << 8 | blue;
}
}
return true;
}
bool ImageBufferData::copyRectFromData(const IntRect& rect, RefPtr<Uint8ClampedArray>& result) const
{
auto rawBitmapSize = bitmap->GetSize();
float scaleFactor = backingStoreSize.width() / rawBitmapSize.width;
IntRect scaledRect = rect;
scaledRect.scale(scaleFactor);
if (!IntRect(IntPoint(), backingStoreSize).contains(scaledRect)) {
// Requested rect is outside the buffer. Return zero-filled buffer.
result->zeroFill();
return true;
}
return copyRectFromSourceToDest(scaledRect, backingStoreSize, data.data(), rect.size(), result->data(), IntPoint());
}
bool ImageBufferData::ensureBackingStore(const IntSize& size) const
{
if (size == backingStoreSize && !data.isEmpty())
return true;
auto numBytes = size.area<RecordOverflow>() * 4;
if (numBytes.hasOverflowed())
return false;
backingStoreSize = size;
data.resizeToFit(numBytes.unsafeGet());
return data.capacity() == numBytes.unsafeGet();
}
// Swizzle the red and blue bytes of the pixels in a buffer
template <AlphaPremultiplication sourceFormat>
void inPlaceSwizzle(uint8_t* byteData, unsigned byteCount)
{
size_t pixelCount = byteCount / 4;
auto* pixelData = reinterpret_cast<uint32_t*>(byteData);
for (size_t i = 0; i < pixelCount; ++i) {
uint32_t pixel = *pixelData;
size_t bytePosition = i * 4;
uint32_t alpha = (pixel & 0xFF000000) >> 24;
uint32_t red = (pixel & 0x00FF0000) >> 16;
uint32_t green = (pixel & 0x0000FF00) >> 8;
uint32_t blue = (pixel & 0x000000FF);
// (P)RGBA -> PBGRA
if (sourceFormat == AlphaPremultiplication::Unpremultiplied) {
if (alpha != 255) {
red = (red * alpha + 254) / 255;
green = (green * alpha + 254) / 255;
blue = (blue * alpha + 254) / 255;
}
}
*pixelData = (alpha << 24) | red << 16 | green << 8 | blue;
++pixelData;
}
}
// Note: Assumes that bytes are already in RGBA format
template <AlphaPremultiplication desiredFormat>
void inPlaceChangePremultiplication(uint8_t* byteData, unsigned byteCount)
{
size_t pixelCount = byteCount / 4;
for (size_t i = 0; i < pixelCount; ++i) {
unsigned x = pixelCount * 4;
unsigned red = byteData[x];
unsigned green = byteData[x + 1];
unsigned blue = byteData[x + 2];
unsigned alpha = byteData[x + 3];
if (desiredFormat == AlphaPremultiplication::Premultiplied) {
if (alpha != 255) {
red = (red * alpha + 254) / 255;
green = (green * alpha + 254) / 255;
blue = (blue * alpha + 254) / 255;
}
} else {
if (alpha && alpha != 255) {
red = red * 255 / alpha;
green = green * 255 / alpha;
blue = blue * 255 / alpha;
}
}
uint32_t* pixel = reinterpret_cast<uint32_t*>(byteData) + i;
*pixel = (alpha << 24) | red << 16 | green << 8 | blue;
}
}
RefPtr<Uint8ClampedArray> ImageBufferData::getData(AlphaPremultiplication desiredFormat, const IntRect& rect, const IntSize& size, bool /* accelerateRendering */, float /* resolutionScale */) const
{
auto numBytes = rect.area<RecordOverflow>() * 4;
if (numBytes.hasOverflowed())
return nullptr;
auto result = Uint8ClampedArray::tryCreateUninitialized(numBytes.unsafeGet());
if (!result)
return nullptr;
if (!bitmap)
return result;
if (!readDataFromBitmapIfNeeded(desiredFormat))
return nullptr;
auto rawBitmapSize = bitmap->GetSize();
IntSize bitmapSize(clampTo<int>(rawBitmapSize.width), clampTo<int>(rawBitmapSize.height));
if (rect.location() == IntPoint() && rect.size() == bitmapSize) {
RELEASE_ASSERT(numBytes.unsafeGet() == data.size());
result->setRange(data.data(), data.size(), 0);
} else {
// Only want part of the bitmap.
if (!copyRectFromData(rect, result))
return nullptr;
}
if (byteFormat != desiredFormat) {
// In-memory data does not match desired format. Need to swizzle the results.
if (desiredFormat == AlphaPremultiplication::Unpremultiplied)
inPlaceChangePremultiplication<AlphaPremultiplication::Unpremultiplied>(result->data(), result->length());
else
inPlaceChangePremultiplication<AlphaPremultiplication::Premultiplied>(result->data(), result->length());
}
return result;
}
bool ImageBufferData::readDataFromBitmapIfNeeded(AlphaPremultiplication desiredFormat) const
{
if (bitmapBufferSync != BitmapBufferSync::BufferOutOfSync)
return true;
IntSize pixelSize = bitmap->GetPixelSize();
auto numBytes = pixelSize.area<RecordOverflow>() * 4;
if (numBytes.hasOverflowed())
return false;
context->endDraw();
COMPtr<ID2D1DeviceContext> d2dDeviceContext = platformContext->deviceContext();
ASSERT(!!d2dDeviceContext);
auto bytesPerRowInData = pixelSize.width() * 4;
// Copy GPU data from the ID2D1Bitmap to a CPU-backed ID2D1Bitmap1
COMPtr<ID2D1Bitmap1> cpuBitmap;
D2D1_BITMAP_PROPERTIES1 bitmapProperties2 = D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_CPU_READ | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, Direct2D::pixelFormat());
HRESULT hr = d2dDeviceContext->CreateBitmap(pixelSize, nullptr, bytesPerRowInData, bitmapProperties2, &cpuBitmap);
if (!SUCCEEDED(hr))
return false;
auto targetPos = D2D1::Point2U();
D2D1_RECT_U dataRect = D2D1::RectU(0, 0, pixelSize.width(), pixelSize.height());
hr = cpuBitmap->CopyFromBitmap(&targetPos, bitmap.get(), &dataRect);
if (!SUCCEEDED(hr))
return false;
D2D1_MAPPED_RECT mappedData;
hr = cpuBitmap->Map(D2D1_MAP_OPTIONS_READ, &mappedData);
if (!SUCCEEDED(hr))
return false;
if (!mappedData.bits)
return false;
if (!ensureBackingStore(pixelSize))
return false;
// Software filters expect RGBA bytes. We need to swizzle from Direct2D's BGRA to be compatible.
if (desiredFormat == AlphaPremultiplication::Premultiplied)
swizzleAndPremultiply<AlphaPremultiplication::Premultiplied>(mappedData.bits, pixelSize.height(), pixelSize.width(), mappedData.pitch, bytesPerRowInData, data.data());
else
swizzleAndPremultiply<AlphaPremultiplication::Unpremultiplied>(mappedData.bits, pixelSize.height(), pixelSize.width(), mappedData.pitch, bytesPerRowInData, data.data());
byteFormat = desiredFormat;
bitmapBufferSync = BitmapBufferSync::InSync;
hr = cpuBitmap->Unmap();
ASSERT(SUCCEEDED(hr));
context->beginDraw();
return true;
}
void ImageBufferData::putData(const Uint8ClampedArray& source, AlphaPremultiplication sourceFormat, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint, const IntSize& size, bool /* accelerateRendering */, float resolutionScale)
{
ASSERT(sourceRect.width() > 0);
ASSERT(sourceRect.height() > 0);
Checked<int> originx = sourceRect.x();
Checked<int> destx = (Checked<int>(destPoint.x()) + sourceRect.x());
destx *= resolutionScale;
ASSERT(destx.unsafeGet() >= 0);
ASSERT(destx.unsafeGet() < size.width());
ASSERT(originx.unsafeGet() >= 0);
ASSERT(originx.unsafeGet() <= sourceRect.maxX());
Checked<int> endx = (Checked<int>(destPoint.x()) + sourceRect.maxX());
endx *= resolutionScale;
ASSERT(endx.unsafeGet() <= size.width());
Checked<int> width = sourceRect.width();
Checked<int> destw = endx - destx;
Checked<int> originy = sourceRect.y();
Checked<int> desty = (Checked<int>(destPoint.y()) + sourceRect.y());
desty *= resolutionScale;
ASSERT(desty.unsafeGet() >= 0);
ASSERT(desty.unsafeGet() < size.height());
ASSERT(originy.unsafeGet() >= 0);
ASSERT(originy.unsafeGet() <= sourceRect.maxY());
Checked<int> endy = (Checked<int>(destPoint.y()) + sourceRect.maxY());
endy *= resolutionScale;
ASSERT(endy.unsafeGet() <= size.height());
Checked<int> height = sourceRect.height();
Checked<int> desth = endy - desty;
if (width <= 0 || height <= 0)
return;
#if !ASSERT_DISABLED
if (bitmap) {
auto pixelSize = bitmap->GetPixelSize();
ASSERT(pixelSize.width >= sourceSize.width());
ASSERT(pixelSize.width >= size.width());
ASSERT(pixelSize.height >= sourceSize.height());
ASSERT(pixelSize.height >= size.height());
}
#endif
if (!ensureBackingStore(size))
return;
if (sourceSize == size) {
memcpy(data.data(), source.data(), source.length());
byteFormat = sourceFormat;
} else {
readDataFromBitmapIfNeeded(byteFormat);
if (!copyRectFromSourceToData(sourceRect, source, sourceFormat))
return;
}
bitmapBufferSync = BitmapBufferSync::BitmapOutOfSync;
}
bool ImageBufferData::copyRectFromSourceToData(const IntRect& sourceRect, const Uint8ClampedArray& source, AlphaPremultiplication sourceFormat)
{
IntSize pixelSize = bitmap->GetPixelSize();
IntRect sourceRectToCopy = sourceRect;
if (!IntRect(IntPoint(), pixelSize).contains(sourceRect))
return false;
auto destBufferPosition = sourceRect.location();
if (sourceFormat == byteFormat)
return copyRectFromSourceToDest(sourceRect, sourceRect.size(), source.data(), backingStoreSize, data.data(), destBufferPosition);
if (byteFormat == AlphaPremultiplication::Unpremultiplied)
return copyRectFromSourceToDestAndSetPremultiplication<AlphaPremultiplication::Unpremultiplied>(sourceRect, sourceRect.size(), source.data(), backingStoreSize, data.data(), destBufferPosition);
return copyRectFromSourceToDestAndSetPremultiplication<AlphaPremultiplication::Premultiplied>(sourceRect, sourceRect.size(), source.data(), backingStoreSize, data.data(), destBufferPosition);
}
void ImageBufferData::loadDataToBitmapIfNeeded()
{
if (bitmapBufferSync != BitmapBufferSync::BitmapOutOfSync || data.isEmpty())
return;
auto pixelSize = bitmap->GetPixelSize();
ASSERT(pixelSize.width >= backingStoreSize.width());
ASSERT(pixelSize.height >= backingStoreSize.height());
Checked<unsigned, RecordOverflow> numBytes = pixelSize.width * pixelSize.height * 4;
if (numBytes.hasOverflowed())
return;
// Software generated bitmap data is in RGBA. We need to swizzle to premultiplied BGRA to be compatible
// with the HWND/HDC render backing we use.
if (byteFormat == AlphaPremultiplication::Unpremultiplied)
inPlaceSwizzle<AlphaPremultiplication::Unpremultiplied>(data.data(), data.size()); // RGBA -> PBGRA
else
inPlaceSwizzle<AlphaPremultiplication::Premultiplied>(data.data(), data.size()); // PRGBA -> PBGRA
auto bytesPerRowInData = backingStoreSize.width() * 4;
HRESULT hr = bitmap->CopyFromMemory(nullptr, data.data(), bytesPerRowInData);
ASSERT(SUCCEEDED(hr));
bitmapBufferSync = BitmapBufferSync::InSync;
}
COMPtr<ID2D1Bitmap> ImageBufferData::compatibleBitmap(ID2D1RenderTarget* renderTarget)
{
loadDataToBitmapIfNeeded();
if (!renderTarget)
return bitmap;
if (platformContext->renderTarget() == renderTarget) {
COMPtr<ID2D1BitmapRenderTarget> bitmapTarget(Query, renderTarget);
if (bitmapTarget) {
COMPtr<ID2D1Bitmap> backingBitmap;
if (SUCCEEDED(bitmapTarget->GetBitmap(&backingBitmap))) {
if (backingBitmap != 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, bitmap.get(), nullptr)))
return copiedBitmap;
}
}
}
}
auto size = bitmap->GetPixelSize();
ASSERT(size.height && size.width);
Checked<unsigned, RecordOverflow> 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 = 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(bitmap->GetPixelSize(), nullptr, bytesPerRow.unsafeGet(), bitmapProperties, &sourceCPUBitmap);
if (!SUCCEEDED(hr))
return nullptr;
if (!sourceCPUBitmap)
return nullptr;
hr = sourceCPUBitmap->CopyFromBitmap(nullptr, 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;
}
} // namespace WebCore
#endif