[go: nahoru, domu]

blob: 9986a2d24067ec9a7713c7a46979069770d84e01 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/paint/solid_color_analyzer.h"
#include <cmath>
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "cc/paint/paint_op.h"
#include "cc/paint/paint_op_buffer_iterator.h"
#include "third_party/skia/include/core/SkTypes.h"
#include "third_party/skia/include/utils/SkNoDrawCanvas.h"
namespace cc {
namespace {
SkColor4f DoSrcOverAlphaBlend(SkColor4f src, SkColor4f dst) {
if (src.fA == 0.0f)
return dst;
if (src.fA == 1.0f)
return src;
// Note: using alpha blending formulas adapted from
// https://en.wikipedia.org/wiki/Alpha_compositing:
//
// outA = srcA + dstA * (1 - srcA)
// outRGB = srcRGB * (srcA / outA) + dstRGB * [dstA * (1 - srcA) / outA]
const float out_alpha = src.fA + dst.fA * (1.0f - src.fA);
if (out_alpha == 0.0f)
return SkColors::kTransparent;
const float inverse_out_alpha = 1.0f / out_alpha;
const float src_weight = src.fA * inverse_out_alpha;
const float dst_weight = dst.fA * (1.0f - src.fA) * inverse_out_alpha;
const float out_red = (src.fR * src_weight + dst.fR * dst_weight);
const float out_green = (src.fG * src_weight + dst.fG * dst_weight);
const float out_blue = (src.fB * src_weight + dst.fB * dst_weight);
return {out_red, out_green, out_blue, out_alpha};
}
bool ActsLikeClear(SkBlendMode mode, float src_alpha) {
switch (mode) {
case SkBlendMode::kClear:
return true;
case SkBlendMode::kSrc:
case SkBlendMode::kSrcIn:
case SkBlendMode::kDstIn:
case SkBlendMode::kSrcOut:
case SkBlendMode::kDstATop:
return src_alpha == 0.0f;
case SkBlendMode::kDstOut:
return src_alpha == 1.0f;
default:
return false;
}
}
bool IsSolidColorBlendMode(SkBlendMode blendmode) {
return blendmode == SkBlendMode::kSrc || blendmode == SkBlendMode::kSrcOver;
}
bool IsSolidColorPaint(const PaintFlags& flags) {
SkBlendMode blendmode = flags.getBlendMode();
// Paint is solid color if the following holds:
// - Style is fill, and there are no special effects.
// - Blend mode is either kSrc or kSrcOver.
bool is_solid_color =
IsSolidColorBlendMode(blendmode) && !flags.HasShader() &&
!flags.getLooper() && !flags.getMaskFilter() && !flags.getColorFilter() &&
!flags.getImageFilter() && flags.getStyle() == PaintFlags::kFill_Style;
#if BUILDFLAG(IS_MAC)
// Additionally, on Mac, we require that the color is opaque due to
// https://crbug.com/922899.
// TODO(andrescj): remove this condition once that bug is fixed.
is_solid_color = (is_solid_color && flags.getColor4f().fA == 1.0f);
#endif // BUILDFLAG(IS_MAC)
return is_solid_color;
}
// Returns true if the specified |drawn_shape| will cover the entire canvas
// and that the canvas is not clipped (i.e. it covers ALL of the canvas).
// We expect this method to return false most of the time so we take
// conservative early-outs when possible.
template <typename T>
bool IsFullQuad(const SkCanvas& canvas, const T& drawn_shape) {
if (!canvas.isClipRect())
return false;
SkIRect clip_bounds;
if (!canvas.getDeviceClipBounds(&clip_bounds))
return false;
// if the clip is smaller than the canvas, we're partly clipped, so abort.
if (!clip_bounds.contains(SkIRect::MakeSize(canvas.getBaseLayerSize())))
return false;
const SkM44& matrix = canvas.getLocalToDevice();
// If the transform results in a non-axis aligned rectangle, then be
// conservative and return false.
if (!MathUtil::SkM44Preserves2DAxisAlignment(matrix))
return false;
SkM44 inverse;
if (!matrix.invert(&inverse))
return false;
// Check that the drawn shape contains the canvas bounds when those bounds
// are transformed into the shape's coordinate space. Since we know the
// transform is axis aligned we only need to test two corners.
SkV4 upper_left = inverse.map(clip_bounds.left(), clip_bounds.top(), 0, 1);
SkV4 lower_right =
inverse.map(clip_bounds.right(), clip_bounds.bottom(), 0, 1);
SkRect transformed_clip_bounds = SkRect::MakeLTRB(
upper_left.x, upper_left.y, lower_right.x, lower_right.y);
return drawn_shape.contains(transformed_clip_bounds);
}
void CalculateSolidColor(SkColor4f src_color,
SkBlendMode blendmode,
SkColor4f* dst_color,
bool* is_solid_color) {
if (blendmode == SkBlendMode::kSrc) {
// In the Src mode, we don't have to worry about what's in the canvas
// because we'll replace it with |src_color|.
*dst_color = src_color;
*is_solid_color = true;
} else {
DCHECK_EQ(SkBlendMode::kSrcOver, blendmode);
// When using the SrcOver mode, we must ensure that either a) we're
// completely occluding what's in the canvas with an opaque color, or
// b) whatever is in the canvas is already a solid color.
if (src_color.fA == 1.0 || *is_solid_color) {
*dst_color = DoSrcOverAlphaBlend(src_color, *dst_color);
*is_solid_color = true;
}
}
}
void CheckIfSolidColor(const SkCanvas& canvas,
SkColor4f color,
SkBlendMode blendmode,
bool* is_solid_color,
bool* is_transparent,
SkColor4f* out_color) {
SkRect rect;
if (!canvas.getLocalClipBounds(&rect)) {
*is_transparent = false;
*is_solid_color = false;
return;
}
bool does_cover_canvas = IsFullQuad(canvas, rect);
if (does_cover_canvas && ActsLikeClear(blendmode, color.fA))
*is_transparent = true;
else if (color.fA != 0.0f || blendmode != SkBlendMode::kSrc)
*is_transparent = false;
bool solid_color_candidate =
does_cover_canvas && IsSolidColorBlendMode(blendmode);
#if BUILDFLAG(IS_MAC)
// Additionally, on Mac, we require that the color is opaque due to
// https://crbug.com/922899.
// TODO(andrescj): remove this condition once that bug is fixed.
solid_color_candidate = (solid_color_candidate && color.fA == 1.0f);
#endif // BUILDFLAG(IS_MAC)
if (solid_color_candidate) {
CalculateSolidColor(color /* src_color */, blendmode,
out_color /* dst_color */, is_solid_color);
} else {
*is_solid_color = false;
}
}
template <typename T>
void CheckIfSolidShape(const SkCanvas& canvas,
const T& shape,
const PaintFlags& flags,
bool* is_solid_color,
bool* is_transparent,
SkColor4f* color) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"SolidColorAnalyzer::CheckIfSolidShape");
if (flags.nothingToDraw())
return;
bool does_cover_canvas = IsFullQuad(canvas, shape);
SkBlendMode blendmode = flags.getBlendMode();
if (does_cover_canvas && ActsLikeClear(blendmode, flags.getColor4f().fA)) {
*is_transparent = true;
} else if (!flags.isFullyTransparent() || blendmode != SkBlendMode::kSrc) {
*is_transparent = false;
}
if (does_cover_canvas && IsSolidColorPaint(flags)) {
CalculateSolidColor(flags.getColor4f() /* src_color */,
flags.getBlendMode(), color /* dst_color */,
is_solid_color);
} else {
*is_solid_color = false;
}
}
bool CheckIfRRectClipCoversCanvas(const SkCanvas& canvas,
const SkRRect& rrect) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"SolidColorAnalyzer::CheckIfRRectClipCoversCanvas");
return IsFullQuad(canvas, rrect);
}
} // namespace
std::optional<SkColor4f> SolidColorAnalyzer::DetermineIfSolidColor(
const PaintOpBuffer& buffer,
const gfx::Rect& rect,
int max_ops_to_analyze,
const std::vector<size_t>* offsets) {
if (buffer.size() == 0 || (offsets && offsets->empty())) {
return SkColors::kTransparent;
}
bool is_solid = true;
bool is_transparent = true;
SkColor4f color = SkColors::kTransparent;
struct Frame {
Frame(PaintOpBuffer::CompositeIterator iter,
const SkM44& original_ctm,
int save_count)
: iter(iter), original_ctm(original_ctm), save_count(save_count) {}
PaintOpBuffer::CompositeIterator iter;
const SkM44 original_ctm;
int save_count = 0;
};
SkNoDrawCanvas canvas(rect.width(), rect.height());
canvas.translate(-rect.x(), -rect.y());
canvas.clipRect(gfx::RectToSkRect(rect), SkClipOp::kIntersect, false);
std::vector<Frame> stack;
// We expect to see at least one DrawRecordOp because of the way items are
// constructed. Reserve this to 2, and go from there.
stack.reserve(2);
stack.emplace_back(PaintOpBuffer::CompositeIterator(buffer, offsets),
canvas.getLocalToDevice(), canvas.getSaveCount());
int num_draw_ops = 0;
while (!stack.empty()) {
auto& frame = stack.back();
if (!frame.iter) {
canvas.restoreToCount(frame.save_count);
stack.pop_back();
if (!stack.empty())
++stack.back().iter;
continue;
}
const PaintOp& op = *frame.iter;
PlaybackParams params(nullptr, SkM44(frame.original_ctm));
switch (op.GetType()) {
case PaintOpType::kDrawRecord: {
const auto& record_op = static_cast<const DrawRecordOp&>(op);
stack.emplace_back(PaintOpBuffer::CompositeIterator(
record_op.record.buffer(), nullptr),
canvas.getLocalToDevice(), canvas.getSaveCount());
continue;
}
// Any of the following ops result in non solid content.
case PaintOpType::kDrawDRRect:
case PaintOpType::kDrawImage:
case PaintOpType::kDrawImageRect:
case PaintOpType::kDrawIRect:
case PaintOpType::kDrawLine:
case PaintOpType::kDrawOval:
case PaintOpType::kDrawPath:
case PaintOpType::kDrawVertices:
return std::nullopt;
// TODO(vmpstr): Add more tests on exceeding max_ops_to_analyze.
case PaintOpType::kDrawRRect: {
if (++num_draw_ops > max_ops_to_analyze)
return std::nullopt;
const auto& rrect_op = static_cast<const DrawRRectOp&>(op);
CheckIfSolidShape(canvas, rrect_op.rrect, rrect_op.flags, &is_solid,
&is_transparent, &color);
break;
}
case PaintOpType::kDrawSkottie:
case PaintOpType::kDrawSlug:
case PaintOpType::kDrawTextBlob:
// Anything that has to do a save layer is probably not solid. As it will
// likely need more than one draw op.
// TODO(vmpstr): We could investigate handling these.
case PaintOpType::kSaveLayer:
case PaintOpType::kSaveLayerAlpha:
// Complex clips will probably result in non solid color as it might not
// cover the canvas.
// TODO(vmpstr): We could investigate handling these.
case PaintOpType::kClipPath:
return std::nullopt;
case PaintOpType::kClipRRect: {
const auto& rrect_op = static_cast<const ClipRRectOp&>(op);
bool does_cover_canvas =
CheckIfRRectClipCoversCanvas(canvas, rrect_op.rrect);
// If the clip covers the full canvas, we can treat it as if there's no
// clip at all and continue, otherwise this is no longer a solid color.
if (!does_cover_canvas)
return std::nullopt;
break;
}
case PaintOpType::kDrawRect: {
if (++num_draw_ops > max_ops_to_analyze)
return std::nullopt;
const auto& rect_op = static_cast<const DrawRectOp&>(op);
CheckIfSolidShape(canvas, rect_op.rect, rect_op.flags, &is_solid,
&is_transparent, &color);
break;
}
case PaintOpType::kDrawColor: {
if (++num_draw_ops > max_ops_to_analyze)
return std::nullopt;
const auto& color_op = static_cast<const DrawColorOp&>(op);
CheckIfSolidColor(canvas, color_op.color, color_op.mode, &is_solid,
&is_transparent, &color);
break;
}
case PaintOpType::kClipRect: {
// SolidColorAnalyzer uses an SkNoDrawCanvas which uses an
// SkNoPixelsDevice which says (without looking) that the canvas's
// clip is always a rect. So, if this clip could result in not
// a rect, this is no longer solid color.
const auto& clip_op = static_cast<const ClipRectOp&>(op);
if (clip_op.op == SkClipOp::kDifference)
return std::nullopt;
op.Raster(&canvas, params);
break;
}
// Don't affect the canvas, so ignore.
case PaintOpType::kAnnotate:
case PaintOpType::kCustomData:
case PaintOpType::kSetNodeId:
case PaintOpType::kNoop:
break;
// The rest of the ops should only affect our state canvas.
case PaintOpType::kConcat:
case PaintOpType::kScale:
case PaintOpType::kSetMatrix:
case PaintOpType::kRestore:
case PaintOpType::kRotate:
case PaintOpType::kSave:
case PaintOpType::kTranslate:
op.Raster(&canvas, params);
break;
}
++frame.iter;
}
if (is_transparent)
return SkColors::kTransparent;
if (is_solid)
return color;
return std::nullopt;
}
} // namespace cc