| /* |
| * 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(¤tTransform); |
| 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(¤tTransform); |
| |
| 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(¤tTransform); |
| |
| 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(¤tTransform); |
| |
| 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(¤tTransform); |
| |
| 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(¤tTransform); |
| |
| 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) |