solid_color_analyzer.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. // Copyright 2017 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "cc/paint/solid_color_analyzer.h"
  5. #include <cmath>
  6. #include "base/trace_event/trace_event.h"
  7. #include "build/build_config.h"
  8. #include "cc/paint/paint_op_buffer.h"
  9. #include "third_party/skia/include/core/SkTypes.h"
  10. #include "third_party/skia/include/utils/SkNoDrawCanvas.h"
  11. namespace cc {
  12. namespace {
  13. SkColor4f DoSrcOverAlphaBlend(SkColor4f src, SkColor4f dst) {
  14. if (src.fA == 0.0f)
  15. return dst;
  16. if (src.fA == 1.0f)
  17. return src;
  18. // Note: using alpha blending formulas adapted from
  19. // https://en.wikipedia.org/wiki/Alpha_compositing:
  20. //
  21. // outA = srcA + dstA * (1 - srcA)
  22. // outRGB = srcRGB * (srcA / outA) + dstRGB * [dstA * (1 - srcA) / outA]
  23. const float out_alpha = src.fA + dst.fA * (1.0f - src.fA);
  24. if (out_alpha == 0.0f)
  25. return SkColors::kTransparent;
  26. const float inverse_out_alpha = 1.0f / out_alpha;
  27. const float src_weight = src.fA * inverse_out_alpha;
  28. const float dst_weight = dst.fA * (1.0f - src.fA) * inverse_out_alpha;
  29. const float out_red = (src.fR * src_weight + dst.fR * dst_weight);
  30. const float out_green = (src.fG * src_weight + dst.fG * dst_weight);
  31. const float out_blue = (src.fB * src_weight + dst.fB * dst_weight);
  32. return {out_red, out_green, out_blue, out_alpha};
  33. }
  34. bool ActsLikeClear(SkBlendMode mode, float src_alpha) {
  35. switch (mode) {
  36. case SkBlendMode::kClear:
  37. return true;
  38. case SkBlendMode::kSrc:
  39. case SkBlendMode::kSrcIn:
  40. case SkBlendMode::kDstIn:
  41. case SkBlendMode::kSrcOut:
  42. case SkBlendMode::kDstATop:
  43. return src_alpha == 0.0f;
  44. case SkBlendMode::kDstOut:
  45. return src_alpha == 1.0f;
  46. default:
  47. return false;
  48. }
  49. }
  50. bool IsSolidColorBlendMode(SkBlendMode blendmode) {
  51. return blendmode == SkBlendMode::kSrc || blendmode == SkBlendMode::kSrcOver;
  52. }
  53. bool IsSolidColorPaint(const PaintFlags& flags) {
  54. SkBlendMode blendmode = flags.getBlendMode();
  55. // Paint is solid color if the following holds:
  56. // - Style is fill, and there are no special effects.
  57. // - Blend mode is either kSrc or kSrcOver.
  58. bool is_solid_color =
  59. IsSolidColorBlendMode(blendmode) && !flags.HasShader() &&
  60. !flags.getLooper() && !flags.getMaskFilter() && !flags.getColorFilter() &&
  61. !flags.getImageFilter() && flags.getStyle() == PaintFlags::kFill_Style;
  62. #if BUILDFLAG(IS_MAC)
  63. // Additionally, on Mac, we require that the color is opaque due to
  64. // https://crbug.com/922899.
  65. // TODO(andrescj): remove this condition once that bug is fixed.
  66. is_solid_color = (is_solid_color && flags.getColor4f().fA == 1.0f);
  67. #endif // BUILDFLAG(IS_MAC)
  68. return is_solid_color;
  69. }
  70. // Returns true if the specified |drawn_shape| will cover the entire canvas
  71. // and that the canvas is not clipped (i.e. it covers ALL of the canvas).
  72. // We expect this method to return false most of the time so we take
  73. // conservative early-outs when possible.
  74. template <typename T>
  75. bool IsFullQuad(const SkCanvas& canvas, const T& drawn_shape) {
  76. if (!canvas.isClipRect())
  77. return false;
  78. SkIRect clip_bounds;
  79. if (!canvas.getDeviceClipBounds(&clip_bounds))
  80. return false;
  81. // if the clip is smaller than the canvas, we're partly clipped, so abort.
  82. if (!clip_bounds.contains(SkIRect::MakeSize(canvas.getBaseLayerSize())))
  83. return false;
  84. const SkM44& matrix = canvas.getLocalToDevice();
  85. // If the transform results in a non-axis aligned rectangle, then be
  86. // conservative and return false.
  87. if (!MathUtil::SkM44Preserves2DAxisAlignment(matrix))
  88. return false;
  89. SkM44 inverse;
  90. if (!matrix.invert(&inverse))
  91. return false;
  92. // Check that the drawn shape contains the canvas bounds when those bounds
  93. // are transformed into the shape's coordinate space. Since we know the
  94. // transform is axis aligned we only need to test two corners.
  95. SkV4 upper_left = inverse.map(clip_bounds.left(), clip_bounds.top(), 0, 1);
  96. SkV4 lower_right =
  97. inverse.map(clip_bounds.right(), clip_bounds.bottom(), 0, 1);
  98. SkRect transformed_clip_bounds = SkRect::MakeLTRB(
  99. upper_left.x, upper_left.y, lower_right.x, lower_right.y);
  100. return drawn_shape.contains(transformed_clip_bounds);
  101. }
  102. void CalculateSolidColor(SkColor4f src_color,
  103. SkBlendMode blendmode,
  104. SkColor4f* dst_color,
  105. bool* is_solid_color) {
  106. if (blendmode == SkBlendMode::kSrc) {
  107. // In the Src mode, we don't have to worry about what's in the canvas
  108. // because we'll replace it with |src_color|.
  109. *dst_color = src_color;
  110. *is_solid_color = true;
  111. } else {
  112. DCHECK_EQ(SkBlendMode::kSrcOver, blendmode);
  113. // When using the SrcOver mode, we must ensure that either a) we're
  114. // completely occluding what's in the canvas with an opaque color, or
  115. // b) whatever is in the canvas is already a solid color.
  116. if (src_color.fA == 1.0 || *is_solid_color) {
  117. *dst_color = DoSrcOverAlphaBlend(src_color, *dst_color);
  118. *is_solid_color = true;
  119. }
  120. }
  121. }
  122. void CheckIfSolidColor(const SkCanvas& canvas,
  123. SkColor4f color,
  124. SkBlendMode blendmode,
  125. bool* is_solid_color,
  126. bool* is_transparent,
  127. SkColor4f* out_color) {
  128. SkRect rect;
  129. if (!canvas.getLocalClipBounds(&rect)) {
  130. *is_transparent = false;
  131. *is_solid_color = false;
  132. return;
  133. }
  134. bool does_cover_canvas = IsFullQuad(canvas, rect);
  135. if (does_cover_canvas && ActsLikeClear(blendmode, color.fA))
  136. *is_transparent = true;
  137. else if (color.fA != 0.0f || blendmode != SkBlendMode::kSrc)
  138. *is_transparent = false;
  139. bool solid_color_candidate =
  140. does_cover_canvas && IsSolidColorBlendMode(blendmode);
  141. #if BUILDFLAG(IS_MAC)
  142. // Additionally, on Mac, we require that the color is opaque due to
  143. // https://crbug.com/922899.
  144. // TODO(andrescj): remove this condition once that bug is fixed.
  145. solid_color_candidate = (solid_color_candidate && color.fA == 1.0f);
  146. #endif // BUILDFLAG(IS_MAC)
  147. if (solid_color_candidate) {
  148. CalculateSolidColor(color /* src_color */, blendmode,
  149. out_color /* dst_color */, is_solid_color);
  150. } else {
  151. *is_solid_color = false;
  152. }
  153. }
  154. template <typename T>
  155. void CheckIfSolidShape(const SkCanvas& canvas,
  156. const T& shape,
  157. const PaintFlags& flags,
  158. bool* is_solid_color,
  159. bool* is_transparent,
  160. SkColor4f* color) {
  161. TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
  162. "SolidColorAnalyzer::CheckIfSolidShape");
  163. if (flags.nothingToDraw())
  164. return;
  165. bool does_cover_canvas = IsFullQuad(canvas, shape);
  166. SkBlendMode blendmode = flags.getBlendMode();
  167. if (does_cover_canvas && ActsLikeClear(blendmode, flags.getColor4f().fA))
  168. *is_transparent = true;
  169. else if (flags.getAlpha() != 0 || blendmode != SkBlendMode::kSrc)
  170. *is_transparent = false;
  171. if (does_cover_canvas && IsSolidColorPaint(flags)) {
  172. CalculateSolidColor(flags.getColor4f() /* src_color */,
  173. flags.getBlendMode(), color /* dst_color */,
  174. is_solid_color);
  175. } else {
  176. *is_solid_color = false;
  177. }
  178. }
  179. bool CheckIfRRectClipCoversCanvas(const SkCanvas& canvas,
  180. const SkRRect& rrect) {
  181. TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
  182. "SolidColorAnalyzer::CheckIfRRectClipCoversCanvas");
  183. return IsFullQuad(canvas, rrect);
  184. }
  185. } // namespace
  186. absl::optional<SkColor4f> SolidColorAnalyzer::DetermineIfSolidColor(
  187. const PaintOpBuffer* buffer,
  188. const gfx::Rect& rect,
  189. int max_ops_to_analyze,
  190. const std::vector<size_t>* offsets) {
  191. if (buffer->size() == 0 || (offsets && offsets->empty()))
  192. return SkColors::kTransparent;
  193. bool is_solid = true;
  194. bool is_transparent = true;
  195. SkColor4f color = SkColors::kTransparent;
  196. struct Frame {
  197. Frame(PaintOpBuffer::CompositeIterator iter,
  198. const SkM44& original_ctm,
  199. int save_count)
  200. : iter(iter), original_ctm(original_ctm), save_count(save_count) {}
  201. PaintOpBuffer::CompositeIterator iter;
  202. const SkM44 original_ctm;
  203. int save_count = 0;
  204. };
  205. SkNoDrawCanvas canvas(rect.width(), rect.height());
  206. canvas.translate(-rect.x(), -rect.y());
  207. canvas.clipRect(gfx::RectToSkRect(rect), SkClipOp::kIntersect, false);
  208. std::vector<Frame> stack;
  209. // We expect to see at least one DrawRecordOp because of the way items are
  210. // constructed. Reserve this to 2, and go from there.
  211. stack.reserve(2);
  212. stack.emplace_back(PaintOpBuffer::CompositeIterator(buffer, offsets),
  213. canvas.getLocalToDevice(), canvas.getSaveCount());
  214. int num_draw_ops = 0;
  215. while (!stack.empty()) {
  216. auto& frame = stack.back();
  217. if (!frame.iter) {
  218. canvas.restoreToCount(frame.save_count);
  219. stack.pop_back();
  220. if (!stack.empty())
  221. ++stack.back().iter;
  222. continue;
  223. }
  224. const PaintOp* op = *frame.iter;
  225. PlaybackParams params(nullptr, SkM44(frame.original_ctm));
  226. switch (op->GetType()) {
  227. case PaintOpType::DrawRecord: {
  228. const DrawRecordOp* record_op = static_cast<const DrawRecordOp*>(op);
  229. stack.emplace_back(
  230. PaintOpBuffer::CompositeIterator(record_op->record.get(), nullptr),
  231. canvas.getLocalToDevice(), canvas.getSaveCount());
  232. continue;
  233. }
  234. // Any of the following ops result in non solid content.
  235. case PaintOpType::DrawDRRect:
  236. case PaintOpType::DrawImage:
  237. case PaintOpType::DrawImageRect:
  238. case PaintOpType::DrawIRect:
  239. case PaintOpType::DrawLine:
  240. case PaintOpType::DrawOval:
  241. case PaintOpType::DrawPath:
  242. return absl::nullopt;
  243. // TODO(vmpstr): Add more tests on exceeding max_ops_to_analyze.
  244. case PaintOpType::DrawRRect: {
  245. if (++num_draw_ops > max_ops_to_analyze)
  246. return absl::nullopt;
  247. const DrawRRectOp* rrect_op = static_cast<const DrawRRectOp*>(op);
  248. CheckIfSolidShape(canvas, rrect_op->rrect, rrect_op->flags, &is_solid,
  249. &is_transparent, &color);
  250. break;
  251. }
  252. case PaintOpType::DrawSkottie:
  253. case PaintOpType::DrawTextBlob:
  254. // Anything that has to do a save layer is probably not solid. As it will
  255. // likely need more than one draw op.
  256. // TODO(vmpstr): We could investigate handling these.
  257. case PaintOpType::SaveLayer:
  258. case PaintOpType::SaveLayerAlpha:
  259. // Complex clips will probably result in non solid color as it might not
  260. // cover the canvas.
  261. // TODO(vmpstr): We could investigate handling these.
  262. case PaintOpType::ClipPath:
  263. return absl::nullopt;
  264. case PaintOpType::ClipRRect: {
  265. const ClipRRectOp* rrect_op = static_cast<const ClipRRectOp*>(op);
  266. bool does_cover_canvas =
  267. CheckIfRRectClipCoversCanvas(canvas, rrect_op->rrect);
  268. // If the clip covers the full canvas, we can treat it as if there's no
  269. // clip at all and continue, otherwise this is no longer a solid color.
  270. if (!does_cover_canvas)
  271. return absl::nullopt;
  272. break;
  273. }
  274. case PaintOpType::DrawRect: {
  275. if (++num_draw_ops > max_ops_to_analyze)
  276. return absl::nullopt;
  277. const DrawRectOp* rect_op = static_cast<const DrawRectOp*>(op);
  278. CheckIfSolidShape(canvas, rect_op->rect, rect_op->flags, &is_solid,
  279. &is_transparent, &color);
  280. break;
  281. }
  282. case PaintOpType::DrawColor: {
  283. if (++num_draw_ops > max_ops_to_analyze)
  284. return absl::nullopt;
  285. const DrawColorOp* color_op = static_cast<const DrawColorOp*>(op);
  286. CheckIfSolidColor(canvas, color_op->color, color_op->mode, &is_solid,
  287. &is_transparent, &color);
  288. break;
  289. }
  290. case PaintOpType::ClipRect: {
  291. // SolidColorAnalyzer uses an SkNoDrawCanvas which uses an
  292. // SkNoPixelsDevice which says (without looking) that the canvas's
  293. // clip is always a rect. So, if this clip could result in not
  294. // a rect, this is no longer solid color.
  295. const ClipRectOp* clip_op = static_cast<const ClipRectOp*>(op);
  296. if (clip_op->op == SkClipOp::kDifference)
  297. return absl::nullopt;
  298. op->Raster(&canvas, params);
  299. break;
  300. }
  301. // Don't affect the canvas, so ignore.
  302. case PaintOpType::Annotate:
  303. case PaintOpType::CustomData:
  304. case PaintOpType::SetNodeId:
  305. case PaintOpType::Noop:
  306. break;
  307. // The rest of the ops should only affect our state canvas.
  308. case PaintOpType::Concat:
  309. case PaintOpType::Scale:
  310. case PaintOpType::SetMatrix:
  311. case PaintOpType::Restore:
  312. case PaintOpType::Rotate:
  313. case PaintOpType::Save:
  314. case PaintOpType::Translate:
  315. op->Raster(&canvas, params);
  316. break;
  317. }
  318. ++frame.iter;
  319. }
  320. if (is_transparent)
  321. return SkColors::kTransparent;
  322. if (is_solid)
  323. return color;
  324. return absl::nullopt;
  325. }
  326. } // namespace cc