xref: /aosp_15_r20/external/skia/src/effects/imagefilters/SkLightingImageFilter.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2012 The Android Open Source Project
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 "include/effects/SkImageFilters.h"
9 
10 #include "include/core/SkColor.h"
11 #include "include/core/SkFlattenable.h"
12 #include "include/core/SkImageFilter.h"
13 #include "include/core/SkM44.h"
14 #include "include/core/SkPoint.h"
15 #include "include/core/SkPoint3.h"
16 #include "include/core/SkRect.h"
17 #include "include/core/SkRefCnt.h"
18 #include "include/core/SkScalar.h"
19 #include "include/core/SkShader.h"
20 #include "include/core/SkTypes.h"
21 #include "include/effects/SkRuntimeEffect.h"
22 #include "include/private/base/SkCPUTypes.h"
23 #include "include/private/base/SkFloatingPoint.h"
24 #include "include/private/base/SkSpan_impl.h"
25 #include "src/core/SkImageFilterTypes.h"
26 #include "src/core/SkImageFilter_Base.h"
27 #include "src/core/SkKnownRuntimeEffects.h"
28 #include "src/core/SkReadBuffer.h"
29 #include "src/core/SkRectPriv.h"
30 #include "src/core/SkWriteBuffer.h"
31 
32 #include <optional>
33 #include <utility>
34 
35 struct SkISize;
36 
37 namespace {
38 // The 3D points/vectors used for lighting don't have a great analog for the rest of the image
39 // filtering system, and don't have any representation for ParameterSpace and LayerSpace. The
40 // SVG spec is also vague on how to handle managing them. Using the principle of least-surprise,
41 // the X and Y coordinates are treated as ParameterSpace<SkPoint|Vector> and the Z will be
42 // scaled by the average of the X and Y scale factors when tranforming to layer space. For uniform
43 // scaling transforms, this has the desirable behavior of uniformly scaling the Z axis as well.
44 struct ZValue {
ZValue__anon5970c9bd0111::ZValue45     ZValue() : fZ(0.f) {}
ZValue__anon5970c9bd0111::ZValue46     ZValue(float z) : fZ(z) {}
operator float__anon5970c9bd0111::ZValue47     operator float() const { return fZ; }
48 
49     float fZ;
50 };
51 } // anonymous namespace
52 
53 namespace skif {
54 template<>
55 class LayerSpace<ZValue> {
56 public:
57     LayerSpace() = default;
LayerSpace(ZValue z)58     explicit LayerSpace(ZValue z) : fData(z) {}
59 
val() const60     float val() const { return fData.fZ; }
61 
Map(const Mapping & mapping,ParameterSpace<ZValue> z)62     static LayerSpace<ZValue> Map(const Mapping& mapping, ParameterSpace<ZValue> z) {
63         // See comment on ZValue for rationale.
64         skif::LayerSpace<skif::Vector> z2d = mapping.paramToLayer(
65                 skif::ParameterSpace<skif::Vector>({ZValue(z), ZValue(z)}));
66         return LayerSpace<ZValue>(SkScalarAve(z2d.x(), z2d.y()));
67     }
68 
69 private:
70     ZValue fData;
71 };
72 } // namespace skif
73 
74 namespace {
75 
76 struct Light {
77     enum class Type {
78         kDistant,
79         kPoint,
80         kSpot,
81         kLast = kSpot
82     };
83 
84     Type fType;
85     SkColor fLightColor; // All lights
86 
87     // Location and direction are decomposed into typed XY and Z for how they are transformed from
88     // parameter space to layer space.
89     skif::ParameterSpace<SkPoint> fLocationXY; // Spotlight and point lights only
90     skif::ParameterSpace<ZValue>  fLocationZ;  //  ""
91 
92     skif::ParameterSpace<skif::Vector> fDirectionXY; // Spotlight and distant lights only
93     skif::ParameterSpace<ZValue>       fDirectionZ;  //  ""
94 
95     // Spotlight only (and unchanged by layer matrix)
96     float fFalloffExponent;
97     float fCosCutoffAngle;
98 
Point__anon5970c9bd0211::Light99     static Light Point(SkColor color, const SkPoint3& location) {
100         return {Type::kPoint,
101                 color,
102                 skif::ParameterSpace<SkPoint>({location.fX, location.fY}),
103                 skif::ParameterSpace<ZValue>(location.fZ),
104                 /*directionXY=*/{},
105                 /*directionZ=*/{},
106                 /*falloffExponent=*/0.f,
107                 /*cutoffAngle=*/0.f};
108     }
109 
Distant__anon5970c9bd0211::Light110     static Light Distant(SkColor color, const SkPoint3& direction) {
111         return {Type::kDistant,
112                 color,
113                 /*locationXY=*/{},
114                 /*locationZ=*/{},
115                 skif::ParameterSpace<skif::Vector>({direction.fX, direction.fY}),
116                 skif::ParameterSpace<ZValue>(direction.fZ),
117                 /*falloffExponent=*/0.f,
118                 /*cutoffAngle=*/0.f};
119     }
120 
Spot__anon5970c9bd0211::Light121     static Light Spot(SkColor color, const SkPoint3& location, const SkPoint3& direction,
122                       float falloffExponent, float cosCutoffAngle) {
123         return {Type::kSpot,
124                 color,
125                 skif::ParameterSpace<SkPoint>({location.fX, location.fY}),
126                 skif::ParameterSpace<ZValue>(location.fZ),
127                 skif::ParameterSpace<skif::Vector>({direction.fX, direction.fY}),
128                 skif::ParameterSpace<ZValue>(direction.fZ),
129                 falloffExponent,
130                 cosCutoffAngle};
131     }
132 };
133 
134 struct Material {
135     enum class Type {
136         kDiffuse,
137         kSpecular,
138         kLast = kSpecular
139     };
140 
141     Type fType;
142     // The base scale factor applied to alpha image to go from [0-1] to [0-depth] before computing
143     // surface normals.
144     skif::ParameterSpace<ZValue> fSurfaceDepth;
145 
146     // Non-geometric
147     float fK; // Reflectance coefficient
148     float fShininess; // Specular only
149 
Diffuse__anon5970c9bd0211::Material150     static Material Diffuse(float k, float surfaceDepth) {
151         return {Type::kDiffuse, skif::ParameterSpace<ZValue>(surfaceDepth), k, 0.f};
152     }
153 
Specular__anon5970c9bd0211::Material154     static Material Specular(float k, float shininess, float surfaceDepth) {
155         return {Type::kSpecular, skif::ParameterSpace<ZValue>(surfaceDepth), k, shininess};
156     }
157 };
158 
159 class SkLightingImageFilter final : public SkImageFilter_Base {
160 public:
SkLightingImageFilter(const Light & light,const Material & material,sk_sp<SkImageFilter> input)161     SkLightingImageFilter(const Light& light, const Material& material, sk_sp<SkImageFilter> input)
162             : SkImageFilter_Base(&input, 1)
163             , fLight(light)
164             , fMaterial(material) {}
165 
166     SkRect computeFastBounds(const SkRect& src) const override;
167 
168 protected:
169     void flatten(SkWriteBuffer&) const override;
170 
171 private:
172     friend void ::SkRegisterLightingImageFilterFlattenables();
173     SK_FLATTENABLE_HOOKS(SkLightingImageFilter)
174     static Light LegacyDeserializeLight(SkReadBuffer& buffer);
175     static sk_sp<SkFlattenable> LegacyDiffuseCreateProc(SkReadBuffer& buffer);
176     static sk_sp<SkFlattenable> LegacySpecularCreateProc(SkReadBuffer& buffer);
177 
onAffectsTransparentBlack() const178     bool onAffectsTransparentBlack() const override { return true; }
179 
180     skif::FilterResult onFilterImage(const skif::Context&) const override;
181 
182     skif::LayerSpace<SkIRect> onGetInputLayerBounds(
183             const skif::Mapping& mapping,
184             const skif::LayerSpace<SkIRect>& desiredOutput,
185             std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;
186 
187     std::optional<skif::LayerSpace<SkIRect>> onGetOutputLayerBounds(
188             const skif::Mapping& mapping,
189             std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;
190 
requiredInput(const skif::LayerSpace<SkIRect> & desiredOutput) const191     skif::LayerSpace<SkIRect> requiredInput(const skif::LayerSpace<SkIRect>& desiredOutput) const {
192         // We request 1px of padding so that the visible normal map can do a regular Sobel kernel
193         // eval. The Sobel kernel is always applied in layer pixels
194         skif::LayerSpace<SkIRect> requiredInput = desiredOutput;
195         requiredInput.outset(skif::LayerSpace<SkISize>({1, 1}));
196         return requiredInput;
197     }
198 
199     Light fLight;
200     Material fMaterial;
201 };
202 
203 // Creates a shader that performs a Sobel filter on the alpha channel of the input image, using
204 // 'edgeBounds' to decide how to modify the kernel weights.
make_normal_shader(sk_sp<SkShader> alphaMap,const skif::LayerSpace<SkIRect> & edgeBounds,skif::LayerSpace<ZValue> surfaceDepth)205 sk_sp<SkShader> make_normal_shader(sk_sp<SkShader> alphaMap,
206                                    const skif::LayerSpace<SkIRect>& edgeBounds,
207                                    skif::LayerSpace<ZValue> surfaceDepth) {
208     const SkRuntimeEffect* normalEffect =
209             GetKnownRuntimeEffect(SkKnownRuntimeEffects::StableKey::kNormal);
210 
211     SkRuntimeShaderBuilder builder(sk_ref_sp(normalEffect));
212     builder.child("alphaMap") = std::move(alphaMap);
213     builder.uniform("edgeBounds") = SkRect::Make(SkIRect(edgeBounds)).makeInset(0.5f, 0.5f);
214     builder.uniform("negSurfaceDepth") = -surfaceDepth.val();
215 
216     return builder.makeShader();
217 }
218 
make_lighting_shader(sk_sp<SkShader> normalMap,Light::Type lightType,SkColor lightColor,skif::LayerSpace<SkPoint> locationXY,skif::LayerSpace<ZValue> locationZ,skif::LayerSpace<skif::Vector> directionXY,skif::LayerSpace<ZValue> directionZ,float falloffExponent,float cosCutoffAngle,Material::Type matType,skif::LayerSpace<ZValue> surfaceDepth,float k,float shininess)219 sk_sp<SkShader> make_lighting_shader(sk_sp<SkShader> normalMap,
220                                      Light::Type lightType,
221                                      SkColor lightColor,
222                                      skif::LayerSpace<SkPoint> locationXY,
223                                      skif::LayerSpace<ZValue> locationZ,
224                                      skif::LayerSpace<skif::Vector> directionXY,
225                                      skif::LayerSpace<ZValue> directionZ,
226                                      float falloffExponent,
227                                      float cosCutoffAngle,
228                                      Material::Type matType,
229                                      skif::LayerSpace<ZValue> surfaceDepth,
230                                      float k,
231                                      float shininess) {
232 
233     const SkRuntimeEffect* lightingEffect =
234             GetKnownRuntimeEffect(SkKnownRuntimeEffects::StableKey::kLighting);
235 
236     SkRuntimeShaderBuilder builder(sk_ref_sp(lightingEffect));
237     builder.child("normalMap") = std::move(normalMap);
238 
239     builder.uniform("materialAndLightType") =
240             SkV4{surfaceDepth.val(),
241                  shininess,
242                  matType == Material::Type::kDiffuse ? 0.f : 1.f,
243                  lightType == Light::Type::kPoint ?
244                          0.f : (lightType == Light::Type::kDistant ? -1.f : 1.f)};
245     builder.uniform("lightPosAndSpotFalloff") =
246             SkV4{locationXY.x(), locationXY.y(), locationZ.val(), falloffExponent};
247 
248     // Pre-normalize the light direction, but this can be (0,0,0) for point lights, which won't use
249     // the uniform anyways. Avoid a division by 0 to keep ASAN happy or in the event that a spot/dir
250     // light have bad user input.
251     SkV3 dir{directionXY.x(), directionXY.y(), directionZ.val()};
252     float invDirLen = dir.length();
253     invDirLen = invDirLen ? 1.0f / invDirLen : 0.f;
254     builder.uniform("lightDirAndSpotCutoff") =
255             SkV4{invDirLen*dir.x, invDirLen*dir.y, invDirLen*dir.z, cosCutoffAngle};
256 
257     // Historically, the Skia lighting image filter did not apply any color space transformation to
258     // the light's color. The SVG spec for the lighting effects does not stipulate how to interpret
259     // the color for a light. Overall, it does not have a principled physically based approach, but
260     // the closest way to interpret it, is:
261     //  - the material's K is a uniformly distributed reflectance coefficient
262     //  - lighting *should* be calculated in a linear color space, which is the default for SVG
263     //    filters. Chromium manages these color transformations using SkImageFilters::ColorFilter
264     //    so it's not necessarily reflected in the Context's color space.
265     //  - it's unspecified in the SVG spec if the light color should be transformed to linear or
266     //    interpreted as linear already. Regardless, if there was any transformation that needed to
267     //    occur, Blink took care of it in the past so adding color space management to the light
268     //    color would be a breaking change.
269     //  - so for now, leave the color un-modified and apply K up front since no color space
270     //    transforms need to be performed on the original light color.
271     const float colorScale = k / 255.f;
272     builder.uniform("lightColor") = SkV3{SkColorGetR(lightColor) * colorScale,
273                                          SkColorGetG(lightColor) * colorScale,
274                                          SkColorGetB(lightColor) * colorScale};
275 
276     return builder.makeShader();
277 }
278 
make_lighting(const Light & light,const Material & material,sk_sp<SkImageFilter> input,const SkImageFilters::CropRect & cropRect)279 sk_sp<SkImageFilter> make_lighting(const Light& light,
280                                    const Material& material,
281                                    sk_sp<SkImageFilter> input,
282                                    const SkImageFilters::CropRect& cropRect) {
283     // According to the spec, ks and kd can be any non-negative number:
284     // http://www.w3.org/TR/SVG/filters.html#feSpecularLightingElement
285     if (!SkIsFinite(material.fK, material.fShininess, ZValue(material.fSurfaceDepth)) ||
286         material.fK < 0.f) {
287         return nullptr;
288     }
289 
290     // Ensure light values are finite, and the cosine should be between -1 and 1
291     if (!SkPoint(light.fLocationXY).isFinite() || !skif::Vector(light.fDirectionXY).isFinite() ||
292         !SkIsFinite(light.fFalloffExponent, light.fCosCutoffAngle,
293                     ZValue(light.fLocationZ), ZValue(light.fDirectionZ)) ||
294         light.fCosCutoffAngle < -1.f || light.fCosCutoffAngle > 1.f) {
295         return nullptr;
296     }
297 
298     // If a crop rect is provided, it clamps both the input (to better match the SVG's normal
299     // boundary condition spec) and the output (because otherwise it has infinite bounds).
300     sk_sp<SkImageFilter> filter = std::move(input);
301     if (cropRect) {
302         filter = SkImageFilters::Crop(*cropRect, std::move(filter));
303     }
304     filter = sk_sp<SkImageFilter>(
305             new SkLightingImageFilter(light, material, std::move(filter)));
306     if (cropRect) {
307         filter = SkImageFilters::Crop(*cropRect, std::move(filter));
308     }
309     return filter;
310 }
311 
312 } // anonymous namespace
313 
DistantLitDiffuse(const SkPoint3 & direction,SkColor lightColor,SkScalar surfaceScale,SkScalar kd,sk_sp<SkImageFilter> input,const CropRect & cropRect)314 sk_sp<SkImageFilter> SkImageFilters::DistantLitDiffuse(
315         const SkPoint3& direction, SkColor lightColor, SkScalar surfaceScale, SkScalar kd,
316         sk_sp<SkImageFilter> input, const CropRect& cropRect) {
317     return make_lighting(Light::Distant(lightColor, direction),
318                          Material::Diffuse(kd, surfaceScale),
319                          std::move(input), cropRect);
320 }
321 
PointLitDiffuse(const SkPoint3 & location,SkColor lightColor,SkScalar surfaceScale,SkScalar kd,sk_sp<SkImageFilter> input,const CropRect & cropRect)322 sk_sp<SkImageFilter> SkImageFilters::PointLitDiffuse(
323         const SkPoint3& location, SkColor lightColor, SkScalar surfaceScale, SkScalar kd,
324         sk_sp<SkImageFilter> input, const CropRect& cropRect) {
325     return make_lighting(Light::Point(lightColor, location),
326                          Material::Diffuse(kd, surfaceScale),
327                          std::move(input), cropRect);
328 }
329 
SpotLitDiffuse(const SkPoint3 & location,const SkPoint3 & target,SkScalar falloffExponent,SkScalar cutoffAngle,SkColor lightColor,SkScalar surfaceScale,SkScalar kd,sk_sp<SkImageFilter> input,const CropRect & cropRect)330 sk_sp<SkImageFilter> SkImageFilters::SpotLitDiffuse(
331         const SkPoint3& location, const SkPoint3& target, SkScalar falloffExponent,
332         SkScalar cutoffAngle, SkColor lightColor, SkScalar surfaceScale, SkScalar kd,
333         sk_sp<SkImageFilter> input, const CropRect& cropRect) {
334     SkPoint3 dir = target - location;
335     float cosCutoffAngle = SkScalarCos(SkDegreesToRadians(cutoffAngle));
336     return make_lighting(Light::Spot(lightColor, location, dir, falloffExponent, cosCutoffAngle),
337                          Material::Diffuse(kd, surfaceScale),
338                          std::move(input), cropRect);
339 }
340 
DistantLitSpecular(const SkPoint3 & direction,SkColor lightColor,SkScalar surfaceScale,SkScalar ks,SkScalar shininess,sk_sp<SkImageFilter> input,const CropRect & cropRect)341 sk_sp<SkImageFilter> SkImageFilters::DistantLitSpecular(
342         const SkPoint3& direction, SkColor lightColor, SkScalar surfaceScale, SkScalar ks,
343         SkScalar shininess, sk_sp<SkImageFilter> input, const CropRect& cropRect) {
344     return make_lighting(Light::Distant(lightColor, direction),
345                          Material::Specular(ks, shininess, surfaceScale),
346                          std::move(input), cropRect);
347 }
348 
PointLitSpecular(const SkPoint3 & location,SkColor lightColor,SkScalar surfaceScale,SkScalar ks,SkScalar shininess,sk_sp<SkImageFilter> input,const CropRect & cropRect)349 sk_sp<SkImageFilter> SkImageFilters::PointLitSpecular(
350         const SkPoint3& location, SkColor lightColor, SkScalar surfaceScale, SkScalar ks,
351         SkScalar shininess, sk_sp<SkImageFilter> input, const CropRect& cropRect) {
352     return make_lighting(Light::Point(lightColor, location),
353                          Material::Specular(ks, shininess, surfaceScale),
354                          std::move(input), cropRect);
355 }
356 
SpotLitSpecular(const SkPoint3 & location,const SkPoint3 & target,SkScalar falloffExponent,SkScalar cutoffAngle,SkColor lightColor,SkScalar surfaceScale,SkScalar ks,SkScalar shininess,sk_sp<SkImageFilter> input,const CropRect & cropRect)357 sk_sp<SkImageFilter> SkImageFilters::SpotLitSpecular(
358         const SkPoint3& location, const SkPoint3& target, SkScalar falloffExponent,
359         SkScalar cutoffAngle, SkColor lightColor, SkScalar surfaceScale, SkScalar ks,
360         SkScalar shininess, sk_sp<SkImageFilter> input, const CropRect& cropRect) {
361     SkPoint3 dir = target - location;
362     float cosCutoffAngle = SkScalarCos(SkDegreesToRadians(cutoffAngle));
363     return make_lighting(Light::Spot(lightColor, location, dir, falloffExponent, cosCutoffAngle),
364                          Material::Specular(ks, shininess, surfaceScale),
365                          std::move(input), cropRect);
366 }
367 
SkRegisterLightingImageFilterFlattenables()368 void SkRegisterLightingImageFilterFlattenables() {
369     SK_REGISTER_FLATTENABLE(SkLightingImageFilter);
370     // TODO (michaelludwig): Remove after grace period for SKPs to stop using old name
371     SkFlattenable::Register("SkDiffuseLightingImageFilter",
372                             SkLightingImageFilter::LegacyDiffuseCreateProc);
373     SkFlattenable::Register("SkSpecularLightingImageFilter",
374                             SkLightingImageFilter::LegacySpecularCreateProc);
375 }
376 
377 ///////////////////////////////////////////////////////////////////////////////
378 
CreateProc(SkReadBuffer & buffer)379 sk_sp<SkFlattenable> SkLightingImageFilter::CreateProc(SkReadBuffer& buffer) {
380     SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
381 
382     Light light;
383     light.fType = buffer.read32LE(Light::Type::kLast);
384     light.fLightColor = buffer.readColor();
385 
386     SkPoint3 lightPos, lightDir;
387     buffer.readPoint3(&lightPos);
388     light.fLocationXY = skif::ParameterSpace<SkPoint>({lightPos.fX, lightPos.fY});
389     light.fLocationZ = skif::ParameterSpace<ZValue>(lightPos.fZ);
390 
391     buffer.readPoint3(&lightDir);
392     light.fDirectionXY = skif::ParameterSpace<skif::Vector>({lightDir.fX, lightDir.fY});
393     light.fDirectionZ = skif::ParameterSpace<ZValue>(lightDir.fZ);
394 
395     light.fFalloffExponent = buffer.readScalar();
396     light.fCosCutoffAngle = buffer.readScalar();
397 
398     Material material;
399     material.fType = buffer.read32LE(Material::Type::kLast);
400     material.fSurfaceDepth = skif::ParameterSpace<ZValue>(buffer.readScalar());
401     material.fK = buffer.readScalar();
402     material.fShininess = buffer.readScalar();
403 
404     if (!buffer.isValid()) {
405         return nullptr;
406     }
407 
408     return make_lighting(light, material, common.getInput(0), common.cropRect());
409 }
410 
LegacyDeserializeLight(SkReadBuffer & buffer)411 Light SkLightingImageFilter::LegacyDeserializeLight(SkReadBuffer& buffer) {
412     // Light::Type has the same order as the legacy SkImageFilterLight::LightType enum
413     Light::Type lightType = buffer.read32LE(Light::Type::kLast);
414     if (!buffer.isValid()) {
415         return {};
416     }
417 
418     // Legacy lights stored just the RGB, but as floats (notably *not* normalized to [0-1])
419     SkColor lightColor = SkColorSetARGB(/*a (ignored)=*/255,
420                                         /*r=*/ (U8CPU) buffer.readScalar(),
421                                         /*g=*/ (U8CPU) buffer.readScalar(),
422                                         /*b=*/ (U8CPU) buffer.readScalar());
423     // Legacy lights only serialized fields specific to that type
424     switch (lightType) {
425         case Light::Type::kDistant: {
426             SkPoint3 dir = {buffer.readScalar(), buffer.readScalar(), buffer.readScalar()};
427             return Light::Distant(lightColor, dir);
428         }
429         case Light::Type::kPoint: {
430             SkPoint3 loc = {buffer.readScalar(), buffer.readScalar(), buffer.readScalar()};
431             return Light::Point(lightColor, loc);
432         }
433         case Light::Type::kSpot: {
434             SkPoint3 loc = {buffer.readScalar(), buffer.readScalar(), buffer.readScalar()};
435             SkPoint3 target = {buffer.readScalar(), buffer.readScalar(), buffer.readScalar()};
436             float falloffExponent = buffer.readScalar();
437             float cosOuterConeAngle = buffer.readScalar();
438             buffer.readScalar(); // skip cosInnerConeAngle, derived from outer cone angle
439             buffer.readScalar(); // skip coneScale, which is a constant
440             buffer.readScalar(); // skip S, which is normalize(target - loc)
441             buffer.readScalar(); //  ""
442             buffer.readScalar(); //  ""
443             return Light::Spot(lightColor, loc, target - loc, falloffExponent, cosOuterConeAngle);
444         }
445     }
446 
447     SkUNREACHABLE; // Validation by read32LE() should avoid this
448 }
449 
LegacyDiffuseCreateProc(SkReadBuffer & buffer)450 sk_sp<SkFlattenable> SkLightingImageFilter::LegacyDiffuseCreateProc(SkReadBuffer& buffer) {
451     SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
452 
453     Light light = LegacyDeserializeLight(buffer);
454 
455     // Legacy implementations used (scale/255) when filtering, but serialized (fScale*255) so the
456     // buffer held the original unmodified surface scale.
457     float surfaceScale = buffer.readScalar();
458     float kd = buffer.readScalar();
459     Material material = Material::Diffuse(kd, surfaceScale);
460 
461     return make_lighting(light, material, common.getInput(0), common.cropRect());
462 }
463 
LegacySpecularCreateProc(SkReadBuffer & buffer)464 sk_sp<SkFlattenable> SkLightingImageFilter::LegacySpecularCreateProc(SkReadBuffer& buffer) {
465     SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
466 
467     Light light = LegacyDeserializeLight(buffer);
468 
469     // Legacy implementations used (scale/255) when filtering, but serialized (fScale*255) so the
470     // buffer held the original unmodified surface scale.
471     float surfaceScale = buffer.readScalar();
472     float ks = buffer.readScalar();
473     float shininess = buffer.readScalar();
474     Material material = Material::Specular(ks, shininess, surfaceScale);
475 
476     return make_lighting(light, material, common.getInput(0), common.cropRect());
477 }
478 
flatten(SkWriteBuffer & buffer) const479 void SkLightingImageFilter::flatten(SkWriteBuffer& buffer) const {
480     this->SkImageFilter_Base::flatten(buffer);
481 
482     // Light
483     buffer.writeInt((int) fLight.fType);
484     buffer.writeColor(fLight.fLightColor);
485 
486     buffer.writePoint(SkPoint(fLight.fLocationXY));
487     buffer.writeScalar(ZValue(fLight.fLocationZ));
488 
489     skif::Vector dirXY{fLight.fDirectionXY};
490     buffer.writePoint(SkPoint{dirXY.fX, dirXY.fY});
491     buffer.writeScalar(ZValue(fLight.fDirectionZ));
492 
493     buffer.writeScalar(fLight.fFalloffExponent);
494     buffer.writeScalar(fLight.fCosCutoffAngle);
495 
496     // Material
497     buffer.writeInt((int) fMaterial.fType);
498     buffer.writeScalar(ZValue(fMaterial.fSurfaceDepth));
499     buffer.writeScalar(fMaterial.fK);
500     buffer.writeScalar(fMaterial.fShininess);
501 }
502 
503 ///////////////////////////////////////////////////////////////////////////////
504 
onFilterImage(const skif::Context & ctx) const505 skif::FilterResult SkLightingImageFilter::onFilterImage(const skif::Context& ctx) const {
506     using ShaderFlags = skif::FilterResult::ShaderFlags;
507 
508     auto mapZToLayer = [&ctx](skif::ParameterSpace<ZValue> z) {
509         return skif::LayerSpace<ZValue>::Map(ctx.mapping(), z);
510     };
511 
512     // Map lighting and material parameters into layer space
513     skif::LayerSpace<ZValue> surfaceDepth = mapZToLayer(fMaterial.fSurfaceDepth);
514     skif::LayerSpace<SkPoint> lightLocationXY = ctx.mapping().paramToLayer(fLight.fLocationXY);
515     skif::LayerSpace<ZValue> lightLocationZ = mapZToLayer(fLight.fLocationZ);
516     skif::LayerSpace<skif::Vector> lightDirXY = ctx.mapping().paramToLayer(fLight.fDirectionXY);
517     skif::LayerSpace<ZValue> lightDirZ = mapZToLayer(fLight.fDirectionZ);
518 
519     // The normal map is determined by a 3x3 kernel, so we request a 1px outset of what should be
520     // filled by the lighting equation. Ideally this means there are no boundary conditions visible.
521     // If the required input is incomplete, the lighting filter handles the boundaries in two ways:
522     // - When the actual child output's edge matches the desired output's edge, it uses clamped
523     //   tiling at the desired output. This approximates the modified Sobel kernel's specified in
524     //   https://drafts.fxtf.org/filter-effects/#feDiffuseLightingElement. NOTE: It's identical to
525     //   the interior kernel and near equal on the 4 edges (only weights are biased differently).
526     //   The four corners' convolution sums with clamped tiling are not equal, but should not be
527     //   objectionable since the normals produced are reasonable and still further processed by the
528     //   lighting equation. The increased complexity is not worth it for just 4 pixels of output.
529     // - However, when the desired output is far larger than the produced image, we process the
530     //   child output with the default decal tiling that the Skia image filter pipeline relies on.
531     //   This creates a visual bevel at the image boundary but avoids producing streaked normals if
532     //   the clamped tiling was used in all scenarios.
533     skif::LayerSpace<SkIRect> requiredInput = this->requiredInput(ctx.desiredOutput());
534     skif::FilterResult childOutput =
535             this->getChildOutput(0, ctx.withNewDesiredOutput(requiredInput));
536 
537     skif::LayerSpace<SkIRect> clampRect = requiredInput; // effectively no clamping of normals
538     if (!childOutput.layerBounds().contains(requiredInput)) {
539         // Adjust clampRect edges to desiredOutput if the actual child output matched the lighting
540         // output size (typical SVG case). Otherwise leave coordinates alone to use decal tiling
541         // automatically for the pixels outside the child image but inside the desired output.
542         auto edgeClamp = [](int actualEdgeValue, int requestedEdgeValue, int outputEdge) {
543             return actualEdgeValue == outputEdge ? outputEdge : requestedEdgeValue;
544         };
545         auto inputRect = childOutput.layerBounds();
546         auto clampTo = ctx.desiredOutput();
547         clampRect = skif::LayerSpace<SkIRect>({
548                 edgeClamp(inputRect.left(),   requiredInput.left(),   clampTo.left()),
549                 edgeClamp(inputRect.top(),    requiredInput.top(),    clampTo.top()),
550                 edgeClamp(inputRect.right(),  requiredInput.right(),  clampTo.right()),
551                 edgeClamp(inputRect.bottom(), requiredInput.bottom(), clampTo.bottom())});
552     }
553 
554     skif::FilterResult::Builder builder{ctx};
555     builder.add(childOutput, /*sampleBounds=*/clampRect, ShaderFlags::kSampledRepeatedly);
556     return builder.eval([&](SkSpan<sk_sp<SkShader>> input) {
557         // TODO: Once shaders are deferred in FilterResult, it will likely make sense to have an
558         // internal normal map filter that uses this shader, and then have the lighting effects as
559         // a separate filter. It's common for multiple lights to use the same input (producing the
560         // same normal map) before being merged together. With a separate normal image filter, its
561         // output would be automatically cached, and the lighting equation shader would be deferred
562         // to the merge's draw operation, making for a maximum of 2 renderpasses instead of N+1.
563         sk_sp<SkShader> normals = make_normal_shader(std::move(input[0]), clampRect, surfaceDepth);
564         return make_lighting_shader(std::move(normals),
565                                     // Light in layer space
566                                     fLight.fType,
567                                     fLight.fLightColor,
568                                     lightLocationXY,
569                                     lightLocationZ,
570                                     lightDirXY,
571                                     lightDirZ,
572                                     fLight.fFalloffExponent,
573                                     fLight.fCosCutoffAngle,
574                                     // Material in layer space
575                                     fMaterial.fType,
576                                     surfaceDepth,
577                                     fMaterial.fK,
578                                     fMaterial.fShininess);
579     });
580 }
581 
onGetInputLayerBounds(const skif::Mapping & mapping,const skif::LayerSpace<SkIRect> & desiredOutput,std::optional<skif::LayerSpace<SkIRect>> contentBounds) const582 skif::LayerSpace<SkIRect> SkLightingImageFilter::onGetInputLayerBounds(
583         const skif::Mapping& mapping,
584         const skif::LayerSpace<SkIRect>& desiredOutput,
585         std::optional<skif::LayerSpace<SkIRect>> contentBounds) const {
586     skif::LayerSpace<SkIRect> requiredInput = this->requiredInput(desiredOutput);
587     return this->getChildInputLayerBounds(0, mapping, requiredInput, contentBounds);
588 }
589 
onGetOutputLayerBounds(const skif::Mapping & mapping,std::optional<skif::LayerSpace<SkIRect>> contentBounds) const590 std::optional<skif::LayerSpace<SkIRect>> SkLightingImageFilter::onGetOutputLayerBounds(
591         const skif::Mapping& mapping,
592         std::optional<skif::LayerSpace<SkIRect>> contentBounds) const {
593     // The lighting equation is defined on the entire plane, even if the input image that defines
594     // the normal map is bounded. It just is evaluated at a constant normal vector, which can still
595     // produce non-constant color since the direction to the eye and light change per pixel.
596     return skif::LayerSpace<SkIRect>::Unbounded();
597 }
598 
computeFastBounds(const SkRect & src) const599 SkRect SkLightingImageFilter::computeFastBounds(const SkRect& src) const {
600     return SkRectPriv::MakeLargeS32();
601 }
602