blob: e14c571ecd18febdbfc6abb470767f2021631e7b [file] [log] [blame]
jvanverthe1a3bc62016-08-12 10:40:38 -07001
2/*
3 * Copyright 2016 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8#include "SampleCode.h"
9#include "SkBlurMask.h"
10#include "SkBlurMaskFilter.h"
11#include "SkCanvas.h"
jvanverth6c177a12016-08-17 07:59:41 -070012#include "SkGaussianEdgeShader.h"
jvanverthe1a3bc62016-08-12 10:40:38 -070013#include "SkPath.h"
14#include "SkPoint3.h"
15#include "SkUtils.h"
16#include "SkView.h"
17#include "sk_tool_utils.h"
18
jvanverth6c177a12016-08-17 07:59:41 -070019////////////////////////////////////////////////////////////////////////////
20
jvanverthe1a3bc62016-08-12 10:40:38 -070021class ShadowsView : public SampleView {
22 SkPath fRectPath;
23 SkPath fRRPath;
24 SkPath fCirclePath;
25 SkPoint3 fLightPos;
26
27 bool fShowAmbient;
28 bool fShowSpot;
jvanverthd7315f912016-08-17 10:06:18 -070029 bool fUseAlt;
jvanverthe1a3bc62016-08-12 10:40:38 -070030 bool fShowObject;
31
32public:
33 ShadowsView()
34 : fShowAmbient(true)
35 , fShowSpot(true)
jvanverthd7315f912016-08-17 10:06:18 -070036 , fUseAlt(true)
jvanverthe1a3bc62016-08-12 10:40:38 -070037 , fShowObject(true) {}
38
39protected:
40 void onOnceBeforeDraw() override {
41 fCirclePath.addCircle(0, 0, 50);
42 fRectPath.addRect(SkRect::MakeXYWH(-100, -50, 200, 100));
43 fRRPath.addRRect(SkRRect::MakeRectXY(SkRect::MakeXYWH(-100, -50, 200, 100), 4, 4));
jvanverthd99858a2016-09-12 07:51:04 -070044 fLightPos = SkPoint3::Make(-700, -700, 2800);
jvanverthe1a3bc62016-08-12 10:40:38 -070045 }
46
47 // overrides from SkEventSink
48 bool onQuery(SkEvent* evt) override {
49 if (SampleCode::TitleQ(*evt)) {
50 SampleCode::TitleR(evt, "AndroidShadows");
51 return true;
52 }
53
54 SkUnichar uni;
55 if (SampleCode::CharQ(*evt, &uni)) {
56 switch (uni) {
57 case 'B':
58 fShowAmbient = !fShowAmbient;
59 break;
60 case 'S':
61 fShowSpot = !fShowSpot;
62 break;
jvanverthd7315f912016-08-17 10:06:18 -070063 case 'T':
64 fUseAlt = !fUseAlt;
65 break;
jvanverthe1a3bc62016-08-12 10:40:38 -070066 case 'O':
67 fShowObject = !fShowObject;
68 break;
69 case '>':
70 fLightPos.fZ += 10;
71 break;
72 case '<':
73 fLightPos.fZ -= 10;
74 break;
75 default:
76 break;
77 }
78 this->inval(nullptr);
79 }
80 return this->INHERITED::onQuery(evt);
81 }
82
83 void drawBG(SkCanvas* canvas) {
84 canvas->drawColor(0xFFDDDDDD);
85 }
86
87 static void GetOcclRect(const SkPath& path, SkRect* occlRect) {
88 SkRect pathRect;
89 SkRRect pathRRect;
90 if (path.isOval(&pathRect)) {
91 *occlRect = sk_tool_utils::compute_central_occluder(SkRRect::MakeOval(pathRect));
92 } else if (path.isRRect(&pathRRect)) {
93 *occlRect = sk_tool_utils::compute_central_occluder(pathRRect);
94 } else if (path.isRect(occlRect)) {
95 // the inverse transform for the spot shadow occluder doesn't always get us
96 // back to exactly the same position, so deducting a little slop
97 occlRect->inset(1, 1);
98 } else {
99 *occlRect = SkRect::MakeEmpty();
100 }
101 }
102
103 void drawAmbientShadow(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
104 SkScalar ambientAlpha) {
105
106 if (ambientAlpha <= 0) {
107 return;
108 }
109
110 const SkScalar kHeightFactor = 1.f / 128.f;
111 const SkScalar kGeomFactor = 64;
112
113 SkScalar umbraAlpha = 1 / (1 + SkMaxScalar(zValue*kHeightFactor, 0));
114 SkScalar radius = zValue*kHeightFactor*kGeomFactor;
115
116 // occlude blur
117 SkRect occlRect;
118 GetOcclRect(path, &occlRect);
119 sk_sp<SkMaskFilter> mf = SkBlurMaskFilter::Make(kNormal_SkBlurStyle,
120 SkBlurMask::ConvertRadiusToSigma(radius),
121 occlRect,
122 SkBlurMaskFilter::kNone_BlurFlag);
123
124 SkPaint paint;
125 paint.setAntiAlias(true);
126 paint.setMaskFilter(std::move(mf));
127 paint.setColor(SkColorSetARGB((unsigned char)(ambientAlpha*umbraAlpha*255.999f), 0, 0, 0));
128 canvas->drawPath(path, paint);
129
130 // draw occlusion rect
jvanverth6c177a12016-08-17 07:59:41 -0700131#if DRAW_OCCL_RECT
jvanverthe1a3bc62016-08-12 10:40:38 -0700132 SkPaint stroke;
133 stroke.setStyle(SkPaint::kStroke_Style);
134 stroke.setColor(SK_ColorBLUE);
135 canvas->drawRect(occlRect, stroke);
jvanverth6c177a12016-08-17 07:59:41 -0700136#endif
137 }
138
139 void drawAmbientShadowAlt(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
140 SkScalar ambientAlpha) {
141
142 if (ambientAlpha <= 0) {
143 return;
144 }
145
146 const SkScalar kHeightFactor = 1.f / 128.f;
147 const SkScalar kGeomFactor = 64;
148
149 SkScalar umbraAlpha = 1 / (1 + SkMaxScalar(zValue*kHeightFactor, 0));
150 SkScalar radius = zValue*kHeightFactor*kGeomFactor;
jvanverthd99858a2016-09-12 07:51:04 -0700151 // distance to outer of edge of geometry from original shape edge
152 SkScalar offset = radius*umbraAlpha;
jvanverth6c177a12016-08-17 07:59:41 -0700153
jvanvertha4f1af82016-08-29 07:17:47 -0700154 SkRect pathRect;
155 SkRRect pathRRect;
jvanverthd99858a2016-09-12 07:51:04 -0700156 SkScalar scaleFactors[2];
157 if (!canvas->getTotalMatrix().getMinMaxScales(scaleFactors)) {
158 return;
159 }
160 if (scaleFactors[0] != scaleFactors[1] || radius*scaleFactors[0] >= 64 ||
jvanvertha4f1af82016-08-29 07:17:47 -0700161 !((path.isOval(&pathRect) && pathRect.width() == pathRect.height()) ||
162 (path.isRRect(&pathRRect) && pathRRect.allCornersCircular()) ||
163 path.isRect(&pathRect))) {
164 this->drawAmbientShadow(canvas, path, zValue, ambientAlpha);
165 return;
166 }
167
jvanverthd99858a2016-09-12 07:51:04 -0700168 // For all of these, we inset the offset rect by half the radius to get our stroke shape.
169 SkScalar strokeOutset = offset - SK_ScalarHalf*radius;
170 // Make sure we'll have a radius of at least 0.5 after xform
171 if (strokeOutset*scaleFactors[0] < 0.5f) {
172 strokeOutset = 0.5f / scaleFactors[0];
173 }
jvanverthd7315f912016-08-17 10:06:18 -0700174 if (path.isOval(nullptr)) {
jvanverthd99858a2016-09-12 07:51:04 -0700175 pathRect.outset(strokeOutset, strokeOutset);
jvanverthd7315f912016-08-17 10:06:18 -0700176 pathRRect = SkRRect::MakeOval(pathRect);
177 } else if (path.isRect(nullptr)) {
jvanverthd99858a2016-09-12 07:51:04 -0700178 pathRect.outset(strokeOutset, strokeOutset);
179 pathRRect = SkRRect::MakeRectXY(pathRect, strokeOutset, strokeOutset);
jvanverth6c177a12016-08-17 07:59:41 -0700180 } else {
jvanverthd99858a2016-09-12 07:51:04 -0700181 pathRRect.outset(strokeOutset, strokeOutset);
jvanverth6c177a12016-08-17 07:59:41 -0700182 }
183
jvanverthd7315f912016-08-17 10:06:18 -0700184 SkPaint paint;
185 paint.setAntiAlias(true);
jvanvertha4f1af82016-08-29 07:17:47 -0700186 paint.setStyle(SkPaint::kStroke_Style);
187 // we outset the stroke a little to cover up AA on the interior edge
jvanverthd99858a2016-09-12 07:51:04 -0700188 SkScalar pad = 0.5f;
189 paint.setStrokeWidth(radius + 2*pad);
190 // handle scale of radius and pad due to CTM
191 radius *= scaleFactors[0];
192 pad *= scaleFactors[0];
193 SkASSERT(radius < 16384);
194 SkASSERT(pad < 64);
195 // Convert radius to 14.2 fixed point and place in the R & G components.
196 // Convert pad to 6.2 fixed point and place in the B component.
197 uint16_t iRadius = (uint16_t)(radius*4.0f);
198 unsigned char alpha = (unsigned char)(ambientAlpha*255.999f);
199 paint.setColor(SkColorSetARGB(alpha, iRadius >> 8, iRadius & 0xff,
200 (unsigned char)(4.0f*pad)));
jvanverthd7315f912016-08-17 10:06:18 -0700201
jvanverthd99858a2016-09-12 07:51:04 -0700202 paint.setShader(SkGaussianEdgeShader::Make(true));
jvanverthd7315f912016-08-17 10:06:18 -0700203 canvas->drawRRect(pathRRect, paint);
jvanverthe1a3bc62016-08-12 10:40:38 -0700204 }
205
206 void drawSpotShadow(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
207 SkPoint3 lightPos, SkScalar lightWidth, SkScalar spotAlpha) {
208 if (spotAlpha <= 0) {
209 return;
210 }
211
212 SkScalar zRatio = zValue / (lightPos.fZ - zValue);
213 if (zRatio < 0.0f) {
214 zRatio = 0.0f;
215 } else if (zRatio > 0.95f) {
216 zRatio = 0.95f;
217 }
jvanverthd99858a2016-09-12 07:51:04 -0700218 SkScalar blurRadius = lightWidth*zRatio;
jvanverthe1a3bc62016-08-12 10:40:38 -0700219
220 // compute the transformation params
221 SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY());
jvanverthd99858a2016-09-12 07:51:04 -0700222 SkMatrix ctmInverse;
223 if (!canvas->getTotalMatrix().invert(&ctmInverse)) {
224 return;
jvanverthe1a3bc62016-08-12 10:40:38 -0700225 }
jvanverthd99858a2016-09-12 07:51:04 -0700226 SkPoint lightPos2D = SkPoint::Make(lightPos.fX, lightPos.fY);
227 ctmInverse.mapPoints(&lightPos2D, 1);
228 SkPoint offset = SkPoint::Make(zRatio*(center.fX - lightPos2D.fX),
229 zRatio*(center.fY - lightPos2D.fY));
230 SkScalar scale = lightPos.fZ / (lightPos.fZ - zValue);
jvanverthe1a3bc62016-08-12 10:40:38 -0700231
232 SkAutoCanvasRestore acr(canvas, true);
233
jvanverthe1a3bc62016-08-12 10:40:38 -0700234 sk_sp<SkMaskFilter> mf = SkBlurMaskFilter::Make(kNormal_SkBlurStyle,
jvanverthd99858a2016-09-12 07:51:04 -0700235 SkBlurMask::ConvertRadiusToSigma(blurRadius),
jvanverthe1a3bc62016-08-12 10:40:38 -0700236 SkBlurMaskFilter::kNone_BlurFlag);
237
238 SkPaint paint;
239 paint.setAntiAlias(true);
240 paint.setMaskFilter(std::move(mf));
241 paint.setColor(SkColorSetARGB((unsigned char)(spotAlpha*255.999f), 0, 0, 0));
242
243 // apply transformation to shadow
jvanverthe1a3bc62016-08-12 10:40:38 -0700244 canvas->scale(scale, scale);
jvanverthd99858a2016-09-12 07:51:04 -0700245 canvas->translate(offset.fX, offset.fY);
jvanverthe1a3bc62016-08-12 10:40:38 -0700246 canvas->drawPath(path, paint);
jvanverthe1a3bc62016-08-12 10:40:38 -0700247 }
248
jvanverthd7315f912016-08-17 10:06:18 -0700249 void drawSpotShadowAlt(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
250 SkPoint3 lightPos, SkScalar lightWidth, SkScalar spotAlpha) {
251 if (spotAlpha <= 0) {
252 return;
253 }
jvanverthe1a3bc62016-08-12 10:40:38 -0700254
jvanverthd7315f912016-08-17 10:06:18 -0700255 SkScalar zRatio = zValue / (lightPos.fZ - zValue);
256 if (zRatio < 0.0f) {
257 zRatio = 0.0f;
258 } else if (zRatio > 0.95f) {
259 zRatio = 0.95f;
260 }
jvanverthd99858a2016-09-12 07:51:04 -0700261 SkScalar radius = 2.0f*lightWidth*zRatio;
jvanverthd7315f912016-08-17 10:06:18 -0700262
jvanvertha4f1af82016-08-29 07:17:47 -0700263 SkRect pathRect;
264 SkRRect pathRRect;
jvanverthd99858a2016-09-12 07:51:04 -0700265 SkScalar scaleFactors[2];
266 if (!canvas->getTotalMatrix().getMinMaxScales(scaleFactors)) {
267 return;
268 }
269 if (scaleFactors[0] != scaleFactors[1] || radius*scaleFactors[0] >= 16384 ||
jvanvertha4f1af82016-08-29 07:17:47 -0700270 !((path.isOval(&pathRect) && pathRect.width() == pathRect.height()) ||
271 (path.isRRect(&pathRRect) && pathRRect.allCornersCircular()) ||
272 path.isRect(&pathRect))) {
273 this->drawSpotShadow(canvas, path, zValue, lightPos, lightWidth, spotAlpha);
274 return;
275 }
276
jvanverthd99858a2016-09-12 07:51:04 -0700277 // For all of these, we need to ensure we have a rrect with radius >= 0.5f in device space
278 SkScalar minRadius = SK_ScalarHalf/scaleFactors[0];
jvanverthd7315f912016-08-17 10:06:18 -0700279 if (path.isOval(nullptr)) {
jvanverthd7315f912016-08-17 10:06:18 -0700280 pathRRect = SkRRect::MakeOval(pathRect);
281 } else if (path.isRect(nullptr)) {
jvanverthd99858a2016-09-12 07:51:04 -0700282 pathRRect = SkRRect::MakeRectXY(pathRect, minRadius, minRadius);
jvanverthd7315f912016-08-17 10:06:18 -0700283 } else {
jvanverthd99858a2016-09-12 07:51:04 -0700284 if (pathRRect.getSimpleRadii().fX < minRadius) {
285 pathRRect.setRectXY(pathRRect.rect(), minRadius, minRadius);
286 }
jvanverthd7315f912016-08-17 10:06:18 -0700287 }
288
jvanverthd99858a2016-09-12 07:51:04 -0700289 // compute the scale and translation for the shadow
290 SkScalar scale = lightPos.fZ / (lightPos.fZ - zValue);
291 SkRRect shadowRRect;
292 pathRRect.transform(SkMatrix::MakeScale(scale, scale), &shadowRRect);
293 SkPoint center = SkPoint::Make(shadowRRect.rect().centerX(), shadowRRect.rect().centerY());
294 SkMatrix ctmInverse;
295 if (!canvas->getTotalMatrix().invert(&ctmInverse)) {
296 return;
297 }
298 SkPoint lightPos2D = SkPoint::Make(lightPos.fX, lightPos.fY);
299 ctmInverse.mapPoints(&lightPos2D, 1);
300 SkPoint offset = SkPoint::Make(zRatio*(center.fX - lightPos2D.fX),
301 zRatio*(center.fY - lightPos2D.fY));
jvanverthd7315f912016-08-17 10:06:18 -0700302 SkAutoCanvasRestore acr(canvas, true);
303
304 SkPaint paint;
305 paint.setAntiAlias(true);
jvanvertha4f1af82016-08-29 07:17:47 -0700306 // We outset the stroke by the length of the translation so the shadow extends to
307 // the edge of the shape. We also add 1/2 to cover up AA on the interior edge.
308 SkScalar pad = offset.length() + 0.5f;
309 // compute area
jvanverthd99858a2016-09-12 07:51:04 -0700310 SkScalar strokeWidth = radius + 2.0f*pad/scaleFactors[0];
311 SkScalar strokedArea = 2.0f*strokeWidth*(shadowRRect.width() + shadowRRect.height());
312 SkScalar filledArea = (shadowRRect.height() + radius)*(shadowRRect.width() + radius);
jvanvertha4f1af82016-08-29 07:17:47 -0700313 // If the area of the stroked geometry is larger than the fill geometry, or
314 // if our pad is too big to convert to 6.2 fixed point, just fill it.
315 if (strokedArea > filledArea || pad >= 64) {
316 pad = 0;
317 paint.setStyle(SkPaint::kStrokeAndFill_Style);
318 paint.setStrokeWidth(radius);
319 } else {
320 paint.setStyle(SkPaint::kStroke_Style);
321 paint.setStrokeWidth(strokeWidth);
322 }
jvanverthd99858a2016-09-12 07:51:04 -0700323 paint.setShader(SkGaussianEdgeShader::Make(true));
jvanverthd7315f912016-08-17 10:06:18 -0700324 // handle scale of radius due to CTM
jvanverthd99858a2016-09-12 07:51:04 -0700325 radius *= scaleFactors[0];
326 // don't need to scale pad as it was computed from the transformed offset
327 SkASSERT(radius < 16384);
jvanvertha4f1af82016-08-29 07:17:47 -0700328 SkASSERT(pad < 64);
jvanverthd99858a2016-09-12 07:51:04 -0700329 // Convert radius to 14.2 fixed point and place in the R & G components.
330 // Convert pad to 6.2 fixed point and place in the B component.
331 uint16_t iRadius = (uint16_t)(radius*4.0f);
332 unsigned char alpha = (unsigned char)(spotAlpha*255.999f);
333 paint.setColor(SkColorSetARGB(alpha, iRadius >> 8, iRadius & 0xff,
334 (unsigned char)(4.0f*pad)));
jvanverthd7315f912016-08-17 10:06:18 -0700335
336 // apply transformation to shadow
337 canvas->translate(offset.fX, offset.fY);
jvanverthd99858a2016-09-12 07:51:04 -0700338 canvas->drawRRect(shadowRRect, paint);
jvanverthd7315f912016-08-17 10:06:18 -0700339 }
340
341 void drawShadowedPath(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
342 const SkPaint& paint, SkScalar ambientAlpha,
343 const SkPoint3& lightPos, SkScalar lightWidth, SkScalar spotAlpha) {
jvanverthe1a3bc62016-08-12 10:40:38 -0700344 if (fShowAmbient) {
jvanverthd7315f912016-08-17 10:06:18 -0700345 if (fUseAlt) {
346 this->drawAmbientShadowAlt(canvas, path, zValue, ambientAlpha);
jvanverth6c177a12016-08-17 07:59:41 -0700347 } else {
jvanverthd7315f912016-08-17 10:06:18 -0700348 this->drawAmbientShadow(canvas, path, zValue, ambientAlpha);
jvanverth6c177a12016-08-17 07:59:41 -0700349 }
jvanverthe1a3bc62016-08-12 10:40:38 -0700350 }
351 if (fShowSpot) {
jvanverthd7315f912016-08-17 10:06:18 -0700352 if (fUseAlt) {
353 this->drawSpotShadowAlt(canvas, path, zValue, lightPos, lightWidth, spotAlpha);
354 } else {
355 this->drawSpotShadow(canvas, path, zValue, lightPos, lightWidth, spotAlpha);
356 }
jvanverthe1a3bc62016-08-12 10:40:38 -0700357 }
358 if (fShowObject) {
359 canvas->drawPath(path, paint);
360 }
361 }
362
363 void onDrawContent(SkCanvas* canvas) override {
364 this->drawBG(canvas);
jvanverthd99858a2016-09-12 07:51:04 -0700365 const SkScalar kLightWidth = 2800;
jvanverthd7315f912016-08-17 10:06:18 -0700366 const SkScalar kAmbientAlpha = 0.25f;
367 const SkScalar kSpotAlpha = 0.25f;
jvanverthe1a3bc62016-08-12 10:40:38 -0700368
369 SkPaint paint;
370 paint.setAntiAlias(true);
371
jvanverthd7315f912016-08-17 10:06:18 -0700372 SkPoint3 lightPos = fLightPos;
373
jvanverthe1a3bc62016-08-12 10:40:38 -0700374 paint.setColor(SK_ColorWHITE);
375 canvas->translate(200, 90);
jvanverthd7315f912016-08-17 10:06:18 -0700376 lightPos.fX += 200;
377 lightPos.fY += 90;
jvanverthd99858a2016-09-12 07:51:04 -0700378 this->drawShadowedPath(canvas, fRectPath, 2, paint, kAmbientAlpha,
jvanverthd7315f912016-08-17 10:06:18 -0700379 lightPos, kLightWidth, kSpotAlpha);
jvanverthe1a3bc62016-08-12 10:40:38 -0700380
381 paint.setColor(SK_ColorRED);
382 canvas->translate(250, 0);
jvanverthd7315f912016-08-17 10:06:18 -0700383 lightPos.fX += 250;
jvanverthd99858a2016-09-12 07:51:04 -0700384 this->drawShadowedPath(canvas, fRRPath, 4, paint, kAmbientAlpha,
jvanverthd7315f912016-08-17 10:06:18 -0700385 lightPos, kLightWidth, kSpotAlpha);
jvanverthe1a3bc62016-08-12 10:40:38 -0700386
387 paint.setColor(SK_ColorBLUE);
388 canvas->translate(-250, 110);
jvanverthd7315f912016-08-17 10:06:18 -0700389 lightPos.fX -= 250;
390 lightPos.fY += 110;
jvanverthd99858a2016-09-12 07:51:04 -0700391 this->drawShadowedPath(canvas, fCirclePath, 8, paint, 0.0f,
jvanverthd7315f912016-08-17 10:06:18 -0700392 lightPos, kLightWidth, 0.5f);
jvanverth6c177a12016-08-17 07:59:41 -0700393
394 paint.setColor(SK_ColorGREEN);
395 canvas->translate(250, 0);
jvanverthd7315f912016-08-17 10:06:18 -0700396 lightPos.fX += 250;
jvanverthd99858a2016-09-12 07:51:04 -0700397 this->drawShadowedPath(canvas, fRRPath, 64, paint, kAmbientAlpha,
jvanverthd7315f912016-08-17 10:06:18 -0700398 lightPos, kLightWidth, kSpotAlpha);
jvanverthe1a3bc62016-08-12 10:40:38 -0700399 }
400
401protected:
402 SkView::Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned modi) override {
403 return new SkView::Click(this);
404 }
405
406 bool onClick(Click *click) override {
407 SkScalar x = click->fCurr.fX;
408 SkScalar y = click->fCurr.fY;
409
410 SkScalar dx = x - click->fPrev.fX;
411 SkScalar dy = y - click->fPrev.fY;
412
413 if (dx != 0 || dy != 0) {
414 fLightPos.fX += dx;
415 fLightPos.fY += dy;
416 this->inval(nullptr);
417 }
418
419 return true;
420 }
421
422private:
423 typedef SkView INHERITED;
424};
425
426//////////////////////////////////////////////////////////////////////////////
427
428static SkView* MyFactory() { return new ShadowsView; }
429static SkViewRegister reg(MyFactory);