| // 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 "third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <memory> |
| |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/numerics/checked_math.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/privacy_budget/identifiability_metrics.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_union_canvasfilter_string.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_union_csscolorvalue_canvasgradient_canvaspattern_string.h" |
| #include "third_party/blink/renderer/core/css/cssom/css_color_value.h" |
| #include "third_party/blink/renderer/core/css/parser/css_parser.h" |
| #include "third_party/blink/renderer/core/dom/events/event.h" |
| #include "third_party/blink/renderer/core/frame/web_feature.h" |
| #include "third_party/blink/renderer/core/html/canvas/text_metrics.h" |
| #include "third_party/blink/renderer/core/html/media/html_video_element.h" |
| #include "third_party/blink/renderer/modules/canvas/canvas2d/canvas_filter.h" |
| #include "third_party/blink/renderer/modules/canvas/canvas2d/canvas_pattern.h" |
| #include "third_party/blink/renderer/modules/canvas/canvas2d/path_2d.h" |
| #include "third_party/blink/renderer/modules/webcodecs/video_frame.h" |
| #include "third_party/blink/renderer/platform/geometry/float_quad.h" |
| #include "third_party/blink/renderer/platform/graphics/bitmap_image.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_context.h" |
| #include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h" |
| #include "third_party/blink/renderer/platform/graphics/stroke_data.h" |
| #include "third_party/blink/renderer/platform/graphics/video_frame_image_util.h" |
| #include "third_party/blink/renderer/platform/heap/heap.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.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 char BaseRenderingContext2D::kAutoKerningString[] = "auto"; |
| const char BaseRenderingContext2D::kNormalKerningString[] = "normal"; |
| const char BaseRenderingContext2D::kNoneKerningString[] = "none"; |
| const char BaseRenderingContext2D::kUltraCondensedString[] = "ultra-condensed"; |
| const char BaseRenderingContext2D::kExtraCondensedString[] = "extra-condensed"; |
| const char BaseRenderingContext2D::kCondensedString[] = "condensed"; |
| const char BaseRenderingContext2D::kSemiCondensedString[] = "semi-condensed"; |
| const char BaseRenderingContext2D::kNormalStretchString[] = "normal"; |
| const char BaseRenderingContext2D::kSemiExpandedString[] = "semi-expanded"; |
| const char BaseRenderingContext2D::kExpandedString[] = "expanded"; |
| const char BaseRenderingContext2D::kExtraExpandedString[] = "extra-expanded"; |
| const char BaseRenderingContext2D::kUltraExpandedString[] = "ultra-expanded"; |
| const char BaseRenderingContext2D::kNormalVariantString[] = "normal"; |
| const char BaseRenderingContext2D::kSmallCapsVariantString[] = "small-caps"; |
| const char BaseRenderingContext2D::kAllSmallCapsVariantString[] = |
| "all-small-caps"; |
| const char BaseRenderingContext2D::kPetiteVariantString[] = "petite-caps"; |
| const char BaseRenderingContext2D::kAllPetiteVariantString[] = |
| "all-petite-caps"; |
| const char BaseRenderingContext2D::kUnicaseVariantString[] = "unicase"; |
| const char BaseRenderingContext2D::kTitlingCapsVariantString[] = "titling-caps"; |
| const double BaseRenderingContext2D::kCDeviceScaleFactor = 1.0; |
| const char BaseRenderingContext2D::kAutoRendering[] = "auto"; |
| const char BaseRenderingContext2D::kOptimizeSpeedRendering[] = "optimizespeed"; |
| const char BaseRenderingContext2D::kOptimizeLegibilityRendering[] = |
| "optimizelegibility"; |
| const char BaseRenderingContext2D::kGeometricPrecisionRendering[] = |
| "geometricprecision"; |
| |
| // Dummy overdraw test for ops that do not support overdraw detection |
| const auto kNoOverdraw = [](const SkIRect& clip_bounds) { return false; }; |
| |
| // After context lost, it waits |kTryRestoreContextInterval| before start the |
| // restore the context. This wait needs to be long enough to avoid spamming the |
| // GPU process with retry attempts and short enough to provide decent UX. It's |
| // currently set to 500ms. |
| const base::TimeDelta kTryRestoreContextInterval = base::Milliseconds(500); |
| |
| BaseRenderingContext2D::BaseRenderingContext2D() |
| : dispatch_context_lost_event_timer_( |
| Thread::Current()->GetTaskRunner(), |
| this, |
| &BaseRenderingContext2D::DispatchContextLostEvent), |
| dispatch_context_restored_event_timer_( |
| Thread::Current()->GetTaskRunner(), |
| this, |
| &BaseRenderingContext2D::DispatchContextRestoredEvent), |
| try_restore_context_event_timer_( |
| Thread::Current()->GetTaskRunner(), |
| this, |
| &BaseRenderingContext2D::TryRestoreContextEvent), |
| clip_antialiasing_(kNotAntiAliased), |
| origin_tainted_by_content_(false) { |
| state_stack_.push_back(MakeGarbageCollected<CanvasRenderingContext2DState>()); |
| } |
| |
| BaseRenderingContext2D::~BaseRenderingContext2D() = default; |
| |
| void BaseRenderingContext2D::save() { |
| if (isContextLost()) |
| return; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kSave); |
| } |
| |
| ValidateStateStack(); |
| |
| DCHECK_GE(state_stack_.size(), 1u); |
| |
| // GetOrCreatePaintCanvas() can call RestoreMatrixClipStack which syncs |
| // canvas to state_stack_. Get the canvas before adjusting state_stack_ to |
| // ensure canvas is synced prior to adjusting state_stack_. |
| cc::PaintCanvas* canvas = GetOrCreatePaintCanvas(); |
| |
| state_stack_.push_back(MakeGarbageCollected<CanvasRenderingContext2DState>( |
| GetState(), CanvasRenderingContext2DState::kDontCopyClipList, |
| CanvasRenderingContext2DState::SaveType::kSaveRestore)); |
| |
| if (canvas) |
| canvas->save(); |
| |
| ValidateStateStack(); |
| } |
| |
| void BaseRenderingContext2D::restore() { |
| if (isContextLost()) |
| return; |
| |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kRestore); |
| } |
| ValidateStateStack(); |
| if (state_stack_.size() <= 1) |
| return; |
| |
| DCHECK_GT(state_stack_.size(), static_cast<WTF::wtf_size_t>(layer_count_)); |
| |
| // Verify that the top of the stack was pushed with Save. |
| if (RuntimeEnabledFeatures::Canvas2dLayersEnabled() && |
| state_stack_.back()->GetSaveType() != |
| CanvasRenderingContext2DState::SaveType::kSaveRestore) { |
| return; |
| } |
| |
| // Verify that the current state's transform is invertible. |
| if (IsTransformInvertible()) |
| path_.Transform(GetState().GetTransform()); |
| |
| PopAndRestore(); |
| } |
| |
| void BaseRenderingContext2D::beginLayer() { |
| if (isContextLost()) |
| return; |
| // TODO(crbug.com/1234113): Instrument new canvas APIs. |
| identifiability_study_helper_.set_encountered_skipped_ops(); |
| |
| ValidateStateStack(); |
| |
| DCHECK_GE(state_stack_.size(), 1u); |
| |
| // GetOrCreatePaintCanvas() can call RestoreMatrixClipStack which syncs |
| // canvas to state_stack_. Get the canvas before adjusting state_stack_ to |
| // ensure canvas is synced prior to adjusting state_stack_. |
| cc::PaintCanvas* canvas = GetOrCreatePaintCanvas(); |
| if (!canvas) |
| return; |
| |
| state_stack_.push_back(MakeGarbageCollected<CanvasRenderingContext2DState>( |
| GetState(), CanvasRenderingContext2DState::kDontCopyClipList, |
| CanvasRenderingContext2DState::SaveType::kBeginEndLayer)); |
| layer_count_++; |
| |
| if (globalAlpha() != 1 && |
| (StateHasFilter() || GetState().ShouldDrawShadows())) { |
| // For alpha and either filters or shadows, we have to split the save into |
| // two layers, so the shadow and filter can properly interact with alpha. |
| // We also need to flip how and where the shadows and filter are applied |
| // if there are shadows. |
| PaintFlags flags; |
| GetState().FillStyle()->ApplyToFlags(flags); |
| flags.setColor(GetState().FillStyle()->PaintColor()); |
| flags.setBlendMode(GetState().GlobalComposite()); |
| flags.setImageFilter(GetState().ShouldDrawShadows() |
| ? GetState().ShadowAndForegroundImageFilter() |
| : StateGetFilter()); |
| canvas->saveLayer(nullptr, &flags); |
| |
| // Push to state stack to keep stack size up to date. |
| state_stack_.push_back(MakeGarbageCollected<CanvasRenderingContext2DState>( |
| GetState(), CanvasRenderingContext2DState::kDontCopyClipList, |
| CanvasRenderingContext2DState::SaveType::kInternalLayer)); |
| |
| PaintFlags extra_flags; |
| GetState().FillStyle()->ApplyToFlags(extra_flags); |
| extra_flags.setColor(GetState().FillStyle()->PaintColor()); |
| extra_flags.setAlpha(globalAlpha() * 255); |
| if (GetState().ShouldDrawShadows()) |
| extra_flags.setImageFilter(StateGetFilter()); |
| canvas->saveLayer(nullptr, &extra_flags); |
| } else { |
| PaintFlags flags; |
| GetState().FillStyle()->ApplyToFlags(flags); |
| flags.setColor(GetState().FillStyle()->PaintColor()); |
| flags.setBlendMode(GetState().GlobalComposite()); |
| // This ComposePaintFilter will work always, whether there is only |
| // shadows, or filters, both of them, or none of them. |
| flags.setImageFilter(sk_make_sp<ComposePaintFilter>( |
| GetState().ShadowAndForegroundImageFilter(), StateGetFilter())); |
| flags.setAlpha(globalAlpha() * 255); |
| canvas->saveLayer(nullptr, &flags); |
| } |
| |
| ValidateStateStack(); |
| |
| // Reset compositing attributes. |
| setShadowOffsetX(0); |
| setShadowOffsetY(0); |
| setShadowBlur(0); |
| GetState().SetShadowColor(Color::kTransparent); |
| DCHECK(!GetState().ShouldDrawShadows()); |
| setGlobalAlpha(1.0); |
| setGlobalCompositeOperation("source-over"); |
| V8UnionCanvasFilterOrString* filter = |
| MakeGarbageCollected<V8UnionCanvasFilterOrString>("none"); |
| setFilter(GetTopExecutionContext(), filter); |
| } |
| |
| void BaseRenderingContext2D::endLayer() { |
| if (isContextLost()) |
| return; |
| // TODO(crbug.com/1234113): Instrument new canvas APIs. |
| identifiability_study_helper_.set_encountered_skipped_ops(); |
| |
| ValidateStateStack(); |
| if (state_stack_.size() <= 1 || layer_count_ <= 0) |
| return; |
| |
| DCHECK_GT(state_stack_.size(), static_cast<WTF::wtf_size_t>(layer_count_)); |
| |
| // Verify that the current state's transform is invertible. |
| if (IsTransformInvertible()) |
| path_.Transform(GetState().GetTransform()); |
| |
| // All saves performed since the last beginLayer are no-ops. |
| while (state_stack_.back() && |
| state_stack_.back()->GetSaveType() == |
| CanvasRenderingContext2DState::SaveType::kSaveRestore) { |
| PopAndRestore(); |
| } |
| |
| // If we do an endLayer, we have to be sure that we did a beginLayer (that |
| // could have introduced an extra state). |
| DCHECK(state_stack_.back()->GetSaveType() == |
| CanvasRenderingContext2DState::SaveType::kBeginEndLayer || |
| state_stack_.back()->GetSaveType() == |
| CanvasRenderingContext2DState::SaveType::kInternalLayer); |
| PopAndRestore(); |
| layer_count_--; |
| } |
| |
| void BaseRenderingContext2D::PopAndRestore() { |
| if (state_stack_.size() <= 1) { |
| NOTREACHED(); |
| return; |
| } |
| |
| state_stack_.pop_back(); |
| state_stack_.back()->ClearResolvedFilter(); |
| |
| SetIsTransformInvertible(GetState().IsTransformInvertible()); |
| if (IsTransformInvertible()) |
| path_.Transform(GetState().GetTransform().Inverse()); |
| |
| cc::PaintCanvas* canvas = GetOrCreatePaintCanvas(); |
| |
| if (!canvas) |
| return; |
| |
| if (state_stack_.back()->GetSaveType() == |
| CanvasRenderingContext2DState::SaveType::kInternalLayer) { |
| // If this is a ExtraState state, it means we have to restore twice, as we |
| // added an extra state while doing a beginLayer. |
| canvas->restore(); |
| state_stack_.pop_back(); |
| DCHECK(state_stack_.back()); |
| state_stack_.back()->ClearResolvedFilter(); |
| DCHECK(state_stack_.back()->GetSaveType() == |
| CanvasRenderingContext2DState::SaveType::kBeginEndLayer); |
| } |
| canvas->restore(); |
| |
| ValidateStateStack(); |
| } |
| |
| void BaseRenderingContext2D::RestoreMatrixClipStack(cc::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(SkM44()); |
| if (curr_state->Get()) { |
| curr_state->Get()->PlaybackClips(c); |
| c->setMatrix( |
| TransformationMatrix::ToSkM44(curr_state->Get()->GetTransform())); |
| } |
| c->save(); |
| } |
| c->restore(); |
| ValidateStateStackWithCanvas(c); |
| } |
| |
| void BaseRenderingContext2D::UnwindStateStack() { |
| if (size_t stack_size = state_stack_.size()) { |
| if (cc::PaintCanvas* sk_canvas = GetPaintCanvas()) { |
| while (--stack_size) |
| sk_canvas->restore(); |
| } |
| } |
| } |
| |
| void BaseRenderingContext2D::ResetInternal() { |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kReset); |
| } |
| ValidateStateStack(); |
| UnwindStateStack(); |
| state_stack_.resize(1); |
| state_stack_.front() = MakeGarbageCollected<CanvasRenderingContext2DState>(); |
| SetIsTransformInvertible(true); |
| path_.Clear(); |
| if (cc::PaintCanvas* c = GetPaintCanvas()) { |
| // 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 |
| // We only want to clear the backing buffer if the surface exists because |
| // this function is also used when the context is lost. |
| clearRect(0, 0, Width(), Height(), /*for_reset=*/true); |
| } |
| ValidateStateStack(); |
| origin_tainted_by_content_ = false; |
| } |
| |
| void BaseRenderingContext2D::reset() { |
| UseCounter::Count(GetTopExecutionContext(), |
| WebFeature::kCanvasRenderingContext2DReset); |
| ResetInternal(); |
| } |
| |
| static V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString* |
| ConvertCanvasStyleToUnionType(CanvasStyle* style) { |
| if (CanvasGradient* gradient = style->GetCanvasGradient()) { |
| return MakeGarbageCollected< |
| V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString>(gradient); |
| } |
| if (CanvasPattern* pattern = style->GetCanvasPattern()) { |
| return MakeGarbageCollected< |
| V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString>(pattern); |
| } |
| return MakeGarbageCollected< |
| V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString>( |
| style->GetColorAsString()); |
| } |
| |
| void BaseRenderingContext2D::IdentifiabilityUpdateForStyleUnion( |
| const V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString* style) { |
| switch (style->GetContentType()) { |
| case V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString:: |
| ContentType::kCSSColorValue: |
| break; |
| case V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString:: |
| ContentType::kCanvasGradient: |
| identifiability_study_helper_.UpdateBuilder( |
| style->GetAsCanvasGradient()->GetIdentifiableToken()); |
| break; |
| case V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString:: |
| ContentType::kCanvasPattern: |
| identifiability_study_helper_.UpdateBuilder( |
| style->GetAsCanvasPattern()->GetIdentifiableToken()); |
| break; |
| case V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString:: |
| ContentType::kString: |
| identifiability_study_helper_.UpdateBuilder( |
| IdentifiabilityBenignStringToken(style->GetAsString())); |
| break; |
| default: |
| // TODO(crbug.com/1234113): Instrument new canvas APIs. |
| identifiability_study_helper_.set_encountered_skipped_ops(); |
| } |
| } |
| |
| RespectImageOrientationEnum |
| BaseRenderingContext2D::RespectImageOrientationInternal( |
| CanvasImageSource* image_source) { |
| if ((image_source->IsImageBitmap() || image_source->IsImageElement()) && |
| image_source->WouldTaintOrigin()) |
| return kRespectImageOrientation; |
| return RespectImageOrientation(); |
| } |
| |
| V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString* |
| BaseRenderingContext2D::strokeStyle() const { |
| return ConvertCanvasStyleToUnionType(GetState().StrokeStyle()); |
| } |
| |
| void BaseRenderingContext2D::setStrokeStyle( |
| const V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString* style) { |
| DCHECK(style); |
| |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetStrokeStyle); |
| IdentifiabilityUpdateForStyleUnion(style); |
| } |
| |
| String color_string; |
| CanvasStyle* canvas_style = nullptr; |
| switch (style->GetContentType()) { |
| case V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString:: |
| ContentType::kCSSColorValue: |
| if (GetCanvasRenderingContextHost() && |
| !RuntimeEnabledFeatures::NewCanvas2DAPIEnabled( |
| GetTopExecutionContext())) |
| return; |
| canvas_style = MakeGarbageCollected<CanvasStyle>( |
| style->GetAsCSSColorValue()->ToColor().Rgb()); |
| break; |
| case V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString:: |
| ContentType::kCanvasGradient: |
| canvas_style = |
| MakeGarbageCollected<CanvasStyle>(style->GetAsCanvasGradient()); |
| break; |
| case V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString:: |
| ContentType::kCanvasPattern: { |
| CanvasPattern* canvas_pattern = style->GetAsCanvasPattern(); |
| if (!origin_tainted_by_content_ && !canvas_pattern->OriginClean()) |
| SetOriginTaintedByContent(); |
| canvas_style = MakeGarbageCollected<CanvasStyle>(canvas_pattern); |
| break; |
| } |
| case V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString:: |
| ContentType::kString: { |
| 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())) { |
| GetState().SetUnparsedStrokeColor(color_string); |
| return; |
| } |
| canvas_style = MakeGarbageCollected<CanvasStyle>(parsed_color.Rgb()); |
| break; |
| } |
| } |
| |
| DCHECK(canvas_style); |
| GetState().SetStrokeStyle(canvas_style); |
| GetState().SetUnparsedStrokeColor(color_string); |
| GetState().ClearResolvedFilter(); |
| } |
| |
| V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString* |
| BaseRenderingContext2D::fillStyle() const { |
| return ConvertCanvasStyleToUnionType(GetState().FillStyle()); |
| } |
| |
| void BaseRenderingContext2D::setFillStyle( |
| const V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString* style) { |
| DCHECK(style); |
| |
| ValidateStateStack(); |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetFillStyle); |
| IdentifiabilityUpdateForStyleUnion(style); |
| } |
| |
| String color_string; |
| CanvasStyle* canvas_style = nullptr; |
| switch (style->GetContentType()) { |
| case V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString:: |
| ContentType::kCSSColorValue: |
| if (GetCanvasRenderingContextHost() && |
| !RuntimeEnabledFeatures::NewCanvas2DAPIEnabled( |
| GetTopExecutionContext())) |
| return; |
| canvas_style = MakeGarbageCollected<CanvasStyle>( |
| style->GetAsCSSColorValue()->ToColor().Rgb()); |
| break; |
| case V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString:: |
| ContentType::kCanvasGradient: |
| canvas_style = |
| MakeGarbageCollected<CanvasStyle>(style->GetAsCanvasGradient()); |
| break; |
| case V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString:: |
| ContentType::kCanvasPattern: { |
| CanvasPattern* canvas_pattern = style->GetAsCanvasPattern(); |
| if (!origin_tainted_by_content_ && !canvas_pattern->OriginClean()) |
| SetOriginTaintedByContent(); |
| canvas_style = MakeGarbageCollected<CanvasStyle>(canvas_pattern); |
| break; |
| } |
| case V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString:: |
| ContentType::kString: { |
| 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())) { |
| GetState().SetUnparsedFillColor(color_string); |
| return; |
| } |
| canvas_style = MakeGarbageCollected<CanvasStyle>(parsed_color.Rgb()); |
| break; |
| } |
| } |
| |
| DCHECK(canvas_style); |
| GetState().SetFillStyle(canvas_style); |
| GetState().SetUnparsedFillColor(color_string); |
| GetState().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; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetLineWidth, |
| width); |
| } |
| GetState().SetLineWidth(ClampTo<float>(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; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetLineCap, cap); |
| } |
| GetState().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; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetLineJoin, join); |
| } |
| GetState().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; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetMiterLimit, |
| limit); |
| } |
| GetState().SetMiterLimit(ClampTo<float>(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; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetShadowOffsetX, |
| x); |
| } |
| GetState().SetShadowOffsetX(ClampTo<float>(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; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetShadowOffsetY, |
| y); |
| } |
| GetState().SetShadowOffsetY(ClampTo<float>(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; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetShadowBlur, |
| blur); |
| } |
| GetState().SetShadowBlur(ClampTo<float>(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; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetShadowColor, |
| color.Rgb()); |
| } |
| GetState().SetShadowColor(color.Rgb()); |
| } |
| |
| const Vector<double>& BaseRenderingContext2D::getLineDash() const { |
| return GetState().LineDash(); |
| } |
| |
| static bool LineDashSequenceIsValid(const Vector<double>& dash) { |
| return std::all_of(dash.begin(), dash.end(), |
| [](double d) { return std::isfinite(d) && d >= 0; }); |
| } |
| |
| void BaseRenderingContext2D::setLineDash(const Vector<double>& dash) { |
| if (!LineDashSequenceIsValid(dash)) |
| return; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetLineDash, |
| base::make_span(dash)); |
| } |
| GetState().SetLineDash(dash); |
| } |
| |
| double BaseRenderingContext2D::lineDashOffset() const { |
| return GetState().LineDashOffset(); |
| } |
| |
| void BaseRenderingContext2D::setLineDashOffset(double offset) { |
| if (!std::isfinite(offset) || GetState().LineDashOffset() == offset) |
| return; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetLineDashOffset, |
| offset); |
| } |
| GetState().SetLineDashOffset(ClampTo<float>(offset)); |
| } |
| |
| double BaseRenderingContext2D::globalAlpha() const { |
| return GetState().GlobalAlpha(); |
| } |
| |
| void BaseRenderingContext2D::setGlobalAlpha(double alpha) { |
| if (!(alpha >= 0 && alpha <= 1)) |
| return; |
| if (GetState().GlobalAlpha() == alpha) |
| return; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetGlobalAlpha, |
| alpha); |
| } |
| GetState().SetGlobalAlpha(alpha); |
| } |
| |
| String BaseRenderingContext2D::globalCompositeOperation() const { |
| return CompositeOperatorName( |
| CompositeOperatorFromSkBlendMode(GetState().GlobalComposite()), |
| BlendModeFromSkBlendMode(GetState().GlobalComposite())); |
| } |
| |
| void BaseRenderingContext2D::setGlobalCompositeOperation( |
| const String& operation) { |
| CompositeOperator op = kCompositeSourceOver; |
| BlendMode blend_mode = BlendMode::kNormal; |
| if (!ParseCompositeAndBlendMode(operation, op, blend_mode)) |
| return; |
| SkBlendMode sk_blend_mode = WebCoreCompositeToSkiaComposite(op, blend_mode); |
| if (GetState().GlobalComposite() == sk_blend_mode) |
| return; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder( |
| CanvasOps::kSetGlobalCompositeOpertion, sk_blend_mode); |
| } |
| GetState().SetGlobalComposite(sk_blend_mode); |
| } |
| |
| const V8UnionCanvasFilterOrString* BaseRenderingContext2D::filter() const { |
| if (CanvasFilter* filter = GetState().GetCanvasFilter()) { |
| return MakeGarbageCollected<V8UnionCanvasFilterOrString>(filter); |
| } |
| return MakeGarbageCollected<V8UnionCanvasFilterOrString>( |
| GetState().UnparsedCSSFilter()); |
| } |
| |
| void BaseRenderingContext2D::setFilter( |
| const ExecutionContext* execution_context, |
| const V8UnionCanvasFilterOrString* input) { |
| if (!input) |
| return; |
| |
| switch (input->GetContentType()) { |
| case V8UnionCanvasFilterOrString::ContentType::kCanvasFilter: |
| if (GetCanvasRenderingContextHost() && |
| RuntimeEnabledFeatures::NewCanvas2DAPIEnabled( |
| GetTopExecutionContext())) { |
| UseCounter::Count(GetTopExecutionContext(), |
| WebFeature::kCanvasRenderingContext2DCanvasFilter); |
| GetState().SetCanvasFilter(input->GetAsCanvasFilter()); |
| SnapshotStateForFilter(); |
| // TODO(crbug.com/1234113): Instrument new canvas APIs. |
| identifiability_study_helper_.set_encountered_skipped_ops(); |
| } |
| break; |
| case V8UnionCanvasFilterOrString::ContentType::kString: { |
| const String& filter_string = input->GetAsString(); |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder( |
| CanvasOps::kSetFilter, |
| IdentifiabilitySensitiveStringToken(filter_string)); |
| } |
| if (!GetState().GetCanvasFilter() && |
| filter_string == GetState().UnparsedCSSFilter()) { |
| return; |
| } |
| const CSSValue* css_value = CSSParser::ParseSingleValue( |
| CSSPropertyID::kFilter, filter_string, |
| MakeGarbageCollected<CSSParserContext>( |
| kHTMLStandardMode, execution_context->GetSecureContextMode())); |
| if (!css_value || css_value->IsCSSWideKeyword()) |
| return; |
| GetState().SetUnparsedCSSFilter(filter_string); |
| GetState().SetCSSFilter(css_value); |
| SnapshotStateForFilter(); |
| break; |
| } |
| } |
| } |
| |
| void BaseRenderingContext2D::scale(double sx, double sy) { |
| // TODO(crbug.com/1140535): Investigate the performance impact of simply |
| // calling the 3d version of this function |
| cc::PaintCanvas* c = GetOrCreatePaintCanvas(); |
| if (!c) |
| return; |
| |
| if (!std::isfinite(sx) || !std::isfinite(sy)) |
| return; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kScale, sx, sy); |
| } |
| |
| TransformationMatrix new_transform = GetState().GetTransform(); |
| float fsx = ClampTo<float>(sx); |
| float fsy = ClampTo<float>(sy); |
| new_transform.ScaleNonUniform(fsx, fsy); |
| if (GetState().GetTransform() == new_transform) |
| return; |
| |
| SetTransform(new_transform); |
| if (!IsTransformInvertible()) |
| return; |
| |
| c->scale(fsx, fsy); |
| path_.Transform(AffineTransform().ScaleNonUniform(1.0 / fsx, 1.0 / fsy)); |
| } |
| |
| void BaseRenderingContext2D::rotate(double angle_in_radians) { |
| cc::PaintCanvas* c = GetOrCreatePaintCanvas(); |
| if (!c) |
| return; |
| |
| if (!std::isfinite(angle_in_radians)) |
| return; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kRotate, |
| angle_in_radians); |
| } |
| |
| TransformationMatrix new_transform = GetState().GetTransform(); |
| new_transform.Rotate(Rad2deg(angle_in_radians)); |
| if (GetState().GetTransform() == new_transform) |
| return; |
| |
| SetTransform(new_transform); |
| if (!IsTransformInvertible()) |
| return; |
| c->rotate(ClampTo<float>(angle_in_radians * (180.0 / kPiFloat))); |
| path_.Transform(AffineTransform().RotateRadians(-angle_in_radians)); |
| } |
| |
| void BaseRenderingContext2D::translate(double tx, double ty) { |
| // TODO(crbug.com/1140535): Investigate the performance impact of simply |
| // calling the 3d version of this function |
| cc::PaintCanvas* c = GetOrCreatePaintCanvas(); |
| if (!c) |
| return; |
| |
| if (!IsTransformInvertible()) |
| return; |
| |
| if (!std::isfinite(tx) || !std::isfinite(ty)) |
| return; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kTranslate, tx, ty); |
| } |
| |
| TransformationMatrix new_transform = GetState().GetTransform(); |
| // clamp to float to avoid float cast overflow when used as SkScalar |
| float ftx = ClampTo<float>(tx); |
| float fty = ClampTo<float>(ty); |
| new_transform.Translate(ftx, fty); |
| if (GetState().GetTransform() == new_transform) |
| return; |
| |
| SetTransform(new_transform); |
| if (!IsTransformInvertible()) |
| return; |
| |
| c->translate(ftx, fty); |
| path_.Transform(AffineTransform().Translate(-ftx, -fty)); |
| } |
| |
| void BaseRenderingContext2D::transform(double m11, |
| double m12, |
| double m21, |
| double m22, |
| double dx, |
| double dy) { |
| // TODO(crbug.com/1140535) Investigate the performance implications of simply |
| // calling the 3d version above with: |
| // transform(m11, m12, 0, 0, m21, m22, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1); |
| cc::PaintCanvas* c = GetOrCreatePaintCanvas(); |
| if (!c) |
| return; |
| |
| if (!std::isfinite(m11) || !std::isfinite(m21) || !std::isfinite(dx) || |
| !std::isfinite(m12) || !std::isfinite(m22) || !std::isfinite(dy)) |
| return; |
| |
| // clamp to float to avoid float cast overflow when used as SkScalar |
| float fm11 = ClampTo<float>(m11); |
| float fm12 = ClampTo<float>(m12); |
| float fm21 = ClampTo<float>(m21); |
| float fm22 = ClampTo<float>(m22); |
| float fdx = ClampTo<float>(dx); |
| float fdy = ClampTo<float>(dy); |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kTransform, fm11, |
| fm12, fm21, fm22, fdx, fdy); |
| } |
| |
| TransformationMatrix transform(fm11, fm12, fm21, fm22, fdx, fdy); |
| TransformationMatrix new_transform = GetState().GetTransform() * transform; |
| if (GetState().GetTransform() == new_transform) |
| return; |
| |
| SetTransform(new_transform); |
| if (!IsTransformInvertible()) |
| return; |
| |
| c->concat(TransformationMatrix::ToSkM44(transform)); |
| path_.Transform(transform.Inverse()); |
| } |
| |
| void BaseRenderingContext2D::resetTransform() { |
| cc::PaintCanvas* c = GetOrCreatePaintCanvas(); |
| if (!c) |
| return; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kResetTransform); |
| } |
| |
| TransformationMatrix ctm = GetState().GetTransform(); |
| bool invertible_ctm = 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. |
| GetState().ResetTransform(); |
| SetIsTransformInvertible(true); |
| // Set the SkCanvas' matrix to identity. |
| c->setMatrix(SkM44()); |
| |
| 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) { |
| 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::setTransform(DOMMatrixInit* transform, |
| ExceptionState& exception_state) { |
| DOMMatrixReadOnly* m = |
| DOMMatrixReadOnly::fromMatrix(transform, exception_state); |
| |
| if (!m) |
| return; |
| |
| setTransform(m->m11(), m->m12(), m->m21(), m->m22(), m->m41(), m->m42()); |
| } |
| |
| DOMMatrix* BaseRenderingContext2D::getTransform() { |
| const TransformationMatrix& t = GetState().GetTransform(); |
| DOMMatrix* m = DOMMatrix::Create(); |
| m->setA(t.A()); |
| m->setB(t.B()); |
| m->setC(t.C()); |
| m->setD(t.D()); |
| m->setE(t.E()); |
| m->setF(t.F()); |
| return m; |
| } |
| |
| TransformationMatrix BaseRenderingContext2D::GetTransform() const { |
| return GetState().GetTransform(); |
| } |
| |
| void BaseRenderingContext2D::beginPath() { |
| path_.Clear(); |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kBeginPath); |
| } |
| } |
| |
| void BaseRenderingContext2D::DrawPathInternal( |
| const Path& path, |
| CanvasRenderingContext2DState::PaintType paint_type, |
| SkPathFillType fill_type, |
| UsePaintCache use_paint_cache) { |
| if (path.IsEmpty()) |
| return; |
| |
| SkPath sk_path = path.GetSkPath(); |
| FloatRect bounds(path.BoundingRect()); |
| if (std::isnan(bounds.x()) || std::isnan(bounds.y()) || |
| std::isnan(bounds.width()) || std::isnan(bounds.height())) |
| return; |
| sk_path.setFillType(fill_type); |
| |
| if (paint_type == CanvasRenderingContext2DState::kStrokePaintType) |
| InflateStrokeRect(bounds); |
| |
| if (!GetOrCreatePaintCanvas()) |
| return; |
| |
| Draw<OverdrawOp::kNone>( |
| [sk_path, use_paint_cache](cc::PaintCanvas* c, |
| const PaintFlags* flags) // draw lambda |
| { c->drawPath(sk_path, *flags, use_paint_cache); }, |
| [](const SkIRect& rect) // overdraw test lambda |
| { return false; }, |
| bounds, paint_type, |
| GetState().HasPattern(paint_type) |
| ? CanvasRenderingContext2DState::kNonOpaqueImage |
| : CanvasRenderingContext2DState::kNoImage, |
| CanvasPerformanceMonitor::DrawType::kPath); |
| } |
| |
| static SkPathFillType ParseWinding(const String& winding_rule_string) { |
| if (winding_rule_string == "nonzero") |
| return SkPathFillType::kWinding; |
| if (winding_rule_string == "evenodd") |
| return SkPathFillType::kEvenOdd; |
| |
| NOTREACHED(); |
| return SkPathFillType::kEvenOdd; |
| } |
| |
| void BaseRenderingContext2D::fill(const String& winding_rule_string) { |
| const SkPathFillType winding_rule = ParseWinding(winding_rule_string); |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kFill, winding_rule); |
| } |
| DrawPathInternal(path_, CanvasRenderingContext2DState::kFillPaintType, |
| winding_rule, UsePaintCache::kDisabled); |
| } |
| |
| void BaseRenderingContext2D::fill(Path2D* dom_path, |
| const String& winding_rule_string) { |
| const SkPathFillType winding_rule = ParseWinding(winding_rule_string); |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder( |
| CanvasOps::kFill__Path, dom_path->GetIdentifiableToken(), winding_rule); |
| } |
| DrawPathInternal(dom_path->GetPath(), |
| CanvasRenderingContext2DState::kFillPaintType, winding_rule, |
| UsePaintCache::kEnabled); |
| } |
| |
| void BaseRenderingContext2D::stroke() { |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kStroke); |
| } |
| DrawPathInternal(path_, CanvasRenderingContext2DState::kStrokePaintType, |
| SkPathFillType::kWinding, UsePaintCache::kDisabled); |
| } |
| |
| void BaseRenderingContext2D::stroke(Path2D* dom_path) { |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder( |
| CanvasOps::kStroke__Path, dom_path->GetIdentifiableToken()); |
| } |
| DrawPathInternal(dom_path->GetPath(), |
| CanvasRenderingContext2DState::kStrokePaintType, |
| SkPathFillType::kWinding, UsePaintCache::kEnabled); |
| } |
| |
| void BaseRenderingContext2D::fillRect(double x, |
| double y, |
| double width, |
| double height) { |
| if (!ValidateRectForCanvas(x, y, width, height)) |
| return; |
| |
| if (!GetOrCreatePaintCanvas()) |
| return; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kFillRect, x, y, |
| width, height); |
| } |
| |
| // clamp to float to avoid float cast overflow when used as SkScalar |
| AdjustRectForCanvas(x, y, width, height); |
| float fx = ClampTo<float>(x); |
| float fy = ClampTo<float>(y); |
| float fwidth = ClampTo<float>(width); |
| float fheight = ClampTo<float>(height); |
| |
| // We are assuming that if the pattern is not accelerated and the current |
| // canvas is accelerated, the texture of the pattern will not be able to be |
| // moved to the texture of the canvas receiving the pattern (because if the |
| // pattern was unaccelerated is because it was not possible to hold that image |
| // in an accelerated texture - that is, into the GPU). That's why we disable |
| // the acceleration to be sure that it will work. |
| if (IsAccelerated() && |
| GetState().HasPattern(CanvasRenderingContext2DState::kFillPaintType) && |
| !GetState().PatternIsAccelerated( |
| CanvasRenderingContext2DState::kFillPaintType)) { |
| DisableAcceleration(); |
| base::UmaHistogramEnumeration( |
| "Blink.Canvas.GPUFallbackToCPU", |
| GPUFallbackToCPUScenario::kLargePatternDrawnToGPU); |
| } |
| |
| SkRect rect = SkRect::MakeXYWH(fx, fy, fwidth, fheight); |
| Draw<OverdrawOp::kNone>( |
| [rect](cc::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, |
| GetState().HasPattern(CanvasRenderingContext2DState::kFillPaintType) |
| ? CanvasRenderingContext2DState::kNonOpaqueImage |
| : CanvasRenderingContext2DState::kNoImage, |
| CanvasPerformanceMonitor::DrawType::kRectangle); |
| } |
| |
| static void StrokeRectOnCanvas(const FloatRect& rect, |
| cc::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.right(), rect.bottom()); |
| 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 (!GetOrCreatePaintCanvas()) |
| return; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kStrokeRect, x, y, |
| width, height); |
| } |
| |
| // clamp to float to avoid float cast overflow when used as SkScalar |
| AdjustRectForCanvas(x, y, width, height); |
| float fx = ClampTo<float>(x); |
| float fy = ClampTo<float>(y); |
| float fwidth = ClampTo<float>(width); |
| float fheight = ClampTo<float>(height); |
| |
| SkRect rect = SkRect::MakeXYWH(fx, fy, fwidth, fheight); |
| FloatRect bounds = rect; |
| InflateStrokeRect(bounds); |
| |
| if (!ValidateRectForCanvas(bounds.x(), bounds.y(), bounds.width(), |
| bounds.height())) |
| return; |
| |
| Draw<OverdrawOp::kNone>( |
| [rect](cc::PaintCanvas* c, const PaintFlags* flags) // draw lambda |
| { StrokeRectOnCanvas(rect, c, flags); }, |
| kNoOverdraw, bounds, CanvasRenderingContext2DState::kStrokePaintType, |
| GetState().HasPattern(CanvasRenderingContext2DState::kStrokePaintType) |
| ? CanvasRenderingContext2DState::kNonOpaqueImage |
| : CanvasRenderingContext2DState::kNoImage, |
| CanvasPerformanceMonitor::DrawType::kRectangle); |
| } |
| |
| void BaseRenderingContext2D::ClipInternal(const Path& path, |
| const String& winding_rule_string, |
| UsePaintCache use_paint_cache) { |
| cc::PaintCanvas* c = GetOrCreatePaintCanvas(); |
| if (!c) { |
| return; |
| } |
| if (!IsTransformInvertible()) { |
| return; |
| } |
| |
| SkPath sk_path = path.GetSkPath(); |
| sk_path.setFillType(ParseWinding(winding_rule_string)); |
| GetState().ClipPath(sk_path, clip_antialiasing_); |
| c->clipPath(sk_path, SkClipOp::kIntersect, clip_antialiasing_ == kAntiAliased, |
| use_paint_cache); |
| } |
| |
| void BaseRenderingContext2D::clip(const String& winding_rule_string) { |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder( |
| CanvasOps::kClip, |
| IdentifiabilitySensitiveStringToken(winding_rule_string)); |
| } |
| ClipInternal(path_, winding_rule_string, UsePaintCache::kDisabled); |
| } |
| |
| void BaseRenderingContext2D::clip(Path2D* dom_path, |
| const String& winding_rule_string) { |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder( |
| CanvasOps::kClip__Path, dom_path->GetIdentifiableToken(), |
| IdentifiabilitySensitiveStringToken(winding_rule_string)); |
| } |
| ClipInternal(dom_path->GetPath(), winding_rule_string, |
| UsePaintCache::kEnabled); |
| } |
| |
| 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) { |
| cc::PaintCanvas* c = GetOrCreatePaintCanvas(); |
| if (!c) |
| return false; |
| if (!IsTransformInvertible()) |
| return false; |
| |
| if (!std::isfinite(x) || !std::isfinite(y)) |
| return false; |
| gfx::PointF point(ClampTo<float>(x), ClampTo<float>(y)); |
| TransformationMatrix ctm = GetState().GetTransform(); |
| gfx::PointF 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) { |
| cc::PaintCanvas* c = GetOrCreatePaintCanvas(); |
| if (!c) |
| return false; |
| if (!IsTransformInvertible()) |
| return false; |
| |
| if (!std::isfinite(x) || !std::isfinite(y)) |
| return false; |
| gfx::PointF point(ClampTo<float>(x), ClampTo<float>(y)); |
| AffineTransform ctm = GetState().GetAffineTransform(); |
| gfx::PointF 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, ctm); |
| } |
| |
| void BaseRenderingContext2D::clearRect(double x, |
| double y, |
| double width, |
| double height, |
| bool for_reset) { |
| if (!ValidateRectForCanvas(x, y, width, height)) |
| return; |
| |
| cc::PaintCanvas* c = GetOrCreatePaintCanvas(); |
| if (!c) |
| return; |
| if (!IsTransformInvertible()) |
| return; |
| |
| SkIRect clip_bounds; |
| if (!c->getDeviceClipBounds(&clip_bounds)) |
| return; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder(CanvasOps::kClearRect, x, y, |
| width, height); |
| } |
| |
| PaintFlags clear_flags; |
| clear_flags.setBlendMode(SkBlendMode::kClear); |
| clear_flags.setStyle(PaintFlags::kFill_Style); |
| |
| // clamp to float to avoid float cast overflow when used as SkScalar |
| AdjustRectForCanvas(x, y, width, height); |
| float fx = ClampTo<float>(x); |
| float fy = ClampTo<float>(y); |
| float fwidth = ClampTo<float>(width); |
| float fheight = ClampTo<float>(height); |
| |
| FloatRect rect(fx, fy, fwidth, fheight); |
| if (RectContainsTransformedRect(rect, clip_bounds)) { |
| if (for_reset) { |
| // In the reset case, we can use kUntransformedUnclippedFill because we |
| // know the state state was reset. |
| CheckOverdraw(rect, &clear_flags, CanvasRenderingContext2DState::kNoImage, |
| OverdrawOp::kContextReset); |
| } else { |
| CheckOverdraw(rect, &clear_flags, CanvasRenderingContext2DState::kNoImage, |
| OverdrawOp::kClearRect); |
| } |
| GetPaintCanvasForDraw(clip_bounds, |
| CanvasPerformanceMonitor::DrawType::kOther) |
| ->drawRect(rect, clear_flags); |
| } else { |
| SkIRect dirty_rect; |
| if (ComputeDirtyRect(rect, clip_bounds, &dirty_rect)) { |
| GetPaintCanvasForDraw(clip_bounds, |
| CanvasPerformanceMonitor::DrawType::kOther) |
| ->drawRect(rect, clear_flags); |
| } |
| } |
| } |
| |
| static inline FloatRect NormalizeRect(const FloatRect& rect) { |
| return FloatRect(std::min(rect.x(), rect.right()), |
| std::min(rect.y(), rect.bottom()), |
| 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->origin(); |
| scaled_src_location.Scale(scale.width(), scale.height()); |
| FloatSize offset = dst_rect->origin() - 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->Offset(offset); |
| } |
| |
| void BaseRenderingContext2D::drawImage(ScriptState* script_state, |
| const V8CanvasImageSource* image_source, |
| double x, |
| double y, |
| ExceptionState& exception_state) { |
| CanvasImageSource* image_source_internal = |
| ToCanvasImageSource(image_source, exception_state); |
| if (!image_source_internal) |
| return; |
| RespectImageOrientationEnum respect_orientation = |
| RespectImageOrientationInternal(image_source_internal); |
| FloatSize default_object_size(Width(), Height()); |
| FloatSize source_rect_size = image_source_internal->ElementSize( |
| default_object_size, respect_orientation); |
| FloatSize dest_rect_size = image_source_internal->DefaultDestinationSize( |
| default_object_size, respect_orientation); |
| 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 V8CanvasImageSource* image_source, |
| double x, |
| double y, |
| double width, |
| double height, |
| ExceptionState& exception_state) { |
| CanvasImageSource* image_source_internal = |
| ToCanvasImageSource(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, |
| RespectImageOrientationInternal(image_source_internal)); |
| 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 V8CanvasImageSource* 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 = |
| ToCanvasImageSource(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; |
| cc::PaintCanvas* c = GetPaintCanvas(); |
| 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; |
| } |
| |
| void BaseRenderingContext2D::DispatchContextLostEvent(TimerBase*) { |
| if (GetCanvasRenderingContextHost() && |
| RuntimeEnabledFeatures::NewCanvas2DAPIEnabled(GetTopExecutionContext())) { |
| Event* event = Event::CreateCancelable(event_type_names::kContextlost); |
| GetCanvasRenderingContextHost()->HostDispatchEvent(event); |
| |
| UseCounter::Count(GetTopExecutionContext(), |
| WebFeature::kCanvasRenderingContext2DContextLostEvent); |
| if (event->defaultPrevented()) { |
| context_restorable_ = false; |
| } |
| } |
| |
| if (context_restorable_ && |
| (context_lost_mode_ == CanvasRenderingContext::kRealLostContext || |
| context_lost_mode_ == CanvasRenderingContext::kSyntheticLostContext)) { |
| try_restore_context_attempt_count_ = 0; |
| try_restore_context_event_timer_.StartOneShot(kTryRestoreContextInterval, |
| FROM_HERE); |
| } |
| } |
| |
| void BaseRenderingContext2D::DispatchContextRestoredEvent(TimerBase*) { |
| // Since canvas may trigger contextlost event by multiple different ways (ex: |
| // gpu crashes and frame eviction), it's possible to triggeer this |
| // function while the context is already restored. In this case, we |
| // abort it here. |
| if (context_lost_mode_ == CanvasRenderingContext::kNotLostContext) |
| return; |
| ResetInternal(); |
| context_lost_mode_ = CanvasRenderingContext::kNotLostContext; |
| if (GetCanvasRenderingContextHost() && |
| RuntimeEnabledFeatures::NewCanvas2DAPIEnabled(GetTopExecutionContext())) { |
| Event* event(Event::Create(event_type_names::kContextrestored)); |
| GetCanvasRenderingContextHost()->HostDispatchEvent(event); |
| UseCounter::Count( |
| GetTopExecutionContext(), |
| WebFeature::kCanvasRenderingContext2DContextRestoredEvent); |
| } |
| } |
| |
| void BaseRenderingContext2D::DrawImageInternal( |
| cc::PaintCanvas* c, |
| CanvasImageSource* image_source, |
| Image* image, |
| const FloatRect& src_rect, |
| const FloatRect& dst_rect, |
| const SkSamplingOptions& sampling, |
| const PaintFlags* flags) { |
| cc::RecordPaintCanvas::DisableFlushCheckScope disable_flush_check_scope( |
| static_cast<cc::RecordPaintCanvas*>(c)); |
| 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; |
| } |
| SkRect bounds = dst_rect; |
| ctm.mapRect(&bounds); |
| if (!bounds.isFinite()) { |
| // There is an earlier check for the correctness of the bounds, but it is |
| // possible that after applying the matrix transformation we get a faulty |
| // set of bounds, so we want to catch this asap and avoid sending a draw |
| // command. crbug.com/1039125 |
| // We want to do this before the save command is sent. |
| return; |
| } |
| c->save(); |
| c->concat(inv_ctm); |
| |
| 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 (image_source->IsVideoElement()) { |
| 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(gfx::Point(), |
| IntSize(video->videoWidth(), video->videoHeight())), |
| &image_flags); |
| } else if (image_source->IsVideoFrame()) { |
| VideoFrame* frame = static_cast<VideoFrame*>(image_source); |
| auto media_frame = frame->frame(); |
| bool ignore_transformation = |
| RespectImageOrientationInternal(image_source) == |
| kDoNotRespectImageOrientation; |
| FloatRect corrected_src_rect = src_rect; |
| |
| if (!ignore_transformation) { |
| auto orientation_enum = VideoTransformationToImageOrientation( |
| media_frame->metadata().transformation.value_or( |
| media::kNoTransformation)); |
| if (ImageOrientation(orientation_enum).UsesWidthAsHeight()) |
| corrected_src_rect = src_rect.TransposedRect(); |
| } |
| |
| c->save(); |
| c->clipRect(dst_rect); |
| c->translate(dst_rect.x(), dst_rect.y()); |
| c->scale(dst_rect.width() / corrected_src_rect.width(), |
| dst_rect.height() / corrected_src_rect.height()); |
| c->translate(-corrected_src_rect.x(), -corrected_src_rect.y()); |
| DrawVideoFrameIntoCanvas(std::move(media_frame), c, image_flags, |
| ignore_transformation); |
| } else { |
| // We always use the image-orientation property on the canvas element |
| // because the alternative would result in complex rules depending on |
| // the source of the image. |
| RespectImageOrientationEnum respect_orientation = |
| RespectImageOrientationInternal(image_source); |
| FloatRect corrected_src_rect = src_rect; |
| if (respect_orientation == kRespectImageOrientation && |
| !image->HasDefaultOrientation()) { |
| corrected_src_rect = image->CorrectSrcRectForImageOrientation( |
| image->SizeAsFloat(kRespectImageOrientation), src_rect); |
| } |
| image_flags.setAntiAlias(ShouldDrawImageAntialiased(dst_rect)); |
| ImageDrawOptions draw_options; |
| draw_options.sampling_options = sampling; |
| draw_options.respect_orientation = respect_orientation; |
| draw_options.clamping_mode = Image::kDoNotClampImageToSourceRect; |
| image->Draw(c, image_flags, dst_rect, corrected_src_rect, draw_options); |
| } |
| |
| c->restoreToCount(initial_save_count); |
| } |
| |
| void BaseRenderingContext2D::SetOriginTaintedByContent() { |
| SetOriginTainted(); |
| origin_tainted_by_content_ = true; |
| for (auto& state : state_stack_) |
| state->ClearResolvedFilter(); |
| } |
| |
| 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 (!GetOrCreatePaintCanvas()) |
| return; |
| |
| scoped_refptr<Image> image; |
| FloatSize default_object_size(Width(), Height()); |
| SourceImageStatus source_image_status = kInvalidSourceImageStatus; |
| if (!image_source->IsVideoElement()) { |
| image = image_source->GetSourceImageForCanvas(&source_image_status, |
| default_object_size); |
| if (source_image_status == kUndecodableSourceImageStatus) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::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; |
| |
| // clamp to float to avoid float cast overflow when used as SkScalar |
| float fsx = ClampTo<float>(sx); |
| float fsy = ClampTo<float>(sy); |
| float fsw = ClampTo<float>(sw); |
| float fsh = ClampTo<float>(sh); |
| float fdx = ClampTo<float>(dx); |
| float fdy = ClampTo<float>(dy); |
| float fdw = ClampTo<float>(dw); |
| float fdh = ClampTo<float>(dh); |
| |
| FloatRect src_rect = NormalizeRect(FloatRect(fsx, fsy, fsw, fsh)); |
| FloatRect dst_rect = NormalizeRect(FloatRect(fdx, fdy, fdw, fdh)); |
| FloatSize image_size = image_source->ElementSize( |
| default_object_size, RespectImageOrientationInternal(image_source)); |
| |
| ClipRectsToImageRect(FloatRect(FloatPoint(), image_size), &src_rect, |
| &dst_rect); |
| |
| if (src_rect.IsEmpty()) |
| return; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder( |
| CanvasOps::kDrawImage, fsx, fsy, fsw, fsh, fdx, fdy, fdw, fdh, |
| image ? image->width() : 0, image ? image->height() : 0); |
| identifiability_study_helper_.set_encountered_partially_digested_image(); |
| } |
| |
| ValidateStateStack(); |
| |
| WillDrawImage(image_source); |
| |
| if (!origin_tainted_by_content_ && WouldTaintOrigin(image_source)) |
| SetOriginTaintedByContent(); |
| |
| Draw<OverdrawOp::kDrawImage>( |
| [this, image_source, image, src_rect, dst_rect]( |
| cc::PaintCanvas* c, const PaintFlags* flags) // draw lambda |
| { |
| SkSamplingOptions sampling = |
| PaintFlags::FilterQualityToSkSamplingOptions( |
| flags ? flags->getFilterQuality() |
| : cc::PaintFlags::FilterQuality::kNone); |
| DrawImageInternal(c, image_source, image.get(), src_rect, dst_rect, |
| sampling, 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, |
| CanvasPerformanceMonitor::DrawType::kImage); |
| } |
| |
| void BaseRenderingContext2D::ClearCanvasForSrcCompositeOp() { |
| FloatRect canvas_rect(0, 0, Width(), Height()); |
| cc::PaintCanvas* c = GetOrCreatePaintCanvas(); |
| 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().GetTransform().MapQuad(quad).ContainsQuad(transformed_quad); |
| } |
| |
| CanvasGradient* BaseRenderingContext2D::createLinearGradient(double x0, |
| double y0, |
| double x1, |
| double y1) { |
| if (!std::isfinite(x0) || !std::isfinite(y0) || !std::isfinite(x1) || |
| !std::isfinite(y1)) |
| return nullptr; |
| |
| // clamp to float to avoid float cast overflow |
| float fx0 = ClampTo<float>(x0); |
| float fy0 = ClampTo<float>(y0); |
| float fx1 = ClampTo<float>(x1); |
| float fy1 = ClampTo<float>(y1); |
| |
| auto* gradient = MakeGarbageCollected<CanvasGradient>(FloatPoint(fx0, fy0), |
| FloatPoint(fx1, fy1)); |
| gradient->SetExecutionContext( |
| identifiability_study_helper_.execution_context()); |
| 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( |
| DOMExceptionCode::kIndexSizeError, |
| String::Format("The %s provided is less than 0.", |
| r0 < 0 ? "r0" : "r1")); |
| return nullptr; |
| } |
| |
| if (!std::isfinite(x0) || !std::isfinite(y0) || !std::isfinite(r0) || |
| !std::isfinite(x1) || !std::isfinite(y1) || !std::isfinite(r1)) |
| return nullptr; |
| |
| // clamp to float to avoid float cast overflow |
| float fx0 = ClampTo<float>(x0); |
| float fy0 = ClampTo<float>(y0); |
| float fr0 = ClampTo<float>(r0); |
| float fx1 = ClampTo<float>(x1); |
| float fy1 = ClampTo<float>(y1); |
| float fr1 = ClampTo<float>(r1); |
| |
| auto* gradient = MakeGarbageCollected<CanvasGradient>( |
| FloatPoint(fx0, fy0), fr0, FloatPoint(fx1, fy1), fr1); |
| gradient->SetExecutionContext( |
| identifiability_study_helper_.execution_context()); |
| return gradient; |
| } |
| |
| CanvasGradient* BaseRenderingContext2D::createConicGradient(double startAngle, |
| double centerX, |
| double centerY) { |
| UseCounter::Count(GetTopExecutionContext(), |
| WebFeature::kCanvasRenderingContext2DConicGradient); |
| if (!std::isfinite(startAngle) || !std::isfinite(centerX) || |
| !std::isfinite(centerY)) |
| return nullptr; |
| // TODO(crbug.com/1234113): Instrument new canvas APIs. |
| identifiability_study_helper_.set_encountered_skipped_ops(); |
| |
| // clamp to float to avoid float cast overflow |
| float a = ClampTo<float>(startAngle); |
| float x = ClampTo<float>(centerX); |
| float y = ClampTo<float>(centerY); |
| |
| // convert |startAngle| from radians to degree and rotate 90 degree, so |
| // |startAngle| at 0 starts from x-axis. |
| a = Rad2deg(a) + 90; |
| |
| auto* gradient = MakeGarbageCollected<CanvasGradient>(a, FloatPoint(x, y)); |
| gradient->SetExecutionContext( |
| identifiability_study_helper_.execution_context()); |
| return gradient; |
| } |
| |
| CanvasPattern* BaseRenderingContext2D::createPattern( |
| ScriptState* script_state, |
| const V8CanvasImageSource* image_source, |
| const String& repetition_type, |
| ExceptionState& exception_state) { |
| CanvasImageSource* image_source_internal = |
| ToCanvasImageSource(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, default_object_size); |
| |
| switch (status) { |
| case kNormalSourceImageStatus: |
| break; |
| case kZeroSizeCanvasSourceImageStatus: |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidStateError, |
| String::Format("The canvas %s is 0.", |
| image_source |
| ->ElementSize(default_object_size, |
| RespectImageOrientationInternal( |
| image_source)) |
| .width() |
| ? "height" |
| : "width")); |
| return nullptr; |
| case kUndecodableSourceImageStatus: |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidStateError, |
| "Source image is in the 'broken' state."); |
| return nullptr; |
| case kInvalidSourceImageStatus: |
| image_for_rendering = BitmapImage::Create(); |
| break; |
| case kIncompleteSourceImageStatus: |
| return nullptr; |
| default: |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| if (!image_for_rendering) |
| return nullptr; |
| |
| bool origin_clean = !WouldTaintOrigin(image_source); |
| |
| auto* pattern = MakeGarbageCollected<CanvasPattern>( |
| std::move(image_for_rendering), repeat_mode, origin_clean); |
| pattern->SetExecutionContext( |
| identifiability_study_helper_.execution_context()); |
| return pattern; |
| } |
| |
| bool BaseRenderingContext2D::ComputeDirtyRect(const FloatRect& local_rect, |
| SkIRect* dirty_rect) { |
| SkIRect clip_bounds; |
| cc::PaintCanvas* paint_canvas = GetOrCreatePaintCanvas(); |
| if (!paint_canvas || !paint_canvas->getDeviceClipBounds(&clip_bounds)) |
| return false; |
| return ComputeDirtyRect(local_rect, clip_bounds, dirty_rect); |
| } |
| |
| ImageData* BaseRenderingContext2D::createImageData( |
| ImageData* image_data, |
| ExceptionState& exception_state) const { |
| ImageData::ValidateAndCreateParams params; |
| params.context_2d_error_mode = true; |
| return ImageData::ValidateAndCreate( |
| image_data->Size().width(), image_data->Size().height(), absl::nullopt, |
| image_data->getSettings(), params, exception_state); |
| } |
| |
| ImageData* BaseRenderingContext2D::createImageData( |
| int sw, |
| int sh, |
| ExceptionState& exception_state) const { |
| ImageData::ValidateAndCreateParams params; |
| params.context_2d_error_mode = true; |
| params.default_color_space = GetCanvas2DColorParams().ColorSpace(); |
| return ImageData::ValidateAndCreate(std::abs(sw), std::abs(sh), absl::nullopt, |
| /*settings=*/nullptr, params, |
| exception_state); |
| } |
| |
| ImageData* BaseRenderingContext2D::createImageData( |
| int sw, |
| int sh, |
| ImageDataSettings* image_data_settings, |
| ExceptionState& exception_state) const { |
| ImageData::ValidateAndCreateParams params; |
| params.context_2d_error_mode = true; |
| params.default_color_space = GetCanvas2DColorParams().ColorSpace(); |
| return ImageData::ValidateAndCreate(std::abs(sw), std::abs(sh), absl::nullopt, |
| image_data_settings, params, |
| exception_state); |
| } |
| |
| ImageData* BaseRenderingContext2D::getImageData( |
| int sx, |
| int sy, |
| int sw, |
| int sh, |
| ExceptionState& exception_state) { |
| return getImageDataInternal(sx, sy, sw, sh, /*image_data_settings=*/nullptr, |
| exception_state); |
| } |
| |
| ImageData* BaseRenderingContext2D::getImageData( |
| int sx, |
| int sy, |
| int sw, |
| int sh, |
| ImageDataSettings* image_data_settings, |
| ExceptionState& exception_state) { |
| return getImageDataInternal(sx, sy, sw, sh, image_data_settings, |
| exception_state); |
| } |
| |
| ImageData* BaseRenderingContext2D::getImageDataInternal( |
| int sx, |
| int sy, |
| int sw, |
| int sh, |
| ImageDataSettings* image_data_settings, |
| ExceptionState& exception_state) { |
| if (!base::CheckMul(sw, sh).IsValid<int>()) { |
| exception_state.ThrowRangeError("Out of memory at ImageData creation"); |
| return nullptr; |
| } |
| |
| if (!OriginClean()) { |
| exception_state.ThrowSecurityError( |
| "The canvas has been tainted by cross-origin data."); |
| } else if (!sw || !sh) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kIndexSizeError, |
| String::Format("The source %s is 0.", sw ? "height" : "width")); |
| } |
| |
| if (exception_state.HadException()) |
| return nullptr; |
| |
| if (sw < 0) { |
| if (!base::CheckAdd(sx, sw).IsValid<int>()) { |
| exception_state.ThrowRangeError("Out of memory at ImageData creation"); |
| return nullptr; |
| } |
| sx += sw; |
| sw = base::saturated_cast<int>(base::SafeUnsignedAbs(sw)); |
| } |
| if (sh < 0) { |
| if (!base::CheckAdd(sy, sh).IsValid<int>()) { |
| exception_state.ThrowRangeError("Out of memory at ImageData creation"); |
| return nullptr; |
| } |
| sy += sh; |
| sh = base::saturated_cast<int>(base::SafeUnsignedAbs(sh)); |
| } |
| |
| if (!base::CheckAdd(sx, sw).IsValid<int>() || |
| !base::CheckAdd(sy, sh).IsValid<int>()) { |
| exception_state.ThrowRangeError("Out of memory at ImageData creation"); |
| return nullptr; |
| } |
| |
| const IntRect image_data_rect(sx, sy, sw, sh); |
| |
| ImageData::ValidateAndCreateParams validate_and_create_params; |
| validate_and_create_params.context_2d_error_mode = true; |
| validate_and_create_params.default_color_space = |
| GetCanvas2DColorParams().ColorSpace(); |
| |
| if (!CanCreateCanvas2dResourceProvider() || isContextLost()) { |
| return ImageData::ValidateAndCreate( |
| sw, sh, absl::nullopt, image_data_settings, validate_and_create_params, |
| exception_state); |
| } |
| |
| // Deferred offscreen canvases might have recorded commands, make sure |
| // that those get drawn here |
| FinalizeFrame(); |
| |
| // TODO(crbug.com/1101055): Remove the check for NewCanvas2DAPI flag once |
| // released. |
| // TODO(crbug.com/1090180): New Canvas2D API utilizes willReadFrequently |
| // attribute that let the users indicate if a canvas will be read frequently |
| // through getImageData, thus uses CPU rendering from the start in such cases. |
| if (GetCanvasRenderingContextHost() && |
| !RuntimeEnabledFeatures::NewCanvas2DAPIEnabled( |
| GetTopExecutionContext())) { |
| // GetImagedata is faster in Unaccelerated canvases. |
| // In Desynchronized canvas disabling the acceleration will break |
| // putImageData: crbug.com/1112060. |
| if (IsAccelerated() && !IsDesynchronized()) { |
| DisableAcceleration(); |
| base::UmaHistogramEnumeration("Blink.Canvas.GPUFallbackToCPU", |
| GPUFallbackToCPUScenario::kGetImageData); |
| } |
| } |
| |
| scoped_refptr<StaticBitmapImage> snapshot = GetImage(); |
| |
| // Determine if the array should be zero initialized, or if it will be |
| // completely overwritten. |
| validate_and_create_params.zero_initialize = false; |
| if (IsAccelerated()) { |
| // GPU readback may fail silently. |
| validate_and_create_params.zero_initialize = true; |
| } else if (snapshot) { |
| // Zero-initialize if some of the readback area is out of bounds. |
| if (image_data_rect.x() < 0 || image_data_rect.y() < 0 || |
| image_data_rect.right() > snapshot->Size().width() || |
| image_data_rect.bottom() > snapshot->Size().height()) { |
| validate_and_create_params.zero_initialize = true; |
| } |
| } |
| |
| ImageData* image_data = |
| ImageData::ValidateAndCreate(sw, sh, absl::nullopt, image_data_settings, |
| validate_and_create_params, exception_state); |
| if (!image_data) |
| return nullptr; |
| |
| // Read pixels into |image_data|. |
| if (snapshot) { |
| SkPixmap image_data_pixmap = image_data->GetSkPixmap(); |
| const bool read_pixels_successful = |
| snapshot->PaintImageForCurrentFrame().readPixels( |
| image_data_pixmap.info(), image_data_pixmap.writable_addr(), |
| image_data_pixmap.rowBytes(), sx, sy); |
| if (!read_pixels_successful) { |
| SkIRect bounds = |
| snapshot->PaintImageForCurrentFrame().GetSkImageInfo().bounds(); |
| DCHECK(!bounds.intersect(SkIRect::MakeXYWH(sx, sy, sw, sh))); |
| } |
| } |
| |
| return image_data; |
| } |
| |
| 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 (!base::CheckMul(dirty_width, dirty_height).IsValid<int>()) { |
| return; |
| } |
| |
| if (data->IsBufferBaseDetached()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "The source data has been detached."); |
| return; |
| } |
| |
| bool hasResourceProvider = CanCreateCanvas2dResourceProvider(); |
| if (!hasResourceProvider) |
| return; |
| |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder( |
| CanvasOps::kPutImageData, data->width(), data->height(), |
| data->GetCanvasColorSpace(), data->GetImageDataStorageFormat(), dx, dy, |
| dirty_x, dirty_y, dirty_width, dirty_height); |
| identifiability_study_helper_.set_encountered_partially_digested_image(); |
| } |
| |
| if (dirty_width < 0) { |
| if (dirty_x < 0) { |
| dirty_x = dirty_width = 0; |
| } else { |
| dirty_x += dirty_width; |
| dirty_width = |
| base::saturated_cast<int>(base::SafeUnsignedAbs(dirty_width)); |
| } |
| } |
| |
| if (dirty_height < 0) { |
| if (dirty_y < 0) { |
| dirty_y = dirty_height = 0; |
| } else { |
| dirty_y += dirty_height; |
| dirty_height = |
| base::saturated_cast<int>(base::SafeUnsignedAbs(dirty_height)); |
| } |
| } |
| |
| gfx::Rect dest_rect(dirty_x, dirty_y, dirty_width, dirty_height); |
| dest_rect.Intersect(gfx::Rect(0, 0, data->width(), data->height())); |
| gfx::Vector2d dest_offset(static_cast<int>(dx), static_cast<int>(dy)); |
| dest_rect.Offset(dest_offset); |
| dest_rect.Intersect(gfx::Rect(0, 0, Width(), Height())); |
| if (dest_rect.IsEmpty()) |
| return; |
| |
| gfx::Rect source_rect = dest_rect; |
| source_rect.Offset(-dest_offset); |
| |
| // 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 uint8 pixel storage format. We use RGBA pixel |
| // order for both ImageData and CanvasResourceProvider, therefore no |
| // additional swizzling is needed. |
| SkPixmap data_pixmap = data->GetSkPixmap(); |
| CanvasColorParams data_color_params( |
| data->GetCanvasColorSpace(), |
| data->GetImageDataStorageFormat() != kUint8ClampedArrayStorageFormat |
| ? CanvasPixelFormat::kF16 |
| : CanvasPixelFormat::kUint8, |
| kNonOpaque); |
| if (data_color_params.ColorSpace() != GetCanvas2DColorParams().ColorSpace() || |
| data_color_params.PixelFormat() != |
| GetCanvas2DColorParams().PixelFormat() || |
| GetCanvas2DColorParams().PixelFormat() == CanvasPixelFormat::kF16) { |
| SkImageInfo converted_info = data_pixmap.info(); |
| converted_info = |
| converted_info.makeColorType(GetCanvas2DColorParams().GetSkColorType()); |
| converted_info = converted_info.makeColorSpace( |
| GetCanvas2DColorParams().GetSkColorSpace()); |
| if (converted_info.colorType() == kN32_SkColorType) |
| converted_info = converted_info.makeColorType(kRGBA_8888_SkColorType); |
| |
| const size_t converted_data_bytes = converted_info.computeMinByteSize(); |
| const size_t converted_row_bytes = converted_info.minRowBytes(); |
| if (SkImageInfo::ByteSizeOverflowed(converted_data_bytes)) |
| return; |
| std::unique_ptr<uint8_t[]> converted_pixels( |
| new uint8_t[converted_data_bytes]); |
| if (data_pixmap.readPixels(converted_info, converted_pixels.get(), |
| converted_row_bytes)) { |
| PutByteArray( |
| SkPixmap(converted_info, converted_pixels.get(), converted_row_bytes), |
| source_rect, dest_offset); |
| } |
| } else { |
| PutByteArray(data_pixmap, source_rect, dest_offset); |
| } |
| |
| GetPaintCanvasForDraw(IntRect(dest_rect), |
| CanvasPerformanceMonitor::DrawType::kImageData); |
| } |
| |
| void BaseRenderingContext2D::PutByteArray(const SkPixmap& source, |
| const gfx::Rect& source_rect, |
| const gfx::Vector2d& dest_offset) { |
| if (!IsCanvas2DBufferValid()) |
| return; |
| |
| DCHECK(gfx::Rect(source.width(), source.height()).Contains(source_rect)); |
| int dest_x = dest_offset.x() + source_rect.x(); |
| DCHECK_GE(dest_x, 0); |
| DCHECK_LT(dest_x, Width()); |
| int dest_y = dest_offset.y() + source_rect.y(); |
| DCHECK_GE(dest_y, 0); |
| DCHECK_LT(dest_y, Height()); |
| |
| SkImageInfo info = |
| source.info().makeWH(source_rect.width(), source_rect.height()); |
| if (kOpaque == GetCanvas2DColorParams().GetOpacityMode()) { |
| // If the surface is opaque, tell it that we are writing opaque |
| // pixels. Writing non-opaque pixels to opaque is undefined in |
| // Skia. There is some discussion about whether it should be |
| // defined in skbug.com/6157. For now, we can get the desired |
| // behavior (memcpy) by pretending the write is opaque. |
| info = info.makeAlphaType(kOpaque_SkAlphaType); |
| } else { |
| info = info.makeAlphaType(kUnpremul_SkAlphaType); |
| } |
| if (info.colorType() == kN32_SkColorType) |
| info = info.makeColorType(kRGBA_8888_SkColorType); |
| |
| WritePixels(info, source.addr(source_rect.x(), source_rect.y()), |
| source.rowBytes(), dest_x, dest_y); |
| } |
| |
| 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.Outset(ClampTo<float>(delta)); |
| } |
| |
| bool BaseRenderingContext2D::imageSmoothingEnabled() const { |
| return GetState().ImageSmoothingEnabled(); |
| } |
| |
| void BaseRenderingContext2D::setImageSmoothingEnabled(bool enabled) { |
| if (enabled == GetState().ImageSmoothingEnabled()) |
| return; |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder( |
| CanvasOps::kSetImageSmoothingEnabled, enabled); |
| } |
| |
| GetState().SetImageSmoothingEnabled(enabled); |
| } |
| |
| String BaseRenderingContext2D::imageSmoothingQuality() const { |
| return GetState().ImageSmoothingQuality(); |
| } |
| |
| void BaseRenderingContext2D::setImageSmoothingQuality(const String& quality) { |
| if (quality == GetState().ImageSmoothingQuality()) |
| return; |
| |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder( |
| CanvasOps::kSetImageSmoothingQuality, |
| IdentifiabilitySensitiveStringToken(quality)); |
| } |
| GetState().SetImageSmoothingQuality(quality); |
| } |
| |
| double BaseRenderingContext2D::letterSpacing() const { |
| return GetState().GetLetterSpacing(); |
| } |
| |
| double BaseRenderingContext2D::wordSpacing() const { |
| return GetState().GetWordSpacing(); |
| } |
| |
| String BaseRenderingContext2D::textRendering() const { |
| return ToStringForIdl(GetState().GetTextRendering()); |
| } |
| |
| float BaseRenderingContext2D::GetFontBaseline( |
| const SimpleFontData& font_data) const { |
| return TextMetrics::GetFontBaseline(GetState().GetTextBaseline(), font_data); |
| } |
| |
| String BaseRenderingContext2D::textAlign() const { |
| return TextAlignName(GetState().GetTextAlign()); |
| } |
| |
| void BaseRenderingContext2D::setTextAlign(const String& s) { |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder( |
| CanvasOps::kSetTextAlign, IdentifiabilityBenignStringToken(s)); |
| } |
| TextAlign align; |
| if (!ParseTextAlign(s, align)) |
| return; |
| if (GetState().GetTextAlign() == align) |
| return; |
| GetState().SetTextAlign(align); |
| } |
| |
| String BaseRenderingContext2D::textBaseline() const { |
| return TextBaselineName(GetState().GetTextBaseline()); |
| } |
| |
| void BaseRenderingContext2D::setTextBaseline(const String& s) { |
| if (identifiability_study_helper_.ShouldUpdateBuilder()) { |
| identifiability_study_helper_.UpdateBuilder( |
| CanvasOps::kSetTextBaseline, IdentifiabilityBenignStringToken(s)); |
| } |
| TextBaseline baseline; |
| if (!ParseTextBaseline(s, baseline)) |
| return; |
| if (GetState().GetTextBaseline() == baseline) |
| return; |
| GetState().SetTextBaseline(baseline); |
| } |
| |
| String BaseRenderingContext2D::fontKerning() const { |
| return FontDescription::ToString(GetState().GetFontKerning()).LowerASCII(); |
| } |
| |
| String BaseRenderingContext2D::fontStretch() const { |
| return FontDescription::ToString(GetState().GetFontStretch()).LowerASCII(); |
| } |
| |
| String BaseRenderingContext2D::fontVariantCaps() const { |
| return FontDescription::ToStringForIdl(GetState().GetFontVariantCaps()); |
| } |
| |
| void BaseRenderingContext2D::Trace(Visitor* visitor) const { |
| visitor->Trace(state_stack_); |
| visitor->Trace(dispatch_context_lost_event_timer_); |
| visitor->Trace(dispatch_context_restored_event_timer_); |
| visitor->Trace(try_restore_context_event_timer_); |
| CanvasPath::Trace(visitor); |
| } |
| |
| 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 { |
| |
| void CanvasOverdrawHistogram(BaseRenderingContext2D::OverdrawOp op) { |
| UMA_HISTOGRAM_ENUMERATION("Blink.Canvas.OverdrawOp", op); |
| } |
| |
| } // unnamed namespace |
| |
| void BaseRenderingContext2D::WillOverwriteCanvas( |
| BaseRenderingContext2D::OverdrawOp op) { |
| auto* host = GetCanvasRenderingContextHost(); |
| if (host) { // CSS paint use cases not counted. |
| UseCounter::Count(GetTopExecutionContext(), |
| WebFeature::kCanvasRenderingContext2DHasOverdraw); |
| CanvasOverdrawHistogram(op); |
| CanvasOverdrawHistogram(OverdrawOp::kTotal); |
| } |
| |
| // We only hit the kHasTransform bucket if the op is affected by transforms. |
| if (op == OverdrawOp::kClearRect || op == OverdrawOp::kDrawImage) { |
| bool has_clip = GetState().HasClip(); |
| bool has_transform = !GetState().GetTransform().IsIdentity(); |
| if (has_clip && has_transform) { |
| CanvasOverdrawHistogram(OverdrawOp::kHasClipAndTransform); |
| } |
| if (has_clip) { |
| CanvasOverdrawHistogram(OverdrawOp::kHasClip); |
| } |
| if (has_transform) { |
| CanvasOverdrawHistogram(OverdrawOp::kHasTransform); |
| } |
| } |
| |
| WillOverwriteCanvas(); |
| } |
| |
| } // namespace blink |