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