blob: f411e5c9b3999301ce7ff9298f30e174c7648828 [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));
jvanverthd7315f912016-08-17 10:06:18 -070044 fLightPos = SkPoint3::Make(-2, -2, 6);
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;
151
jvanvertha4f1af82016-08-29 07:17:47 -0700152 SkRect pathRect;
153 SkRRect pathRRect;
154 if (radius >= 64 ||
155 !((path.isOval(&pathRect) && pathRect.width() == pathRect.height()) ||
156 (path.isRRect(&pathRRect) && pathRRect.allCornersCircular()) ||
157 path.isRect(&pathRect))) {
158 this->drawAmbientShadow(canvas, path, zValue, ambientAlpha);
159 return;
160 }
161
162 // For all of these, we outset the rect by half the radius to get our stroke shape.
163 SkScalar halfRadius = SK_ScalarHalf*radius;
jvanverthd7315f912016-08-17 10:06:18 -0700164 if (path.isOval(nullptr)) {
jvanvertha4f1af82016-08-29 07:17:47 -0700165 pathRect.outset(halfRadius, halfRadius);
jvanverthd7315f912016-08-17 10:06:18 -0700166 pathRRect = SkRRect::MakeOval(pathRect);
167 } else if (path.isRect(nullptr)) {
jvanvertha4f1af82016-08-29 07:17:47 -0700168 pathRect.outset(halfRadius, halfRadius);
169 pathRRect = SkRRect::MakeRectXY(pathRect, halfRadius, halfRadius);
jvanverth6c177a12016-08-17 07:59:41 -0700170 } else {
jvanvertha4f1af82016-08-29 07:17:47 -0700171 pathRRect.outset(halfRadius, halfRadius);
jvanverth6c177a12016-08-17 07:59:41 -0700172 }
173
jvanverthd7315f912016-08-17 10:06:18 -0700174 SkPaint paint;
175 paint.setAntiAlias(true);
jvanvertha4f1af82016-08-29 07:17:47 -0700176 paint.setStyle(SkPaint::kStroke_Style);
177 // we outset the stroke a little to cover up AA on the interior edge
178 paint.setStrokeWidth(radius + 1);
jvanverthd7315f912016-08-17 10:06:18 -0700179 // handle scale of radius due to CTM
180 SkScalar maxScale = canvas->getTotalMatrix().getMaxScale();
181 radius *= maxScale;
182 unsigned char gray = (unsigned char)(ambientAlpha*umbraAlpha*255.999f);
jvanvertha4f1af82016-08-29 07:17:47 -0700183 SkASSERT(radius < 64);
184 // Convert radius to 6.2 fixed point and place in the G component.
185 paint.setColor(SkColorSetARGB(1, gray, (unsigned char)(4.0f*radius), 0));
jvanverthd7315f912016-08-17 10:06:18 -0700186
fmalita9da5dbd2016-08-17 11:33:29 -0700187 sk_sp<SkShader> gaussShader = SkGaussianEdgeShader::Make();
jvanverthd7315f912016-08-17 10:06:18 -0700188 paint.setShader(gaussShader);
189 canvas->drawRRect(pathRRect, paint);
jvanverthe1a3bc62016-08-12 10:40:38 -0700190 }
191
192 void drawSpotShadow(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
193 SkPoint3 lightPos, SkScalar lightWidth, SkScalar spotAlpha) {
194 if (spotAlpha <= 0) {
195 return;
196 }
197
198 SkScalar zRatio = zValue / (lightPos.fZ - zValue);
199 if (zRatio < 0.0f) {
200 zRatio = 0.0f;
201 } else if (zRatio > 0.95f) {
202 zRatio = 0.95f;
203 }
204 SkScalar radius = lightWidth*zRatio;
205
206 // compute the transformation params
207 SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY());
208 canvas->getTotalMatrix().mapPoints(&center, 1);
209 SkPoint offset = SkPoint::Make(-zRatio*(lightPos.fX - center.fX),
210 -zRatio*(lightPos.fY - center.fY));
211 SkScalar scale = lightPos.fZ / (lightPos.fZ - zValue);
212 if (scale < 1.0f) {
213 scale = 1.0f;
214 } else if (scale > 1024.f) {
215 scale = 1024.f;
216 }
217
218 SkAutoCanvasRestore acr(canvas, true);
219
220 SkRect occlRect;
221 GetOcclRect(path, &occlRect);
222 // apply inverse transform
223 occlRect.offset(-offset);
jvanverthd7315f912016-08-17 10:06:18 -0700224#if 0
225 // It looks like the scale may be invalid
226 SkScalar scale = lightPos.fZ / (lightPos.fZ - zValue);
227 if (scale < 1.0f) {
228 scale = 1.0f;
229 } else if (scale > 1024.f) {
230 scale = 1024.f;
231 }
jvanverthe1a3bc62016-08-12 10:40:38 -0700232 occlRect.fLeft /= scale;
233 occlRect.fRight /= scale;
234 occlRect.fTop /= scale;
235 occlRect.fBottom /= scale;
jvanverthd7315f912016-08-17 10:06:18 -0700236#endif
jvanverthe1a3bc62016-08-12 10:40:38 -0700237 sk_sp<SkMaskFilter> mf = SkBlurMaskFilter::Make(kNormal_SkBlurStyle,
238 SkBlurMask::ConvertRadiusToSigma(radius),
239 occlRect,
240 SkBlurMaskFilter::kNone_BlurFlag);
241
242 SkPaint paint;
243 paint.setAntiAlias(true);
244 paint.setMaskFilter(std::move(mf));
245 paint.setColor(SkColorSetARGB((unsigned char)(spotAlpha*255.999f), 0, 0, 0));
246
247 // apply transformation to shadow
248 canvas->translate(offset.fX, offset.fY);
jvanverthd7315f912016-08-17 10:06:18 -0700249#if 0
250 // It looks like the scale may be invalid
jvanverthe1a3bc62016-08-12 10:40:38 -0700251 canvas->scale(scale, scale);
jvanverthd7315f912016-08-17 10:06:18 -0700252#endif
jvanverthe1a3bc62016-08-12 10:40:38 -0700253 canvas->drawPath(path, paint);
254
255 // draw occlusion rect
jvanverth6c177a12016-08-17 07:59:41 -0700256#if DRAW_OCCL_RECT
jvanverthe1a3bc62016-08-12 10:40:38 -0700257 SkPaint stroke;
258 stroke.setStyle(SkPaint::kStroke_Style);
259 stroke.setColor(SK_ColorRED);
jvanverth6c177a12016-08-17 07:59:41 -0700260 canvas->drawRect(occlRect, stroke)
261#endif
jvanverthe1a3bc62016-08-12 10:40:38 -0700262 }
263
jvanverthd7315f912016-08-17 10:06:18 -0700264 void drawSpotShadowAlt(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
265 SkPoint3 lightPos, SkScalar lightWidth, SkScalar spotAlpha) {
266 if (spotAlpha <= 0) {
267 return;
268 }
jvanverthe1a3bc62016-08-12 10:40:38 -0700269
jvanverthd7315f912016-08-17 10:06:18 -0700270 SkScalar zRatio = zValue / (lightPos.fZ - zValue);
271 if (zRatio < 0.0f) {
272 zRatio = 0.0f;
273 } else if (zRatio > 0.95f) {
274 zRatio = 0.95f;
275 }
276 SkScalar radius = lightWidth*zRatio;
277
jvanvertha4f1af82016-08-29 07:17:47 -0700278 SkRect pathRect;
279 SkRRect pathRRect;
280 if (radius >= 64 ||
281 !((path.isOval(&pathRect) && pathRect.width() == pathRect.height()) ||
282 (path.isRRect(&pathRRect) && pathRRect.allCornersCircular()) ||
283 path.isRect(&pathRect))) {
284 this->drawSpotShadow(canvas, path, zValue, lightPos, lightWidth, spotAlpha);
285 return;
286 }
287
288 // For all of these, we outset the rect by half the radius to get our stroke shape.
289 SkScalar halfRadius = SK_ScalarHalf*radius;
jvanverthd7315f912016-08-17 10:06:18 -0700290 if (path.isOval(nullptr)) {
jvanvertha4f1af82016-08-29 07:17:47 -0700291 pathRect.outset(halfRadius, halfRadius);
jvanverthd7315f912016-08-17 10:06:18 -0700292 pathRRect = SkRRect::MakeOval(pathRect);
293 } else if (path.isRect(nullptr)) {
jvanvertha4f1af82016-08-29 07:17:47 -0700294 pathRect.outset(halfRadius, halfRadius);
295 pathRRect = SkRRect::MakeRectXY(pathRect, halfRadius, halfRadius);
jvanverthd7315f912016-08-17 10:06:18 -0700296 } else {
jvanvertha4f1af82016-08-29 07:17:47 -0700297 pathRRect.outset(halfRadius, halfRadius);
jvanverthd7315f912016-08-17 10:06:18 -0700298 }
299
300 // compute the transformation params
301 SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY());
302 canvas->getTotalMatrix().mapPoints(&center, 1);
303 SkPoint offset = SkPoint::Make(-zRatio*(lightPos.fX - center.fX),
304 -zRatio*(lightPos.fY - center.fY));
305 SkAutoCanvasRestore acr(canvas, true);
306
307 SkPaint paint;
308 paint.setAntiAlias(true);
jvanvertha4f1af82016-08-29 07:17:47 -0700309 // We outset the stroke by the length of the translation so the shadow extends to
310 // the edge of the shape. We also add 1/2 to cover up AA on the interior edge.
311 SkScalar pad = offset.length() + 0.5f;
312 // compute area
313 SkScalar strokeWidth = radius + 2.0f*pad;
314 SkScalar strokedArea = 2.0f*strokeWidth*(pathRRect.width() + pathRRect.height());
315 SkScalar filledArea = (pathRRect.height() + radius)*(pathRRect.width() + radius);
316 // If the area of the stroked geometry is larger than the fill geometry, or
317 // if our pad is too big to convert to 6.2 fixed point, just fill it.
318 if (strokedArea > filledArea || pad >= 64) {
319 pad = 0;
320 paint.setStyle(SkPaint::kStrokeAndFill_Style);
321 paint.setStrokeWidth(radius);
322 } else {
323 paint.setStyle(SkPaint::kStroke_Style);
324 paint.setStrokeWidth(strokeWidth);
325 }
fmalita9da5dbd2016-08-17 11:33:29 -0700326 sk_sp<SkShader> gaussShader = SkGaussianEdgeShader::Make();
jvanverthd7315f912016-08-17 10:06:18 -0700327 paint.setShader(gaussShader);
328 // handle scale of radius due to CTM
329 SkScalar maxScale = canvas->getTotalMatrix().getMaxScale();
330 radius *= maxScale;
331 unsigned char gray = (unsigned char)(spotAlpha*255.999f);
jvanvertha4f1af82016-08-29 07:17:47 -0700332 SkASSERT(radius < 64);
333 SkASSERT(pad < 64);
334 // Convert radius and pad to 6.2 fixed point and place in the G & B components.
335 paint.setColor(SkColorSetARGB(1, gray, (unsigned char)(radius*4.0f),
336 (unsigned char)(pad*4.0f)));
jvanverthd7315f912016-08-17 10:06:18 -0700337
338 // apply transformation to shadow
339 canvas->translate(offset.fX, offset.fY);
340#if 0
341 // It looks like the scale may be invalid
342 SkScalar scale = lightPos.fZ / (lightPos.fZ - zValue);
343 if (scale < 1.0f) {
344 scale = 1.0f;
345 } else if (scale > 1024.f) {
346 scale = 1024.f;
347 }
348 canvas->scale(scale, scale);
349#endif
350 canvas->drawRRect(pathRRect, paint);
351 }
352
353 void drawShadowedPath(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
354 const SkPaint& paint, SkScalar ambientAlpha,
355 const SkPoint3& lightPos, SkScalar lightWidth, SkScalar spotAlpha) {
jvanverthe1a3bc62016-08-12 10:40:38 -0700356 if (fShowAmbient) {
jvanverthd7315f912016-08-17 10:06:18 -0700357 if (fUseAlt) {
358 this->drawAmbientShadowAlt(canvas, path, zValue, ambientAlpha);
jvanverth6c177a12016-08-17 07:59:41 -0700359 } else {
jvanverthd7315f912016-08-17 10:06:18 -0700360 this->drawAmbientShadow(canvas, path, zValue, ambientAlpha);
jvanverth6c177a12016-08-17 07:59:41 -0700361 }
jvanverthe1a3bc62016-08-12 10:40:38 -0700362 }
363 if (fShowSpot) {
jvanverthd7315f912016-08-17 10:06:18 -0700364 if (fUseAlt) {
365 this->drawSpotShadowAlt(canvas, path, zValue, lightPos, lightWidth, spotAlpha);
366 } else {
367 this->drawSpotShadow(canvas, path, zValue, lightPos, lightWidth, spotAlpha);
368 }
jvanverthe1a3bc62016-08-12 10:40:38 -0700369 }
370 if (fShowObject) {
371 canvas->drawPath(path, paint);
372 }
373 }
374
375 void onDrawContent(SkCanvas* canvas) override {
376 this->drawBG(canvas);
jvanverthd7315f912016-08-17 10:06:18 -0700377 const SkScalar kLightWidth = 3;
378 const SkScalar kAmbientAlpha = 0.25f;
379 const SkScalar kSpotAlpha = 0.25f;
jvanverthe1a3bc62016-08-12 10:40:38 -0700380
381 SkPaint paint;
382 paint.setAntiAlias(true);
383
jvanverthd7315f912016-08-17 10:06:18 -0700384 SkPoint3 lightPos = fLightPos;
385
jvanverthe1a3bc62016-08-12 10:40:38 -0700386 paint.setColor(SK_ColorWHITE);
387 canvas->translate(200, 90);
jvanverthd7315f912016-08-17 10:06:18 -0700388 lightPos.fX += 200;
389 lightPos.fY += 90;
390 this->drawShadowedPath(canvas, fRectPath, 5, paint, kAmbientAlpha,
391 lightPos, kLightWidth, kSpotAlpha);
jvanverthe1a3bc62016-08-12 10:40:38 -0700392
393 paint.setColor(SK_ColorRED);
394 canvas->translate(250, 0);
jvanverthd7315f912016-08-17 10:06:18 -0700395 lightPos.fX += 250;
396 this->drawShadowedPath(canvas, fRRPath, 5, paint, kAmbientAlpha,
397 lightPos, kLightWidth, kSpotAlpha);
jvanverthe1a3bc62016-08-12 10:40:38 -0700398
399 paint.setColor(SK_ColorBLUE);
400 canvas->translate(-250, 110);
jvanverthd7315f912016-08-17 10:06:18 -0700401 lightPos.fX -= 250;
402 lightPos.fY += 110;
403 this->drawShadowedPath(canvas, fCirclePath, 5, paint, 0.0f,
404 lightPos, kLightWidth, 0.5f);
jvanverth6c177a12016-08-17 07:59:41 -0700405
406 paint.setColor(SK_ColorGREEN);
407 canvas->translate(250, 0);
jvanverthd7315f912016-08-17 10:06:18 -0700408 lightPos.fX += 250;
409 this->drawShadowedPath(canvas, fRRPath, 5, paint, kAmbientAlpha,
410 lightPos, kLightWidth, kSpotAlpha);
jvanverthe1a3bc62016-08-12 10:40:38 -0700411 }
412
413protected:
414 SkView::Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned modi) override {
415 return new SkView::Click(this);
416 }
417
418 bool onClick(Click *click) override {
419 SkScalar x = click->fCurr.fX;
420 SkScalar y = click->fCurr.fY;
421
422 SkScalar dx = x - click->fPrev.fX;
423 SkScalar dy = y - click->fPrev.fY;
424
425 if (dx != 0 || dy != 0) {
426 fLightPos.fX += dx;
427 fLightPos.fY += dy;
428 this->inval(nullptr);
429 }
430
431 return true;
432 }
433
434private:
435 typedef SkView INHERITED;
436};
437
438//////////////////////////////////////////////////////////////////////////////
439
440static SkView* MyFactory() { return new ShadowsView; }
441static SkViewRegister reg(MyFactory);