| // Copyright 2015 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/CanvasRenderingContext2DState.h" |
| |
| #include <memory> |
| #include "core/css/resolver/FilterOperationResolver.h" |
| #include "core/css/resolver/StyleBuilder.h" |
| #include "core/css/resolver/StyleResolverState.h" |
| #include "core/html/HTMLCanvasElement.h" |
| #include "core/paint/FilterEffectBuilder.h" |
| #include "core/style/ComputedStyle.h" |
| #include "core/style/FilterOperation.h" |
| #include "core/svg/SVGFilterElement.h" |
| #include "modules/canvas/canvas2d/CanvasGradient.h" |
| #include "modules/canvas/canvas2d/CanvasPattern.h" |
| #include "modules/canvas/canvas2d/CanvasRenderingContext2D.h" |
| #include "modules/canvas/canvas2d/CanvasStyle.h" |
| #include "platform/fonts/FontSelector.h" |
| #include "platform/graphics/DrawLooperBuilder.h" |
| #include "platform/graphics/filters/FilterEffect.h" |
| #include "platform/graphics/filters/PaintFilterBuilder.h" |
| #include "platform/graphics/paint/PaintCanvas.h" |
| #include "platform/graphics/paint/PaintFlags.h" |
| #include "platform/graphics/skia/SkiaUtils.h" |
| #include "third_party/skia/include/effects/SkDashPathEffect.h" |
| #include "third_party/skia/include/effects/SkDropShadowImageFilter.h" |
| |
| static const char defaultFont[] = "10px sans-serif"; |
| static const char defaultFilter[] = "none"; |
| |
| namespace blink { |
| |
| CanvasRenderingContext2DState::CanvasRenderingContext2DState() |
| : unrealized_save_count_(0), |
| stroke_style_(CanvasStyle::CreateFromRGBA(SK_ColorBLACK)), |
| fill_style_(CanvasStyle::CreateFromRGBA(SK_ColorBLACK)), |
| shadow_blur_(0), |
| shadow_color_(Color::kTransparent), |
| global_alpha_(1), |
| line_dash_offset_(0), |
| unparsed_font_(defaultFont), |
| unparsed_filter_(defaultFilter), |
| text_align_(kStartTextAlign), |
| text_baseline_(kAlphabeticTextBaseline), |
| direction_(kDirectionInherit), |
| realized_font_(false), |
| is_transform_invertible_(true), |
| has_clip_(false), |
| has_complex_clip_(false), |
| fill_style_dirty_(true), |
| stroke_style_dirty_(true), |
| line_dash_dirty_(false), |
| image_smoothing_quality_(kLow_SkFilterQuality) { |
| fill_flags_.setStyle(PaintFlags::kFill_Style); |
| fill_flags_.setAntiAlias(true); |
| image_flags_.setStyle(PaintFlags::kFill_Style); |
| image_flags_.setAntiAlias(true); |
| stroke_flags_.setStyle(PaintFlags::kStroke_Style); |
| stroke_flags_.setStrokeWidth(1); |
| stroke_flags_.setStrokeCap(PaintFlags::kButt_Cap); |
| stroke_flags_.setStrokeMiter(10); |
| stroke_flags_.setStrokeJoin(PaintFlags::kMiter_Join); |
| stroke_flags_.setAntiAlias(true); |
| SetImageSmoothingEnabled(true); |
| } |
| |
| CanvasRenderingContext2DState::CanvasRenderingContext2DState( |
| const CanvasRenderingContext2DState& other, |
| ClipListCopyMode mode) |
| : unrealized_save_count_(other.unrealized_save_count_), |
| unparsed_stroke_color_(other.unparsed_stroke_color_), |
| unparsed_fill_color_(other.unparsed_fill_color_), |
| stroke_style_(other.stroke_style_), |
| fill_style_(other.fill_style_), |
| stroke_flags_(other.stroke_flags_), |
| fill_flags_(other.fill_flags_), |
| image_flags_(other.image_flags_), |
| shadow_offset_(other.shadow_offset_), |
| shadow_blur_(other.shadow_blur_), |
| shadow_color_(other.shadow_color_), |
| empty_draw_looper_(other.empty_draw_looper_), |
| shadow_only_draw_looper_(other.shadow_only_draw_looper_), |
| shadow_and_foreground_draw_looper_( |
| other.shadow_and_foreground_draw_looper_), |
| shadow_only_image_filter_(other.shadow_only_image_filter_), |
| shadow_and_foreground_image_filter_( |
| other.shadow_and_foreground_image_filter_), |
| global_alpha_(other.global_alpha_), |
| transform_(other.transform_), |
| line_dash_(other.line_dash_), |
| line_dash_offset_(other.line_dash_offset_), |
| unparsed_font_(other.unparsed_font_), |
| font_(other.font_), |
| font_for_filter_(other.font_for_filter_), |
| unparsed_filter_(other.unparsed_filter_), |
| filter_value_(other.filter_value_), |
| resolved_filter_(other.resolved_filter_), |
| text_align_(other.text_align_), |
| text_baseline_(other.text_baseline_), |
| direction_(other.direction_), |
| realized_font_(other.realized_font_), |
| is_transform_invertible_(other.is_transform_invertible_), |
| has_clip_(other.has_clip_), |
| has_complex_clip_(other.has_complex_clip_), |
| fill_style_dirty_(other.fill_style_dirty_), |
| stroke_style_dirty_(other.stroke_style_dirty_), |
| line_dash_dirty_(other.line_dash_dirty_), |
| image_smoothing_enabled_(other.image_smoothing_enabled_), |
| image_smoothing_quality_(other.image_smoothing_quality_) { |
| if (mode == kCopyClipList) { |
| clip_list_ = other.clip_list_; |
| } |
| if (realized_font_) |
| font_.GetFontSelector()->RegisterForInvalidationCallbacks(this); |
| } |
| |
| CanvasRenderingContext2DState::~CanvasRenderingContext2DState() {} |
| |
| void CanvasRenderingContext2DState::FontsNeedUpdate( |
| FontSelector* font_selector) { |
| DCHECK_EQ(font_selector, font_.GetFontSelector()); |
| DCHECK(realized_font_); |
| |
| font_.Update(font_selector); |
| // FIXME: We only really need to invalidate the resolved filter if the font |
| // update above changed anything and the filter uses font-dependent units. |
| resolved_filter_.reset(); |
| } |
| |
| void CanvasRenderingContext2DState::Trace(blink::Visitor* visitor) { |
| visitor->Trace(stroke_style_); |
| visitor->Trace(fill_style_); |
| visitor->Trace(filter_value_); |
| FontSelectorClient::Trace(visitor); |
| } |
| |
| void CanvasRenderingContext2DState::SetLineDashOffset(double offset) { |
| line_dash_offset_ = offset; |
| line_dash_dirty_ = true; |
| } |
| |
| void CanvasRenderingContext2DState::SetLineDash(const Vector<double>& dash) { |
| line_dash_ = dash; |
| // Spec requires the concatenation of two copies the dash list when the |
| // number of elements is odd |
| if (dash.size() % 2) |
| line_dash_.AppendVector(dash); |
| |
| line_dash_dirty_ = true; |
| } |
| |
| static bool HasANonZeroElement(const Vector<double>& line_dash) { |
| for (size_t i = 0; i < line_dash.size(); i++) { |
| if (line_dash[i] != 0.0) |
| return true; |
| } |
| return false; |
| } |
| |
| void CanvasRenderingContext2DState::UpdateLineDash() const { |
| if (!line_dash_dirty_) |
| return; |
| |
| if (!HasANonZeroElement(line_dash_)) { |
| stroke_flags_.setPathEffect(nullptr); |
| } else { |
| Vector<float> line_dash(line_dash_.size()); |
| std::copy(line_dash_.begin(), line_dash_.end(), line_dash.begin()); |
| stroke_flags_.setPathEffect(SkDashPathEffect::Make( |
| line_dash.data(), line_dash.size(), line_dash_offset_)); |
| } |
| |
| line_dash_dirty_ = false; |
| } |
| |
| void CanvasRenderingContext2DState::SetStrokeStyle(CanvasStyle* style) { |
| stroke_style_ = style; |
| stroke_style_dirty_ = true; |
| } |
| |
| void CanvasRenderingContext2DState::SetFillStyle(CanvasStyle* style) { |
| fill_style_ = style; |
| fill_style_dirty_ = true; |
| } |
| |
| void CanvasRenderingContext2DState::UpdateStrokeStyle() const { |
| if (!stroke_style_dirty_) |
| return; |
| |
| int clamped_alpha = ClampedAlphaForBlending(global_alpha_); |
| DCHECK(stroke_style_); |
| stroke_style_->ApplyToFlags(stroke_flags_); |
| stroke_flags_.setColor( |
| ScaleAlpha(stroke_style_->PaintColor(), clamped_alpha)); |
| stroke_style_dirty_ = false; |
| } |
| |
| void CanvasRenderingContext2DState::UpdateFillStyle() const { |
| if (!fill_style_dirty_) |
| return; |
| |
| int clamped_alpha = ClampedAlphaForBlending(global_alpha_); |
| DCHECK(fill_style_); |
| fill_style_->ApplyToFlags(fill_flags_); |
| fill_flags_.setColor(ScaleAlpha(fill_style_->PaintColor(), clamped_alpha)); |
| fill_style_dirty_ = false; |
| } |
| |
| CanvasStyle* CanvasRenderingContext2DState::Style(PaintType paint_type) const { |
| switch (paint_type) { |
| case kFillPaintType: |
| return FillStyle(); |
| case kStrokePaintType: |
| return StrokeStyle(); |
| case kImagePaintType: |
| return nullptr; |
| } |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| void CanvasRenderingContext2DState::SetShouldAntialias(bool should_antialias) { |
| fill_flags_.setAntiAlias(should_antialias); |
| stroke_flags_.setAntiAlias(should_antialias); |
| image_flags_.setAntiAlias(should_antialias); |
| } |
| |
| bool CanvasRenderingContext2DState::ShouldAntialias() const { |
| DCHECK(fill_flags_.isAntiAlias() == stroke_flags_.isAntiAlias() && |
| fill_flags_.isAntiAlias() == image_flags_.isAntiAlias()); |
| return fill_flags_.isAntiAlias(); |
| } |
| |
| void CanvasRenderingContext2DState::SetGlobalAlpha(double alpha) { |
| global_alpha_ = alpha; |
| stroke_style_dirty_ = true; |
| fill_style_dirty_ = true; |
| int image_alpha = ClampedAlphaForBlending(alpha); |
| image_flags_.setAlpha(image_alpha > 255 ? 255 : image_alpha); |
| } |
| |
| void CanvasRenderingContext2DState::ClipPath( |
| const SkPath& path, |
| AntiAliasingMode anti_aliasing_mode) { |
| clip_list_.ClipPath(path, anti_aliasing_mode, |
| AffineTransformToSkMatrix(transform_)); |
| has_clip_ = true; |
| if (!path.isRect(nullptr)) |
| has_complex_clip_ = true; |
| } |
| |
| void CanvasRenderingContext2DState::SetFont(const Font& font, |
| FontSelector* selector) { |
| font_ = font; |
| font_.Update(selector); |
| realized_font_ = true; |
| if (selector) |
| selector->RegisterForInvalidationCallbacks(this); |
| } |
| |
| const Font& CanvasRenderingContext2DState::GetFont() const { |
| DCHECK(realized_font_); |
| return font_; |
| } |
| |
| void CanvasRenderingContext2DState::SetTransform( |
| const AffineTransform& transform) { |
| is_transform_invertible_ = transform.IsInvertible(); |
| transform_ = transform; |
| } |
| |
| void CanvasRenderingContext2DState::ResetTransform() { |
| transform_.MakeIdentity(); |
| is_transform_invertible_ = true; |
| } |
| |
| sk_sp<PaintFilter> CanvasRenderingContext2DState::GetFilterForOffscreenCanvas( |
| IntSize canvas_size, |
| BaseRenderingContext2D* context) const { |
| if (!filter_value_) |
| return nullptr; |
| |
| if (resolved_filter_) |
| return resolved_filter_; |
| |
| FilterOperations operations = |
| FilterOperationResolver::CreateOffscreenFilterOperations(*filter_value_); |
| |
| // We can't reuse m_fillFlags and m_strokeFlags for the filter, since these |
| // incorporate the global alpha, which isn't applicable here. |
| PaintFlags fill_flags_for_filter; |
| fill_style_->ApplyToFlags(fill_flags_for_filter); |
| fill_flags_for_filter.setColor(fill_style_->PaintColor()); |
| PaintFlags stroke_flags_for_filter; |
| stroke_style_->ApplyToFlags(stroke_flags_for_filter); |
| stroke_flags_for_filter.setColor(stroke_style_->PaintColor()); |
| |
| FilterEffectBuilder filter_effect_builder( |
| FloatRect((FloatPoint()), FloatSize(canvas_size)), |
| 1.0f, // Deliberately ignore zoom on the canvas element. |
| &fill_flags_for_filter, &stroke_flags_for_filter); |
| |
| FilterEffect* last_effect = filter_effect_builder.BuildFilterEffect( |
| operations, !context->OriginClean()); |
| if (last_effect) { |
| // TODO(chrishtr): Taint the origin if needed. crbug.com/792506. |
| resolved_filter_ = |
| PaintFilterBuilder::Build(last_effect, kInterpolationSpaceSRGB); |
| } |
| |
| return resolved_filter_; |
| } |
| |
| sk_sp<PaintFilter> CanvasRenderingContext2DState::GetFilter( |
| Element* style_resolution_host, |
| IntSize canvas_size, |
| CanvasRenderingContext2D* context) const { |
| if (!filter_value_) |
| return nullptr; |
| |
| // StyleResolverState cannot be used in frame-less documents. |
| if (!style_resolution_host->GetDocument().GetFrame()) |
| return nullptr; |
| |
| if (!resolved_filter_) { |
| // Update the filter value to the proper base URL if needed. |
| if (filter_value_->MayContainUrl()) |
| filter_value_->ReResolveUrl(style_resolution_host->GetDocument()); |
| |
| scoped_refptr<ComputedStyle> filter_style = ComputedStyle::Create(); |
| // Must set font in case the filter uses any font-relative units (em, ex) |
| filter_style->SetFont(font_for_filter_); |
| |
| StyleResolverState resolver_state(style_resolution_host->GetDocument(), |
| style_resolution_host, filter_style.get(), |
| filter_style.get()); |
| resolver_state.SetStyle(filter_style); |
| |
| StyleBuilder::ApplyProperty(GetCSSPropertyFilter(), resolver_state, |
| *filter_value_); |
| resolver_state.LoadPendingResources(); |
| |
| // We can't reuse m_fillFlags and m_strokeFlags for the filter, since these |
| // incorporate the global alpha, which isn't applicable here. |
| PaintFlags fill_flags_for_filter; |
| fill_style_->ApplyToFlags(fill_flags_for_filter); |
| fill_flags_for_filter.setColor(fill_style_->PaintColor()); |
| PaintFlags stroke_flags_for_filter; |
| stroke_style_->ApplyToFlags(stroke_flags_for_filter); |
| stroke_flags_for_filter.setColor(stroke_style_->PaintColor()); |
| |
| FilterEffectBuilder filter_effect_builder( |
| style_resolution_host, |
| FloatRect((FloatPoint()), FloatSize(canvas_size)), |
| 1.0f, // Deliberately ignore zoom on the canvas element. |
| &fill_flags_for_filter, &stroke_flags_for_filter); |
| |
| if (FilterEffect* last_effect = filter_effect_builder.BuildFilterEffect( |
| filter_style->Filter(), !context->OriginClean())) { |
| resolved_filter_ = |
| PaintFilterBuilder::Build(last_effect, kInterpolationSpaceSRGB); |
| if (resolved_filter_) { |
| context->UpdateFilterReferences(filter_style->Filter()); |
| if (last_effect->OriginTainted()) |
| context->SetOriginTainted(); |
| } |
| } |
| } |
| |
| return resolved_filter_; |
| } |
| |
| bool CanvasRenderingContext2DState::HasFilterForOffscreenCanvas( |
| IntSize canvas_size, |
| BaseRenderingContext2D* context) const { |
| // Checking for a non-null m_filterValue isn't sufficient, since this value |
| // might refer to a non-existent filter. |
| return !!GetFilterForOffscreenCanvas(canvas_size, context); |
| } |
| |
| bool CanvasRenderingContext2DState::HasFilter( |
| Element* style_resolution_host, |
| IntSize canvas_size, |
| CanvasRenderingContext2D* context) const { |
| // Checking for a non-null m_filterValue isn't sufficient, since this value |
| // might refer to a non-existent filter. |
| return !!GetFilter(style_resolution_host, canvas_size, context); |
| } |
| |
| void CanvasRenderingContext2DState::ClearResolvedFilter() const { |
| resolved_filter_.reset(); |
| } |
| |
| SkDrawLooper* CanvasRenderingContext2DState::EmptyDrawLooper() const { |
| if (!empty_draw_looper_) |
| empty_draw_looper_ = DrawLooperBuilder().DetachDrawLooper(); |
| |
| return empty_draw_looper_.get(); |
| } |
| |
| SkDrawLooper* CanvasRenderingContext2DState::ShadowOnlyDrawLooper() const { |
| if (!shadow_only_draw_looper_) { |
| DrawLooperBuilder draw_looper_builder; |
| draw_looper_builder.AddShadow(shadow_offset_, shadow_blur_, shadow_color_, |
| DrawLooperBuilder::kShadowIgnoresTransforms, |
| DrawLooperBuilder::kShadowRespectsAlpha); |
| shadow_only_draw_looper_ = draw_looper_builder.DetachDrawLooper(); |
| } |
| return shadow_only_draw_looper_.get(); |
| } |
| |
| SkDrawLooper* CanvasRenderingContext2DState::ShadowAndForegroundDrawLooper() |
| const { |
| if (!shadow_and_foreground_draw_looper_) { |
| DrawLooperBuilder draw_looper_builder; |
| draw_looper_builder.AddShadow(shadow_offset_, shadow_blur_, shadow_color_, |
| DrawLooperBuilder::kShadowIgnoresTransforms, |
| DrawLooperBuilder::kShadowRespectsAlpha); |
| draw_looper_builder.AddUnmodifiedContent(); |
| shadow_and_foreground_draw_looper_ = draw_looper_builder.DetachDrawLooper(); |
| } |
| return shadow_and_foreground_draw_looper_.get(); |
| } |
| |
| sk_sp<PaintFilter> CanvasRenderingContext2DState::ShadowOnlyImageFilter() |
| const { |
| if (!shadow_only_image_filter_) { |
| double sigma = SkBlurRadiusToSigma(shadow_blur_); |
| shadow_only_image_filter_ = sk_make_sp<DropShadowPaintFilter>( |
| shadow_offset_.Width(), shadow_offset_.Height(), sigma, sigma, |
| shadow_color_, SkDropShadowImageFilter::kDrawShadowOnly_ShadowMode, |
| nullptr); |
| } |
| return shadow_only_image_filter_; |
| } |
| |
| sk_sp<PaintFilter> |
| CanvasRenderingContext2DState::ShadowAndForegroundImageFilter() const { |
| if (!shadow_and_foreground_image_filter_) { |
| double sigma = SkBlurRadiusToSigma(shadow_blur_); |
| shadow_and_foreground_image_filter_ = sk_make_sp<DropShadowPaintFilter>( |
| shadow_offset_.Width(), shadow_offset_.Height(), sigma, sigma, |
| shadow_color_, |
| SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode, nullptr); |
| } |
| return shadow_and_foreground_image_filter_; |
| } |
| |
| void CanvasRenderingContext2DState::ShadowParameterChanged() { |
| shadow_only_draw_looper_.reset(); |
| shadow_and_foreground_draw_looper_.reset(); |
| shadow_only_image_filter_.reset(); |
| shadow_and_foreground_image_filter_.reset(); |
| } |
| |
| void CanvasRenderingContext2DState::SetShadowOffsetX(double x) { |
| shadow_offset_.SetWidth(x); |
| ShadowParameterChanged(); |
| } |
| |
| void CanvasRenderingContext2DState::SetShadowOffsetY(double y) { |
| shadow_offset_.SetHeight(y); |
| ShadowParameterChanged(); |
| } |
| |
| void CanvasRenderingContext2DState::SetShadowBlur(double shadow_blur) { |
| shadow_blur_ = shadow_blur; |
| ShadowParameterChanged(); |
| } |
| |
| void CanvasRenderingContext2DState::SetShadowColor(SkColor shadow_color) { |
| shadow_color_ = shadow_color; |
| ShadowParameterChanged(); |
| } |
| |
| void CanvasRenderingContext2DState::SetFilter(const CSSValue* filter_value) { |
| filter_value_ = filter_value; |
| resolved_filter_.reset(); |
| } |
| |
| void CanvasRenderingContext2DState::SetGlobalComposite(SkBlendMode mode) { |
| stroke_flags_.setBlendMode(mode); |
| fill_flags_.setBlendMode(mode); |
| image_flags_.setBlendMode(mode); |
| } |
| |
| SkBlendMode CanvasRenderingContext2DState::GlobalComposite() const { |
| return stroke_flags_.getBlendMode(); |
| } |
| |
| void CanvasRenderingContext2DState::SetImageSmoothingEnabled(bool enabled) { |
| image_smoothing_enabled_ = enabled; |
| UpdateFilterQuality(); |
| } |
| |
| bool CanvasRenderingContext2DState::ImageSmoothingEnabled() const { |
| return image_smoothing_enabled_; |
| } |
| |
| void CanvasRenderingContext2DState::SetImageSmoothingQuality( |
| const String& quality_string) { |
| if (quality_string == "low") { |
| image_smoothing_quality_ = kLow_SkFilterQuality; |
| } else if (quality_string == "medium") { |
| image_smoothing_quality_ = kMedium_SkFilterQuality; |
| } else if (quality_string == "high") { |
| image_smoothing_quality_ = kHigh_SkFilterQuality; |
| } else { |
| return; |
| } |
| UpdateFilterQuality(); |
| } |
| |
| String CanvasRenderingContext2DState::ImageSmoothingQuality() const { |
| switch (image_smoothing_quality_) { |
| case kLow_SkFilterQuality: |
| return "low"; |
| case kMedium_SkFilterQuality: |
| return "medium"; |
| case kHigh_SkFilterQuality: |
| return "high"; |
| default: |
| NOTREACHED(); |
| return "low"; |
| } |
| } |
| |
| void CanvasRenderingContext2DState::UpdateFilterQuality() const { |
| if (!image_smoothing_enabled_) { |
| UpdateFilterQualityWithSkFilterQuality(kNone_SkFilterQuality); |
| } else { |
| UpdateFilterQualityWithSkFilterQuality(image_smoothing_quality_); |
| } |
| } |
| |
| void CanvasRenderingContext2DState::UpdateFilterQualityWithSkFilterQuality( |
| const SkFilterQuality& filter_quality) const { |
| stroke_flags_.setFilterQuality(filter_quality); |
| fill_flags_.setFilterQuality(filter_quality); |
| image_flags_.setFilterQuality(filter_quality); |
| } |
| |
| bool CanvasRenderingContext2DState::ShouldDrawShadows() const { |
| return AlphaChannel(shadow_color_) && |
| (shadow_blur_ || !shadow_offset_.IsZero()); |
| } |
| |
| const PaintFlags* CanvasRenderingContext2DState::GetFlags( |
| PaintType paint_type, |
| ShadowMode shadow_mode, |
| ImageType image_type) const { |
| PaintFlags* flags; |
| switch (paint_type) { |
| case kStrokePaintType: |
| UpdateLineDash(); |
| UpdateStrokeStyle(); |
| flags = &stroke_flags_; |
| break; |
| default: |
| NOTREACHED(); |
| // no break on purpose: flags needs to be assigned to avoid compiler warning |
| // about uninitialized variable. |
| case kFillPaintType: |
| UpdateFillStyle(); |
| flags = &fill_flags_; |
| break; |
| case kImagePaintType: |
| flags = &image_flags_; |
| break; |
| } |
| |
| if ((!ShouldDrawShadows() && shadow_mode == kDrawShadowAndForeground) || |
| shadow_mode == kDrawForegroundOnly) { |
| flags->setLooper(nullptr); |
| flags->setImageFilter(nullptr); |
| return flags; |
| } |
| |
| if (!ShouldDrawShadows() && shadow_mode == kDrawShadowOnly) { |
| flags->setLooper(sk_ref_sp(EmptyDrawLooper())); // draw nothing |
| flags->setImageFilter(nullptr); |
| return flags; |
| } |
| |
| if (shadow_mode == kDrawShadowOnly) { |
| if (image_type == kNonOpaqueImage || filter_value_) { |
| flags->setLooper(nullptr); |
| flags->setImageFilter(ShadowOnlyImageFilter()); |
| return flags; |
| } |
| flags->setLooper(sk_ref_sp(ShadowOnlyDrawLooper())); |
| flags->setImageFilter(nullptr); |
| return flags; |
| } |
| |
| DCHECK(shadow_mode == kDrawShadowAndForeground); |
| if (image_type == kNonOpaqueImage) { |
| flags->setLooper(nullptr); |
| flags->setImageFilter(ShadowAndForegroundImageFilter()); |
| return flags; |
| } |
| flags->setLooper(sk_ref_sp(ShadowAndForegroundDrawLooper())); |
| flags->setImageFilter(nullptr); |
| return flags; |
| } |
| |
| } // namespace blink |