1 | // Copyright 2013 The Flutter 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 | |
5 | #include "flutter/flow/layers/physical_shape_layer.h" |
6 | |
7 | #include "flutter/flow/paint_utils.h" |
8 | #include "third_party/skia/include/utils/SkShadowUtils.h" |
9 | |
10 | namespace flutter { |
11 | |
12 | const SkScalar kLightHeight = 600; |
13 | const SkScalar kLightRadius = 800; |
14 | |
15 | PhysicalShapeLayer::PhysicalShapeLayer(SkColor color, |
16 | SkColor shadow_color, |
17 | float elevation, |
18 | const SkPath& path, |
19 | Clip clip_behavior) |
20 | : color_(color), |
21 | shadow_color_(shadow_color), |
22 | elevation_(elevation), |
23 | path_(path), |
24 | clip_behavior_(clip_behavior) {} |
25 | |
26 | void PhysicalShapeLayer::Preroll(PrerollContext* context, |
27 | const SkMatrix& matrix) { |
28 | TRACE_EVENT0("flutter" , "PhysicalShapeLayer::Preroll" ); |
29 | Layer::AutoPrerollSaveLayerState save = |
30 | Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer()); |
31 | |
32 | SkRect child_paint_bounds; |
33 | PrerollChildren(context, matrix, &child_paint_bounds); |
34 | |
35 | if (elevation_ == 0) { |
36 | set_paint_bounds(path_.getBounds()); |
37 | } else { |
38 | // We will draw the shadow in Paint(), so add some margin to the paint |
39 | // bounds to leave space for the shadow. We fill this whole region and clip |
40 | // children to it so we don't need to join the child paint bounds. |
41 | set_paint_bounds(ComputeShadowBounds(path_.getBounds(), elevation_, |
42 | context->frame_device_pixel_ratio)); |
43 | } |
44 | } |
45 | |
46 | void PhysicalShapeLayer::Paint(PaintContext& context) const { |
47 | TRACE_EVENT0("flutter" , "PhysicalShapeLayer::Paint" ); |
48 | FML_DCHECK(needs_painting()); |
49 | |
50 | if (elevation_ != 0) { |
51 | DrawShadow(context.leaf_nodes_canvas, path_, shadow_color_, elevation_, |
52 | SkColorGetA(color_) != 0xff, context.frame_device_pixel_ratio); |
53 | } |
54 | |
55 | // Call drawPath without clip if possible for better performance. |
56 | SkPaint paint; |
57 | paint.setColor(color_); |
58 | paint.setAntiAlias(true); |
59 | if (clip_behavior_ != Clip::antiAliasWithSaveLayer) { |
60 | context.leaf_nodes_canvas->drawPath(path_, paint); |
61 | } |
62 | |
63 | int saveCount = context.internal_nodes_canvas->save(); |
64 | switch (clip_behavior_) { |
65 | case Clip::hardEdge: |
66 | context.internal_nodes_canvas->clipPath(path_, false); |
67 | break; |
68 | case Clip::antiAlias: |
69 | context.internal_nodes_canvas->clipPath(path_, true); |
70 | break; |
71 | case Clip::antiAliasWithSaveLayer: |
72 | context.internal_nodes_canvas->clipPath(path_, true); |
73 | context.internal_nodes_canvas->saveLayer(paint_bounds(), nullptr); |
74 | break; |
75 | case Clip::none: |
76 | break; |
77 | } |
78 | |
79 | if (UsesSaveLayer()) { |
80 | // If we want to avoid the bleeding edge artifact |
81 | // (https://github.com/flutter/flutter/issues/18057#issue-328003931) |
82 | // using saveLayer, we have to call drawPaint instead of drawPath as |
83 | // anti-aliased drawPath will always have such artifacts. |
84 | context.leaf_nodes_canvas->drawPaint(paint); |
85 | } |
86 | |
87 | PaintChildren(context); |
88 | |
89 | context.internal_nodes_canvas->restoreToCount(saveCount); |
90 | } |
91 | |
92 | SkRect PhysicalShapeLayer::ComputeShadowBounds(const SkRect& bounds, |
93 | float elevation, |
94 | float pixel_ratio) { |
95 | // The shadow offset is calculated as follows: |
96 | // .--- (kLightRadius) |
97 | // -------/ (light) |
98 | // | / |
99 | // | / |
100 | // |/ |
101 | // |O |
102 | // /| (kLightHeight) |
103 | // / | |
104 | // / | |
105 | // / | |
106 | // / | |
107 | // ------------- (layer) |
108 | // /| | |
109 | // / | | (elevation) |
110 | // A / | |B |
111 | // ------------------------------------------------ (canvas) |
112 | // --- (extent of shadow) |
113 | // |
114 | // E = lt } t = (r + w/2)/h |
115 | // } => |
116 | // r + w/2 = ht } E = (l/h)(r + w/2) |
117 | // |
118 | // Where: E = extent of shadow |
119 | // l = elevation of layer |
120 | // r = radius of the light source |
121 | // w = width of the layer |
122 | // h = light height |
123 | // t = tangent of AOB, i.e., multiplier for elevation to extent |
124 | // tangent for x |
125 | double tx = |
126 | (kLightRadius * pixel_ratio + bounds.width() * 0.5) / kLightHeight; |
127 | // tangent for y |
128 | double ty = |
129 | (kLightRadius * pixel_ratio + bounds.height() * 0.5) / kLightHeight; |
130 | SkRect shadow_bounds(bounds); |
131 | shadow_bounds.outset(elevation * tx, elevation * ty); |
132 | |
133 | return shadow_bounds; |
134 | } |
135 | |
136 | void PhysicalShapeLayer::DrawShadow(SkCanvas* canvas, |
137 | const SkPath& path, |
138 | SkColor color, |
139 | float elevation, |
140 | bool transparentOccluder, |
141 | SkScalar dpr) { |
142 | const SkScalar kAmbientAlpha = 0.039f; |
143 | const SkScalar kSpotAlpha = 0.25f; |
144 | |
145 | SkShadowFlags flags = transparentOccluder |
146 | ? SkShadowFlags::kTransparentOccluder_ShadowFlag |
147 | : SkShadowFlags::kNone_ShadowFlag; |
148 | const SkRect& bounds = path.getBounds(); |
149 | SkScalar shadow_x = (bounds.left() + bounds.right()) / 2; |
150 | SkScalar shadow_y = bounds.top() - 600.0f; |
151 | SkColor inAmbient = SkColorSetA(color, kAmbientAlpha * SkColorGetA(color)); |
152 | SkColor inSpot = SkColorSetA(color, kSpotAlpha * SkColorGetA(color)); |
153 | SkColor ambientColor, spotColor; |
154 | SkShadowUtils::ComputeTonalColors(inAmbient, inSpot, &ambientColor, |
155 | &spotColor); |
156 | SkShadowUtils::DrawShadow( |
157 | canvas, path, SkPoint3::Make(0, 0, dpr * elevation), |
158 | SkPoint3::Make(shadow_x, shadow_y, dpr * kLightHeight), |
159 | dpr * kLightRadius, ambientColor, spotColor, flags); |
160 | } |
161 | |
162 | } // namespace flutter |
163 | |