blob: be2d47cff18207e56bc56027d12c7278a5d91614 [file] [log] [blame]
// 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