Direct2DOperations.cpp   [plain text]


/*
 * 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->createBrush(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->createBrush(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() == TextDrawingMode::Fill && 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.colorWithAlphaMultipliedBy(fillColor.alphaAsFloat());
        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 drawPlatformImage(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());
    drawPlatformImage(platformContext, deviceBitmap.get(), imageSize, destRect, srcRect, options, globalAlpha, shadowState);
}

void drawPlatformImage(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)