| // 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::setAA(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::setCaps(SkPaint::Cap cap) { |
| paint_.setStrokeCap(cap); |
| } |
| void SkPaintDispatchHelper::setJoins(SkPaint::Join join) { |
| paint_.setStrokeJoin(join); |
| } |
| void SkPaintDispatchHelper::setDrawStyle(SkPaint::Style style) { |
| paint_.setStyle(style); |
| } |
| void SkPaintDispatchHelper::setStrokeWidth(SkScalar width) { |
| paint_.setStrokeWidth(width); |
| } |
| void SkPaintDispatchHelper::setMiterLimit(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); |
| } |
| void SkMatrixDispatchHelper::scale(SkScalar sx, SkScalar sy) { |
| matrix_.preScale(sx, sy); |
| } |
| void SkMatrixDispatchHelper::rotate(SkScalar degrees) { |
| matrix_.preRotate(degrees); |
| } |
| void SkMatrixDispatchHelper::skew(SkScalar sx, SkScalar sy) { |
| matrix_.preSkew(sx, sy); |
| } |
| void SkMatrixDispatchHelper::transform2x3(SkScalar mxx, |
| SkScalar mxy, |
| SkScalar mxt, |
| SkScalar myx, |
| SkScalar myy, |
| SkScalar myt) { |
| matrix_.preConcat(SkMatrix::MakeAll(mxx, mxy, mxt, myx, myy, myt, 0, 0, 1)); |
| } |
| void SkMatrixDispatchHelper::transform3x3(SkScalar mxx, |
| SkScalar mxy, |
| SkScalar mxt, |
| SkScalar myx, |
| SkScalar myy, |
| SkScalar myt, |
| SkScalar px, |
| SkScalar py, |
| SkScalar pt) { |
| matrix_.preConcat( |
| SkMatrix::MakeAll(mxx, mxy, mxt, myx, myy, myt, px, py, pt)); |
| } |
| void SkMatrixDispatchHelper::save() { |
| saved_.push_back(matrix_); |
| } |
| void SkMatrixDispatchHelper::restore() { |
| matrix_ = saved_.back(); |
| saved_.pop_back(); |
| } |
| void SkMatrixDispatchHelper::reset() { |
| matrix_.reset(); |
| } |
| |
| void ClipBoundsDispatchHelper::clipRect(const SkRect& rect, |
| bool isAA, |
| SkClipOp clip_op) { |
| if (clip_op == SkClipOp::kIntersect) { |
| intersect(rect); |
| } |
| } |
| void ClipBoundsDispatchHelper::clipRRect(const SkRRect& rrect, |
| bool isAA, |
| SkClipOp clip_op) { |
| if (clip_op == SkClipOp::kIntersect) { |
| intersect(rrect.getBounds()); |
| } |
| } |
| void ClipBoundsDispatchHelper::clipPath(const SkPath& path, |
| bool isAA, |
| SkClipOp clip_op) { |
| if (clip_op == SkClipOp::kIntersect) { |
| intersect(path.getBounds()); |
| } |
| } |
| void ClipBoundsDispatchHelper::intersect(const SkRect& rect) { |
| SkRect devClipBounds = matrix().mapRect(rect); |
| if (!bounds_.intersect(devClipBounds)) { |
| bounds_.setEmpty(); |
| } |
| } |
| void ClipBoundsDispatchHelper::save() { |
| saved_.push_back(bounds_); |
| } |
| void ClipBoundsDispatchHelper::restore() { |
| bounds_ = saved_.back(); |
| saved_.pop_back(); |
| } |
| |
| void DisplayListBoundsCalculator::saveLayer(const SkRect* bounds, |
| bool with_paint) { |
| SkMatrixDispatchHelper::save(); |
| ClipBoundsDispatchHelper::save(); |
| saved_infos_.emplace_back( |
| with_paint ? std::make_unique<SaveLayerWithPaintInfo>( |
| this, accumulator_, matrix(), bounds, paint()) |
| : std::make_unique<SaveLayerInfo>(accumulator_, matrix())); |
| accumulator_ = saved_infos_.back()->save(); |
| SkMatrixDispatchHelper::reset(); |
| } |
| void DisplayListBoundsCalculator::save() { |
| SkMatrixDispatchHelper::save(); |
| ClipBoundsDispatchHelper::save(); |
| saved_infos_.emplace_back(std::make_unique<SaveInfo>(accumulator_)); |
| accumulator_ = saved_infos_.back()->save(); |
| } |
| void DisplayListBoundsCalculator::restore() { |
| if (!saved_infos_.empty()) { |
| SkMatrixDispatchHelper::restore(); |
| ClipBoundsDispatchHelper::restore(); |
| accumulator_ = saved_infos_.back()->restore(); |
| saved_infos_.pop_back(); |
| } |
| } |
| |
| void DisplayListBoundsCalculator::drawPaint() { |
| if (!bounds_cull_.isEmpty()) { |
| root_accumulator_.accumulate(bounds_cull_); |
| } |
| } |
| void DisplayListBoundsCalculator::drawColor(SkColor color, SkBlendMode mode) { |
| if (!bounds_cull_.isEmpty()) { |
| root_accumulator_.accumulate(bounds_cull_); |
| } |
| } |
| void DisplayListBoundsCalculator::drawLine(const SkPoint& p0, |
| const SkPoint& p1) { |
| SkRect bounds = SkRect::MakeLTRB(p0.fX, p0.fY, p1.fX, p1.fY).makeSorted(); |
| accumulateRect(bounds, true); |
| } |
| void DisplayListBoundsCalculator::drawRect(const SkRect& rect) { |
| accumulateRect(rect); |
| } |
| void DisplayListBoundsCalculator::drawOval(const SkRect& bounds) { |
| accumulateRect(bounds); |
| } |
| void DisplayListBoundsCalculator::drawCircle(const SkPoint& center, |
| SkScalar radius) { |
| accumulateRect(SkRect::MakeLTRB(center.fX - radius, center.fY - radius, |
| center.fX + radius, center.fY + radius)); |
| } |
| void DisplayListBoundsCalculator::drawRRect(const SkRRect& rrect) { |
| accumulateRect(rrect.getBounds()); |
| } |
| void DisplayListBoundsCalculator::drawDRRect(const SkRRect& outer, |
| const SkRRect& inner) { |
| accumulateRect(outer.getBounds()); |
| } |
| void DisplayListBoundsCalculator::drawPath(const SkPath& path) { |
| accumulateRect(path.getBounds()); |
| } |
| 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); |
| } |
| 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]); |
| } |
| accumulateRect(ptBounds.getBounds(), true); |
| } |
| } |
| void DisplayListBoundsCalculator::drawVertices(const sk_sp<SkVertices> vertices, |
| SkBlendMode mode) { |
| accumulateRect(vertices->bounds()); |
| } |
| void DisplayListBoundsCalculator::drawImage(const sk_sp<SkImage> image, |
| const SkPoint point, |
| const SkSamplingOptions& sampling) { |
| SkRect bounds = SkRect::Make(image->bounds()); |
| bounds.offset(point); |
| accumulateRect(bounds); |
| } |
| void DisplayListBoundsCalculator::drawImageRect( |
| const sk_sp<SkImage> image, |
| const SkRect& src, |
| const SkRect& dst, |
| const SkSamplingOptions& sampling, |
| SkCanvas::SrcRectConstraint constraint) { |
| accumulateRect(dst); |
| } |
| void DisplayListBoundsCalculator::drawImageNine(const sk_sp<SkImage> image, |
| const SkIRect& center, |
| const SkRect& dst, |
| SkFilterMode filter) { |
| accumulateRect(dst); |
| } |
| void DisplayListBoundsCalculator::drawImageLattice( |
| const sk_sp<SkImage> image, |
| const SkCanvas::Lattice& lattice, |
| const SkRect& dst, |
| SkFilterMode filter, |
| bool with_paint) { |
| accumulateRect(dst); |
| } |
| 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) { |
| 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.isNotEmpty()) { |
| accumulateRect(atlasBounds.getBounds()); |
| } |
| } |
| void DisplayListBoundsCalculator::drawPicture(const sk_sp<SkPicture> picture, |
| const SkMatrix* pic_matrix, |
| bool with_save_layer) { |
| // 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); |
| } |
| if (with_save_layer) { |
| accumulateRect(bounds); |
| } else { |
| matrix().mapRect(&bounds); |
| accumulator_->accumulate(bounds); |
| } |
| } |
| void DisplayListBoundsCalculator::drawDisplayList( |
| const sk_sp<DisplayList> display_list) { |
| accumulateRect(display_list->bounds()); |
| } |
| void DisplayListBoundsCalculator::drawTextBlob(const sk_sp<SkTextBlob> blob, |
| SkScalar x, |
| SkScalar y) { |
| accumulateRect(blob->bounds().makeOffset(x, y)); |
| } |
| void DisplayListBoundsCalculator::drawShadow(const SkPath& path, |
| const SkColor color, |
| const SkScalar elevation, |
| bool occludes, |
| SkScalar dpr) { |
| accumulateRect( |
| PhysicalShapeLayer::ComputeShadowBounds(path, elevation, dpr, matrix())); |
| } |
| |
| void DisplayListBoundsCalculator::accumulateRect(const SkRect& rect, |
| bool forceStroke) { |
| SkRect dstRect = rect; |
| const SkPaint& p = paint(); |
| if (forceStroke) { |
| if (p.getStyle() == SkPaint::kFill_Style) { |
| setDrawStyle(SkPaint::kStroke_Style); |
| } else { |
| forceStroke = false; |
| } |
| } |
| if (p.canComputeFastBounds()) { |
| dstRect = p.computeFastBounds(rect, &dstRect); |
| matrix().mapRect(&dstRect); |
| accumulator_->accumulate(dstRect); |
| } else { |
| root_accumulator_.accumulate(bounds_cull_); |
| } |
| if (forceStroke) { |
| setDrawStyle(SkPaint::kFill_Style); |
| } |
| } |
| |
| DisplayListBoundsCalculator::SaveInfo::SaveInfo(BoundsAccumulator* accumulator) |
| : saved_accumulator_(accumulator) {} |
| BoundsAccumulator* DisplayListBoundsCalculator::SaveInfo::save() { |
| // No need to swap out the accumulator for a normal save |
| return saved_accumulator_; |
| } |
| BoundsAccumulator* DisplayListBoundsCalculator::SaveInfo::restore() { |
| return saved_accumulator_; |
| } |
| |
| DisplayListBoundsCalculator::SaveLayerInfo::SaveLayerInfo( |
| BoundsAccumulator* accumulator, |
| const SkMatrix& matrix) |
| : SaveInfo(accumulator), matrix_(matrix) {} |
| BoundsAccumulator* DisplayListBoundsCalculator::SaveLayerInfo::save() { |
| // Use the local layerAccumulator until restore is called and |
| // then transform (and adjust with paint if necessary) on restore() |
| return &layer_accumulator_; |
| } |
| BoundsAccumulator* DisplayListBoundsCalculator::SaveLayerInfo::restore() { |
| SkRect layer_bounds = layer_accumulator_.getBounds(); |
| layer_bounds.roundOut(&layer_bounds); |
| matrix_.mapRect(&layer_bounds); |
| saved_accumulator_->accumulate(layer_bounds); |
| return saved_accumulator_; |
| } |
| |
| DisplayListBoundsCalculator::SaveLayerWithPaintInfo::SaveLayerWithPaintInfo( |
| DisplayListBoundsCalculator* calculator, |
| BoundsAccumulator* accumulator, |
| const SkMatrix& saveMatrix, |
| const SkRect* saveBounds, |
| const SkPaint& savePaint) |
| : SaveLayerInfo(accumulator, saveMatrix), |
| calculator_(calculator), |
| paint_(savePaint) { |
| if (saveBounds) { |
| bounds_.emplace(*saveBounds); |
| } |
| } |
| |
| static bool PaintNopsOnTransparenBlack(const SkPaint& paint) { |
| SkImageFilter* image_filter = paint.getImageFilter(); |
| // 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; |
| } |
| |
| SkColorFilter* color_filter = paint.getColorFilter(); |
| // 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; |
| } |
| |
| const auto blend_mode = paint.asBlendMode(); |
| 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; |
| } |
| } |
| |
| BoundsAccumulator* |
| DisplayListBoundsCalculator::SaveLayerWithPaintInfo::restore() { |
| SkRect layer_bounds; |
| if (paint_.canComputeFastBounds() && PaintNopsOnTransparenBlack(paint_)) { |
| // The ideal situation. The paint can compute the bounds AND the |
| // surrounding transparent pixels will not affect the destination. |
| layer_bounds = layer_accumulator_.getBounds(); |
| layer_bounds = paint_.computeFastBounds(layer_bounds, &layer_bounds); |
| } else if (bounds_.has_value()) { |
| // Bounds were provided by the save layer, the operation will affect |
| // all of those bounds. |
| layer_bounds = bounds_.value(); |
| } else { |
| // Bounds were not provided for the save layer. We will fill to the |
| // cull bounds provided to the original DisplayList. |
| calculator_->root_accumulator_.accumulate(calculator_->bounds_cull_); |
| // There is no need to process the layer bounds further as we just |
| // expanded bounds to the cull rect of the DisplayList. |
| return saved_accumulator_; |
| } |
| layer_bounds.roundOut(&layer_bounds); |
| matrix_.mapRect(&layer_bounds); |
| saved_accumulator_->accumulate(layer_bounds); |
| return saved_accumulator_; |
| } |
| |
| } // namespace flutter |