1 /*
2 * Copyright 2017 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "SkSpotShadowMaskFilter.h"
9 #include "SkReadBuffer.h"
10 #include "SkStringUtils.h"
11 #include "SkWriteBuffer.h"
12
13 #if SK_SUPPORT_GPU
14 #include "GrContext.h"
15 #include "GrRenderTargetContext.h"
16 #include "GrFragmentProcessor.h"
17 #include "GrStyle.h"
18 #include "GrTexture.h"
19 #include "GrTextureProxy.h"
20 #include "SkStrokeRec.h"
21 #endif
22
23 class SkSpotShadowMaskFilterImpl : public SkMaskFilter {
24 public:
25 SkSpotShadowMaskFilterImpl(SkScalar occluderHeight, const SkPoint3& lightPos,
26 SkScalar lightRadius, SkScalar spotAlpha, uint32_t flags);
27
28 // overrides from SkMaskFilter
29 SkMask::Format getFormat() const override;
30 bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&,
31 SkIPoint* margin) const override;
32
33 #if SK_SUPPORT_GPU
34 bool canFilterMaskGPU(const SkRRect& devRRect,
35 const SkIRect& clipBounds,
36 const SkMatrix& ctm,
37 SkRect* maskRect) const override;
38 bool directFilterMaskGPU(GrContext*,
39 GrRenderTargetContext* drawContext,
40 GrPaint&&,
41 const GrClip&,
42 const SkMatrix& viewMatrix,
43 const SkStrokeRec& strokeRec,
44 const SkPath& path) const override;
45 bool directFilterRRectMaskGPU(GrContext*,
46 GrRenderTargetContext* drawContext,
47 GrPaint&&,
48 const GrClip&,
49 const SkMatrix& viewMatrix,
50 const SkStrokeRec& strokeRec,
51 const SkRRect& rrect,
52 const SkRRect& devRRect) const override;
53 sk_sp<GrTextureProxy> filterMaskGPU(GrContext*,
54 sk_sp<GrTextureProxy> srcProxy,
55 const SkMatrix& ctm,
56 const SkIRect& maskRect) const override;
57 #endif
58
59 void computeFastBounds(const SkRect&, SkRect*) const override;
60
61 SK_TO_STRING_OVERRIDE()
62 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSpotShadowMaskFilterImpl)
63
64 private:
65 SkScalar fOccluderHeight;
66 SkPoint3 fLightPos;
67 SkScalar fLightRadius;
68 SkScalar fSpotAlpha;
69 uint32_t fFlags;
70
71 SkSpotShadowMaskFilterImpl(SkReadBuffer&);
72 void flatten(SkWriteBuffer&) const override;
73
74 friend class SkSpotShadowMaskFilter;
75
76 typedef SkMaskFilter INHERITED;
77 };
78
Make(SkScalar occluderHeight,const SkPoint3 & lightPos,SkScalar lightRadius,SkScalar spotAlpha,uint32_t flags)79 sk_sp<SkMaskFilter> SkSpotShadowMaskFilter::Make(SkScalar occluderHeight, const SkPoint3& lightPos,
80 SkScalar lightRadius, SkScalar spotAlpha,
81 uint32_t flags) {
82 // add some param checks here for early exit
83
84 return sk_sp<SkMaskFilter>(new SkSpotShadowMaskFilterImpl(occluderHeight, lightPos,
85 lightRadius, spotAlpha, flags));
86 }
87
88 ///////////////////////////////////////////////////////////////////////////////////////////////////
89
SkSpotShadowMaskFilterImpl(SkScalar occluderHeight,const SkPoint3 & lightPos,SkScalar lightRadius,SkScalar spotAlpha,uint32_t flags)90 SkSpotShadowMaskFilterImpl::SkSpotShadowMaskFilterImpl(SkScalar occluderHeight,
91 const SkPoint3& lightPos,
92 SkScalar lightRadius,
93 SkScalar spotAlpha,
94 uint32_t flags)
95 : fOccluderHeight(occluderHeight)
96 , fLightPos(lightPos)
97 , fLightRadius(lightRadius)
98 , fSpotAlpha(spotAlpha)
99 , fFlags(flags) {
100 SkASSERT(fOccluderHeight > 0);
101 SkASSERT(fLightPos.z() > 0 && fLightPos.z() > fOccluderHeight);
102 SkASSERT(fLightRadius > 0);
103 SkASSERT(fSpotAlpha >= 0);
104 }
105
getFormat() const106 SkMask::Format SkSpotShadowMaskFilterImpl::getFormat() const {
107 return SkMask::kA8_Format;
108 }
109
filterMask(SkMask * dst,const SkMask & src,const SkMatrix & matrix,SkIPoint * margin) const110 bool SkSpotShadowMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src,
111 const SkMatrix& matrix,
112 SkIPoint* margin) const {
113 // TODO something
114 return false;
115 }
116
computeFastBounds(const SkRect & src,SkRect * dst) const117 void SkSpotShadowMaskFilterImpl::computeFastBounds(const SkRect& src, SkRect* dst) const {
118 // TODO compute based on ambient + spot data
119 dst->set(src.fLeft, src.fTop, src.fRight, src.fBottom);
120 }
121
CreateProc(SkReadBuffer & buffer)122 sk_sp<SkFlattenable> SkSpotShadowMaskFilterImpl::CreateProc(SkReadBuffer& buffer) {
123 const SkScalar occluderHeight = buffer.readScalar();
124 const SkScalar lightX = buffer.readScalar();
125 const SkScalar lightY = buffer.readScalar();
126 const SkScalar lightZ = buffer.readScalar();
127 const SkPoint3 lightPos = SkPoint3::Make(lightX, lightY, lightZ);
128 const SkScalar lightRadius = buffer.readScalar();
129 const SkScalar spotAlpha = buffer.readScalar();
130 const uint32_t flags = buffer.readUInt();
131
132 return SkSpotShadowMaskFilter::Make(occluderHeight, lightPos, lightRadius,
133 spotAlpha, flags);
134 }
135
flatten(SkWriteBuffer & buffer) const136 void SkSpotShadowMaskFilterImpl::flatten(SkWriteBuffer& buffer) const {
137 buffer.writeScalar(fOccluderHeight);
138 buffer.writeScalar(fLightPos.fX);
139 buffer.writeScalar(fLightPos.fY);
140 buffer.writeScalar(fLightPos.fZ);
141 buffer.writeScalar(fLightRadius);
142 buffer.writeScalar(fSpotAlpha);
143 buffer.writeUInt(fFlags);
144 }
145
146 #if SK_SUPPORT_GPU
147
148 ///////////////////////////////////////////////////////////////////////////////////////////////////
149
canFilterMaskGPU(const SkRRect & devRRect,const SkIRect & clipBounds,const SkMatrix & ctm,SkRect * maskRect) const150 bool SkSpotShadowMaskFilterImpl::canFilterMaskGPU(const SkRRect& devRRect,
151 const SkIRect& clipBounds,
152 const SkMatrix& ctm,
153 SkRect* maskRect) const {
154 // TODO
155 *maskRect = devRRect.rect();
156 return true;
157 }
158
directFilterMaskGPU(GrContext * context,GrRenderTargetContext * rtContext,GrPaint && paint,const GrClip & clip,const SkMatrix & viewMatrix,const SkStrokeRec & strokeRec,const SkPath & path) const159 bool SkSpotShadowMaskFilterImpl::directFilterMaskGPU(GrContext* context,
160 GrRenderTargetContext* rtContext,
161 GrPaint&& paint,
162 const GrClip& clip,
163 const SkMatrix& viewMatrix,
164 const SkStrokeRec& strokeRec,
165 const SkPath& path) const {
166 SkASSERT(rtContext);
167 // TODO: this will not handle local coordinates properly
168
169 if (fSpotAlpha <= 0.0f) {
170 return true;
171 }
172
173 // only convex paths for now
174 if (!path.isConvex()) {
175 return false;
176 }
177
178 if (strokeRec.getStyle() != SkStrokeRec::kFill_Style) {
179 return false;
180 }
181
182 // if circle
183 // TODO: switch to SkScalarNearlyEqual when either oval renderer is updated or we
184 // have our own GeometryProc.
185 if (path.isOval(nullptr) && path.getBounds().width() == path.getBounds().height()) {
186 SkRRect rrect = SkRRect::MakeOval(path.getBounds());
187 return this->directFilterRRectMaskGPU(context, rtContext, std::move(paint), clip,
188 SkMatrix::I(), strokeRec, rrect, rrect);
189 } else if (path.isRect(nullptr)) {
190 SkRRect rrect = SkRRect::MakeRect(path.getBounds());
191 return this->directFilterRRectMaskGPU(context, rtContext, std::move(paint), clip,
192 SkMatrix::I(), strokeRec, rrect, rrect);
193 }
194
195 return false;
196 }
197
directFilterRRectMaskGPU(GrContext *,GrRenderTargetContext * rtContext,GrPaint && paint,const GrClip & clip,const SkMatrix & viewMatrix,const SkStrokeRec & strokeRec,const SkRRect & rrect,const SkRRect & devRRect) const198 bool SkSpotShadowMaskFilterImpl::directFilterRRectMaskGPU(GrContext*,
199 GrRenderTargetContext* rtContext,
200 GrPaint&& paint,
201 const GrClip& clip,
202 const SkMatrix& viewMatrix,
203 const SkStrokeRec& strokeRec,
204 const SkRRect& rrect,
205 const SkRRect& devRRect) const {
206 // It's likely the caller has already done these checks, but we have to be sure.
207 // TODO: support analytic blurring of general rrect
208
209 // Fast path only supports filled rrects for now.
210 // TODO: fill and stroke as well.
211 if (SkStrokeRec::kFill_Style != strokeRec.getStyle()) {
212 return false;
213 }
214 // Fast path only supports simple rrects with circular corners.
215 SkASSERT(devRRect.allCornersCircular());
216 if (!rrect.isRect() && !rrect.isOval() && !rrect.isSimple()) {
217 return false;
218 }
219 // Fast path only supports uniform scale.
220 SkScalar scaleFactors[2];
221 if (!viewMatrix.getMinMaxScales(scaleFactors)) {
222 // matrix is degenerate
223 return false;
224 }
225 if (scaleFactors[0] != scaleFactors[1]) {
226 return false;
227 }
228 SkScalar scaleFactor = scaleFactors[0];
229
230 // For all of these, we need to ensure we have a rrect with radius >= 0.5f in device space
231 const SkScalar minRadius = 0.5f / scaleFactor;
232 bool isRect = rrect.getSimpleRadii().fX <= minRadius;
233
234 // TODO: take flags into account when generating shadow data
235
236 if (fSpotAlpha > 0.0f) {
237 float zRatio = SkTPin(fOccluderHeight / (fLightPos.fZ - fOccluderHeight), 0.0f, 0.95f);
238
239 SkScalar srcSpaceSpotRadius = 2.0f * fLightRadius * zRatio;
240
241 SkRRect spotRRect;
242 if (isRect) {
243 spotRRect = SkRRect::MakeRectXY(rrect.rect(), minRadius, minRadius);
244 } else {
245 spotRRect = rrect;
246 }
247
248 SkRRect spotShadowRRect;
249 // Compute the scale and translation for the spot shadow.
250 const SkScalar scale = fLightPos.fZ / (fLightPos.fZ - fOccluderHeight);
251 spotRRect.transform(SkMatrix::MakeScale(scale, scale), &spotShadowRRect);
252
253 SkPoint center = SkPoint::Make(spotShadowRRect.rect().centerX(),
254 spotShadowRRect.rect().centerY());
255 SkMatrix ctmInverse;
256 if (!viewMatrix.invert(&ctmInverse)) {
257 SkDebugf("Matrix is degenerate. Will not render spot shadow!\n");
258 //**** TODO: this is not good
259 return true;
260 }
261 SkPoint lightPos2D = SkPoint::Make(fLightPos.fX, fLightPos.fY);
262 ctmInverse.mapPoints(&lightPos2D, 1);
263 const SkPoint spotOffset = SkPoint::Make(zRatio*(center.fX - lightPos2D.fX),
264 zRatio*(center.fY - lightPos2D.fY));
265
266 // We want to extend the stroked area in so that it meets up with the caster
267 // geometry. The stroked geometry will, by definition already be inset half the
268 // stroke width but we also have to account for the scaling.
269 SkScalar scaleOffset = (scale - 1.0f) * SkTMax(SkTMax(SkTAbs(rrect.rect().fLeft),
270 SkTAbs(rrect.rect().fRight)),
271 SkTMax(SkTAbs(rrect.rect().fTop),
272 SkTAbs(rrect.rect().fBottom)));
273 SkScalar insetAmount = spotOffset.length() - (0.5f * srcSpaceSpotRadius) + scaleOffset;
274
275 // Compute area
276 SkScalar strokeWidth = srcSpaceSpotRadius + insetAmount;
277 SkScalar strokedArea = 2.0f*strokeWidth *
278 (spotShadowRRect.width() + spotShadowRRect.height());
279 SkScalar filledArea = (spotShadowRRect.height() + srcSpaceSpotRadius) *
280 (spotShadowRRect.width() + srcSpaceSpotRadius);
281
282 GrColor4f color = paint.getColor4f();
283 color.fRGBA[3] *= fSpotAlpha;
284 paint.setColor4f(color);
285
286 SkStrokeRec spotStrokeRec(SkStrokeRec::kFill_InitStyle);
287 // If the area of the stroked geometry is larger than the fill geometry,
288 // or if the caster is transparent, just fill it.
289 if (strokedArea > filledArea ||
290 fFlags & SkShadowFlags::kTransparentOccluder_ShadowFlag) {
291 spotStrokeRec.setStrokeStyle(srcSpaceSpotRadius, true);
292 } else {
293 // Since we can't have unequal strokes, inset the shadow rect so the inner
294 // and outer edges of the stroke will land where we want.
295 SkRect insetRect = spotShadowRRect.rect().makeInset(insetAmount / 2.0f,
296 insetAmount / 2.0f);
297 SkScalar insetRad = SkTMax(spotShadowRRect.getSimpleRadii().fX - insetAmount / 2.0f,
298 minRadius);
299 spotShadowRRect = SkRRect::MakeRectXY(insetRect, insetRad, insetRad);
300 spotStrokeRec.setStrokeStyle(strokeWidth, false);
301 }
302
303 // handle scale of radius and pad due to CTM
304 const SkScalar devSpaceSpotRadius = srcSpaceSpotRadius * scaleFactor;
305
306 spotShadowRRect.offset(spotOffset.fX, spotOffset.fY);
307
308 rtContext->drawShadowRRect(clip, std::move(paint), viewMatrix, spotShadowRRect,
309 devSpaceSpotRadius, GrStyle(spotStrokeRec, nullptr));
310 }
311
312 return true;
313 }
314
filterMaskGPU(GrContext *,sk_sp<GrTextureProxy> srcProxy,const SkMatrix & ctm,const SkIRect & maskRect) const315 sk_sp<GrTextureProxy> SkSpotShadowMaskFilterImpl::filterMaskGPU(GrContext*,
316 sk_sp<GrTextureProxy> srcProxy,
317 const SkMatrix& ctm,
318 const SkIRect& maskRect) const {
319 // This filter is generative and doesn't operate on pre-existing masks
320 return nullptr;
321 }
322
323 #endif
324
325 #ifndef SK_IGNORE_TO_STRING
toString(SkString * str) const326 void SkSpotShadowMaskFilterImpl::toString(SkString* str) const {
327 str->append("SkSpotShadowMaskFilterImpl: (");
328
329 str->append("occluderHeight: ");
330 str->appendScalar(fOccluderHeight);
331 str->append(" ");
332
333 str->append("lightPos: (");
334 str->appendScalar(fLightPos.fX);
335 str->append(", ");
336 str->appendScalar(fLightPos.fY);
337 str->append(", ");
338 str->appendScalar(fLightPos.fZ);
339 str->append(") ");
340
341 str->append("lightRadius: ");
342 str->appendScalar(fLightRadius);
343 str->append(" ");
344
345 str->append("spotAlpha: ");
346 str->appendScalar(fSpotAlpha);
347 str->append(" ");
348
349 str->append("flags: (");
350 if (fFlags) {
351 bool needSeparator = false;
352 SkAddFlagToString(str,
353 SkToBool(fFlags & SkShadowFlags::kTransparentOccluder_ShadowFlag),
354 "TransparentOccluder", &needSeparator);
355 SkAddFlagToString(str,
356 SkToBool(fFlags & SkShadowFlags::kGaussianEdge_ShadowFlag),
357 "GaussianEdge", &needSeparator);
358 SkAddFlagToString(str,
359 SkToBool(fFlags & SkShadowFlags::kLargerUmbra_ShadowFlag),
360 "LargerUmbra", &needSeparator);
361 } else {
362 str->append("None");
363 }
364 str->append("))");
365 }
366 #endif
367
368 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkSpotShadowMaskFilter)
369 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkSpotShadowMaskFilterImpl)
370 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
371