SampleAndroidShadows.cpp revision fdb1bdf1aa4e7f14befcb3acc3f586e7734190ea
1
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"
12#include "SkGaussianEdgeShader.h"
13#include "SkPath.h"
14#include "SkPoint3.h"
15#include "SkShadowUtils.h"
16#include "SkUtils.h"
17#include "SkView.h"
18#include "sk_tool_utils.h"
19
20#define USE_SHADOW_UTILS
21
22////////////////////////////////////////////////////////////////////////////
23
24class ShadowsView : public SampleView {
25    SkPath    fRectPath;
26    SkPath    fRRPath;
27    SkPath    fCirclePath;
28    SkPath    fFunkyRRPath;
29    SkPath    fCubicPath;
30    SkPoint3  fLightPos;
31
32    bool      fShowAmbient;
33    bool      fShowSpot;
34    bool      fUseAlt;
35    bool      fShowObject;
36    bool      fIgnoreShadowAlpha;
37
38public:
39    ShadowsView()
40        : fShowAmbient(true)
41        , fShowSpot(true)
42        , fUseAlt(true)
43        , fShowObject(true)
44        , fIgnoreShadowAlpha(false) {}
45
46protected:
47    void onOnceBeforeDraw() override {
48        fCirclePath.addCircle(0, 0, 50);
49        fRectPath.addRect(SkRect::MakeXYWH(-100, -50, 200, 100));
50        fRRPath.addRRect(SkRRect::MakeRectXY(SkRect::MakeXYWH(-100, -50, 200, 100), 4, 4));
51        fFunkyRRPath.addRoundRect(SkRect::MakeXYWH(-50, -50, SK_Scalar1 * 100, SK_Scalar1 * 100),
52                                  40 * SK_Scalar1, 20 * SK_Scalar1,
53                                  SkPath::kCW_Direction);
54        fCubicPath.cubicTo(100 * SK_Scalar1, 50 * SK_Scalar1,
55                           20 * SK_Scalar1, 100 * SK_Scalar1,
56                           0 * SK_Scalar1, 0 * SK_Scalar1);
57
58        fLightPos = SkPoint3::Make(-700, -700, 2800);
59    }
60
61    // overrides from SkEventSink
62    bool onQuery(SkEvent* evt) override {
63        if (SampleCode::TitleQ(*evt)) {
64            SampleCode::TitleR(evt, "AndroidShadows");
65            return true;
66        }
67
68        SkUnichar uni;
69        if (SampleCode::CharQ(*evt, &uni)) {
70            switch (uni) {
71                case 'B':
72                    fShowAmbient = !fShowAmbient;
73                    break;
74                case 'S':
75                    fShowSpot = !fShowSpot;
76                    break;
77                case 'T':
78                    fUseAlt = !fUseAlt;
79                    break;
80                case 'O':
81                    fShowObject = !fShowObject;
82                    break;
83                case '>':
84                    fLightPos.fZ += 10;
85                    break;
86                case '<':
87                    fLightPos.fZ -= 10;
88                    break;
89                case '?':
90                    fIgnoreShadowAlpha = !fIgnoreShadowAlpha;
91                    break;
92                default:
93                    break;
94            }
95            this->inval(nullptr);
96        }
97        return this->INHERITED::onQuery(evt);
98    }
99
100    void drawBG(SkCanvas* canvas) {
101        canvas->drawColor(0xFFDDDDDD);
102    }
103
104    static void GetOcclRect(const SkPath& path, SkRect* occlRect) {
105        SkRect pathRect;
106        SkRRect pathRRect;
107        if (path.isOval(&pathRect)) {
108            *occlRect = sk_tool_utils::compute_central_occluder(SkRRect::MakeOval(pathRect));
109        } else if (path.isRRect(&pathRRect)) {
110            *occlRect = sk_tool_utils::compute_central_occluder(pathRRect);
111        } else if (path.isRect(occlRect)) {
112            // the inverse transform for the spot shadow occluder doesn't always get us
113            // back to exactly the same position, so deducting a little slop
114            occlRect->inset(1, 1);
115        } else {
116            *occlRect = SkRect::MakeEmpty();
117        }
118    }
119
120    void drawAmbientShadow(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
121                           SkScalar ambientAlpha) {
122
123        if (ambientAlpha <= 0) {
124            return;
125        }
126
127        const SkScalar kHeightFactor = 1.f / 128.f;
128        const SkScalar kGeomFactor = 64;
129
130        SkScalar umbraAlpha = 1 / (1 + SkMaxScalar(zValue*kHeightFactor, 0));
131        SkScalar radius = zValue*kHeightFactor*kGeomFactor;
132
133        // occlude blur
134        SkRect occlRect;
135        GetOcclRect(path, &occlRect);
136        sk_sp<SkMaskFilter> mf = SkBlurMaskFilter::Make(kNormal_SkBlurStyle,
137                                                        SkBlurMask::ConvertRadiusToSigma(radius),
138                                                        occlRect,
139                                                        SkBlurMaskFilter::kNone_BlurFlag);
140
141        SkPaint paint;
142        paint.setAntiAlias(true);
143        paint.setMaskFilter(std::move(mf));
144        paint.setColor(SkColorSetARGB(fIgnoreShadowAlpha
145                                    ? 255
146                                    : (unsigned char)(ambientAlpha*umbraAlpha*255.999f), 0, 0, 0));
147        canvas->drawPath(path, paint);
148
149        // draw occlusion rect
150#if DRAW_OCCL_RECT
151        SkPaint stroke;
152        stroke.setStyle(SkPaint::kStroke_Style);
153        stroke.setColor(SK_ColorBLUE);
154        canvas->drawRect(occlRect, stroke);
155#endif
156    }
157
158    void drawAmbientShadowAlt(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
159                              SkScalar ambientAlpha) {
160
161        if (ambientAlpha <= 0) {
162            return;
163        }
164
165        const SkScalar kHeightFactor = 1.f / 128.f;
166        const SkScalar kGeomFactor = 64;
167
168        SkScalar umbraAlpha = 1 / (1 + SkMaxScalar(zValue*kHeightFactor, 0));
169        SkScalar radius = zValue*kHeightFactor*kGeomFactor;
170        // distance to outer of edge of geometry from original shape edge
171        SkScalar offset = radius*umbraAlpha;
172
173        SkRect pathRect;
174        SkRRect pathRRect;
175        SkScalar scaleFactors[2];
176        if (!canvas->getTotalMatrix().getMinMaxScales(scaleFactors)) {
177            return;
178        }
179        if (scaleFactors[0] != scaleFactors[1] || radius*scaleFactors[0] >= 64 ||
180            !((path.isOval(&pathRect) && pathRect.width() == pathRect.height()) ||
181              (path.isRRect(&pathRRect) && pathRRect.allCornersCircular()) ||
182              path.isRect(&pathRect))) {
183            this->drawAmbientShadow(canvas, path, zValue, ambientAlpha);
184            return;
185        }
186
187        // For all of these, we inset the offset rect by half the radius to get our stroke shape.
188        SkScalar strokeOutset = offset - SK_ScalarHalf*radius;
189        // Make sure we'll have a radius of at least 0.5 after xform
190        if (strokeOutset*scaleFactors[0] < 0.5f) {
191            strokeOutset = 0.5f / scaleFactors[0];
192        }
193        if (path.isOval(nullptr)) {
194            pathRect.outset(strokeOutset, strokeOutset);
195            pathRRect = SkRRect::MakeOval(pathRect);
196        } else if (path.isRect(nullptr)) {
197            pathRect.outset(strokeOutset, strokeOutset);
198            pathRRect = SkRRect::MakeRectXY(pathRect, strokeOutset, strokeOutset);
199        } else {
200            pathRRect.outset(strokeOutset, strokeOutset);
201        }
202
203        SkPaint paint;
204        paint.setAntiAlias(true);
205        paint.setStyle(SkPaint::kStroke_Style);
206        // we outset the stroke a little to cover up AA on the interior edge
207        SkScalar pad = 0.5f;
208        paint.setStrokeWidth(radius + 2*pad);
209        // handle scale of radius and pad due to CTM
210        radius *= scaleFactors[0];
211        pad *= scaleFactors[0];
212        SkASSERT(radius < 16384);
213        SkASSERT(pad < 64);
214        // Convert radius to 14.2 fixed point and place in the R & G components.
215        // Convert pad to 6.2 fixed point and place in the B component.
216        uint16_t iRadius = (uint16_t)(radius*4.0f);
217        unsigned char alpha = (unsigned char)(ambientAlpha*255.999f);
218        paint.setColor(SkColorSetARGB(fIgnoreShadowAlpha ? 255 : alpha,
219                                      iRadius >> 8, iRadius & 0xff,
220                                      (unsigned char)(4.0f*pad)));
221
222        paint.setShader(SkGaussianEdgeShader::Make());
223        canvas->drawRRect(pathRRect, paint);
224    }
225
226    void drawSpotShadow(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
227                        SkPoint3 lightPos, SkScalar lightWidth, SkScalar spotAlpha) {
228        if (spotAlpha <= 0) {
229            return;
230        }
231
232        SkScalar zRatio = zValue / (lightPos.fZ - zValue);
233        if (zRatio < 0.0f) {
234            zRatio = 0.0f;
235        } else if (zRatio > 0.95f) {
236            zRatio = 0.95f;
237        }
238        SkScalar blurRadius = lightWidth*zRatio;
239
240        // compute the transformation params
241        SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY());
242        SkMatrix ctmInverse;
243        if (!canvas->getTotalMatrix().invert(&ctmInverse)) {
244            return;
245        }
246        SkPoint lightPos2D = SkPoint::Make(lightPos.fX, lightPos.fY);
247        ctmInverse.mapPoints(&lightPos2D, 1);
248        SkPoint offset = SkPoint::Make(zRatio*(center.fX - lightPos2D.fX),
249                                       zRatio*(center.fY - lightPos2D.fY));
250        SkScalar scale = lightPos.fZ / (lightPos.fZ - zValue);
251
252        SkAutoCanvasRestore acr(canvas, true);
253
254        sk_sp<SkMaskFilter> mf = SkBlurMaskFilter::Make(kNormal_SkBlurStyle,
255                                                        SkBlurMask::ConvertRadiusToSigma(blurRadius),
256                                                        SkBlurMaskFilter::kNone_BlurFlag);
257
258        SkPaint paint;
259        paint.setAntiAlias(true);
260        paint.setMaskFilter(std::move(mf));
261        paint.setColor(SkColorSetARGB(fIgnoreShadowAlpha
262                                                ? 255
263                                                : (unsigned char)(spotAlpha*255.999f), 0, 0, 0));
264
265        // apply transformation to shadow
266        canvas->scale(scale, scale);
267        canvas->translate(offset.fX, offset.fY);
268        canvas->drawPath(path, paint);
269    }
270
271    void drawSpotShadowAlt(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
272                        SkPoint3 lightPos, SkScalar lightWidth, SkScalar spotAlpha) {
273        if (spotAlpha <= 0) {
274            return;
275        }
276
277        SkScalar zRatio = zValue / (lightPos.fZ - zValue);
278        if (zRatio < 0.0f) {
279            zRatio = 0.0f;
280        } else if (zRatio > 0.95f) {
281            zRatio = 0.95f;
282        }
283        SkScalar radius = 2.0f*lightWidth*zRatio;
284
285        SkRect pathRect;
286        SkRRect pathRRect;
287        SkScalar scaleFactors[2];
288        if (!canvas->getTotalMatrix().getMinMaxScales(scaleFactors)) {
289            return;
290        }
291        if (scaleFactors[0] != scaleFactors[1] || radius*scaleFactors[0] >= 16384 ||
292            !((path.isOval(&pathRect) && pathRect.width() == pathRect.height()) ||
293              (path.isRRect(&pathRRect) && pathRRect.allCornersCircular()) ||
294              path.isRect(&pathRect))) {
295            this->drawSpotShadow(canvas, path, zValue, lightPos, lightWidth, spotAlpha);
296            return;
297        }
298
299        // For all of these, we need to ensure we have a rrect with radius >= 0.5f in device space
300        const SkScalar minRadius = SK_ScalarHalf/scaleFactors[0];
301        if (path.isOval(nullptr)) {
302            pathRRect = SkRRect::MakeOval(pathRect);
303        } else if (path.isRect(nullptr)) {
304            pathRRect = SkRRect::MakeRectXY(pathRect, minRadius, minRadius);
305        } else {
306            if (pathRRect.getSimpleRadii().fX < minRadius) {
307                pathRRect.setRectXY(pathRRect.rect(), minRadius, minRadius);
308            }
309        }
310
311        // compute the scale and translation for the shadow
312        SkScalar scale = lightPos.fZ / (lightPos.fZ - zValue);
313        SkRRect shadowRRect;
314        pathRRect.transform(SkMatrix::MakeScale(scale, scale), &shadowRRect);
315        SkPoint center = SkPoint::Make(shadowRRect.rect().centerX(), shadowRRect.rect().centerY());
316        SkMatrix ctmInverse;
317        if (!canvas->getTotalMatrix().invert(&ctmInverse)) {
318            return;
319        }
320        SkPoint lightPos2D = SkPoint::Make(lightPos.fX, lightPos.fY);
321        ctmInverse.mapPoints(&lightPos2D, 1);
322        SkPoint offset = SkPoint::Make(zRatio*(center.fX - lightPos2D.fX),
323                                       zRatio*(center.fY - lightPos2D.fY));
324        SkAutoCanvasRestore acr(canvas, true);
325
326        SkPaint paint;
327        paint.setAntiAlias(true);
328        // We want to extend the stroked area in so that it meets up with the caster
329        // geometry. The stroked geometry will, by definition already be inset half the
330        // stroke width but we also have to account for the scaling.
331        // We also add 1/2 to cover up AA on the interior edge.
332        SkScalar scaleOffset = (scale - 1.0f) * SkTMax(SkTMax(SkTAbs(pathRect.fLeft),
333                                                              SkTAbs(pathRect.fRight)),
334                                                       SkTMax(SkTAbs(pathRect.fTop),
335                                                              SkTAbs(pathRect.fBottom)));
336        SkScalar insetAmount = offset.length() - (0.5f * radius) + scaleOffset + 0.5f;
337
338        // compute area
339        SkScalar strokeWidth = radius + insetAmount;
340        SkScalar strokedArea = 2.0f*strokeWidth*(shadowRRect.width() + shadowRRect.height());
341        SkScalar filledArea = (shadowRRect.height() + radius)*(shadowRRect.width() + radius);
342        // If the area of the stroked geometry is larger than the fill geometry, or
343        // if our pad is too big to convert to 6.2 fixed point, just fill it.
344        if (strokedArea > filledArea) {
345            paint.setStyle(SkPaint::kStrokeAndFill_Style);
346            paint.setStrokeWidth(radius);
347        } else {
348            // Since we can't have unequal strokes, inset the shadow rect so the inner
349            // and outer edges of the stroke will land where we want.
350            SkRect insetRect = shadowRRect.rect().makeInset(insetAmount/2.0f, insetAmount/2.0f);
351            SkScalar insetRad = SkTMax(shadowRRect.getSimpleRadii().fX - insetAmount/2.0f,
352                                       minRadius);
353
354            shadowRRect = SkRRect::MakeRectXY(insetRect, insetRad, insetRad);
355            paint.setStyle(SkPaint::kStroke_Style);
356            paint.setStrokeWidth(strokeWidth);
357        }
358        paint.setShader(SkGaussianEdgeShader::Make());
359        // handle scale of radius due to CTM
360        radius *= scaleFactors[0];
361        // don't need to scale pad as it was computed from the transformed offset
362        SkASSERT(radius < 16384);
363        SkScalar pad = 0;
364        SkASSERT(pad < 64);
365        // Convert radius to 14.2 fixed point and place in the R & G components.
366        // Convert pad to 6.2 fixed point and place in the B component.
367        uint16_t iRadius = (uint16_t)(radius*4.0f);
368        unsigned char alpha = (unsigned char)(spotAlpha*255.999f);
369        paint.setColor(SkColorSetARGB(fIgnoreShadowAlpha ? 255 : alpha,
370                                      iRadius >> 8, iRadius & 0xff,
371                                      (unsigned char)(4.0f*pad)));
372
373        // apply transformation to shadow
374        canvas->translate(offset.fX, offset.fY);
375        canvas->drawRRect(shadowRRect, paint);
376    }
377
378    void drawShadowedPath(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
379                          const SkPaint& paint, SkScalar ambientAlpha,
380                          const SkPoint3& lightPos, SkScalar lightWidth, SkScalar spotAlpha) {
381#ifdef USE_SHADOW_UTILS
382        if (fUseAlt) {
383            if (fShowAmbient) {
384                this->drawAmbientShadowAlt(canvas, path, zValue, ambientAlpha);
385            }
386            if (fShowSpot) {
387                this->drawSpotShadowAlt(canvas, path, zValue, lightPos, lightWidth, spotAlpha);
388            }
389        } else {
390            if (!fShowAmbient) {
391                ambientAlpha = 0;
392            }
393            if (!fShowSpot) {
394                spotAlpha = 0;
395            }
396            SkShadowUtils::DrawShadow(canvas, path, zValue, lightPos, lightWidth,
397                                      ambientAlpha, spotAlpha, SK_ColorBLACK);
398        }
399#else
400        if (fShowAmbient) {
401            if (fUseAlt) {
402                this->drawAmbientShadowAlt(canvas, path, zValue, ambientAlpha);
403            } else {
404                this->drawAmbientShadow(canvas, path, zValue, ambientAlpha);
405            }
406        }
407        if (fShowSpot) {
408            if (fUseAlt) {
409                this->drawSpotShadowAlt(canvas, path, zValue, lightPos, lightWidth, spotAlpha);
410            } else {
411                this->drawSpotShadow(canvas, path, zValue, lightPos, lightWidth, spotAlpha);
412            }
413        }
414#endif
415
416        if (fShowObject) {
417            canvas->drawPath(path, paint);
418        } else {
419            SkPaint strokePaint;
420
421            strokePaint.setColor(paint.getColor());
422            strokePaint.setStyle(SkPaint::kStroke_Style);
423
424            canvas->drawPath(path, strokePaint);
425        }
426    }
427
428    void onDrawContent(SkCanvas* canvas) override {
429        this->drawBG(canvas);
430        const SkScalar kLightWidth = 2800;
431        const SkScalar kAmbientAlpha = 0.25f;
432        const SkScalar kSpotAlpha = 0.25f;
433
434        SkPaint paint;
435        paint.setAntiAlias(true);
436
437        SkPoint3 lightPos = fLightPos;
438
439        paint.setColor(SK_ColorWHITE);
440        canvas->translate(200, 90);
441        lightPos.fX += 200;
442        lightPos.fY += 90;
443        this->drawShadowedPath(canvas, fRRPath, 2, paint, kAmbientAlpha,
444                               lightPos, kLightWidth, kSpotAlpha);
445
446        paint.setColor(SK_ColorRED);
447        canvas->translate(250, 0);
448        lightPos.fX += 250;
449        this->drawShadowedPath(canvas, fRectPath, 4, paint, kAmbientAlpha,
450                               lightPos, kLightWidth, kSpotAlpha);
451
452        paint.setColor(SK_ColorBLUE);
453        canvas->translate(-250, 110);
454        lightPos.fX -= 250;
455        lightPos.fY += 110;
456        this->drawShadowedPath(canvas, fCirclePath, 8, paint, 0,
457                               lightPos, kLightWidth, 0.5f);
458
459        paint.setColor(SK_ColorGREEN);
460        canvas->translate(250, 0);
461        lightPos.fX += 250;
462        this->drawShadowedPath(canvas, fRRPath, 64, paint, kAmbientAlpha,
463                               lightPos, kLightWidth, kSpotAlpha);
464
465        paint.setColor(SK_ColorYELLOW);
466        canvas->translate(-250, 110);
467        lightPos.fX -= 250;
468        lightPos.fY += 110;
469        this->drawShadowedPath(canvas, fFunkyRRPath, 8, paint, kAmbientAlpha,
470                               lightPos, kLightWidth, kSpotAlpha);
471
472        paint.setColor(SK_ColorCYAN);
473        canvas->translate(250, 0);
474        lightPos.fX += 250;
475        this->drawShadowedPath(canvas, fCubicPath, 16, paint, kAmbientAlpha,
476                               lightPos, kLightWidth, kSpotAlpha);
477    }
478
479protected:
480    SkView::Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned modi) override {
481        return new SkView::Click(this);
482    }
483
484    bool onClick(Click *click) override {
485        SkScalar x = click->fCurr.fX;
486        SkScalar y = click->fCurr.fY;
487
488        SkScalar dx = x - click->fPrev.fX;
489        SkScalar dy = y - click->fPrev.fY;
490
491        if (dx != 0 || dy != 0) {
492            fLightPos.fX += dx;
493            fLightPos.fY += dy;
494            this->inval(nullptr);
495        }
496
497        return true;
498    }
499
500private:
501    typedef SkView INHERITED;
502};
503
504//////////////////////////////////////////////////////////////////////////////
505
506static SkView* MyFactory() { return new ShadowsView; }
507static SkViewRegister reg(MyFactory);
508