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 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 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 106 SkMask::Format SkSpotShadowMaskFilterImpl::getFormat() const { 107 return SkMask::kA8_Format; 108 } 109 110 bool SkSpotShadowMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src, 111 const SkMatrix& matrix, 112 SkIPoint* margin) const { 113 // TODO something 114 return false; 115 } 116 117 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 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 136 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 150 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 159 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 198 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 315 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 326 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