123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371 |
- // Copyright 2017 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 "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_buffer.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.getAlpha() != 0 || 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
- absl::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::DrawRecord: {
- const DrawRecordOp* record_op = static_cast<const DrawRecordOp*>(op);
- stack.emplace_back(
- PaintOpBuffer::CompositeIterator(record_op->record.get(), nullptr),
- canvas.getLocalToDevice(), canvas.getSaveCount());
- continue;
- }
- // Any of the following ops result in non solid content.
- case PaintOpType::DrawDRRect:
- case PaintOpType::DrawImage:
- case PaintOpType::DrawImageRect:
- case PaintOpType::DrawIRect:
- case PaintOpType::DrawLine:
- case PaintOpType::DrawOval:
- case PaintOpType::DrawPath:
- return absl::nullopt;
- // TODO(vmpstr): Add more tests on exceeding max_ops_to_analyze.
- case PaintOpType::DrawRRect: {
- if (++num_draw_ops > max_ops_to_analyze)
- return absl::nullopt;
- const DrawRRectOp* rrect_op = static_cast<const DrawRRectOp*>(op);
- CheckIfSolidShape(canvas, rrect_op->rrect, rrect_op->flags, &is_solid,
- &is_transparent, &color);
- break;
- }
- case PaintOpType::DrawSkottie:
- case PaintOpType::DrawTextBlob:
- // 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::SaveLayer:
- case PaintOpType::SaveLayerAlpha:
- // 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::ClipPath:
- return absl::nullopt;
- case PaintOpType::ClipRRect: {
- const ClipRRectOp* 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 absl::nullopt;
- break;
- }
- case PaintOpType::DrawRect: {
- if (++num_draw_ops > max_ops_to_analyze)
- return absl::nullopt;
- const DrawRectOp* rect_op = static_cast<const DrawRectOp*>(op);
- CheckIfSolidShape(canvas, rect_op->rect, rect_op->flags, &is_solid,
- &is_transparent, &color);
- break;
- }
- case PaintOpType::DrawColor: {
- if (++num_draw_ops > max_ops_to_analyze)
- return absl::nullopt;
- const DrawColorOp* color_op = static_cast<const DrawColorOp*>(op);
- CheckIfSolidColor(canvas, color_op->color, color_op->mode, &is_solid,
- &is_transparent, &color);
- break;
- }
- case PaintOpType::ClipRect: {
- // 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 ClipRectOp* clip_op = static_cast<const ClipRectOp*>(op);
- if (clip_op->op == SkClipOp::kDifference)
- return absl::nullopt;
- op->Raster(&canvas, params);
- break;
- }
- // Don't affect the canvas, so ignore.
- case PaintOpType::Annotate:
- case PaintOpType::CustomData:
- case PaintOpType::SetNodeId:
- case PaintOpType::Noop:
- break;
- // The rest of the ops should only affect our state canvas.
- case PaintOpType::Concat:
- case PaintOpType::Scale:
- case PaintOpType::SetMatrix:
- case PaintOpType::Restore:
- case PaintOpType::Rotate:
- case PaintOpType::Save:
- case PaintOpType::Translate:
- op->Raster(&canvas, params);
- break;
- }
- ++frame.iter;
- }
- if (is_transparent)
- return SkColors::kTransparent;
- if (is_solid)
- return color;
- return absl::nullopt;
- }
- } // namespace cc
|