blob: 18b8718161c6a0e11e447d44aab26e892b652e66 [file] [log] [blame]
/*
* Copyright (C) 2006-2019 Apple Inc. All rights reserved.
* Copyright (C) 2007 Alp Toker <alp@atoker.com>
* Copyright (C) 2008, 2009 Dirk Schulze <krit@webkit.org>
* Copyright (C) 2008 Nuanti Ltd.
* Copyright (C) 2009 Brent Fulgham <bfulgham@webkit.org>
* Copyright (C) 2010, 2011 Igalia S.L.
* Copyright (C) Research In Motion Limited 2010. All rights reserved.
* Copyright (C) 2012, Intel Corporation
*
* 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 "Direct2DOperations.h"
#if USE(DIRECT2D)
#include "Direct2DUtilities.h"
#include "FloatConversion.h"
#include "FloatRect.h"
#include "GraphicsContextPlatformPrivateDirect2D.h"
#include "Image.h"
#include "ImageBuffer.h"
#include "ImageDecoderDirect2D.h"
#include "NotImplemented.h"
#include "Path.h"
#include "PlatformContextDirect2D.h"
#include "ShadowBlur.h"
#include <algorithm>
#include <d2d1.h>
namespace WebCore {
namespace Direct2D {
enum PatternAdjustment { NoAdjustment, AdjustPatternForGlobalAlpha };
enum AlphaPreservation { DoNotPreserveAlpha, PreserveAlpha };
// FIXME: Replace once GraphicsContext::dashedLineCornerWidthForStrokeWidth()
// is refactored as a static public function.
static float dashedLineCornerWidthForStrokeWidth(float strokeWidth, StrokeStyle strokeStyle, float strokeThickness)
{
return strokeStyle == DottedStroke ? strokeThickness : std::min(2.0f * strokeThickness, std::max(strokeThickness, strokeWidth / 3.0f));
}
// FIXME: Replace once GraphicsContext::dashedLinePatternWidthForStrokeWidth()
// is refactored as a static public function.
static float dashedLinePatternWidthForStrokeWidth(float strokeWidth, StrokeStyle strokeStyle, float strokeThickness)
{
return strokeStyle == DottedStroke ? strokeThickness : std::min(3.0f * strokeThickness, std::max(strokeThickness, strokeWidth / 3.0f));
}
// FIXME: Replace once GraphicsContext::dashedLinePatternOffsetForPatternAndStrokeWidth()
// is refactored as a static public function.
static float dashedLinePatternOffsetForPatternAndStrokeWidth(float patternWidth, float strokeWidth)
{
// Pattern starts with full fill and ends with the empty fill.
// 1. Let's start with the empty phase after the corner.
// 2. Check if we've got odd or even number of patterns and whether they fully cover the line.
// 3. In case of even number of patterns and/or remainder, move the pattern start position
// so that the pattern is balanced between the corners.
float patternOffset = patternWidth;
int numberOfSegments = std::floor(strokeWidth / patternWidth);
bool oddNumberOfSegments = numberOfSegments % 2;
float remainder = strokeWidth - (numberOfSegments * patternWidth);
if (oddNumberOfSegments && remainder)
patternOffset -= remainder / 2.0f;
else if (!oddNumberOfSegments) {
if (remainder)
patternOffset += patternOffset - (patternWidth + remainder) / 2.0f;
else
patternOffset += patternWidth / 2.0f;
}
return patternOffset;
}
// FIXME: Replace once GraphicsContext::centerLineAndCutOffCorners()
// is refactored as a static public function.
static Vector<FloatPoint> centerLineAndCutOffCorners(bool isVerticalLine, float cornerWidth, FloatPoint point1, FloatPoint point2)
{
// Center line and cut off corners for pattern painting.
if (isVerticalLine) {
float centerOffset = (point2.x() - point1.x()) / 2.0f;
point1.move(centerOffset, cornerWidth);
point2.move(-centerOffset, -cornerWidth);
} else {
float centerOffset = (point2.y() - point1.y()) / 2.0f;
point1.move(cornerWidth, centerOffset);
point2.move(-cornerWidth, -centerOffset);
}
return { point1, point2 };
}
namespace State {
void setStrokeThickness(PlatformContextDirect2D& platformContext, float strokeThickness)
{
platformContext.setStrokeThickness(strokeThickness);
}
void setStrokeStyle(PlatformContextDirect2D& platformContext, StrokeStyle strokeStyle)
{
platformContext.setStrokeStyle(strokeStyle);
}
void setCompositeOperation(PlatformContextDirect2D& platformContext, CompositeOperator compositeOperation, BlendMode blendMode)
{
D2D1_BLEND_MODE targetBlendMode = D2D1_BLEND_MODE_MULTIPLY;
D2D1_COMPOSITE_MODE targetCompositeMode = D2D1_COMPOSITE_MODE_FORCE_DWORD;
if (blendMode != BlendMode::Normal) {
switch (blendMode) {
case BlendMode::Multiply:
targetBlendMode = D2D1_BLEND_MODE_MULTIPLY;
break;
case BlendMode::Screen:
targetBlendMode = D2D1_BLEND_MODE_SCREEN;
break;
case BlendMode::Overlay:
targetBlendMode = D2D1_BLEND_MODE_OVERLAY;
break;
case BlendMode::Darken:
targetBlendMode = D2D1_BLEND_MODE_DARKEN;
break;
case BlendMode::Lighten:
targetBlendMode = D2D1_BLEND_MODE_LIGHTEN;
break;
case BlendMode::ColorDodge:
targetBlendMode = D2D1_BLEND_MODE_COLOR_DODGE;
break;
case BlendMode::ColorBurn:
targetBlendMode = D2D1_BLEND_MODE_COLOR_BURN;
break;
case BlendMode::HardLight:
targetBlendMode = D2D1_BLEND_MODE_HARD_LIGHT;
break;
case BlendMode::SoftLight:
targetBlendMode = D2D1_BLEND_MODE_SOFT_LIGHT;
break;
case BlendMode::Difference:
targetBlendMode = D2D1_BLEND_MODE_DIFFERENCE;
break;
case BlendMode::Exclusion:
targetBlendMode = D2D1_BLEND_MODE_EXCLUSION;
break;
case BlendMode::Hue:
targetBlendMode = D2D1_BLEND_MODE_HUE;
break;
case BlendMode::Saturation:
targetBlendMode = D2D1_BLEND_MODE_SATURATION;
break;
case BlendMode::Color:
targetBlendMode = D2D1_BLEND_MODE_COLOR;
break;
case BlendMode::Luminosity:
targetBlendMode = D2D1_BLEND_MODE_LUMINOSITY;
break;
case BlendMode::PlusDarker:
targetBlendMode = D2D1_BLEND_MODE_DARKER_COLOR;
break;
case BlendMode::PlusLighter:
targetBlendMode = D2D1_BLEND_MODE_LIGHTER_COLOR;
break;
default:
break;
}
} else {
switch (compositeOperation) {
case CompositeOperator::Clear:
// FIXME: targetBlendMode = D2D1_BLEND_MODE_CLEAR;
break;
case CompositeOperator::Copy:
targetCompositeMode = D2D1_COMPOSITE_MODE_SOURCE_COPY;
break;
case CompositeOperator::SourceOver:
targetCompositeMode = D2D1_COMPOSITE_MODE_SOURCE_OVER;
break;
case CompositeOperator::SourceIn:
targetCompositeMode = D2D1_COMPOSITE_MODE_SOURCE_IN;
break;
case CompositeOperator::SourceOut:
targetCompositeMode = D2D1_COMPOSITE_MODE_SOURCE_OUT;
break;
case CompositeOperator::SourceAtop:
targetCompositeMode = D2D1_COMPOSITE_MODE_SOURCE_ATOP;
break;
case CompositeOperator::DestinationOver:
targetCompositeMode = D2D1_COMPOSITE_MODE_DESTINATION_OVER;
break;
case CompositeOperator::DestinationIn:
targetCompositeMode = D2D1_COMPOSITE_MODE_DESTINATION_IN;
break;
case CompositeOperator::DestinationOut:
targetCompositeMode = D2D1_COMPOSITE_MODE_DESTINATION_OUT;
break;
case CompositeOperator::DestinationAtop:
targetCompositeMode = D2D1_COMPOSITE_MODE_DESTINATION_ATOP;
break;
case CompositeOperator::XOR:
targetCompositeMode = D2D1_COMPOSITE_MODE_XOR;
break;
case CompositeOperator::PlusDarker:
targetBlendMode = D2D1_BLEND_MODE_DARKEN;
break;
case CompositeOperator::PlusLighter:
targetBlendMode = D2D1_BLEND_MODE_LIGHTEN;
break;
case CompositeOperator::Difference:
targetBlendMode = D2D1_BLEND_MODE_DIFFERENCE;
break;
}
}
platformContext.setBlendAndCompositeMode(targetBlendMode, targetCompositeMode);
}
void setShouldAntialias(PlatformContextDirect2D& platformContext, bool enable)
{
auto antialiasMode = enable ? D2D1_ANTIALIAS_MODE_PER_PRIMITIVE : D2D1_ANTIALIAS_MODE_ALIASED;
platformContext.renderTarget()->SetAntialiasMode(antialiasMode);
}
void setCTM(PlatformContextDirect2D& platformContext, const AffineTransform& transform)
{
ASSERT(platformContext.renderTarget());
platformContext.renderTarget()->SetTransform(transform);
if (auto* graphicsContextPrivate = platformContext.graphicsContextPrivate())
graphicsContextPrivate->setCTM(transform);
}
AffineTransform getCTM(PlatformContextDirect2D& platformContext)
{
ASSERT(platformContext.renderTarget());
D2D1_MATRIX_3X2_F currentTransform;
platformContext.renderTarget()->GetTransform(&currentTransform);
return currentTransform;
}
IntRect getClipBounds(PlatformContextDirect2D& platformContext)
{
D2D1_SIZE_F clipSize;
if (auto clipLayer = platformContext.clipLayer())
clipSize = clipLayer->GetSize();
else
clipSize = platformContext.renderTarget()->GetSize();
FloatRect clipBounds(IntPoint(), clipSize);
return enclosingIntRect(clipBounds);
}
FloatRect roundToDevicePixels(PlatformContextDirect2D& platformContext, const FloatRect& rect)
{
notImplemented();
return rect;
}
bool isAcceleratedContext(PlatformContextDirect2D& platformContext)
{
auto renderProperties = D2D1::RenderTargetProperties();
renderProperties.type = D2D1_RENDER_TARGET_TYPE_HARDWARE;
return platformContext.renderTarget()->IsSupported(&renderProperties);
}
} // namespace State
FillSource::FillSource(const GraphicsContextState& state, const GraphicsContext& context)
: globalAlpha(state.alpha)
, color(state.fillColor)
, fillRule(state.fillRule)
{
ASSERT(context.hasPlatformContext());
auto& platformContext = *context.platformContext();
if (state.fillPattern) {
AffineTransform userToBaseCTM; // FIXME: This isn't really needed on Windows
brush = state.fillPattern->createPlatformPattern(context, state.alpha, userToBaseCTM);
} else if (state.fillGradient && !state.fillGradient->stops().isEmpty())
brush = state.fillGradient->createPlatformGradientIfNecessary(platformContext.renderTarget());
else
brush = platformContext.brushWithColor(color);
}
StrokeSource::StrokeSource(const GraphicsContextState& state, const GraphicsContext& context)
: globalAlpha(state.alpha)
, thickness(state.strokeThickness)
, color(state.strokeColor)
{
ASSERT(context.hasPlatformContext());
auto& platformContext = *context.platformContext();
if (state.strokePattern) {
AffineTransform userToBaseCTM; // FIXME: This isn't really needed on Windows
brush = state.strokePattern->createPlatformPattern(context, state.alpha, userToBaseCTM);
} else if (state.strokeGradient)
brush = state.strokeGradient->createPlatformGradientIfNecessary(platformContext.renderTarget());
else
brush = platformContext.brushWithColor(color);
}
ShadowState::ShadowState(const GraphicsContextState& state)
: offset(state.shadowOffset)
, blur(state.shadowBlur)
, color(state.shadowColor)
, ignoreTransforms(state.shadowsIgnoreTransforms)
, globalAlpha(state.alpha)
, globalCompositeOperator(state.compositeOperator)
{
}
bool ShadowState::isVisible() const
{
return color.isVisible() && (offset.width() || offset.height() || blur);
}
bool ShadowState::isRequired(PlatformContextDirect2D& platformContext) const
{
// We can't avoid ShadowBlur if the shadow has blur.
if (color.isVisible() && blur)
return true;
// We can avoid ShadowBlur and optimize, since we're not drawing on a
// canvas and box shadows are affected by the transformation matrix.
if (!ignoreTransforms)
return false;
// We can avoid ShadowBlur, since there are no transformations to apply to the canvas.
if (State::getCTM(platformContext).isIdentity())
return false;
// Otherwise, no chance avoiding ShadowBlur.
return true;
}
void setLineCap(PlatformContextDirect2D& platformContext, LineCap lineCap)
{
D2D1_CAP_STYLE capStyle = D2D1_CAP_STYLE_FLAT;
switch (lineCap) {
case RoundCap:
capStyle = D2D1_CAP_STYLE_ROUND;
break;
case SquareCap:
capStyle = D2D1_CAP_STYLE_SQUARE;
break;
case ButtCap:
default:
capStyle = D2D1_CAP_STYLE_FLAT;
break;
}
platformContext.setLineCap(capStyle);
}
void setLineDash(PlatformContextDirect2D& platformContext, const DashArray& dashes, float dashOffset)
{
platformContext.setDashes(dashes);
platformContext.setDashOffset(dashOffset);
}
void setLineJoin(PlatformContextDirect2D& platformContext, LineJoin lineJoin)
{
D2D1_LINE_JOIN joinStyle = D2D1_LINE_JOIN_MITER;
switch (lineJoin) {
case RoundJoin:
joinStyle = D2D1_LINE_JOIN_ROUND;
break;
case BevelJoin:
joinStyle = D2D1_LINE_JOIN_BEVEL;
break;
case MiterJoin:
default:
joinStyle = D2D1_LINE_JOIN_MITER;
break;
}
platformContext.setLineJoin(joinStyle);
}
void setMiterLimit(PlatformContextDirect2D& platformContext, float miterLimit)
{
platformContext.setMiterLimit(miterLimit);
}
void fillRect(PlatformContextDirect2D& platformContext, const FloatRect& rect, const FillSource& fillSource, const ShadowState& shadowState)
{
auto context = platformContext.renderTarget();
platformContext.setTags(1, __LINE__);
drawWithShadow(platformContext, rect, shadowState, [&platformContext, rect, &fillSource](ID2D1RenderTarget* renderTarget) {
const D2D1_RECT_F d2dRect = rect;
renderTarget->FillRectangle(&d2dRect, fillSource.brush.get());
});
}
void fillRect(PlatformContextDirect2D& platformContext, const FloatRect& rect, const Color& color, const ShadowState& shadowState)
{
auto context = platformContext.renderTarget();
platformContext.setTags(1, __LINE__);
drawWithShadow(platformContext, rect, shadowState, [&platformContext, color, rect](ID2D1RenderTarget* renderTarget) {
const D2D1_RECT_F d2dRect = rect;
renderTarget->FillRectangle(&d2dRect, platformContext.brushWithColor(color).get());
});
}
void fillRoundedRect(PlatformContextDirect2D& platformContext, const FloatRoundedRect& rect, const Color& color, const ShadowState& shadowState)
{
auto context = platformContext.renderTarget();
platformContext.setTags(1, __LINE__);
const FloatRect& r = rect.rect();
const FloatRoundedRect::Radii& radii = rect.radii();
bool equalWidths = (radii.topLeft().width() == radii.topRight().width() && radii.topRight().width() == radii.bottomLeft().width() && radii.bottomLeft().width() == radii.bottomRight().width());
bool equalHeights = (radii.topLeft().height() == radii.bottomLeft().height() && radii.bottomLeft().height() == radii.topRight().height() && radii.topRight().height() == radii.bottomRight().height());
bool hasCustomFill = false; // FIXME: Why isn't a FillSource passed to this function?
if (!hasCustomFill && equalWidths && equalHeights && radii.topLeft().width() * 2 == r.width() && radii.topLeft().height() * 2 == r.height()) {
drawWithShadow(platformContext, r, shadowState, [&platformContext, color, rect, radii, r](ID2D1RenderTarget* renderTarget) {
auto roundedRect = D2D1::RoundedRect(r, radii.topLeft().width(), radii.topLeft().height());
renderTarget->FillRoundedRectangle(&roundedRect, platformContext.brushWithColor(color).get());
});
} else {
Path path;
path.addRoundedRect(rect);
fillPath(platformContext, path, color, shadowState);
}
}
void fillRectWithRoundedHole(PlatformContextDirect2D& platformContext, const FloatRect& rect, const FloatRoundedRect& roundedHoleRect, const FillSource& fillSource, const ShadowState& shadowState)
{
auto context = platformContext.renderTarget();
platformContext.setTags(1, __LINE__);
Path path;
path.addRect(rect);
if (!roundedHoleRect.radii().isZero())
path.addRoundedRect(roundedHoleRect);
else
path.addRect(roundedHoleRect.rect());
FillSource fillWithHoleSource = fillSource;
fillWithHoleSource.fillRule = WindRule::EvenOdd;
fillPath(platformContext, path, fillWithHoleSource, shadowState);
}
void fillRectWithGradient(PlatformContextDirect2D& platformContext, const FloatRect& rect, ID2D1Brush* gradient)
{
platformContext.setTags(1, __LINE__);
drawWithoutShadow(platformContext, [&platformContext, rect, &gradient](ID2D1RenderTarget* renderTarget) {
const D2D1_RECT_F d2dRect = rect;
renderTarget->FillRectangle(&d2dRect, gradient);
});
}
void fillPath(PlatformContextDirect2D& platformContext, const Path& path, const FillSource& fillSource, const ShadowState& shadowState)
{
path.closeAnyOpenGeometries(D2D1_FIGURE_END_OPEN);
auto context = platformContext.renderTarget();
platformContext.setTags(1, __LINE__);
COMPtr<ID2D1GeometryGroup> pathToFill;
path.createGeometryWithFillMode(fillSource.fillRule, pathToFill);
platformContext.setTags(1, __LINE__);
Function<void(ID2D1RenderTarget*)> drawFunction = [&platformContext, &pathToFill, &fillSource](ID2D1RenderTarget* renderTarget) {
renderTarget->FillGeometry(pathToFill.get(), fillSource.brush.get());
};
if (shadowState.isVisible()) {
FloatRect boundingRect = path.fastBoundingRect();
drawWithShadow(platformContext, boundingRect, shadowState, WTFMove(drawFunction));
} else
drawWithoutShadow(platformContext, WTFMove(drawFunction));
}
void fillPath(PlatformContextDirect2D& platformContext, const Path& path, const Color& color, const ShadowState& shadowState)
{
auto context = platformContext.renderTarget();
platformContext.setTags(1, __LINE__);
COMPtr<ID2D1GeometryGroup> pathToFill;
path.createGeometryWithFillMode(WindRule::EvenOdd, pathToFill);
platformContext.setTags(1, __LINE__);
Function<void(ID2D1RenderTarget*)> drawFunction = [&platformContext, &pathToFill, color](ID2D1RenderTarget* renderTarget) {
renderTarget->FillGeometry(pathToFill.get(), platformContext.brushWithColor(color).get());
};
if (shadowState.isVisible()) {
FloatRect boundingRect = path.fastBoundingRect();
drawWithShadow(platformContext, boundingRect, shadowState, WTFMove(drawFunction));
} else
drawWithoutShadow(platformContext, WTFMove(drawFunction));
}
void strokeRect(PlatformContextDirect2D& platformContext, const FloatRect& rect, float lineWidth, const StrokeSource& strokeSource, const ShadowState& shadowState)
{
drawWithShadow(platformContext, rect, shadowState, [&platformContext, &strokeSource, rect, lineWidth](ID2D1RenderTarget* renderTarget) {
platformContext.setTags(1, __LINE__);
const D2D1_RECT_F d2dRect = rect;
renderTarget->DrawRectangle(&d2dRect, strokeSource.brush.get(), lineWidth, platformContext.strokeStyle());
});
}
void strokePath(PlatformContextDirect2D& platformContext, const Path& path, const StrokeSource& strokeSource, const ShadowState& shadowState)
{
auto context = platformContext.renderTarget();
platformContext.setTags(1, __LINE__);
Function<void(ID2D1RenderTarget*)> drawFunction = [&platformContext, &path, &strokeSource](ID2D1RenderTarget* renderTarget) {
renderTarget->DrawGeometry(path.platformPath(), strokeSource.brush.get(), strokeSource.thickness, platformContext.strokeStyle());
};
if (shadowState.isVisible()) {
auto boundingRect = path.fastBoundingRectForStroke(platformContext);
drawWithShadow(platformContext, boundingRect, shadowState, WTFMove(drawFunction));
} else
drawWithoutShadow(platformContext, WTFMove(drawFunction));
}
void drawPath(PlatformContextDirect2D& platformContext, const Path& path, const StrokeSource& strokeSource, const ShadowState&)
{
auto context = platformContext.renderTarget();
path.closeAnyOpenGeometries(D2D1_FIGURE_END_OPEN);
platformContext.setTags(1, __LINE__);
drawWithoutShadow(platformContext, [&platformContext, &strokeSource, &path](ID2D1RenderTarget* renderTarget) {
renderTarget->DrawGeometry(path.platformPath(), strokeSource.brush.get(), strokeSource.thickness, platformContext.strokeStyle());
});
flush(platformContext);
}
static void drawWithShadowHelper(ID2D1RenderTarget* context, ID2D1BitmapRenderTarget* bitmapTarget, const FloatRect& boundingRect, const Color& shadowColor, const FloatSize& shadowOffset, float shadowBlur)
{
COMPtr<ID2D1Bitmap> currentCanvas = Direct2D::createBitmapCopyFromContext(bitmapTarget);
if (!currentCanvas)
return;
COMPtr<ID2D1DeviceContext> shadowDeviceContext;
HRESULT hr = bitmapTarget->QueryInterface(&shadowDeviceContext);
RELEASE_ASSERT(SUCCEEDED(hr));
// Create the shadow effect
COMPtr<ID2D1Effect> shadowEffect;
hr = shadowDeviceContext->CreateEffect(CLSID_D2D1Shadow, &shadowEffect);
RELEASE_ASSERT(SUCCEEDED(hr));
shadowEffect->SetInput(0, currentCanvas.get());
shadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR, static_cast<D2D1_VECTOR_4F>(shadowColor));
float blurDeviation = 0.5 * shadowBlur;
shadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, blurDeviation);
COMPtr<ID2D1Effect> shadowTransformEffect;
hr = shadowDeviceContext->CreateEffect(CLSID_D2D12DAffineTransform, &shadowTransformEffect);
RELEASE_ASSERT(SUCCEEDED(hr));
shadowTransformEffect->SetInputEffect(0, shadowEffect.get());
// Must flip the axis for the shadow offset, since Direct2D's Y axis is inverted.
auto shadowTranslation = D2D1::Matrix3x2F::Translation(shadowOffset.width(), -shadowOffset.height());
shadowTransformEffect->SetValue(D2D1_2DAFFINETRANSFORM_PROP_TRANSFORM_MATRIX, shadowTranslation);
COMPtr<ID2D1Effect> compositor;
hr = shadowDeviceContext->CreateEffect(CLSID_D2D1Composite, &compositor);
RELEASE_ASSERT(SUCCEEDED(hr));
compositor->SetInputEffect(0, shadowTransformEffect.get());
compositor->SetInput(1, currentCanvas.get());
shadowDeviceContext->BeginDraw();
shadowDeviceContext->Clear(D2D1::ColorF(0, 0, 0, 0));
shadowDeviceContext->DrawImage(compositor.get());
hr = shadowDeviceContext->EndDraw();
RELEASE_ASSERT(SUCCEEDED(hr));
COMPtr<ID2D1Bitmap> bitmap;
hr = bitmapTarget->GetBitmap(&bitmap);
RELEASE_ASSERT(SUCCEEDED(hr));
IntSize pixelSize = context->GetPixelSize();
COMPtr<ID2D1Bitmap> cropped = Direct2D::createBitmap(bitmapTarget, pixelSize);
if (!cropped)
return;
auto scaleFactor = bitmapResolution(context);
FloatRect scaledBoundingRect(boundingRect);
scaledBoundingRect.scale(scaleFactor.width(), scaleFactor.height());
// Take the magnitude of the offset. If the origin was outside the drawing context, we stretched to include those points,
// and so we need to crop them out. If the origin was inside, we want to honor the offset.
int x = clampTo<int>(std::abs(scaledBoundingRect.x()));
int y = clampTo<int>(std::abs(scaledBoundingRect.y()));
int width = clampTo<int>(scaledBoundingRect.width());
int height = clampTo<int>(scaledBoundingRect.height());
IntRect outputRect(IntPoint(x, y), IntSize(width, height));
IntRect destContextRect(IntPoint(), pixelSize);
if (outputRect.intersects(destContextRect))
outputRect.intersect(destContextRect);
D2D1_RECT_U cropRect = outputRect;
auto targetPoint = D2D1::Point2U();
cropped->CopyFromBitmap(&targetPoint, bitmap.get(), &cropRect);
context->DrawBitmap(cropped.get());
}
void drawWithShadow(PlatformContextDirect2D& platformContext, FloatRect boundingRect, const ShadowState& shadowState, WTF::Function<void(ID2D1RenderTarget*)>&& drawCommands)
{
if (!shadowState.isVisible()) {
drawWithoutShadow(platformContext, WTFMove(drawCommands));
return;
}
platformContext.notifyPreDrawObserver();
auto* context = platformContext.renderTarget();
FloatSize renderTargetSize = context->GetSize();
const FloatRect contextRect(FloatPoint(), renderTargetSize);
FloatRect workingContextRect(contextRect);
if (boundingRect.isInfinite())
boundingRect = workingContextRect;
if (!contextRect.contains(boundingRect))
workingContextRect.unite(boundingRect);
workingContextRect.expand(std::abs(shadowState.offset.width()) + shadowState.blur, std::abs(shadowState.offset.height()) + shadowState.blur);
auto scaleFactor = bitmapResolution(context);
workingContextRect.scale(scaleFactor);
IntSize pixelDimensions(clampTo<int>(workingContextRect.width()), clampTo<int>(workingContextRect.height()));
// Render the current geometry to a bitmap context
COMPtr<ID2D1BitmapRenderTarget> bitmapTarget = createBitmapRenderTargetOfSize(pixelDimensions, context);
D2D1_MATRIX_3X2_F currentTransform;
bitmapTarget->GetTransform(&currentTransform);
if (!contextRect.contains(boundingRect.location())) {
auto translation = D2D1::Matrix3x2F::Translation(-boundingRect.location().x(), -boundingRect.location().y());
bitmapTarget->SetTransform(translation * currentTransform);
}
bitmapTarget->BeginDraw();
drawCommands(bitmapTarget.get());
HRESULT hr = bitmapTarget->EndDraw();
RELEASE_ASSERT(SUCCEEDED(hr));
bitmapTarget->SetTransform(currentTransform);
drawWithShadowHelper(context, bitmapTarget.get(), boundingRect, shadowState.color, shadowState.offset, shadowState.blur);
platformContext.notifyPostDrawObserver();
}
void drawWithoutShadow(PlatformContextDirect2D& platformContext, WTF::Function<void(ID2D1RenderTarget*)>&& drawCommands)
{
platformContext.notifyPreDrawObserver();
drawCommands(platformContext.renderTarget());
platformContext.notifyPostDrawObserver();
}
void clearRect(PlatformContextDirect2D& platformContext, const FloatRect& rect)
{
drawWithoutShadow(platformContext, [&platformContext, rect](ID2D1RenderTarget* renderTarget) {
FloatRect renderTargetRect(FloatPoint(), renderTarget->GetSize());
D2D1::Matrix3x2F matrix;
renderTarget->GetTransform(&matrix);
AffineTransform transform(matrix);
FloatRect rectToClear(rect);
if (!transform.preservesAxisAlignment()) {
COMPtr<ID2D1RectangleGeometry> rectangle;
HRESULT hr = GraphicsContext::systemFactory()->CreateRectangleGeometry(rectToClear, &rectangle);
RELEASE_ASSERT(SUCCEEDED(hr));
renderTarget->FillGeometry(rectangle.get(), platformContext.brushWithColor(D2D1::ColorF(0, 0, 0, 0)).get());
return;
}
rectToClear = transform.mapRect(rectToClear);
if (rectToClear.contains(renderTargetRect)) {
platformContext.setTags(1, __LINE__);
renderTarget->Clear(D2D1::ColorF(0, 0, 0, 0));
return;
}
platformContext.setTags(1, __LINE__);
renderTarget->PushAxisAlignedClip(rectToClear, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
renderTarget->Clear(D2D1::ColorF(0, 0, 0, 0));
renderTarget->PopAxisAlignedClip();
});
}
void drawGlyphs(PlatformContextDirect2D& platformContext, const FillSource& fillSource, const StrokeSource& strokeSource, const ShadowState& shadowState, const FloatPoint& point, const Font& font, double syntheticBoldOffset, const Vector<unsigned short>& glyphs, const Vector<float>& horizontalAdvances, const Vector<DWRITE_GLYPH_OFFSET>& glyphOffsets, float xOffset, TextDrawingModeFlags textDrawingMode, float strokeThickness, const FloatSize& shadowOffset, const Color& shadowColor)
{
auto renderTarget = platformContext.renderTarget();
const FontPlatformData& platformData = font.platformData();
PlatformContextStateSaver stateSaver(platformContext);
D2D1_MATRIX_3X2_F matrix;
renderTarget->GetTransform(&matrix);
/*
// FIXME: Get Synthetic Oblique working
if (platformData.syntheticOblique()) {
static float skew = -tanf(syntheticObliqueAngle() * piFloat / 180.0f);
auto skewMatrix = D2D1::Matrix3x2F::Skew(skew, 0);
context->SetTransform(matrix * skewMatrix);
}
*/
RELEASE_ASSERT(platformData.dwFont());
RELEASE_ASSERT(platformData.dwFontFace());
DWRITE_GLYPH_RUN glyphRun;
glyphRun.fontFace = platformData.dwFontFace();
glyphRun.fontEmSize = platformData.size();
glyphRun.glyphCount = glyphs.size();
glyphRun.glyphIndices = glyphs.data();
glyphRun.glyphAdvances = horizontalAdvances.data();
glyphRun.glyphOffsets = nullptr;
glyphRun.isSideways = platformData.orientation() == FontOrientation::Vertical;
glyphRun.bidiLevel = 0;
/*
// FIXME(): Get Shadows working
FloatSize shadowOffset;
float shadowBlur;
Color shadowColor;
graphicsContext.getShadow(shadowOffset, shadowBlur, shadowColor);
bool hasSimpleShadow = graphicsContext.textDrawingMode() == TextModeFill && shadowColor.isValid() && !shadowBlur && (!graphicsContext.shadowsIgnoreTransforms() || graphicsContext.getCTM().isIdentityOrTranslationOrFlipped());
if (hasSimpleShadow) {
// Paint simple shadows ourselves instead of relying on CG shadows, to avoid losing subpixel antialiasing.
graphicsContext.clearShadow();
Color fillColor = graphicsContext.fillColor();
Color shadowFillColor(shadowColor.red(), shadowColor.green(), shadowColor.blue(), shadowColor.alpha() * fillColor.alpha() / 255);
float shadowTextX = point.x() + shadowOffset.width();
// If shadows are ignoring transforms, then we haven't applied the Y coordinate flip yet, so down is negative.
float shadowTextY = point.y() + shadowOffset.height() * (graphicsContext.shadowsIgnoreTransforms() ? -1 : 1);
auto shadowBrush = graphicsContext.brushWithColor(shadowFillColor);
context->DrawGlyphRun(D2D1::Point2F(shadowTextX, shadowTextY), &glyphRun, shadowBrush);
if (font.syntheticBoldOffset())
context->DrawGlyphRun(D2D1::Point2F(shadowTextX + font.syntheticBoldOffset(), shadowTextY), &glyphRun, shadowBrush);
}
*/
renderTarget->DrawGlyphRun(D2D1::Point2F(point.x(), point.y()), &glyphRun, fillSource.brush.get());
// if (font.syntheticBoldOffset())
// context->DrawGlyphRun(D2D1::Point2F(point.x() + font.syntheticBoldOffset(), point.y()), &glyphRun, graphicsContext.solidFillBrush());
// if (hasSimpleShadow)
// graphicsContext.setShadow(shadowOffset, shadowBlur, shadowColor);
stateSaver.restore();
platformContext.notifyPostDrawObserver();
}
void drawNativeImage(PlatformContextDirect2D& platformContext, IWICBitmap* image, const FloatSize& originalImageSize, const FloatRect& destRect, const FloatRect& srcRect, const ImagePaintingOptions& options, float globalAlpha, const ShadowState& shadowState)
{
auto nativeImageSize = bitmapSize(image);
COMPtr<ID2D1Bitmap> deviceBitmap;
HRESULT hr = platformContext.renderTarget()->CreateBitmapFromWicBitmap(image, &deviceBitmap);
if (!SUCCEEDED(hr))
return;
auto imageSize = bitmapSize(deviceBitmap.get());
drawNativeImage(platformContext, deviceBitmap.get(), imageSize, destRect, srcRect, options, globalAlpha, shadowState);
}
void drawNativeImage(PlatformContextDirect2D& platformContext, ID2D1Bitmap* image, const FloatSize& imageSize, const FloatRect& destRect, const FloatRect& srcRect, const ImagePaintingOptions& options, float globalAlpha, const ShadowState& shadowState)
{
auto bitmapSize = image->GetSize();
float currHeight = options.orientation().usesWidthAsHeight() ? bitmapSize.width : bitmapSize.height;
if (currHeight <= srcRect.y())
return;
auto context = platformContext.renderTarget();
D2D1_MATRIX_3X2_F ctm;
context->GetTransform(&ctm);
AffineTransform transform(ctm);
PlatformContextStateSaver stateSaver(platformContext);
bool shouldUseSubimage = false;
// If the source rect is a subportion of the image, then we compute an inflated destination rect that will hold the entire image
// and then set a clip to the portion that we want to display.
FloatRect adjustedDestRect = destRect;
if (srcRect.size() != imageSize) {
// FIXME: Implement image scaling
notImplemented();
}
// If the image is only partially loaded, then shrink the destination rect that we're drawing into accordingly.
if (!shouldUseSubimage && currHeight < imageSize.height())
adjustedDestRect.setHeight(adjustedDestRect.height() * currHeight / imageSize.height());
State::setCompositeOperation(platformContext, options.compositeOperator(), options.blendMode());
// ImageOrientation expects the origin to be at (0, 0).
transform.translate(adjustedDestRect.x(), adjustedDestRect.y());
context->SetTransform(transform);
adjustedDestRect.setLocation(FloatPoint());
if (options.orientation() != ImageOrientation::None) {
concatCTM(platformContext, options.orientation().transformFromDefault(adjustedDestRect.size()));
if (options.orientation().usesWidthAsHeight()) {
// The destination rect will have it's width and height already reversed for the orientation of
// the image, as it was needed for page layout, so we need to reverse it back here.
adjustedDestRect = FloatRect(adjustedDestRect.x(), adjustedDestRect.y(), adjustedDestRect.height(), adjustedDestRect.width());
}
}
platformContext.setTags(1, __LINE__);
drawWithShadow(platformContext, adjustedDestRect, shadowState, [image, adjustedDestRect, globalAlpha, srcRect](ID2D1RenderTarget* renderTarget) {
renderTarget->DrawBitmap(image, adjustedDestRect, globalAlpha, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, srcRect);
});
if (!stateSaver.didSave())
context->SetTransform(ctm);
}
void drawPattern(PlatformContextDirect2D& platformContext, COMPtr<ID2D1Bitmap>&& tileImage, const IntSize& size, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, CompositeOperator compositeOperator, BlendMode blendMode)
{
auto context = platformContext.renderTarget();
PlatformContextStateSaver stateSaver(platformContext);
clip(platformContext, destRect);
State::setCompositeOperation(platformContext, compositeOperator, blendMode);
auto bitmapBrushProperties = D2D1::BitmapBrushProperties();
bitmapBrushProperties.extendModeX = D2D1_EXTEND_MODE_WRAP;
bitmapBrushProperties.extendModeY = D2D1_EXTEND_MODE_WRAP;
// Create a brush transformation so we paint using the section of the image we care about.
AffineTransform transformation = patternTransform;
transformation.translate(destRect.location());
auto brushProperties = D2D1::BrushProperties();
brushProperties.transform = transformation;
brushProperties.opacity = 1.0f;
// If we only want a subset of the bitmap, we need to create a cropped bitmap image. According to the documentation,
// this does not allocate new bitmap memory.
if (size.width() > destRect.width() || size.height() > destRect.height()) {
float dpiX = 0;
float dpiY = 0;
tileImage->GetDpi(&dpiX, &dpiY);
auto bitmapProperties = D2D1::BitmapProperties(tileImage->GetPixelFormat(), dpiX, dpiY);
COMPtr<ID2D1Bitmap> subImage;
HRESULT hr = context->CreateBitmap(IntSize(tileRect.size()), bitmapProperties, &subImage);
if (SUCCEEDED(hr)) {
D2D1_RECT_U finishRect = IntRect(tileRect);
hr = subImage->CopyFromBitmap(nullptr, tileImage.get(), &finishRect);
if (SUCCEEDED(hr))
tileImage = subImage;
}
}
COMPtr<ID2D1BitmapBrush> patternBrush;
HRESULT hr = context->CreateBitmapBrush(tileImage.get(), &bitmapBrushProperties, &brushProperties, &patternBrush);
ASSERT(SUCCEEDED(hr));
if (!SUCCEEDED(hr))
return;
drawWithoutShadow(platformContext, [destRect, patternBrush](ID2D1RenderTarget* renderTarget) {
const D2D1_RECT_F d2dRect = destRect;
renderTarget->FillRectangle(&d2dRect, patternBrush.get());
});
}
void drawRect(PlatformContextDirect2D& platformContext, const FloatRect& rect, float borderThickness, const Color& fillColor, StrokeStyle strokeStyle, const Color& strokeColor)
{
// FIXME: this function does not handle patterns and gradients like drawPath does, it probably should.
ASSERT(!rect.isEmpty());
platformContext.setTags(1, __LINE__);
drawWithoutShadow(platformContext, [&platformContext, rect, fillColor, strokeColor, strokeStyle](ID2D1RenderTarget* renderTarget) {
const D2D1_RECT_F d2dRect = rect;
auto fillBrush = platformContext.brushWithColor(fillColor);
renderTarget->FillRectangle(&d2dRect, fillBrush.get());
if (strokeStyle != NoStroke) {
auto strokeBrush = platformContext.brushWithColor(strokeColor);
renderTarget->DrawRectangle(&d2dRect, strokeBrush.get(), platformContext.strokeThickness(), platformContext.strokeStyle());
}
});
}
void drawLine(PlatformContextDirect2D& platformContext, const FloatPoint& point1, const FloatPoint& point2, StrokeStyle strokeStyle, const Color& strokeColor, float strokeThickness, bool shouldAntialias)
{
bool isVerticalLine = (point1.x() + strokeThickness == point2.x());
float strokeWidth = isVerticalLine ? point2.y() - point1.y() : point2.x() - point1.x();
if (!strokeThickness || !strokeWidth)
return;
auto context = platformContext.renderTarget();
float cornerWidth = 0;
bool drawsDashedLine = strokeStyle == DottedStroke || strokeStyle == DashedStroke;
COMPtr<ID2D1StrokeStyle> d2dStrokeStyle;
PlatformContextStateSaver stateSaver(platformContext, drawsDashedLine);
if (drawsDashedLine) {
// Figure out end points to ensure we always paint corners.
cornerWidth = dashedLineCornerWidthForStrokeWidth(strokeWidth, strokeStyle, strokeThickness);
strokeWidth -= 2 * cornerWidth;
float patternWidth = dashedLinePatternWidthForStrokeWidth(strokeWidth, strokeStyle, strokeThickness);
// Check if corner drawing sufficiently covers the line.
if (strokeWidth <= patternWidth + 1)
return;
float patternOffset = dashedLinePatternOffsetForPatternAndStrokeWidth(patternWidth, strokeWidth);
const float dashes[2] = { patternWidth, patternWidth };
auto strokeStyleProperties = platformContext.strokeStyleProperties();
GraphicsContext::systemFactory()->CreateStrokeStyle(&strokeStyleProperties, dashes, ARRAYSIZE(dashes), &d2dStrokeStyle);
platformContext.setPatternWidth(patternWidth);
platformContext.setPatternOffset(patternOffset);
platformContext.setDashes(DashArray(2, patternWidth));
d2dStrokeStyle = platformContext.strokeStyle();
}
auto centeredPoints = centerLineAndCutOffCorners(isVerticalLine, cornerWidth, point1, point2);
auto p1 = centeredPoints[0];
auto p2 = centeredPoints[1];
platformContext.setTags(1, __LINE__);
drawWithoutShadow(platformContext, [&platformContext, p1, p2, d2dStrokeStyle, strokeColor, strokeThickness](ID2D1RenderTarget* renderTarget) {
renderTarget->DrawLine(p1, p2, platformContext.brushWithColor(strokeColor).get(), strokeThickness, d2dStrokeStyle.get());
});
}
void drawLinesForText(PlatformContextDirect2D&, const FloatPoint&, float, const DashArray&, bool, bool, const Color&)
{
notImplemented();
}
void drawDotsForDocumentMarker(PlatformContextDirect2D&, const FloatRect&, DocumentMarkerLineStyle)
{
notImplemented();
}
void fillEllipse(PlatformContextDirect2D& platformContext, const FloatRect& rect, const Color& fillColor, StrokeStyle strokeStyle, const Color& strokeColor, float strokeThickness)
{
auto ellipse = D2D1::Ellipse(rect.center(), 0.5 * rect.width(), 0.5 * rect.height());
platformContext.setTags(1, __LINE__);
drawWithoutShadow(platformContext, [&platformContext, ellipse, fillColor, strokeStyle, strokeColor, strokeThickness](ID2D1RenderTarget* renderTarget) {
auto fillBrush = platformContext.brushWithColor(fillColor);
renderTarget->FillEllipse(&ellipse, fillBrush.get());
if (strokeStyle != StrokeStyle::NoStroke) {
auto strokeBrush = platformContext.brushWithColor(strokeColor);
renderTarget->DrawEllipse(&ellipse, strokeBrush.get(), strokeThickness, platformContext.strokeStyle());
}
});
}
void drawEllipse(PlatformContextDirect2D& platformContext, const FloatRect& rect, StrokeStyle strokeStyle, const Color& strokeColor, float strokeThickness)
{
if (strokeStyle == StrokeStyle::NoStroke)
return;
auto ellipse = D2D1::Ellipse(rect.center(), 0.5 * rect.width(), 0.5 * rect.height());
platformContext.setTags(1, __LINE__);
drawWithoutShadow(platformContext, [&platformContext, ellipse, strokeColor, strokeThickness](ID2D1RenderTarget* renderTarget) {
auto strokeBrush = platformContext.brushWithColor(strokeColor);
renderTarget->DrawEllipse(&ellipse, strokeBrush.get(), strokeThickness, platformContext.strokeStyle());
});
}
void drawFocusRing(PlatformContextDirect2D&, const Path&, float, const Color&)
{
notImplemented();
}
void drawFocusRing(PlatformContextDirect2D&, const Vector<FloatRect>&, float, const Color&)
{
notImplemented();
}
void flush(PlatformContextDirect2D& platformContext)
{
ASSERT(platformContext.renderTarget());
D2D1_TAG first, second;
HRESULT hr = platformContext.renderTarget()->Flush(&first, &second);
RELEASE_ASSERT(SUCCEEDED(hr));
}
void save(PlatformContextDirect2D& platformContext)
{
platformContext.save();
if (auto* graphicsContextPrivate = platformContext.graphicsContextPrivate())
graphicsContextPrivate->save();
}
void restore(PlatformContextDirect2D& platformContext)
{
platformContext.restore();
if (auto* graphicsContextPrivate = platformContext.graphicsContextPrivate())
graphicsContextPrivate->restore();
}
void translate(PlatformContextDirect2D& platformContext, float x, float y)
{
ASSERT(platformContext.renderTarget());
D2D1_MATRIX_3X2_F currentTransform;
platformContext.renderTarget()->GetTransform(&currentTransform);
auto translation = D2D1::Matrix3x2F::Translation(x, y);
platformContext.renderTarget()->SetTransform(translation * currentTransform);
if (auto* graphicsContextPrivate = platformContext.graphicsContextPrivate())
graphicsContextPrivate->translate(x, y);
}
void rotate(PlatformContextDirect2D& platformContext, float angleInRadians)
{
ASSERT(platformContext.renderTarget());
D2D1_MATRIX_3X2_F currentTransform;
platformContext.renderTarget()->GetTransform(&currentTransform);
auto rotation = D2D1::Matrix3x2F::Rotation(rad2deg(angleInRadians));
platformContext.renderTarget()->SetTransform(rotation * currentTransform);
if (auto* graphicsContextPrivate = platformContext.graphicsContextPrivate())
graphicsContextPrivate->rotate(angleInRadians);
}
void scale(PlatformContextDirect2D& platformContext, const FloatSize& size)
{
ASSERT(platformContext.renderTarget());
D2D1_MATRIX_3X2_F currentTransform;
platformContext.renderTarget()->GetTransform(&currentTransform);
auto scale = D2D1::Matrix3x2F::Scale(size);
platformContext.renderTarget()->SetTransform(scale * currentTransform);
if (auto* graphicsContextPrivate = platformContext.graphicsContextPrivate())
graphicsContextPrivate->scale(size);
}
void concatCTM(PlatformContextDirect2D& platformContext, const AffineTransform& affineTransform)
{
ASSERT(platformContext.renderTarget());
D2D1_MATRIX_3X2_F currentTransform;
platformContext.renderTarget()->GetTransform(&currentTransform);
D2D1_MATRIX_3X2_F transformToConcat = affineTransform;
platformContext.renderTarget()->SetTransform(transformToConcat * currentTransform);
if (auto* graphicsContextPrivate = platformContext.graphicsContextPrivate())
graphicsContextPrivate->concatCTM(affineTransform);
}
void beginTransparencyLayer(PlatformContextDirect2D& platformContext, float opacity)
{
PlatformContextDirect2D::TransparencyLayerState transparencyLayer;
transparencyLayer.opacity = opacity;
transparencyLayer.renderTarget = createBitmapRenderTarget(platformContext.renderTarget());
platformContext.m_transparencyLayerStack.append(WTFMove(transparencyLayer));
platformContext.m_transparencyLayerStack.last().renderTarget->BeginDraw();
platformContext.m_transparencyLayerStack.last().renderTarget->Clear(D2D1::ColorF(0, 0, 0, 0));
}
void endTransparencyLayer(PlatformContextDirect2D& platformContext)
{
auto currentLayer = platformContext.m_transparencyLayerStack.takeLast();
auto renderTarget = currentLayer.renderTarget;
if (!renderTarget)
return;
HRESULT hr = renderTarget->EndDraw();
RELEASE_ASSERT(SUCCEEDED(hr));
auto context = platformContext.renderTarget();
if (currentLayer.hasShadow)
drawWithShadowHelper(context, renderTarget.get(), FloatRect(), currentLayer.shadowColor, currentLayer.shadowOffset, currentLayer.shadowBlur);
else {
COMPtr<ID2D1Bitmap> bitmap;
hr = renderTarget->GetBitmap(&bitmap);
RELEASE_ASSERT(SUCCEEDED(hr));
COMPtr<ID2D1BitmapBrush> bitmapBrush;
auto bitmapBrushProperties = D2D1::BitmapBrushProperties();
auto brushProperties = D2D1::BrushProperties();
HRESULT hr = context->CreateBitmapBrush(bitmap.get(), bitmapBrushProperties, brushProperties, &bitmapBrush);
RELEASE_ASSERT(SUCCEEDED(hr));
auto size = bitmap->GetSize();
auto rectInDIP = D2D1::RectF(0, 0, size.width, size.height);
context->FillRectangle(rectInDIP, bitmapBrush.get());
}
}
void clip(PlatformContextDirect2D& platformContext, const FloatRect& rect)
{
platformContext.renderTarget()->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
platformContext.pushClip(PlatformContextDirect2D::AxisAlignedClip);
if (auto* graphicsContextPrivate = platformContext.graphicsContextPrivate())
graphicsContextPrivate->clip(rect);
}
void clipOut(PlatformContextDirect2D& platformContext, const FloatRect& rect)
{
Path path;
path.addRect(rect);
clipOut(platformContext, path);
}
void clipOut(PlatformContextDirect2D& platformContext, const Path& path)
{
// To clip Out we need the intersection of the infinite
// clipping rect and the path we just created.
D2D1_SIZE_F rendererSize = platformContext.renderTarget()->GetSize();
FloatRect clipBounds(0, 0, rendererSize.width, rendererSize.height);
Path boundingRect;
boundingRect.addRect(clipBounds);
boundingRect.appendGeometry(path.platformPath());
COMPtr<ID2D1GeometryGroup> pathToClip;
boundingRect.createGeometryWithFillMode(WindRule::EvenOdd, pathToClip);
clipPath(platformContext, pathToClip.get());
}
void clipPath(PlatformContextDirect2D& platformContext, const Path& path, WindRule clipRule)
{
if (path.isEmpty()) {
Direct2D::clip(platformContext, FloatRect());
return;
}
COMPtr<ID2D1GeometryGroup> pathToClip;
path.createGeometryWithFillMode(clipRule, pathToClip);
clipPath(platformContext, pathToClip.get());
if (auto* graphicsContextPrivate = platformContext.graphicsContextPrivate())
graphicsContextPrivate->clip(path);
}
void clipPath(PlatformContextDirect2D& platformContext, ID2D1Geometry* path)
{
COMPtr<ID2D1Layer> clipLayer;
HRESULT hr = platformContext.renderTarget()->CreateLayer(&clipLayer);
if (!SUCCEEDED(hr))
return;
platformContext.renderTarget()->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), path), clipLayer.get());
platformContext.pushRenderClip(PlatformContextDirect2D::LayerClip);
platformContext.setActiveLayer(WTFMove(clipLayer));
}
void clipToImageBuffer(PlatformContextDirect2D&, ID2D1RenderTarget*, const FloatRect&)
{
notImplemented();
}
void copyBits(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);
uint32_t* destRow = reinterpret_cast<uint32_t*>(destRows + destStride * y);
memcpy(destRow, srcRow, colCount);
}
}
} // namespace Direct2D
} // namespace WebCore
#endif // USE(DIRECT2D)