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