| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "modules/canvas/canvas2d/BaseRenderingContext2D.h" |
| |
| #include "bindings/core/v8/ExceptionMessages.h" |
| #include "bindings/core/v8/ExceptionState.h" |
| #include "core/css/cssom/CSSURLImageValue.h" |
| #include "core/css/parser/CSSParser.h" |
| #include "core/dom/ExecutionContext.h" |
| #include "core/html/HTMLCanvasElement.h" |
| #include "core/html/HTMLImageElement.h" |
| #include "core/html/ImageData.h" |
| #include "core/html/TextMetrics.h" |
| #include "core/html/media/HTMLVideoElement.h" |
| #include "core/imagebitmap/ImageBitmap.h" |
| #include "core/offscreencanvas/OffscreenCanvas.h" |
| #include "core/svg/SVGImageElement.h" |
| #include "core/typed_arrays/ArrayBufferViewHelpers.h" |
| #include "modules/canvas/canvas2d/CanvasGradient.h" |
| #include "modules/canvas/canvas2d/CanvasPattern.h" |
| #include "modules/canvas/canvas2d/CanvasStyle.h" |
| #include "modules/canvas/canvas2d/Path2D.h" |
| #include "platform/Histogram.h" |
| #include "platform/bindings/ScriptState.h" |
| #include "platform/geometry/FloatQuad.h" |
| #include "platform/graphics/CanvasHeuristicParameters.h" |
| #include "platform/graphics/Color.h" |
| #include "platform/graphics/Image.h" |
| #include "platform/graphics/ImageBuffer.h" |
| #include "platform/graphics/StrokeData.h" |
| #include "platform/graphics/paint/PaintCanvas.h" |
| #include "platform/graphics/paint/PaintFlags.h" |
| #include "platform/graphics/skia/SkiaUtils.h" |
| #include "platform/runtime_enabled_features.h" |
| #include "platform/wtf/CheckedNumeric.h" |
| |
| namespace blink { |
| |
| const char BaseRenderingContext2D::kDefaultFont[] = "10px sans-serif"; |
| const char BaseRenderingContext2D::kInheritDirectionString[] = "inherit"; |
| const char BaseRenderingContext2D::kRtlDirectionString[] = "rtl"; |
| const char BaseRenderingContext2D::kLtrDirectionString[] = "ltr"; |
| const double BaseRenderingContext2D::kCDeviceScaleFactor = 1.0; |
| |
| BaseRenderingContext2D::BaseRenderingContext2D() |
| : clip_antialiasing_(kNotAntiAliased) { |
| state_stack_.push_back(CanvasRenderingContext2DState::Create()); |
| } |
| |
| BaseRenderingContext2D::~BaseRenderingContext2D() {} |
| |
| CanvasRenderingContext2DState& BaseRenderingContext2D::ModifiableState() { |
| RealizeSaves(); |
| return *state_stack_.back(); |
| } |
| |
| void BaseRenderingContext2D::RealizeSaves() { |
| ValidateStateStack(); |
| if (GetState().HasUnrealizedSaves()) { |
| DCHECK_GE(state_stack_.size(), 1u); |
| // Reduce the current state's unrealized count by one now, |
| // to reflect the fact we are saving one state. |
| state_stack_.back()->Restore(); |
| state_stack_.push_back(CanvasRenderingContext2DState::Create( |
| GetState(), CanvasRenderingContext2DState::kDontCopyClipList)); |
| // Set the new state's unrealized count to 0, because it has no outstanding |
| // saves. |
| // We need to do this explicitly because the copy constructor and operator= |
| // used by the Vector operations copy the unrealized count from the previous |
| // state (in turn necessary to support correct resizing and unwinding of the |
| // stack). |
| state_stack_.back()->ResetUnrealizedSaveCount(); |
| PaintCanvas* canvas = DrawingCanvas(); |
| if (canvas) |
| canvas->save(); |
| ValidateStateStack(); |
| } |
| } |
| |
| void BaseRenderingContext2D::save() { |
| state_stack_.back()->Save(); |
| } |
| |
| void BaseRenderingContext2D::restore() { |
| ValidateStateStack(); |
| if (GetState().HasUnrealizedSaves()) { |
| // We never realized the save, so just record that it was unnecessary. |
| state_stack_.back()->Restore(); |
| return; |
| } |
| DCHECK_GE(state_stack_.size(), 1u); |
| if (state_stack_.size() <= 1) |
| return; |
| path_.Transform(GetState().Transform()); |
| state_stack_.pop_back(); |
| state_stack_.back()->ClearResolvedFilter(); |
| path_.Transform(GetState().Transform().Inverse()); |
| PaintCanvas* c = DrawingCanvas(); |
| if (c) |
| c->restore(); |
| |
| ValidateStateStack(); |
| } |
| |
| void BaseRenderingContext2D::RestoreMatrixClipStack(PaintCanvas* c) const { |
| if (!c) |
| return; |
| HeapVector<Member<CanvasRenderingContext2DState>>::const_iterator curr_state; |
| DCHECK(state_stack_.begin() < state_stack_.end()); |
| for (curr_state = state_stack_.begin(); curr_state < state_stack_.end(); |
| curr_state++) { |
| c->setMatrix(SkMatrix::I()); |
| if (curr_state->Get()) { |
| curr_state->Get()->PlaybackClips(c); |
| c->setMatrix(AffineTransformToSkMatrix(curr_state->Get()->Transform())); |
| } |
| c->save(); |
| } |
| c->restore(); |
| ValidateStateStack(); |
| } |
| |
| void BaseRenderingContext2D::UnwindStateStack() { |
| if (size_t stack_size = state_stack_.size()) { |
| if (PaintCanvas* sk_canvas = ExistingDrawingCanvas()) { |
| while (--stack_size) |
| sk_canvas->restore(); |
| } |
| } |
| } |
| |
| void BaseRenderingContext2D::Reset() { |
| ValidateStateStack(); |
| UnwindStateStack(); |
| state_stack_.resize(1); |
| state_stack_.front() = CanvasRenderingContext2DState::Create(); |
| path_.Clear(); |
| if (PaintCanvas* c = ExistingDrawingCanvas()) { |
| // The canvas should always have an initial/unbalanced save frame, which |
| // we use to reset the top level matrix and clip here. |
| DCHECK_EQ(c->getSaveCount(), 2); |
| c->restore(); |
| c->save(); |
| DCHECK(c->getTotalMatrix().isIdentity()); |
| #if DCHECK_IS_ON() |
| SkIRect clip_bounds; |
| DCHECK(c->getDeviceClipBounds(&clip_bounds)); |
| DCHECK(clip_bounds == c->imageInfo().bounds()); |
| #endif |
| } |
| ValidateStateStack(); |
| } |
| |
| static inline void ConvertCanvasStyleToUnionType( |
| CanvasStyle* style, |
| StringOrCanvasGradientOrCanvasPattern& return_value) { |
| if (CanvasGradient* gradient = style->GetCanvasGradient()) { |
| return_value.SetCanvasGradient(gradient); |
| return; |
| } |
| if (CanvasPattern* pattern = style->GetCanvasPattern()) { |
| return_value.SetCanvasPattern(pattern); |
| return; |
| } |
| return_value.SetString(style->GetColor()); |
| } |
| |
| void BaseRenderingContext2D::strokeStyle( |
| StringOrCanvasGradientOrCanvasPattern& return_value) const { |
| ConvertCanvasStyleToUnionType(GetState().StrokeStyle(), return_value); |
| } |
| |
| void BaseRenderingContext2D::setStrokeStyle( |
| const StringOrCanvasGradientOrCanvasPattern& style) { |
| DCHECK(!style.IsNull()); |
| |
| String color_string; |
| CanvasStyle* canvas_style = nullptr; |
| if (style.IsString()) { |
| color_string = style.GetAsString(); |
| if (color_string == GetState().UnparsedStrokeColor()) |
| return; |
| Color parsed_color = 0; |
| if (!ParseColorOrCurrentColor(parsed_color, color_string)) |
| return; |
| if (GetState().StrokeStyle()->IsEquivalentRGBA(parsed_color.Rgb())) { |
| ModifiableState().SetUnparsedStrokeColor(color_string); |
| return; |
| } |
| canvas_style = CanvasStyle::CreateFromRGBA(parsed_color.Rgb()); |
| } else if (style.IsCanvasGradient()) { |
| canvas_style = CanvasStyle::CreateFromGradient(style.GetAsCanvasGradient()); |
| } else if (style.IsCanvasPattern()) { |
| CanvasPattern* canvas_pattern = style.GetAsCanvasPattern(); |
| |
| if (OriginClean() && !canvas_pattern->OriginClean()) |
| SetOriginTainted(); |
| |
| canvas_style = CanvasStyle::CreateFromPattern(canvas_pattern); |
| } |
| |
| DCHECK(canvas_style); |
| |
| ModifiableState().SetStrokeStyle(canvas_style); |
| ModifiableState().SetUnparsedStrokeColor(color_string); |
| ModifiableState().ClearResolvedFilter(); |
| } |
| |
| void BaseRenderingContext2D::fillStyle( |
| StringOrCanvasGradientOrCanvasPattern& return_value) const { |
| ConvertCanvasStyleToUnionType(GetState().FillStyle(), return_value); |
| } |
| |
| void BaseRenderingContext2D::setFillStyle( |
| const StringOrCanvasGradientOrCanvasPattern& style) { |
| DCHECK(!style.IsNull()); |
| ValidateStateStack(); |
| String color_string; |
| CanvasStyle* canvas_style = nullptr; |
| if (style.IsString()) { |
| color_string = style.GetAsString(); |
| if (color_string == GetState().UnparsedFillColor()) |
| return; |
| Color parsed_color = 0; |
| if (!ParseColorOrCurrentColor(parsed_color, color_string)) |
| return; |
| if (GetState().FillStyle()->IsEquivalentRGBA(parsed_color.Rgb())) { |
| ModifiableState().SetUnparsedFillColor(color_string); |
| return; |
| } |
| canvas_style = CanvasStyle::CreateFromRGBA(parsed_color.Rgb()); |
| } else if (style.IsCanvasGradient()) { |
| canvas_style = CanvasStyle::CreateFromGradient(style.GetAsCanvasGradient()); |
| } else if (style.IsCanvasPattern()) { |
| CanvasPattern* canvas_pattern = style.GetAsCanvasPattern(); |
| |
| if (OriginClean() && !canvas_pattern->OriginClean()) |
| SetOriginTainted(); |
| if (canvas_pattern->GetPattern()->IsTextureBacked()) |
| DisableDeferral(kDisableDeferralReasonUsingTextureBackedPattern); |
| canvas_style = CanvasStyle::CreateFromPattern(canvas_pattern); |
| } |
| |
| DCHECK(canvas_style); |
| ModifiableState().SetFillStyle(canvas_style); |
| ModifiableState().SetUnparsedFillColor(color_string); |
| ModifiableState().ClearResolvedFilter(); |
| } |
| |
| double BaseRenderingContext2D::lineWidth() const { |
| return GetState().LineWidth(); |
| } |
| |
| void BaseRenderingContext2D::setLineWidth(double width) { |
| if (!std::isfinite(width) || width <= 0) |
| return; |
| if (GetState().LineWidth() == width) |
| return; |
| ModifiableState().SetLineWidth(width); |
| } |
| |
| String BaseRenderingContext2D::lineCap() const { |
| return LineCapName(GetState().GetLineCap()); |
| } |
| |
| void BaseRenderingContext2D::setLineCap(const String& s) { |
| LineCap cap; |
| if (!ParseLineCap(s, cap)) |
| return; |
| if (GetState().GetLineCap() == cap) |
| return; |
| ModifiableState().SetLineCap(cap); |
| } |
| |
| String BaseRenderingContext2D::lineJoin() const { |
| return LineJoinName(GetState().GetLineJoin()); |
| } |
| |
| void BaseRenderingContext2D::setLineJoin(const String& s) { |
| LineJoin join; |
| if (!ParseLineJoin(s, join)) |
| return; |
| if (GetState().GetLineJoin() == join) |
| return; |
| ModifiableState().SetLineJoin(join); |
| } |
| |
| double BaseRenderingContext2D::miterLimit() const { |
| return GetState().MiterLimit(); |
| } |
| |
| void BaseRenderingContext2D::setMiterLimit(double limit) { |
| if (!std::isfinite(limit) || limit <= 0) |
| return; |
| if (GetState().MiterLimit() == limit) |
| return; |
| ModifiableState().SetMiterLimit(limit); |
| } |
| |
| double BaseRenderingContext2D::shadowOffsetX() const { |
| return GetState().ShadowOffset().Width(); |
| } |
| |
| void BaseRenderingContext2D::setShadowOffsetX(double x) { |
| if (!std::isfinite(x)) |
| return; |
| if (GetState().ShadowOffset().Width() == x) |
| return; |
| ModifiableState().SetShadowOffsetX(x); |
| } |
| |
| double BaseRenderingContext2D::shadowOffsetY() const { |
| return GetState().ShadowOffset().Height(); |
| } |
| |
| void BaseRenderingContext2D::setShadowOffsetY(double y) { |
| if (!std::isfinite(y)) |
| return; |
| if (GetState().ShadowOffset().Height() == y) |
| return; |
| ModifiableState().SetShadowOffsetY(y); |
| } |
| |
| double BaseRenderingContext2D::shadowBlur() const { |
| return GetState().ShadowBlur(); |
| } |
| |
| void BaseRenderingContext2D::setShadowBlur(double blur) { |
| if (!std::isfinite(blur) || blur < 0) |
| return; |
| if (GetState().ShadowBlur() == blur) |
| return; |
| ModifiableState().SetShadowBlur(blur); |
| } |
| |
| String BaseRenderingContext2D::shadowColor() const { |
| return Color(GetState().ShadowColor()).Serialized(); |
| } |
| |
| void BaseRenderingContext2D::setShadowColor(const String& color_string) { |
| Color color; |
| if (!ParseColorOrCurrentColor(color, color_string)) |
| return; |
| if (GetState().ShadowColor() == color) |
| return; |
| ModifiableState().SetShadowColor(color.Rgb()); |
| } |
| |
| const Vector<double>& BaseRenderingContext2D::getLineDash() const { |
| return GetState().LineDash(); |
| } |
| |
| static bool LineDashSequenceIsValid(const Vector<double>& dash) { |
| for (size_t i = 0; i < dash.size(); i++) { |
| if (!std::isfinite(dash[i]) || dash[i] < 0) |
| return false; |
| } |
| return true; |
| } |
| |
| void BaseRenderingContext2D::setLineDash(const Vector<double>& dash) { |
| if (!LineDashSequenceIsValid(dash)) |
| return; |
| ModifiableState().SetLineDash(dash); |
| } |
| |
| double BaseRenderingContext2D::lineDashOffset() const { |
| return GetState().LineDashOffset(); |
| } |
| |
| void BaseRenderingContext2D::setLineDashOffset(double offset) { |
| if (!std::isfinite(offset) || GetState().LineDashOffset() == offset) |
| return; |
| ModifiableState().SetLineDashOffset(offset); |
| } |
| |
| double BaseRenderingContext2D::globalAlpha() const { |
| return GetState().GlobalAlpha(); |
| } |
| |
| void BaseRenderingContext2D::setGlobalAlpha(double alpha) { |
| if (!(alpha >= 0 && alpha <= 1)) |
| return; |
| if (GetState().GlobalAlpha() == alpha) |
| return; |
| ModifiableState().SetGlobalAlpha(alpha); |
| } |
| |
| String BaseRenderingContext2D::globalCompositeOperation() const { |
| return CompositeOperatorName( |
| CompositeOperatorFromSkia(GetState().GlobalComposite()), |
| BlendModeFromSkia(GetState().GlobalComposite())); |
| } |
| |
| void BaseRenderingContext2D::setGlobalCompositeOperation( |
| const String& operation) { |
| CompositeOperator op = kCompositeSourceOver; |
| WebBlendMode blend_mode = WebBlendMode::kNormal; |
| if (!ParseCompositeAndBlendOperator(operation, op, blend_mode)) |
| return; |
| SkBlendMode xfermode = WebCoreCompositeToSkiaComposite(op, blend_mode); |
| if (GetState().GlobalComposite() == xfermode) |
| return; |
| ModifiableState().SetGlobalComposite(xfermode); |
| } |
| |
| String BaseRenderingContext2D::filter() const { |
| return GetState().UnparsedFilter(); |
| } |
| |
| void BaseRenderingContext2D::setFilter( |
| const ExecutionContext* execution_context, |
| const String& filter_string) { |
| if (filter_string == GetState().UnparsedFilter()) |
| return; |
| |
| const CSSValue* filter_value = CSSParser::ParseSingleValue( |
| CSSPropertyFilter, filter_string, |
| CSSParserContext::Create(kHTMLStandardMode, |
| execution_context->SecureContextMode())); |
| |
| if (!filter_value || filter_value->IsCSSWideKeyword()) |
| return; |
| |
| ModifiableState().SetUnparsedFilter(filter_string); |
| ModifiableState().SetFilter(filter_value); |
| SnapshotStateForFilter(); |
| } |
| |
| SVGMatrixTearOff* BaseRenderingContext2D::currentTransform() const { |
| return SVGMatrixTearOff::Create(GetState().Transform()); |
| } |
| |
| void BaseRenderingContext2D::setCurrentTransform( |
| SVGMatrixTearOff* matrix_tear_off) { |
| const AffineTransform& transform = matrix_tear_off->Value(); |
| setTransform(transform.A(), transform.B(), transform.C(), transform.D(), |
| transform.E(), transform.F()); |
| } |
| |
| void BaseRenderingContext2D::scale(double sx, double sy) { |
| PaintCanvas* c = DrawingCanvas(); |
| if (!c) |
| return; |
| |
| if (!std::isfinite(sx) || !std::isfinite(sy)) |
| return; |
| |
| AffineTransform new_transform = GetState().Transform(); |
| new_transform.ScaleNonUniform(sx, sy); |
| if (GetState().Transform() == new_transform) |
| return; |
| |
| ModifiableState().SetTransform(new_transform); |
| if (!GetState().IsTransformInvertible()) |
| return; |
| |
| c->scale(sx, sy); |
| path_.Transform(AffineTransform().ScaleNonUniform(1.0 / sx, 1.0 / sy)); |
| } |
| |
| void BaseRenderingContext2D::rotate(double angle_in_radians) { |
| PaintCanvas* c = DrawingCanvas(); |
| if (!c) |
| return; |
| |
| if (!std::isfinite(angle_in_radians)) |
| return; |
| |
| AffineTransform new_transform = GetState().Transform(); |
| new_transform.RotateRadians(angle_in_radians); |
| if (GetState().Transform() == new_transform) |
| return; |
| |
| ModifiableState().SetTransform(new_transform); |
| if (!GetState().IsTransformInvertible()) |
| return; |
| c->rotate(angle_in_radians * (180.0 / piFloat)); |
| path_.Transform(AffineTransform().RotateRadians(-angle_in_radians)); |
| } |
| |
| void BaseRenderingContext2D::translate(double tx, double ty) { |
| PaintCanvas* c = DrawingCanvas(); |
| if (!c) |
| return; |
| if (!GetState().IsTransformInvertible()) |
| return; |
| |
| if (!std::isfinite(tx) || !std::isfinite(ty)) |
| return; |
| |
| AffineTransform new_transform = GetState().Transform(); |
| new_transform.Translate(tx, ty); |
| if (GetState().Transform() == new_transform) |
| return; |
| |
| ModifiableState().SetTransform(new_transform); |
| if (!GetState().IsTransformInvertible()) |
| return; |
| c->translate(tx, ty); |
| path_.Transform(AffineTransform().Translate(-tx, -ty)); |
| } |
| |
| void BaseRenderingContext2D::transform(double m11, |
| double m12, |
| double m21, |
| double m22, |
| double dx, |
| double dy) { |
| PaintCanvas* c = DrawingCanvas(); |
| if (!c) |
| return; |
| |
| if (!std::isfinite(m11) || !std::isfinite(m21) || !std::isfinite(dx) || |
| !std::isfinite(m12) || !std::isfinite(m22) || !std::isfinite(dy)) |
| return; |
| |
| AffineTransform transform(m11, m12, m21, m22, dx, dy); |
| AffineTransform new_transform = GetState().Transform() * transform; |
| if (GetState().Transform() == new_transform) |
| return; |
| |
| ModifiableState().SetTransform(new_transform); |
| if (!GetState().IsTransformInvertible()) |
| return; |
| |
| c->concat(AffineTransformToSkMatrix(transform)); |
| path_.Transform(transform.Inverse()); |
| } |
| |
| void BaseRenderingContext2D::resetTransform() { |
| PaintCanvas* c = DrawingCanvas(); |
| if (!c) |
| return; |
| |
| AffineTransform ctm = GetState().Transform(); |
| bool invertible_ctm = GetState().IsTransformInvertible(); |
| // It is possible that CTM is identity while CTM is not invertible. |
| // When CTM becomes non-invertible, realizeSaves() can make CTM identity. |
| if (ctm.IsIdentity() && invertible_ctm) |
| return; |
| |
| // resetTransform() resolves the non-invertible CTM state. |
| ModifiableState().ResetTransform(); |
| c->setMatrix(AffineTransformToSkMatrix(BaseTransform())); |
| |
| if (invertible_ctm) |
| path_.Transform(ctm); |
| // When else, do nothing because all transform methods didn't update m_path |
| // when CTM became non-invertible. |
| // It means that resetTransform() restores m_path just before CTM became |
| // non-invertible. |
| } |
| |
| void BaseRenderingContext2D::setTransform(double m11, |
| double m12, |
| double m21, |
| double m22, |
| double dx, |
| double dy) { |
| PaintCanvas* c = DrawingCanvas(); |
| if (!c) |
| return; |
| |
| if (!std::isfinite(m11) || !std::isfinite(m21) || !std::isfinite(dx) || |
| !std::isfinite(m12) || !std::isfinite(m22) || !std::isfinite(dy)) |
| return; |
| |
| resetTransform(); |
| transform(m11, m12, m21, m22, dx, dy); |
| } |
| |
| void BaseRenderingContext2D::beginPath() { |
| path_.Clear(); |
| } |
| |
| static bool ValidateRectForCanvas(double& x, |
| double& y, |
| double& width, |
| double& height) { |
| if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(width) || |
| !std::isfinite(height)) |
| return false; |
| |
| if (!width && !height) |
| return false; |
| |
| if (width < 0) { |
| width = -width; |
| x -= width; |
| } |
| |
| if (height < 0) { |
| height = -height; |
| y -= height; |
| } |
| |
| return true; |
| } |
| |
| bool BaseRenderingContext2D::IsFullCanvasCompositeMode(SkBlendMode op) { |
| // See 4.8.11.1.3 Compositing |
| // CompositeSourceAtop and CompositeDestinationOut are not listed here as the |
| // platforms already implement the specification's behavior. |
| return op == SkBlendMode::kSrcIn || op == SkBlendMode::kSrcOut || |
| op == SkBlendMode::kDstIn || op == SkBlendMode::kDstATop; |
| } |
| |
| static bool IsPathExpensive(const Path& path) { |
| const SkPath& sk_path = path.GetSkPath(); |
| if (CanvasHeuristicParameters::kConcavePathsAreExpensive && |
| !sk_path.isConvex()) |
| return true; |
| |
| if (sk_path.countPoints() > |
| CanvasHeuristicParameters::kExpensivePathPointCount) |
| return true; |
| |
| return false; |
| } |
| |
| void BaseRenderingContext2D::DrawPathInternal( |
| const Path& path, |
| CanvasRenderingContext2DState::PaintType paint_type, |
| SkPath::FillType fill_type) { |
| if (path.IsEmpty()) |
| return; |
| |
| SkPath sk_path = path.GetSkPath(); |
| FloatRect bounds = path.BoundingRect(); |
| sk_path.setFillType(fill_type); |
| |
| if (paint_type == CanvasRenderingContext2DState::kStrokePaintType) |
| InflateStrokeRect(bounds); |
| |
| if (!DrawingCanvas()) |
| return; |
| |
| if (Draw([&sk_path](PaintCanvas* c, const PaintFlags* flags) // draw lambda |
| { c->drawPath(sk_path, *flags); }, |
| [](const SkIRect& rect) // overdraw test lambda |
| { return false; }, |
| bounds, paint_type)) { |
| if (IsPathExpensive(path)) { |
| ImageBuffer* buffer = GetImageBuffer(); |
| if (buffer) |
| buffer->SetHasExpensiveOp(); |
| } |
| } |
| } |
| |
| static SkPath::FillType ParseWinding(const String& winding_rule_string) { |
| if (winding_rule_string == "nonzero") |
| return SkPath::kWinding_FillType; |
| if (winding_rule_string == "evenodd") |
| return SkPath::kEvenOdd_FillType; |
| |
| NOTREACHED(); |
| return SkPath::kEvenOdd_FillType; |
| } |
| |
| void BaseRenderingContext2D::fill(const String& winding_rule_string) { |
| DrawPathInternal(path_, CanvasRenderingContext2DState::kFillPaintType, |
| ParseWinding(winding_rule_string)); |
| } |
| |
| void BaseRenderingContext2D::fill(Path2D* dom_path, |
| const String& winding_rule_string) { |
| DrawPathInternal(dom_path->GetPath(), |
| CanvasRenderingContext2DState::kFillPaintType, |
| ParseWinding(winding_rule_string)); |
| } |
| |
| void BaseRenderingContext2D::stroke() { |
| DrawPathInternal(path_, CanvasRenderingContext2DState::kStrokePaintType); |
| } |
| |
| void BaseRenderingContext2D::stroke(Path2D* dom_path) { |
| DrawPathInternal(dom_path->GetPath(), |
| CanvasRenderingContext2DState::kStrokePaintType); |
| } |
| |
| void BaseRenderingContext2D::fillRect(double x, |
| double y, |
| double width, |
| double height) { |
| if (!ValidateRectForCanvas(x, y, width, height)) |
| return; |
| |
| if (!DrawingCanvas()) |
| return; |
| |
| SkRect rect = SkRect::MakeXYWH(x, y, width, height); |
| Draw([&rect](PaintCanvas* c, const PaintFlags* flags) // draw lambda |
| { c->drawRect(rect, *flags); }, |
| [&rect, this](const SkIRect& clip_bounds) // overdraw test lambda |
| { return RectContainsTransformedRect(rect, clip_bounds); }, |
| rect, CanvasRenderingContext2DState::kFillPaintType); |
| } |
| |
| static void StrokeRectOnCanvas(const FloatRect& rect, |
| PaintCanvas* canvas, |
| const PaintFlags* flags) { |
| DCHECK_EQ(flags->getStyle(), PaintFlags::kStroke_Style); |
| if ((rect.Width() > 0) != (rect.Height() > 0)) { |
| // When stroking, we must skip the zero-dimension segments |
| SkPath path; |
| path.moveTo(rect.X(), rect.Y()); |
| path.lineTo(rect.MaxX(), rect.MaxY()); |
| path.close(); |
| canvas->drawPath(path, *flags); |
| return; |
| } |
| canvas->drawRect(rect, *flags); |
| } |
| |
| void BaseRenderingContext2D::strokeRect(double x, |
| double y, |
| double width, |
| double height) { |
| if (!ValidateRectForCanvas(x, y, width, height)) |
| return; |
| |
| if (!DrawingCanvas()) |
| return; |
| |
| SkRect rect = SkRect::MakeXYWH(x, y, width, height); |
| FloatRect bounds = rect; |
| InflateStrokeRect(bounds); |
| Draw([&rect](PaintCanvas* c, const PaintFlags* flags) // draw lambda |
| { StrokeRectOnCanvas(rect, c, flags); }, |
| [](const SkIRect& clip_bounds) // overdraw test lambda |
| { return false; }, |
| bounds, CanvasRenderingContext2DState::kStrokePaintType); |
| } |
| |
| void BaseRenderingContext2D::ClipInternal(const Path& path, |
| const String& winding_rule_string) { |
| PaintCanvas* c = DrawingCanvas(); |
| if (!c) { |
| return; |
| } |
| if (!GetState().IsTransformInvertible()) { |
| return; |
| } |
| |
| SkPath sk_path = path.GetSkPath(); |
| sk_path.setFillType(ParseWinding(winding_rule_string)); |
| ModifiableState().ClipPath(sk_path, clip_antialiasing_); |
| c->clipPath(sk_path, SkClipOp::kIntersect, |
| clip_antialiasing_ == kAntiAliased); |
| if (CanvasHeuristicParameters::kComplexClipsAreExpensive && |
| !sk_path.isRect(nullptr) && HasImageBuffer()) { |
| GetImageBuffer()->SetHasExpensiveOp(); |
| } |
| } |
| |
| void BaseRenderingContext2D::clip(const String& winding_rule_string) { |
| ClipInternal(path_, winding_rule_string); |
| } |
| |
| void BaseRenderingContext2D::clip(Path2D* dom_path, |
| const String& winding_rule_string) { |
| ClipInternal(dom_path->GetPath(), winding_rule_string); |
| } |
| |
| bool BaseRenderingContext2D::isPointInPath(const double x, |
| const double y, |
| const String& winding_rule_string) { |
| return IsPointInPathInternal(path_, x, y, winding_rule_string); |
| } |
| |
| bool BaseRenderingContext2D::isPointInPath(Path2D* dom_path, |
| const double x, |
| const double y, |
| const String& winding_rule_string) { |
| return IsPointInPathInternal(dom_path->GetPath(), x, y, winding_rule_string); |
| } |
| |
| bool BaseRenderingContext2D::IsPointInPathInternal( |
| const Path& path, |
| const double x, |
| const double y, |
| const String& winding_rule_string) { |
| PaintCanvas* c = DrawingCanvas(); |
| if (!c) |
| return false; |
| if (!GetState().IsTransformInvertible()) |
| return false; |
| |
| FloatPoint point(x, y); |
| if (!std::isfinite(point.X()) || !std::isfinite(point.Y())) |
| return false; |
| AffineTransform ctm = GetState().Transform(); |
| FloatPoint transformed_point = ctm.Inverse().MapPoint(point); |
| |
| return path.Contains(transformed_point, |
| SkFillTypeToWindRule(ParseWinding(winding_rule_string))); |
| } |
| |
| bool BaseRenderingContext2D::isPointInStroke(const double x, const double y) { |
| return IsPointInStrokeInternal(path_, x, y); |
| } |
| |
| bool BaseRenderingContext2D::isPointInStroke(Path2D* dom_path, |
| const double x, |
| const double y) { |
| return IsPointInStrokeInternal(dom_path->GetPath(), x, y); |
| } |
| |
| bool BaseRenderingContext2D::IsPointInStrokeInternal(const Path& path, |
| const double x, |
| const double y) { |
| PaintCanvas* c = DrawingCanvas(); |
| if (!c) |
| return false; |
| if (!GetState().IsTransformInvertible()) |
| return false; |
| |
| FloatPoint point(x, y); |
| if (!std::isfinite(point.X()) || !std::isfinite(point.Y())) |
| return false; |
| AffineTransform ctm = GetState().Transform(); |
| FloatPoint transformed_point = ctm.Inverse().MapPoint(point); |
| |
| StrokeData stroke_data; |
| stroke_data.SetThickness(GetState().LineWidth()); |
| stroke_data.SetLineCap(GetState().GetLineCap()); |
| stroke_data.SetLineJoin(GetState().GetLineJoin()); |
| stroke_data.SetMiterLimit(GetState().MiterLimit()); |
| Vector<float> line_dash(GetState().LineDash().size()); |
| std::copy(GetState().LineDash().begin(), GetState().LineDash().end(), |
| line_dash.begin()); |
| stroke_data.SetLineDash(line_dash, GetState().LineDashOffset()); |
| return path.StrokeContains(transformed_point, stroke_data); |
| } |
| |
| void BaseRenderingContext2D::clearRect(double x, |
| double y, |
| double width, |
| double height) { |
| usage_counters_.num_clear_rect_calls++; |
| |
| if (!ValidateRectForCanvas(x, y, width, height)) |
| return; |
| |
| PaintCanvas* c = DrawingCanvas(); |
| if (!c) |
| return; |
| if (!GetState().IsTransformInvertible()) |
| return; |
| |
| SkIRect clip_bounds; |
| if (!c->getDeviceClipBounds(&clip_bounds)) |
| return; |
| |
| PaintFlags clear_flags; |
| clear_flags.setBlendMode(SkBlendMode::kClear); |
| clear_flags.setStyle(PaintFlags::kFill_Style); |
| FloatRect rect(x, y, width, height); |
| |
| if (RectContainsTransformedRect(rect, clip_bounds)) { |
| CheckOverdraw(rect, &clear_flags, CanvasRenderingContext2DState::kNoImage, |
| kClipFill); |
| if (DrawingCanvas()) |
| DrawingCanvas()->drawRect(rect, clear_flags); |
| DidDraw(clip_bounds); |
| } else { |
| SkIRect dirty_rect; |
| if (ComputeDirtyRect(rect, clip_bounds, &dirty_rect)) { |
| c->drawRect(rect, clear_flags); |
| DidDraw(dirty_rect); |
| } |
| } |
| } |
| |
| static inline FloatRect NormalizeRect(const FloatRect& rect) { |
| return FloatRect(std::min(rect.X(), rect.MaxX()), |
| std::min(rect.Y(), rect.MaxY()), |
| std::max(rect.Width(), -rect.Width()), |
| std::max(rect.Height(), -rect.Height())); |
| } |
| |
| static inline void ClipRectsToImageRect(const FloatRect& image_rect, |
| FloatRect* src_rect, |
| FloatRect* dst_rect) { |
| if (image_rect.Contains(*src_rect)) |
| return; |
| |
| // Compute the src to dst transform |
| FloatSize scale(dst_rect->Size().Width() / src_rect->Size().Width(), |
| dst_rect->Size().Height() / src_rect->Size().Height()); |
| FloatPoint scaled_src_location = src_rect->Location(); |
| scaled_src_location.Scale(scale.Width(), scale.Height()); |
| FloatSize offset = dst_rect->Location() - scaled_src_location; |
| |
| src_rect->Intersect(image_rect); |
| |
| // To clip the destination rectangle in the same proportion, transform the |
| // clipped src rect |
| *dst_rect = *src_rect; |
| dst_rect->Scale(scale.Width(), scale.Height()); |
| dst_rect->Move(offset); |
| } |
| |
| static inline CanvasImageSource* ToImageSourceInternal( |
| const CanvasImageSourceUnion& value, |
| ExceptionState& exception_state) { |
| if (value.IsCSSImageValue()) { |
| if (RuntimeEnabledFeatures::CSSPaintAPIEnabled()) |
| return value.GetAsCSSImageValue(); |
| exception_state.ThrowTypeError("CSSImageValue is not yet supported"); |
| return nullptr; |
| } |
| if (value.IsHTMLImageElement()) |
| return value.GetAsHTMLImageElement(); |
| if (value.IsHTMLVideoElement()) { |
| HTMLVideoElement* video = value.GetAsHTMLVideoElement(); |
| video->VideoWillBeDrawnToCanvas(); |
| return video; |
| } |
| if (value.IsSVGImageElement()) |
| return value.GetAsSVGImageElement(); |
| if (value.IsHTMLCanvasElement()) |
| return value.GetAsHTMLCanvasElement(); |
| if (value.IsImageBitmap()) { |
| if (static_cast<ImageBitmap*>(value.GetAsImageBitmap())->IsNeutered()) { |
| exception_state.ThrowDOMException( |
| kInvalidStateError, String::Format("The image source is detached")); |
| return nullptr; |
| } |
| return value.GetAsImageBitmap(); |
| } |
| if (value.IsOffscreenCanvas()) { |
| if (static_cast<OffscreenCanvas*>(value.GetAsOffscreenCanvas()) |
| ->IsNeutered()) { |
| exception_state.ThrowDOMException( |
| kInvalidStateError, String::Format("The image source is detached")); |
| return nullptr; |
| } |
| return value.GetAsOffscreenCanvas(); |
| } |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| void BaseRenderingContext2D::drawImage( |
| ScriptState* script_state, |
| const CanvasImageSourceUnion& image_source, |
| double x, |
| double y, |
| ExceptionState& exception_state) { |
| CanvasImageSource* image_source_internal = |
| ToImageSourceInternal(image_source, exception_state); |
| if (!image_source_internal) |
| return; |
| FloatSize default_object_size(Width(), Height()); |
| FloatSize source_rect_size = |
| image_source_internal->ElementSize(default_object_size); |
| FloatSize dest_rect_size = |
| image_source_internal->DefaultDestinationSize(default_object_size); |
| drawImage(script_state, image_source_internal, 0, 0, source_rect_size.Width(), |
| source_rect_size.Height(), x, y, dest_rect_size.Width(), |
| dest_rect_size.Height(), exception_state); |
| } |
| |
| void BaseRenderingContext2D::drawImage( |
| ScriptState* script_state, |
| const CanvasImageSourceUnion& image_source, |
| double x, |
| double y, |
| double width, |
| double height, |
| ExceptionState& exception_state) { |
| CanvasImageSource* image_source_internal = |
| ToImageSourceInternal(image_source, exception_state); |
| if (!image_source_internal) |
| return; |
| FloatSize default_object_size(this->Width(), this->Height()); |
| FloatSize source_rect_size = |
| image_source_internal->ElementSize(default_object_size); |
| drawImage(script_state, image_source_internal, 0, 0, source_rect_size.Width(), |
| source_rect_size.Height(), x, y, width, height, exception_state); |
| } |
| |
| void BaseRenderingContext2D::drawImage( |
| ScriptState* script_state, |
| const CanvasImageSourceUnion& image_source, |
| double sx, |
| double sy, |
| double sw, |
| double sh, |
| double dx, |
| double dy, |
| double dw, |
| double dh, |
| ExceptionState& exception_state) { |
| CanvasImageSource* image_source_internal = |
| ToImageSourceInternal(image_source, exception_state); |
| if (!image_source_internal) |
| return; |
| drawImage(script_state, image_source_internal, sx, sy, sw, sh, dx, dy, dw, dh, |
| exception_state); |
| } |
| |
| bool BaseRenderingContext2D::ShouldDrawImageAntialiased( |
| const FloatRect& dest_rect) const { |
| if (!GetState().ShouldAntialias()) |
| return false; |
| PaintCanvas* c = DrawingCanvas(); |
| DCHECK(c); |
| |
| const SkMatrix& ctm = c->getTotalMatrix(); |
| // Don't disable anti-aliasing if we're rotated or skewed. |
| if (!ctm.rectStaysRect()) |
| return true; |
| // Check if the dimensions of the destination are "small" (less than one |
| // device pixel). To prevent sudden drop-outs. Since we know that |
| // kRectStaysRect_Mask is set, the matrix either has scale and no skew or |
| // vice versa. We can query the kAffine_Mask flag to determine which case |
| // it is. |
| // FIXME: This queries the CTM while drawing, which is generally |
| // discouraged. Always drawing with AA can negatively impact performance |
| // though - that's why it's not always on. |
| SkScalar width_expansion, height_expansion; |
| if (ctm.getType() & SkMatrix::kAffine_Mask) { |
| width_expansion = ctm[SkMatrix::kMSkewY]; |
| height_expansion = ctm[SkMatrix::kMSkewX]; |
| } else { |
| width_expansion = ctm[SkMatrix::kMScaleX]; |
| height_expansion = ctm[SkMatrix::kMScaleY]; |
| } |
| return dest_rect.Width() * fabs(width_expansion) < 1 || |
| dest_rect.Height() * fabs(height_expansion) < 1; |
| } |
| |
| static bool IsDrawScalingDown(const FloatRect& src_rect, |
| const FloatRect& dst_rect, |
| float x_scale_squared, |
| float y_scale_squared) { |
| return dst_rect.Width() * dst_rect.Width() * x_scale_squared < |
| src_rect.Width() * src_rect.Width() && |
| dst_rect.Height() * dst_rect.Height() * y_scale_squared < |
| src_rect.Height() * src_rect.Height(); |
| } |
| |
| void BaseRenderingContext2D::DrawImageInternal(PaintCanvas* c, |
| CanvasImageSource* image_source, |
| Image* image, |
| const FloatRect& src_rect, |
| const FloatRect& dst_rect, |
| const PaintFlags* flags) { |
| int initial_save_count = c->getSaveCount(); |
| PaintFlags image_flags = *flags; |
| |
| if (flags->getImageFilter()) { |
| SkMatrix ctm = c->getTotalMatrix(); |
| SkMatrix inv_ctm; |
| if (!ctm.invert(&inv_ctm)) { |
| // There is an earlier check for invertibility, but the arithmetic |
| // in AffineTransform is not exactly identical, so it is possible |
| // for SkMatrix to find the transform to be non-invertible at this stage. |
| // crbug.com/504687 |
| return; |
| } |
| c->save(); |
| c->concat(inv_ctm); |
| SkRect bounds = dst_rect; |
| ctm.mapRect(&bounds); |
| PaintFlags layer_flags; |
| layer_flags.setBlendMode(flags->getBlendMode()); |
| layer_flags.setImageFilter(flags->getImageFilter()); |
| |
| c->saveLayer(&bounds, &layer_flags); |
| c->concat(ctm); |
| image_flags.setBlendMode(SkBlendMode::kSrcOver); |
| image_flags.setImageFilter(nullptr); |
| } |
| |
| if (!imageSmoothingEnabled() && |
| IsDrawScalingDown(src_rect, dst_rect, |
| GetState().Transform().XScaleSquared(), |
| GetState().Transform().YScaleSquared())) |
| image_flags.setFilterQuality(kLow_SkFilterQuality); |
| |
| if (!image_source->IsVideoElement()) { |
| image_flags.setAntiAlias(ShouldDrawImageAntialiased(dst_rect)); |
| image->Draw(c, image_flags, dst_rect, src_rect, |
| kDoNotRespectImageOrientation, |
| Image::kDoNotClampImageToSourceRect, Image::kSyncDecode); |
| } else { |
| c->save(); |
| c->clipRect(dst_rect); |
| c->translate(dst_rect.X(), dst_rect.Y()); |
| c->scale(dst_rect.Width() / src_rect.Width(), |
| dst_rect.Height() / src_rect.Height()); |
| c->translate(-src_rect.X(), -src_rect.Y()); |
| HTMLVideoElement* video = static_cast<HTMLVideoElement*>(image_source); |
| video->PaintCurrentFrame( |
| c, |
| IntRect(IntPoint(), IntSize(video->videoWidth(), video->videoHeight())), |
| &image_flags); |
| } |
| |
| c->restoreToCount(initial_save_count); |
| } |
| |
| bool ShouldDisableDeferral(CanvasImageSource* image_source, |
| DisableDeferralReason* reason) { |
| DCHECK(reason); |
| DCHECK_EQ(*reason, kDisableDeferralReasonUnknown); |
| |
| if (image_source->IsVideoElement()) { |
| *reason = kDisableDeferralReasonDrawImageOfVideo; |
| return true; |
| } |
| if (image_source->IsCanvasElement()) { |
| HTMLCanvasElement* canvas = static_cast<HTMLCanvasElement*>(image_source); |
| if (canvas->IsAnimated2d()) { |
| *reason = kDisableDeferralReasonDrawImageOfAnimated2dCanvas; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void BaseRenderingContext2D::drawImage(ScriptState* script_state, |
| CanvasImageSource* image_source, |
| double sx, |
| double sy, |
| double sw, |
| double sh, |
| double dx, |
| double dy, |
| double dw, |
| double dh, |
| ExceptionState& exception_state) { |
| if (!DrawingCanvas()) |
| return; |
| |
| // TODO(xidachen): After collecting some data, come back and prune off |
| // the ones that is not needed. |
| double start_time = WTF::MonotonicallyIncreasingTime(); |
| Optional<CustomCountHistogram> timer; |
| if (GetImageBuffer() && GetImageBuffer()->IsAccelerated()) { |
| if (image_source->IsVideoElement()) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_video_gpu, |
| ("Blink.Canvas.DrawImage.Video.GPU", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_video_gpu); |
| } else if (image_source->IsCanvasElement()) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_canvas_gpu, |
| ("Blink.Canvas.DrawImage.Canvas.GPU", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_canvas_gpu); |
| } else if (image_source->IsSVGSource()) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_svggpu, |
| ("Blink.Canvas.DrawImage.SVG.GPU", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_svggpu); |
| } else if (image_source->IsImageBitmap()) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_image_bitmap_gpu, |
| ("Blink.Canvas.DrawImage.ImageBitmap.GPU", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_image_bitmap_gpu); |
| } else if (image_source->IsOffscreenCanvas()) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_offscreencanvas_gpu, |
| ("Blink.Canvas.DrawImage.OffscreenCanvas.GPU", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_offscreencanvas_gpu); |
| } else { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_others_gpu, |
| ("Blink.Canvas.DrawImage.Others.GPU", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_others_gpu); |
| } |
| } else if (GetImageBuffer() && GetImageBuffer()->IsRecording()) { |
| if (image_source->IsVideoElement()) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_video_display_list, |
| ("Blink.Canvas.DrawImage.Video.DisplayList", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_video_display_list); |
| } else if (image_source->IsCanvasElement()) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_canvas_display_list, |
| ("Blink.Canvas.DrawImage.Canvas.DisplayList", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_canvas_display_list); |
| } else if (image_source->IsSVGSource()) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_svg_display_list, |
| ("Blink.Canvas.DrawImage.SVG.DisplayList", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_svg_display_list); |
| } else if (image_source->IsImageBitmap()) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_image_bitmap_display_list, |
| ("Blink.Canvas.DrawImage.ImageBitmap.DisplayList", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_image_bitmap_display_list); |
| } else { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_others_display_list, |
| ("Blink.Canvas.DrawImage.Others.DisplayList", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_others_display_list); |
| } |
| } else { |
| if (image_source->IsVideoElement()) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_video_cpu, |
| ("Blink.Canvas.DrawImage.Video.CPU", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_video_cpu); |
| } else if (image_source->IsCanvasElement()) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_canvas_cpu, |
| ("Blink.Canvas.DrawImage.Canvas.CPU", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_canvas_cpu); |
| } else if (image_source->IsSVGSource()) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_svgcpu, |
| ("Blink.Canvas.DrawImage.SVG.CPU", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_svgcpu); |
| } else if (image_source->IsImageBitmap()) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_image_bitmap_cpu, |
| ("Blink.Canvas.DrawImage.ImageBitmap.CPU", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_image_bitmap_cpu); |
| } else if (image_source->IsOffscreenCanvas()) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_offscreencanvas_cpu, |
| ("Blink.Canvas.DrawImage.OffscreenCanvas.CPU", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_offscreencanvas_cpu); |
| } else { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_others_cpu, |
| ("Blink.Canvas.DrawImage.Others.CPU", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_others_cpu); |
| } |
| } |
| |
| scoped_refptr<Image> image; |
| FloatSize default_object_size(Width(), Height()); |
| SourceImageStatus source_image_status = kInvalidSourceImageStatus; |
| if (!image_source->IsVideoElement()) { |
| AccelerationHint hint = |
| (GetImageBuffer() && GetImageBuffer()->IsAccelerated()) |
| ? kPreferAcceleration |
| : kPreferNoAcceleration; |
| image = image_source->GetSourceImageForCanvas(&source_image_status, hint, |
| kSnapshotReasonDrawImage, |
| default_object_size); |
| if (source_image_status == kUndecodableSourceImageStatus) { |
| exception_state.ThrowDOMException( |
| kInvalidStateError, |
| "The HTMLImageElement provided is in the 'broken' state."); |
| } |
| if (!image || !image->width() || !image->height()) |
| return; |
| } else { |
| if (!static_cast<HTMLVideoElement*>(image_source)->HasAvailableVideoFrame()) |
| return; |
| } |
| |
| if (!std::isfinite(dx) || !std::isfinite(dy) || !std::isfinite(dw) || |
| !std::isfinite(dh) || !std::isfinite(sx) || !std::isfinite(sy) || |
| !std::isfinite(sw) || !std::isfinite(sh) || !dw || !dh || !sw || !sh) |
| return; |
| |
| FloatRect src_rect = NormalizeRect(FloatRect(sx, sy, sw, sh)); |
| FloatRect dst_rect = NormalizeRect(FloatRect(dx, dy, dw, dh)); |
| FloatSize image_size = image_source->ElementSize(default_object_size); |
| |
| ClipRectsToImageRect(FloatRect(FloatPoint(), image_size), &src_rect, |
| &dst_rect); |
| |
| image_source->AdjustDrawRects(&src_rect, &dst_rect); |
| |
| if (src_rect.IsEmpty()) |
| return; |
| |
| DisableDeferralReason reason = kDisableDeferralReasonUnknown; |
| if (ShouldDisableDeferral(image_source, &reason)) |
| DisableDeferral(reason); |
| else if (image->IsTextureBacked()) |
| DisableDeferral(kDisableDeferralDrawImageWithTextureBackedSourceImage); |
| |
| ValidateStateStack(); |
| |
| WillDrawImage(image_source); |
| |
| ValidateStateStack(); |
| |
| // Heuristic for disabling acceleration based on anticipated texture upload |
| // overhead. |
| // See comments in CanvasHeuristicParameters.h for explanation. |
| ImageBuffer* buffer = GetImageBuffer(); |
| if (buffer && buffer->IsAccelerated() && !image_source->IsAccelerated()) { |
| float src_area = src_rect.Width() * src_rect.Height(); |
| if (src_area > |
| CanvasHeuristicParameters::kDrawImageTextureUploadHardSizeLimit) { |
| buffer->DisableAcceleration(); |
| } else if (src_area > CanvasHeuristicParameters:: |
| kDrawImageTextureUploadSoftSizeLimit) { |
| SkRect bounds = dst_rect; |
| SkMatrix ctm = DrawingCanvas()->getTotalMatrix(); |
| ctm.mapRect(&bounds); |
| float dst_area = dst_rect.Width() * dst_rect.Height(); |
| if (src_area > |
| dst_area * CanvasHeuristicParameters:: |
| kDrawImageTextureUploadSoftSizeLimitScaleThreshold) { |
| buffer->DisableAcceleration(); |
| } |
| } |
| } |
| |
| ValidateStateStack(); |
| |
| Draw( |
| [this, &image_source, &image, &src_rect, dst_rect]( |
| PaintCanvas* c, const PaintFlags* flags) // draw lambda |
| { |
| DrawImageInternal(c, image_source, image.get(), src_rect, dst_rect, |
| flags); |
| }, |
| [this, &dst_rect](const SkIRect& clip_bounds) // overdraw test lambda |
| { return RectContainsTransformedRect(dst_rect, clip_bounds); }, |
| dst_rect, CanvasRenderingContext2DState::kImagePaintType, |
| image_source->IsOpaque() |
| ? CanvasRenderingContext2DState::kOpaqueImage |
| : CanvasRenderingContext2DState::kNonOpaqueImage); |
| |
| ValidateStateStack(); |
| |
| bool is_expensive = false; |
| |
| if (CanvasHeuristicParameters::kSVGImageSourcesAreExpensive && |
| image_source->IsSVGSource()) |
| is_expensive = true; |
| |
| if (image_size.Width() * image_size.Height() > |
| Width() * Height() * CanvasHeuristicParameters::kExpensiveImageSizeRatio) |
| is_expensive = true; |
| |
| if (is_expensive) { |
| ImageBuffer* buffer = GetImageBuffer(); |
| if (buffer) |
| buffer->SetHasExpensiveOp(); |
| } |
| |
| if (OriginClean() && |
| WouldTaintOrigin(image_source, ExecutionContext::From(script_state))) |
| SetOriginTainted(); |
| |
| timer->Count((WTF::MonotonicallyIncreasingTime() - start_time) * |
| WTF::Time::kMicrosecondsPerSecond); |
| } |
| |
| void BaseRenderingContext2D::ClearCanvas() { |
| FloatRect canvas_rect(0, 0, Width(), Height()); |
| CheckOverdraw(canvas_rect, nullptr, CanvasRenderingContext2DState::kNoImage, |
| kClipFill); |
| PaintCanvas* c = DrawingCanvas(); |
| if (c) |
| c->clear(HasAlpha() ? SK_ColorTRANSPARENT : SK_ColorBLACK); |
| } |
| |
| bool BaseRenderingContext2D::RectContainsTransformedRect( |
| const FloatRect& rect, |
| const SkIRect& transformed_rect) const { |
| FloatQuad quad(rect); |
| FloatQuad transformed_quad( |
| FloatRect(transformed_rect.x(), transformed_rect.y(), |
| transformed_rect.width(), transformed_rect.height())); |
| return GetState().Transform().MapQuad(quad).ContainsQuad(transformed_quad); |
| } |
| |
| CanvasGradient* BaseRenderingContext2D::createLinearGradient(double x0, |
| double y0, |
| double x1, |
| double y1) { |
| CanvasGradient* gradient = |
| CanvasGradient::Create(FloatPoint(x0, y0), FloatPoint(x1, y1)); |
| return gradient; |
| } |
| |
| CanvasGradient* BaseRenderingContext2D::createRadialGradient( |
| double x0, |
| double y0, |
| double r0, |
| double x1, |
| double y1, |
| double r1, |
| ExceptionState& exception_state) { |
| if (r0 < 0 || r1 < 0) { |
| exception_state.ThrowDOMException( |
| kIndexSizeError, String::Format("The %s provided is less than 0.", |
| r0 < 0 ? "r0" : "r1")); |
| return nullptr; |
| } |
| |
| CanvasGradient* gradient = |
| CanvasGradient::Create(FloatPoint(x0, y0), r0, FloatPoint(x1, y1), r1); |
| return gradient; |
| } |
| |
| CanvasPattern* BaseRenderingContext2D::createPattern( |
| ScriptState* script_state, |
| const CanvasImageSourceUnion& image_source, |
| const String& repetition_type, |
| ExceptionState& exception_state) { |
| CanvasImageSource* image_source_internal = |
| ToImageSourceInternal(image_source, exception_state); |
| if (!image_source_internal) { |
| return nullptr; |
| } |
| |
| return createPattern(script_state, image_source_internal, repetition_type, |
| exception_state); |
| } |
| |
| CanvasPattern* BaseRenderingContext2D::createPattern( |
| ScriptState* script_state, |
| CanvasImageSource* image_source, |
| const String& repetition_type, |
| ExceptionState& exception_state) { |
| if (!image_source) { |
| return nullptr; |
| } |
| |
| Pattern::RepeatMode repeat_mode = |
| CanvasPattern::ParseRepetitionType(repetition_type, exception_state); |
| if (exception_state.HadException()) |
| return nullptr; |
| |
| SourceImageStatus status; |
| |
| FloatSize default_object_size(Width(), Height()); |
| scoped_refptr<Image> image_for_rendering = |
| image_source->GetSourceImageForCanvas(&status, kPreferNoAcceleration, |
| kSnapshotReasonCreatePattern, |
| default_object_size); |
| |
| switch (status) { |
| case kNormalSourceImageStatus: |
| break; |
| case kZeroSizeCanvasSourceImageStatus: |
| exception_state.ThrowDOMException( |
| kInvalidStateError, |
| String::Format("The canvas %s is 0.", |
| image_source->ElementSize(default_object_size).Width() |
| ? "height" |
| : "width")); |
| return nullptr; |
| case kUndecodableSourceImageStatus: |
| exception_state.ThrowDOMException( |
| kInvalidStateError, "Source image is in the 'broken' state."); |
| return nullptr; |
| case kInvalidSourceImageStatus: |
| image_for_rendering = Image::NullImage(); |
| break; |
| case kIncompleteSourceImageStatus: |
| return nullptr; |
| default: |
| NOTREACHED(); |
| return nullptr; |
| } |
| DCHECK(image_for_rendering); |
| |
| bool origin_clean = |
| !WouldTaintOrigin(image_source, ExecutionContext::From(script_state)); |
| |
| return CanvasPattern::Create(std::move(image_for_rendering), repeat_mode, |
| origin_clean); |
| } |
| |
| bool BaseRenderingContext2D::ComputeDirtyRect(const FloatRect& local_rect, |
| SkIRect* dirty_rect) { |
| SkIRect clip_bounds; |
| if (!DrawingCanvas()->getDeviceClipBounds(&clip_bounds)) |
| return false; |
| return ComputeDirtyRect(local_rect, clip_bounds, dirty_rect); |
| } |
| |
| bool BaseRenderingContext2D::ComputeDirtyRect( |
| const FloatRect& local_rect, |
| const SkIRect& transformed_clip_bounds, |
| SkIRect* dirty_rect) { |
| FloatRect canvas_rect = GetState().Transform().MapRect(local_rect); |
| |
| if (AlphaChannel(GetState().ShadowColor())) { |
| FloatRect shadow_rect(canvas_rect); |
| shadow_rect.Move(GetState().ShadowOffset()); |
| shadow_rect.Inflate(GetState().ShadowBlur()); |
| canvas_rect.Unite(shadow_rect); |
| } |
| |
| SkIRect canvas_i_rect; |
| static_cast<SkRect>(canvas_rect).roundOut(&canvas_i_rect); |
| if (!canvas_i_rect.intersect(transformed_clip_bounds)) |
| return false; |
| |
| if (dirty_rect) |
| *dirty_rect = canvas_i_rect; |
| |
| return true; |
| } |
| |
| ImageDataColorSettings |
| BaseRenderingContext2D::GetColorSettingsAsImageDataColorSettings() const { |
| ImageDataColorSettings color_settings; |
| color_settings.setColorSpace(ColorSpaceAsString()); |
| if (PixelFormat() == kF16CanvasPixelFormat) |
| color_settings.setStorageFormat(kFloat32ArrayStorageFormatName); |
| return color_settings; |
| } |
| |
| ImageData* BaseRenderingContext2D::createImageData( |
| ImageData* image_data, |
| ExceptionState& exception_state) const { |
| ImageData* result = nullptr; |
| ImageDataColorSettings color_settings = |
| GetColorSettingsAsImageDataColorSettings(); |
| result = ImageData::Create(image_data->Size(), &color_settings); |
| if (!result) |
| exception_state.ThrowRangeError("Out of memory at ImageData creation"); |
| return result; |
| } |
| |
| ImageData* BaseRenderingContext2D::createImageData( |
| int sw, |
| int sh, |
| ExceptionState& exception_state) const { |
| if (!sw || !sh) { |
| exception_state.ThrowDOMException( |
| kIndexSizeError, |
| String::Format("The source %s is 0.", sw ? "height" : "width")); |
| return nullptr; |
| } |
| |
| IntSize size(abs(sw), abs(sh)); |
| ImageData* result = nullptr; |
| ImageDataColorSettings color_settings = |
| GetColorSettingsAsImageDataColorSettings(); |
| result = ImageData::Create(size, &color_settings); |
| |
| if (!result) |
| exception_state.ThrowRangeError("Out of memory at ImageData creation"); |
| return result; |
| } |
| |
| ImageData* BaseRenderingContext2D::createImageData( |
| unsigned width, |
| unsigned height, |
| ImageDataColorSettings& color_settings, |
| ExceptionState& exception_state) const { |
| return ImageData::CreateImageData(width, height, color_settings, |
| exception_state); |
| } |
| |
| ImageData* BaseRenderingContext2D::createImageData( |
| ImageDataArray& data_array, |
| unsigned width, |
| unsigned height, |
| ExceptionState& exception_state) const { |
| ImageDataColorSettings color_settings; |
| return ImageData::CreateImageData(data_array, width, height, color_settings, |
| exception_state); |
| } |
| |
| ImageData* BaseRenderingContext2D::createImageData( |
| ImageDataArray& data_array, |
| unsigned width, |
| unsigned height, |
| ImageDataColorSettings& color_settings, |
| ExceptionState& exception_state) const { |
| return ImageData::CreateImageData(data_array, width, height, color_settings, |
| exception_state); |
| } |
| |
| ImageData* BaseRenderingContext2D::getImageData( |
| int sx, |
| int sy, |
| int sw, |
| int sh, |
| ExceptionState& exception_state) { |
| if (!WTF::CheckMul(sw, sh).IsValid<int>()) { |
| exception_state.ThrowRangeError("Out of memory at ImageData creation"); |
| return nullptr; |
| } |
| |
| usage_counters_.num_get_image_data_calls++; |
| usage_counters_.area_get_image_data_calls += sw * sh; |
| if (!OriginClean()) { |
| exception_state.ThrowSecurityError( |
| "The canvas has been tainted by cross-origin data."); |
| } else if (!sw || !sh) { |
| exception_state.ThrowDOMException( |
| kIndexSizeError, |
| String::Format("The source %s is 0.", sw ? "height" : "width")); |
| } |
| |
| if (exception_state.HadException()) |
| return nullptr; |
| |
| if (sw < 0) { |
| sx += sw; |
| sw = -sw; |
| } |
| if (sh < 0) { |
| sy += sh; |
| sh = -sh; |
| } |
| |
| if (!WTF::CheckAdd(sx, sw).IsValid<int>() || |
| !WTF::CheckAdd(sy, sh).IsValid<int>()) { |
| exception_state.ThrowRangeError("Out of memory at ImageData creation"); |
| return nullptr; |
| } |
| |
| Optional<ScopedUsHistogramTimer> timer; |
| if (GetImageBuffer() && GetImageBuffer()->IsAccelerated()) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_gpu, |
| ("Blink.Canvas.GetImageData.GPU", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_gpu); |
| } else if (GetImageBuffer() && GetImageBuffer()->IsRecording()) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_display_list, |
| ("Blink.Canvas.GetImageData.DisplayList", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_display_list); |
| } else { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_cpu, |
| ("Blink.Canvas.GetImageData.CPU", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_cpu); |
| } |
| |
| IntRect image_data_rect(sx, sy, sw, sh); |
| ImageBuffer* buffer = GetImageBuffer(); |
| ImageDataColorSettings color_settings = |
| GetColorSettingsAsImageDataColorSettings(); |
| if (!buffer || isContextLost()) { |
| ImageData* result = |
| ImageData::Create(image_data_rect.Size(), &color_settings); |
| if (!result) |
| exception_state.ThrowRangeError("Out of memory at ImageData creation"); |
| return result; |
| } |
| |
| WTF::ArrayBufferContents contents; |
| if (!buffer->GetImageData(image_data_rect, contents)) { |
| exception_state.ThrowRangeError("Out of memory at ImageData creation"); |
| return nullptr; |
| } |
| |
| NeedsFinalizeFrame(); |
| |
| // Convert pixels to proper storage format if needed |
| if (PixelFormat() != kRGBA8CanvasPixelFormat) { |
| ImageDataStorageFormat storage_format = |
| ImageData::GetImageDataStorageFormat(color_settings.storageFormat()); |
| DOMArrayBufferView* array_buffer_view = |
| ImageData::ConvertPixelsFromCanvasPixelFormatToImageDataStorageFormat( |
| contents, PixelFormat(), storage_format); |
| return ImageData::Create(image_data_rect.Size(), |
| NotShared<DOMArrayBufferView>(array_buffer_view), |
| &color_settings); |
| } |
| DOMArrayBuffer* array_buffer = DOMArrayBuffer::Create(contents); |
| return ImageData::Create( |
| image_data_rect.Size(), |
| NotShared<DOMUint8ClampedArray>(DOMUint8ClampedArray::Create( |
| array_buffer, 0, array_buffer->ByteLength())), |
| &color_settings); |
| } |
| |
| void BaseRenderingContext2D::putImageData(ImageData* data, |
| int dx, |
| int dy, |
| ExceptionState& exception_state) { |
| putImageData(data, dx, dy, 0, 0, data->width(), data->height(), |
| exception_state); |
| } |
| |
| void BaseRenderingContext2D::putImageData(ImageData* data, |
| int dx, |
| int dy, |
| int dirty_x, |
| int dirty_y, |
| int dirty_width, |
| int dirty_height, |
| ExceptionState& exception_state) { |
| if (!WTF::CheckMul(dirty_width, dirty_height).IsValid<int>()) { |
| return; |
| } |
| usage_counters_.num_put_image_data_calls++; |
| usage_counters_.area_put_image_data_calls += dirty_width * dirty_height; |
| |
| if (data->BufferBase()->IsNeutered()) { |
| exception_state.ThrowDOMException(kInvalidStateError, |
| "The source data has been neutered."); |
| return; |
| } |
| |
| ImageBuffer* buffer = GetImageBuffer(); |
| if (!buffer) |
| return; |
| |
| if (dirty_width < 0) { |
| dirty_x += dirty_width; |
| dirty_width = -dirty_width; |
| } |
| |
| if (dirty_height < 0) { |
| dirty_y += dirty_height; |
| dirty_height = -dirty_height; |
| } |
| |
| IntRect dest_rect(dirty_x, dirty_y, dirty_width, dirty_height); |
| dest_rect.Intersect(IntRect(0, 0, data->width(), data->height())); |
| IntSize dest_offset(static_cast<int>(dx), static_cast<int>(dy)); |
| dest_rect.Move(dest_offset); |
| dest_rect.Intersect(IntRect(IntPoint(), buffer->Size())); |
| if (dest_rect.IsEmpty()) |
| return; |
| |
| Optional<ScopedUsHistogramTimer> timer; |
| if (GetImageBuffer() && GetImageBuffer()->IsAccelerated()) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_gpu, |
| ("Blink.Canvas.PutImageData.GPU", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_gpu); |
| } else if (GetImageBuffer() && GetImageBuffer()->IsRecording()) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_display_list, |
| ("Blink.Canvas.PutImageData.DisplayList", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_display_list); |
| } else { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| CustomCountHistogram, scoped_us_counter_cpu, |
| ("Blink.Canvas.PutImageData.CPU", 0, 10000000, 50)); |
| timer.emplace(scoped_us_counter_cpu); |
| } |
| |
| IntRect source_rect(dest_rect); |
| source_rect.Move(-dest_offset); |
| |
| CheckOverdraw(dest_rect, nullptr, CanvasRenderingContext2DState::kNoImage, |
| kUntransformedUnclippedFill); |
| |
| // Color / format convert ImageData to context 2D settings if needed. Color / |
| // format conversion is not needed only if context 2D and ImageData are both |
| // in sRGB color space and use 8-8-8-8 pixel storage format. We use RGBA pixel |
| // order for both ImageData and ImageBuffer, therefore no additional swizzling |
| // is needed. |
| CanvasColorParams data_color_params = data->GetCanvasColorParams(); |
| CanvasColorParams context_color_params = |
| CanvasColorParams(ColorSpace(), PixelFormat(), kNonOpaque); |
| if (data_color_params.NeedsColorConversion(context_color_params) || |
| PixelFormat() == kF16CanvasPixelFormat) { |
| unsigned data_length = |
| data->Size().Area() * context_color_params.BytesPerPixel(); |
| std::unique_ptr<uint8_t[]> converted_pixels(new uint8_t[data_length]); |
| if (data->ImageDataInCanvasColorSettings(ColorSpace(), PixelFormat(), |
| converted_pixels.get(), |
| kRGBAColorType)) { |
| buffer->PutByteArray(converted_pixels.get(), |
| IntSize(data->width(), data->height()), source_rect, |
| IntPoint(dest_offset)); |
| } |
| } else { |
| buffer->PutByteArray(data->data()->Data(), |
| IntSize(data->width(), data->height()), source_rect, |
| IntPoint(dest_offset)); |
| } |
| DidDraw(dest_rect); |
| } |
| |
| void BaseRenderingContext2D::InflateStrokeRect(FloatRect& rect) const { |
| // Fast approximation of the stroke's bounding rect. |
| // This yields a slightly oversized rect but is very fast |
| // compared to Path::strokeBoundingRect(). |
| static const double kRoot2 = sqrtf(2); |
| double delta = GetState().LineWidth() / 2; |
| if (GetState().GetLineJoin() == kMiterJoin) |
| delta *= GetState().MiterLimit(); |
| else if (GetState().GetLineCap() == kSquareCap) |
| delta *= kRoot2; |
| |
| rect.Inflate(delta); |
| } |
| |
| bool BaseRenderingContext2D::imageSmoothingEnabled() const { |
| return GetState().ImageSmoothingEnabled(); |
| } |
| |
| void BaseRenderingContext2D::setImageSmoothingEnabled(bool enabled) { |
| if (enabled == GetState().ImageSmoothingEnabled()) |
| return; |
| |
| ModifiableState().SetImageSmoothingEnabled(enabled); |
| } |
| |
| String BaseRenderingContext2D::imageSmoothingQuality() const { |
| return GetState().ImageSmoothingQuality(); |
| } |
| |
| void BaseRenderingContext2D::setImageSmoothingQuality(const String& quality) { |
| if (quality == GetState().ImageSmoothingQuality()) |
| return; |
| |
| ModifiableState().SetImageSmoothingQuality(quality); |
| } |
| |
| void BaseRenderingContext2D::CheckOverdraw( |
| const SkRect& rect, |
| const PaintFlags* flags, |
| CanvasRenderingContext2DState::ImageType image_type, |
| DrawType draw_type) { |
| PaintCanvas* c = DrawingCanvas(); |
| if (!c || !GetImageBuffer()->IsRecording()) |
| return; |
| |
| SkRect device_rect; |
| if (draw_type == kUntransformedUnclippedFill) { |
| device_rect = rect; |
| } else { |
| DCHECK_EQ(draw_type, kClipFill); |
| if (GetState().HasComplexClip()) |
| return; |
| |
| SkIRect sk_i_bounds; |
| if (!c->getDeviceClipBounds(&sk_i_bounds)) |
| return; |
| device_rect = SkRect::Make(sk_i_bounds); |
| } |
| |
| const SkImageInfo& image_info = c->imageInfo(); |
| if (!device_rect.contains( |
| SkRect::MakeWH(image_info.width(), image_info.height()))) |
| return; |
| |
| bool is_source_over = true; |
| unsigned alpha = 0xFF; |
| if (flags) { |
| if (flags->getLooper() || flags->getImageFilter() || flags->getMaskFilter()) |
| return; |
| |
| SkBlendMode mode = flags->getBlendMode(); |
| is_source_over = mode == SkBlendMode::kSrcOver; |
| if (!is_source_over && mode != SkBlendMode::kSrc && |
| mode != SkBlendMode::kClear) |
| return; // The code below only knows how to handle Src, SrcOver, and |
| // Clear |
| |
| alpha = flags->getAlpha(); |
| |
| if (is_source_over && |
| image_type == CanvasRenderingContext2DState::kNoImage) { |
| if (flags->HasShader()) { |
| if (flags->ShaderIsOpaque() && alpha == 0xFF) |
| GetImageBuffer()->WillOverwriteCanvas(); |
| return; |
| } |
| } |
| } |
| |
| if (is_source_over) { |
| // With source over, we need to certify that alpha == 0xFF for all pixels |
| if (image_type == CanvasRenderingContext2DState::kNonOpaqueImage) |
| return; |
| if (alpha < 0xFF) |
| return; |
| } |
| |
| GetImageBuffer()->WillOverwriteCanvas(); |
| } |
| |
| float BaseRenderingContext2D::GetFontBaseline( |
| const FontMetrics& font_metrics) const { |
| return TextMetrics::GetFontBaseline(GetState().GetTextBaseline(), |
| font_metrics); |
| } |
| |
| String BaseRenderingContext2D::textAlign() const { |
| return TextAlignName(GetState().GetTextAlign()); |
| } |
| |
| void BaseRenderingContext2D::setTextAlign(const String& s) { |
| TextAlign align; |
| if (!ParseTextAlign(s, align)) |
| return; |
| if (GetState().GetTextAlign() == align) |
| return; |
| ModifiableState().SetTextAlign(align); |
| } |
| |
| String BaseRenderingContext2D::textBaseline() const { |
| return TextBaselineName(GetState().GetTextBaseline()); |
| } |
| |
| void BaseRenderingContext2D::setTextBaseline(const String& s) { |
| TextBaseline baseline; |
| if (!ParseTextBaseline(s, baseline)) |
| return; |
| if (GetState().GetTextBaseline() == baseline) |
| return; |
| ModifiableState().SetTextBaseline(baseline); |
| } |
| |
| const BaseRenderingContext2D::UsageCounters& |
| BaseRenderingContext2D::GetUsage() { |
| return usage_counters_; |
| } |
| |
| void BaseRenderingContext2D::Trace(blink::Visitor* visitor) { |
| visitor->Trace(state_stack_); |
| } |
| |
| BaseRenderingContext2D::UsageCounters::UsageCounters() |
| : num_draw_calls{0, 0, 0, 0, 0, 0, 0}, |
| bounding_box_perimeter_draw_calls{0.0f, 0.0f, 0.0f, 0.0f, |
| 0.0f, 0.0f, 0.0f}, |
| bounding_box_area_draw_calls{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, |
| bounding_box_area_fill_type{0.0f, 0.0f, 0.0f, 0.0f}, |
| num_non_convex_fill_path_calls(0), |
| non_convex_fill_path_area(0.0f), |
| num_radial_gradients(0), |
| num_linear_gradients(0), |
| num_patterns(0), |
| num_draw_with_complex_clips(0), |
| num_blurred_shadows(0), |
| bounding_box_area_times_shadow_blur_squared(0.0f), |
| bounding_box_perimeter_times_shadow_blur_squared(0.0f), |
| num_filters(0), |
| num_get_image_data_calls(0), |
| area_get_image_data_calls(0.0), |
| num_put_image_data_calls(0), |
| area_put_image_data_calls(0.0), |
| num_clear_rect_calls(0), |
| num_draw_focus_calls(0), |
| num_frames_since_reset(0) {} |
| |
| } // namespace blink |