| // Copyright 2013 The Flutter 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 <math.h> |
| #include <type_traits> |
| |
| #include "flutter/flow/display_list_utils.h" |
| #include "flutter/flow/layers/physical_shape_layer.h" |
| #include "flutter/fml/logging.h" |
| |
| #include "third_party/skia/include/core/SkMaskFilter.h" |
| #include "third_party/skia/include/core/SkPath.h" |
| #include "third_party/skia/include/core/SkRSXform.h" |
| #include "third_party/skia/include/core/SkTextBlob.h" |
| #include "third_party/skia/include/utils/SkShadowUtils.h" |
| |
| namespace flutter { |
| |
| // clang-format off |
| constexpr float invert_color_matrix[20] = { |
| -1.0, 0, 0, 1.0, 0, |
| 0, -1.0, 0, 1.0, 0, |
| 0, 0, -1.0, 1.0, 0, |
| 1.0, 1.0, 1.0, 1.0, 0 |
| }; |
| // clang-format on |
| |
| void SkPaintDispatchHelper::setAntiAlias(bool aa) { |
| paint_.setAntiAlias(aa); |
| } |
| void SkPaintDispatchHelper::setDither(bool dither) { |
| paint_.setDither(dither); |
| } |
| void SkPaintDispatchHelper::setInvertColors(bool invert) { |
| invert_colors_ = invert; |
| paint_.setColorFilter(makeColorFilter()); |
| } |
| void SkPaintDispatchHelper::setStrokeCap(SkPaint::Cap cap) { |
| paint_.setStrokeCap(cap); |
| } |
| void SkPaintDispatchHelper::setStrokeJoin(SkPaint::Join join) { |
| paint_.setStrokeJoin(join); |
| } |
| void SkPaintDispatchHelper::setStyle(SkPaint::Style style) { |
| paint_.setStyle(style); |
| } |
| void SkPaintDispatchHelper::setStrokeWidth(SkScalar width) { |
| paint_.setStrokeWidth(width); |
| } |
| void SkPaintDispatchHelper::setStrokeMiter(SkScalar limit) { |
| paint_.setStrokeMiter(limit); |
| } |
| void SkPaintDispatchHelper::setColor(SkColor color) { |
| paint_.setColor(color); |
| } |
| void SkPaintDispatchHelper::setBlendMode(SkBlendMode mode) { |
| paint_.setBlendMode(mode); |
| } |
| void SkPaintDispatchHelper::setBlender(sk_sp<SkBlender> blender) { |
| paint_.setBlender(blender); |
| } |
| void SkPaintDispatchHelper::setShader(sk_sp<SkShader> shader) { |
| paint_.setShader(shader); |
| } |
| void SkPaintDispatchHelper::setImageFilter(sk_sp<SkImageFilter> filter) { |
| paint_.setImageFilter(filter); |
| } |
| void SkPaintDispatchHelper::setColorFilter(sk_sp<SkColorFilter> filter) { |
| color_filter_ = filter; |
| paint_.setColorFilter(makeColorFilter()); |
| } |
| void SkPaintDispatchHelper::setPathEffect(sk_sp<SkPathEffect> effect) { |
| paint_.setPathEffect(effect); |
| } |
| void SkPaintDispatchHelper::setMaskFilter(sk_sp<SkMaskFilter> filter) { |
| paint_.setMaskFilter(filter); |
| } |
| void SkPaintDispatchHelper::setMaskBlurFilter(SkBlurStyle style, |
| SkScalar sigma) { |
| paint_.setMaskFilter(SkMaskFilter::MakeBlur(style, sigma)); |
| } |
| |
| sk_sp<SkColorFilter> SkPaintDispatchHelper::makeColorFilter() { |
| if (!invert_colors_) { |
| return color_filter_; |
| } |
| sk_sp<SkColorFilter> invert_filter = |
| SkColorFilters::Matrix(invert_color_matrix); |
| if (color_filter_) { |
| invert_filter = invert_filter->makeComposed(color_filter_); |
| } |
| return invert_filter; |
| } |
| |
| void SkMatrixDispatchHelper::translate(SkScalar tx, SkScalar ty) { |
| matrix_.preTranslate(tx, ty); |
| matrix33_ = matrix_.asM33(); |
| } |
| void SkMatrixDispatchHelper::scale(SkScalar sx, SkScalar sy) { |
| matrix_.preScale(sx, sy); |
| matrix33_ = matrix_.asM33(); |
| } |
| void SkMatrixDispatchHelper::rotate(SkScalar degrees) { |
| matrix33_.setRotate(degrees); |
| matrix_.preConcat(matrix33_); |
| matrix33_ = matrix_.asM33(); |
| } |
| void SkMatrixDispatchHelper::skew(SkScalar sx, SkScalar sy) { |
| matrix33_.setSkew(sx, sy); |
| matrix_.preConcat(matrix33_); |
| matrix33_ = matrix_.asM33(); |
| } |
| |
| // clang-format off |
| |
| // 2x3 2D affine subset of a 4x4 transform in row major order |
| void SkMatrixDispatchHelper::transform2DAffine( |
| SkScalar mxx, SkScalar mxy, SkScalar mxt, |
| SkScalar myx, SkScalar myy, SkScalar myt) { |
| matrix_.preConcat({ |
| mxx, mxy, 0 , mxt, |
| myx, myy, 0 , myt, |
| 0 , 0 , 1 , 0 , |
| 0 , 0 , 0 , 1 , |
| }); |
| matrix33_ = matrix_.asM33(); |
| } |
| // full 4x4 transform in row major order |
| void SkMatrixDispatchHelper::transformFullPerspective( |
| SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt, |
| SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt, |
| SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt, |
| SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) { |
| matrix_.preConcat({ |
| mxx, mxy, mxz, mxt, |
| myx, myy, myz, myt, |
| mzx, mzy, mzz, mzt, |
| mwx, mwy, mwz, mwt, |
| }); |
| matrix33_ = matrix_.asM33(); |
| } |
| |
| // clang-format on |
| |
| void SkMatrixDispatchHelper::save() { |
| saved_.push_back(matrix_); |
| } |
| void SkMatrixDispatchHelper::restore() { |
| matrix_ = saved_.back(); |
| matrix33_ = matrix_.asM33(); |
| saved_.pop_back(); |
| } |
| void SkMatrixDispatchHelper::reset() { |
| matrix_.setIdentity(); |
| matrix33_ = matrix_.asM33(); |
| } |
| |
| void ClipBoundsDispatchHelper::clipRect(const SkRect& rect, |
| SkClipOp clip_op, |
| bool is_aa) { |
| if (clip_op == SkClipOp::kIntersect) { |
| intersect(rect, is_aa); |
| } |
| } |
| void ClipBoundsDispatchHelper::clipRRect(const SkRRect& rrect, |
| SkClipOp clip_op, |
| bool is_aa) { |
| if (clip_op == SkClipOp::kIntersect) { |
| intersect(rrect.getBounds(), is_aa); |
| } |
| } |
| void ClipBoundsDispatchHelper::clipPath(const SkPath& path, |
| SkClipOp clip_op, |
| bool is_aa) { |
| if (clip_op == SkClipOp::kIntersect) { |
| intersect(path.getBounds(), is_aa); |
| } |
| } |
| void ClipBoundsDispatchHelper::intersect(const SkRect& rect, bool is_aa) { |
| SkRect devClipBounds = matrix().mapRect(rect); |
| if (is_aa) { |
| devClipBounds.roundOut(&devClipBounds); |
| } |
| if (has_clip_) { |
| if (!bounds_.intersect(devClipBounds)) { |
| bounds_.setEmpty(); |
| } |
| } else { |
| has_clip_ = true; |
| if (devClipBounds.isEmpty()) { |
| bounds_.setEmpty(); |
| } else { |
| bounds_ = devClipBounds; |
| } |
| } |
| } |
| void ClipBoundsDispatchHelper::save() { |
| if (!has_clip_) { |
| saved_.push_back(SkRect::MakeLTRB(0, 0, -1, -1)); |
| } else if (bounds_.isEmpty()) { |
| saved_.push_back(SkRect::MakeEmpty()); |
| } else { |
| saved_.push_back(bounds_); |
| } |
| } |
| void ClipBoundsDispatchHelper::restore() { |
| bounds_ = saved_.back(); |
| saved_.pop_back(); |
| has_clip_ = (bounds_.fLeft <= bounds_.fRight && // |
| bounds_.fTop <= bounds_.fBottom); |
| if (!has_clip_) { |
| bounds_.setEmpty(); |
| } |
| } |
| void ClipBoundsDispatchHelper::reset(const SkRect* cull_rect) { |
| if ((has_clip_ = cull_rect != nullptr) && !cull_rect->isEmpty()) { |
| bounds_ = *cull_rect; |
| } else { |
| bounds_.setEmpty(); |
| } |
| } |
| |
| DisplayListBoundsCalculator::DisplayListBoundsCalculator( |
| const SkRect* cull_rect) |
| : ClipBoundsDispatchHelper(cull_rect) { |
| layer_infos_.emplace_back(std::make_unique<RootLayerData>()); |
| accumulator_ = layer_infos_.back()->layer_accumulator(); |
| } |
| void DisplayListBoundsCalculator::setStrokeCap(SkPaint::Cap cap) { |
| cap_is_square_ = (cap == SkPaint::kSquare_Cap); |
| } |
| void DisplayListBoundsCalculator::setStrokeJoin(SkPaint::Join join) { |
| join_is_miter_ = (join == SkPaint::kMiter_Join); |
| } |
| void DisplayListBoundsCalculator::setStyle(SkPaint::Style style) { |
| style_ = style; |
| } |
| void DisplayListBoundsCalculator::setStrokeWidth(SkScalar width) { |
| half_stroke_width_ = std::max(width * 0.5f, kMinStrokeWidth); |
| } |
| void DisplayListBoundsCalculator::setStrokeMiter(SkScalar limit) { |
| miter_limit_ = std::max(limit, 1.0f); |
| } |
| void DisplayListBoundsCalculator::setBlendMode(SkBlendMode mode) { |
| blend_mode_ = mode; |
| } |
| void DisplayListBoundsCalculator::setBlender(sk_sp<SkBlender> blender) { |
| SkPaint paint; |
| paint.setBlender(std::move(blender)); |
| blend_mode_ = paint.asBlendMode(); |
| } |
| void DisplayListBoundsCalculator::setImageFilter(sk_sp<SkImageFilter> filter) { |
| image_filter_ = std::move(filter); |
| } |
| void DisplayListBoundsCalculator::setColorFilter(sk_sp<SkColorFilter> filter) { |
| color_filter_ = std::move(filter); |
| } |
| void DisplayListBoundsCalculator::setPathEffect(sk_sp<SkPathEffect> effect) { |
| path_effect_ = std::move(effect); |
| } |
| void DisplayListBoundsCalculator::setMaskFilter(sk_sp<SkMaskFilter> filter) { |
| mask_filter_ = std::move(filter); |
| mask_sigma_pad_ = 0.0f; |
| } |
| void DisplayListBoundsCalculator::setMaskBlurFilter(SkBlurStyle style, |
| SkScalar sigma) { |
| mask_sigma_pad_ = std::max(3.0f * sigma, 0.0f); |
| mask_filter_ = nullptr; |
| } |
| void DisplayListBoundsCalculator::save() { |
| SkMatrixDispatchHelper::save(); |
| ClipBoundsDispatchHelper::save(); |
| layer_infos_.emplace_back(std::make_unique<SaveData>(accumulator_)); |
| accumulator_ = layer_infos_.back()->layer_accumulator(); |
| } |
| void DisplayListBoundsCalculator::saveLayer(const SkRect* bounds, |
| bool with_paint) { |
| SkMatrixDispatchHelper::save(); |
| ClipBoundsDispatchHelper::save(); |
| if (with_paint) { |
| layer_infos_.emplace_back(std::make_unique<SaveLayerData>( |
| accumulator_, image_filter_, paint_nops_on_transparency())); |
| } else { |
| layer_infos_.emplace_back( |
| std::make_unique<SaveLayerData>(accumulator_, nullptr, true)); |
| } |
| accumulator_ = layer_infos_.back()->layer_accumulator(); |
| // Accumulate the layer in its own coordinate system and then |
| // filter and transform its bounds on restore. |
| SkMatrixDispatchHelper::reset(); |
| if (bounds) { |
| clipRect(*bounds, SkClipOp::kIntersect, false); |
| } |
| } |
| void DisplayListBoundsCalculator::restore() { |
| if (layer_infos_.size() > 1) { |
| SkMatrixDispatchHelper::restore(); |
| ClipBoundsDispatchHelper::restore(); |
| accumulator_ = layer_infos_.back()->restore_accumulator(); |
| SkRect layer_bounds = layer_infos_.back()->layer_bounds(); |
| // Must read unbounded state after layer_bounds |
| bool layer_unbounded = layer_infos_.back()->is_unbounded(); |
| layer_infos_.pop_back(); |
| |
| // We accumulate the bounds even if the layer was unbounded because |
| // the unbounded state may be contained at a higher level, so we at |
| // least accumulate our best estimate about what we have. |
| if (!layer_bounds.isEmpty()) { |
| // kUnfiltered because the layer already applied all bounds |
| // modifications based on the attributes that were in place |
| // when it was instantiated. Modifying it further base on the |
| // current attributes would mix attribute states. |
| AccumulateRect(layer_bounds, kSaveLayerFlags); |
| } |
| if (layer_unbounded) { |
| AccumulateUnbounded(); |
| } |
| } |
| } |
| |
| void DisplayListBoundsCalculator::drawPaint() { |
| AccumulateUnbounded(); |
| } |
| void DisplayListBoundsCalculator::drawColor(SkColor color, SkBlendMode mode) { |
| AccumulateUnbounded(); |
| } |
| void DisplayListBoundsCalculator::drawLine(const SkPoint& p0, |
| const SkPoint& p1) { |
| SkRect bounds = SkRect::MakeLTRB(p0.fX, p0.fY, p1.fX, p1.fY).makeSorted(); |
| DisplayListAttributeFlags flags = |
| (bounds.width() > 0.0f && bounds.height() > 0.0f) ? kDrawLineFlags |
| : kDrawHVLineFlags; |
| AccumulateRect(bounds, flags); |
| } |
| void DisplayListBoundsCalculator::drawRect(const SkRect& rect) { |
| AccumulateRect(rect, kDrawRectFlags); |
| } |
| void DisplayListBoundsCalculator::drawOval(const SkRect& bounds) { |
| AccumulateRect(bounds, kDrawOvalFlags); |
| } |
| void DisplayListBoundsCalculator::drawCircle(const SkPoint& center, |
| SkScalar radius) { |
| AccumulateRect(SkRect::MakeLTRB(center.fX - radius, center.fY - radius, |
| center.fX + radius, center.fY + radius), |
| kDrawCircleFlags); |
| } |
| void DisplayListBoundsCalculator::drawRRect(const SkRRect& rrect) { |
| AccumulateRect(rrect.getBounds(), kDrawRRectFlags); |
| } |
| void DisplayListBoundsCalculator::drawDRRect(const SkRRect& outer, |
| const SkRRect& inner) { |
| AccumulateRect(outer.getBounds(), kDrawDRRectFlags); |
| } |
| void DisplayListBoundsCalculator::drawPath(const SkPath& path) { |
| if (path.isInverseFillType()) { |
| AccumulateUnbounded(); |
| } else { |
| AccumulateRect(path.getBounds(), kDrawPathFlags); |
| } |
| } |
| void DisplayListBoundsCalculator::drawArc(const SkRect& bounds, |
| SkScalar start, |
| SkScalar sweep, |
| bool useCenter) { |
| // This could be tighter if we compute where the start and end |
| // angles are and then also consider the quadrants swept and |
| // the center if specified. |
| AccumulateRect(bounds, |
| useCenter // |
| ? kDrawArcWithCenterFlags |
| : kDrawArcNoCenterFlags); |
| } |
| void DisplayListBoundsCalculator::drawPoints(SkCanvas::PointMode mode, |
| uint32_t count, |
| const SkPoint pts[]) { |
| if (count > 0) { |
| BoundsAccumulator ptBounds; |
| for (size_t i = 0; i < count; i++) { |
| ptBounds.accumulate(pts[i]); |
| } |
| SkRect point_bounds = ptBounds.bounds(); |
| switch (mode) { |
| case SkCanvas::kPoints_PointMode: |
| AccumulateRect(point_bounds, kDrawPointsAsPointsFlags); |
| break; |
| case SkCanvas::kLines_PointMode: |
| AccumulateRect(point_bounds, kDrawPointsAsLinesFlags); |
| break; |
| case SkCanvas::kPolygon_PointMode: |
| AccumulateRect(point_bounds, kDrawPointsAsPolygonFlags); |
| break; |
| } |
| } |
| } |
| void DisplayListBoundsCalculator::drawVertices(const sk_sp<SkVertices> vertices, |
| SkBlendMode mode) { |
| AccumulateRect(vertices->bounds(), kDrawVerticesFlags); |
| } |
| void DisplayListBoundsCalculator::drawImage(const sk_sp<SkImage> image, |
| const SkPoint point, |
| const SkSamplingOptions& sampling, |
| bool render_with_attributes) { |
| SkRect bounds = SkRect::MakeXYWH(point.fX, point.fY, // |
| image->width(), image->height()); |
| DisplayListAttributeFlags flags = render_with_attributes // |
| ? kDrawImageWithPaintFlags |
| : kDrawImageFlags; |
| AccumulateRect(bounds, flags); |
| } |
| void DisplayListBoundsCalculator::drawImageRect( |
| const sk_sp<SkImage> image, |
| const SkRect& src, |
| const SkRect& dst, |
| const SkSamplingOptions& sampling, |
| bool render_with_attributes, |
| SkCanvas::SrcRectConstraint constraint) { |
| DisplayListAttributeFlags flags = render_with_attributes |
| ? kDrawImageRectWithPaintFlags |
| : kDrawImageRectFlags; |
| AccumulateRect(dst, flags); |
| } |
| void DisplayListBoundsCalculator::drawImageNine(const sk_sp<SkImage> image, |
| const SkIRect& center, |
| const SkRect& dst, |
| SkFilterMode filter, |
| bool render_with_attributes) { |
| DisplayListAttributeFlags flags = render_with_attributes |
| ? kDrawImageNineWithPaintFlags |
| : kDrawImageNineFlags; |
| AccumulateRect(dst, flags); |
| } |
| void DisplayListBoundsCalculator::drawImageLattice( |
| const sk_sp<SkImage> image, |
| const SkCanvas::Lattice& lattice, |
| const SkRect& dst, |
| SkFilterMode filter, |
| bool render_with_attributes) { |
| DisplayListAttributeFlags flags = render_with_attributes |
| ? kDrawImageLatticeWithPaintFlags |
| : kDrawImageLatticeFlags; |
| AccumulateRect(dst, flags); |
| } |
| void DisplayListBoundsCalculator::drawAtlas(const sk_sp<SkImage> atlas, |
| const SkRSXform xform[], |
| const SkRect tex[], |
| const SkColor colors[], |
| int count, |
| SkBlendMode mode, |
| const SkSamplingOptions& sampling, |
| const SkRect* cullRect, |
| bool render_with_attributes) { |
| SkPoint quad[4]; |
| BoundsAccumulator atlasBounds; |
| for (int i = 0; i < count; i++) { |
| const SkRect& src = tex[i]; |
| xform[i].toQuad(src.width(), src.height(), quad); |
| for (int j = 0; j < 4; j++) { |
| atlasBounds.accumulate(quad[j]); |
| } |
| } |
| if (atlasBounds.is_not_empty()) { |
| DisplayListAttributeFlags flags = render_with_attributes // |
| ? kDrawAtlasWithPaintFlags |
| : kDrawAtlasFlags; |
| AccumulateRect(atlasBounds.bounds(), flags); |
| } |
| } |
| void DisplayListBoundsCalculator::drawPicture(const sk_sp<SkPicture> picture, |
| const SkMatrix* pic_matrix, |
| bool render_with_attributes) { |
| // TODO(flar) cull rect really cannot be trusted in general, but it will |
| // work for SkPictures generated from our own PictureRecorder or any |
| // picture captured with an SkRTreeFactory or accurate bounds estimate. |
| SkRect bounds = picture->cullRect(); |
| if (pic_matrix) { |
| pic_matrix->mapRect(&bounds); |
| } |
| DisplayListAttributeFlags flags = render_with_attributes // |
| ? kDrawPictureWithPaintFlags |
| : kDrawPictureFlags; |
| AccumulateRect(bounds, flags); |
| } |
| void DisplayListBoundsCalculator::drawDisplayList( |
| const sk_sp<DisplayList> display_list) { |
| AccumulateRect(display_list->bounds(), kDrawDisplayListFlags); |
| } |
| void DisplayListBoundsCalculator::drawTextBlob(const sk_sp<SkTextBlob> blob, |
| SkScalar x, |
| SkScalar y) { |
| AccumulateRect(blob->bounds().makeOffset(x, y), kDrawTextBlobFlags); |
| } |
| void DisplayListBoundsCalculator::drawShadow(const SkPath& path, |
| const SkColor color, |
| const SkScalar elevation, |
| bool transparent_occluder, |
| SkScalar dpr) { |
| SkRect shadow_bounds = |
| PhysicalShapeLayer::ComputeShadowBounds(path, elevation, dpr, matrix()); |
| AccumulateRect(shadow_bounds, kDrawShadowFlags); |
| } |
| |
| bool DisplayListBoundsCalculator::ComputeFilteredBounds(SkRect& bounds, |
| SkImageFilter* filter) { |
| if (filter) { |
| if (!filter->canComputeFastBounds()) { |
| return false; |
| } |
| bounds = filter->computeFastBounds(bounds); |
| } |
| return true; |
| } |
| |
| bool DisplayListBoundsCalculator::AdjustBoundsForPaint( |
| SkRect& bounds, |
| DisplayListAttributeFlags flags) { |
| if (flags.ignores_paint()) { |
| return true; |
| } |
| |
| if (flags.is_geometric()) { |
| // Path effect occurs before stroking... |
| DisplayListSpecialGeometryFlags special_flags = |
| flags.WithPathEffect(path_effect_); |
| if (path_effect_) { |
| SkPaint p; |
| p.setPathEffect(path_effect_); |
| if (!p.canComputeFastBounds()) { |
| return false; |
| } |
| bounds = p.computeFastBounds(bounds, &bounds); |
| } |
| |
| if (flags.is_stroked(style_)) { |
| // Determine the max multiplier to the stroke width first. |
| SkScalar pad = 1.0f; |
| if (join_is_miter_ && special_flags.may_have_acute_joins()) { |
| pad = std::max(pad, miter_limit_); |
| } |
| if (cap_is_square_ && special_flags.may_have_diagonal_caps()) { |
| pad = std::max(pad, SK_ScalarSqrt2); |
| } |
| pad *= half_stroke_width_; |
| bounds.outset(pad, pad); |
| } |
| } |
| |
| if (flags.applies_mask_filter()) { |
| if (mask_filter_) { |
| SkPaint p; |
| p.setMaskFilter(mask_filter_); |
| if (!p.canComputeFastBounds()) { |
| return false; |
| } |
| bounds = p.computeFastBounds(bounds, &bounds); |
| } |
| if (mask_sigma_pad_ > 0.0f) { |
| bounds.outset(mask_sigma_pad_, mask_sigma_pad_); |
| } |
| } |
| |
| if (flags.applies_image_filter()) { |
| return ComputeFilteredBounds(bounds, image_filter_.get()); |
| } |
| |
| return true; |
| } |
| |
| void DisplayListBoundsCalculator::AccumulateUnbounded() { |
| if (has_clip()) { |
| accumulator_->accumulate(clip_bounds()); |
| } else { |
| layer_infos_.back()->set_unbounded(); |
| } |
| } |
| void DisplayListBoundsCalculator::AccumulateRect( |
| SkRect& rect, |
| DisplayListAttributeFlags flags) { |
| if (AdjustBoundsForPaint(rect, flags)) { |
| matrix().mapRect(&rect); |
| if (!has_clip() || rect.intersect(clip_bounds())) { |
| accumulator_->accumulate(rect); |
| } |
| } else { |
| AccumulateUnbounded(); |
| } |
| } |
| |
| bool DisplayListBoundsCalculator::paint_nops_on_transparency() { |
| // SkImageFilter::canComputeFastBounds tests for transparency behavior |
| // This test assumes that the blend mode checked down below will |
| // NOP on transparent black. |
| if (image_filter_ && !image_filter_->canComputeFastBounds()) { |
| return false; |
| } |
| |
| // We filter the transparent black that is used for the background of a |
| // saveLayer and make sure it returns transparent black. If it does, then |
| // the color filter will leave all area surrounding the contents of the |
| // save layer untouched out to the edge of the output surface. |
| // This test assumes that the blend mode checked down below will |
| // NOP on transparent black. |
| if (color_filter_ && |
| color_filter_->filterColor(SK_ColorTRANSPARENT) != SK_ColorTRANSPARENT) { |
| return false; |
| } |
| |
| if (!blend_mode_) { |
| return false; // can we query other blenders for this? |
| } |
| // Unusual blendmodes require us to process a saved layer |
| // even with operations outisde the clip. |
| // For example, DstIn is used by masking layers. |
| // https://code.google.com/p/skia/issues/detail?id=1291 |
| // https://crbug.com/401593 |
| switch (blend_mode_.value()) { |
| // For each of the following transfer modes, if the source |
| // alpha is zero (our transparent black), the resulting |
| // blended pixel is not necessarily equal to the original |
| // destination pixel. |
| // Mathematically, any time in the following equations where |
| // the result is not d assuming source is 0 |
| case SkBlendMode::kClear: // r = 0 |
| case SkBlendMode::kSrc: // r = s |
| case SkBlendMode::kSrcIn: // r = s * da |
| case SkBlendMode::kDstIn: // r = d * sa |
| case SkBlendMode::kSrcOut: // r = s * (1-da) |
| case SkBlendMode::kDstATop: // r = d*sa + s*(1-da) |
| case SkBlendMode::kModulate: // r = s*d |
| return false; |
| break; |
| |
| // And in these equations, the result must be d if the |
| // source is 0 |
| case SkBlendMode::kDst: // r = d |
| case SkBlendMode::kSrcOver: // r = s + (1-sa)*d |
| case SkBlendMode::kDstOver: // r = d + (1-da)*s |
| case SkBlendMode::kDstOut: // r = d * (1-sa) |
| case SkBlendMode::kSrcATop: // r = s*da + d*(1-sa) |
| case SkBlendMode::kXor: // r = s*(1-da) + d*(1-sa) |
| case SkBlendMode::kPlus: // r = min(s + d, 1) |
| case SkBlendMode::kScreen: // r = s + d - s*d |
| case SkBlendMode::kOverlay: // multiply or screen, depending on dest |
| case SkBlendMode::kDarken: // rc = s + d - max(s*da, d*sa), |
| // ra = kSrcOver |
| case SkBlendMode::kLighten: // rc = s + d - min(s*da, d*sa), |
| // ra = kSrcOver |
| case SkBlendMode::kColorDodge: // brighten destination to reflect source |
| case SkBlendMode::kColorBurn: // darken destination to reflect source |
| case SkBlendMode::kHardLight: // multiply or screen, depending on source |
| case SkBlendMode::kSoftLight: // lighten or darken, depending on source |
| case SkBlendMode::kDifference: // rc = s + d - 2*(min(s*da, d*sa)), |
| // ra = kSrcOver |
| case SkBlendMode::kExclusion: // rc = s + d - two(s*d), ra = kSrcOver |
| case SkBlendMode::kMultiply: // r = s*(1-da) + d*(1-sa) + s*d |
| case SkBlendMode::kHue: // ra = kSrcOver |
| case SkBlendMode::kSaturation: // ra = kSrcOver |
| case SkBlendMode::kColor: // ra = kSrcOver |
| case SkBlendMode::kLuminosity: // ra = kSrcOver |
| return true; |
| break; |
| } |
| } |
| |
| } // namespace flutter |