Skip to content

Fix feDisplacementMap applying scale twice - #1157

Open
bhaskargurram-ai wants to merge 1 commit into
linebender:mainfrom
bhaskargurram-ai:fix/fe-displacement-map-scale
Open

bhaskargurram-ai wants to merge 1 commit into
linebender:mainfrom
bhaskargurram-ai:fix/fe-displacement-map-scale

Conversation

@bhaskargurram-ai

Copy link
Copy Markdown

apply_displacement_map converts scale into pixels with scale_coordinates(fe.scale(), fe.scale(), ts), and then displacement_map::apply multiplied the offset by fe.scale() a second time, so the effective displacement was scale². For scale="30" that is a ±450 px offset: almost every sample lands outside the filter region and only a few specks of the source survive, which is what #1019 (and Figma/Illustrator "distort" exports) show.

This PR:

  • uses scale once (P'(x,y) = P(x + scale·(XC − ½), y + scale·(YC − ½)), with scale converted from user units to pixels as before);
  • reads the displacement map with non-premultiplied color values, which the spec requires ("The calculations using the pixel values from in2 are performed using non-premultiplied color values") and which the kernel's own doc comment already asked for; Skia does the same (unpremul in sk_displacement);
  • adds regression tests: a solid-color map (analytically verifiable shift of scale/2), the same map at 50% opacity (must render identically), primitiveUnits="objectBoundingBox", scale="0", and the fractal-noise reproducer from the issue.

With the issue's reproducer, the rect goes from 11 opaque pixels to 10,753 (unfiltered: 10,800), i.e. a solid interior with eroded edges, matching what Chrome/Firefox/Safari produce.

Not changed here: in is still converted to the color-interpolation-filters space like the other primitives (Chrome does this too), and objectBoundingBox still averages the bbox width/height into one scalar (Chrome only uses the horizontal scale, so there is no clear per-axis reference).

Fixes #1019

…d map

`apply_displacement_map` converts `scale` into pixels via
`scale_coordinates(fe.scale(), fe.scale(), ts)` and then
`displacement_map::apply` multiplied the offset by `fe.scale()` again,
so the effective displacement was `scale^2`. For `scale="30"` that is a
±450px offset, which samples outside the filter region for almost every
pixel and leaves only scattered specks of the source.

The map is now read with non-premultiplied color values, as the spec and
the kernel's own contract require.

Fixes linebender#1019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feDisplacementMap is not rendered correctly

1 participant