Repository: linebender/resvg (language: rust)

Issue:
When `feImage` contains `x` or `y`, the rendering is incorrect.
```xml
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <rect width="200" height="200" fill="#ffdd88"/>

  <defs>
    <rect fill="#ff0000" opacity="1" id="rect" width="100" height="100"/>
    <filter id="colorful-image" >
      <feImage xlink:href="#rect" href="#rect" x="30" y="50" 
        result="feImageResultnode" />
      <feMerge>
        <feMergeNode in="feImageResultnode" />
      </feMerge>
    </filter>
  </defs>
  <rect x="30" y="50" width="100" height="100" fill="#1e1705" filter="url(#colorful-image)"/>
  <rect x="30" y="50" width="100" height="100" stroke="#00f" fill="none"/>
</svg>
```
![](https://pichuang.oss-cn-beijing.aliyuncs.com/img/202311221843954.png)
i get left


Discussion / hints:
In `filter/mod.rs/apply_inner`, content on the left and top was mistakenly cropped first. After that, the `sub_pixmap` wasn't drawn correctly to its respective position. Using `<feImage xlink:href="#rect" href="#rect" x="40" y="60"` will make the issue more apparent.
Will take a look. `feImage` in general is a minefield and essentially unsupported. Mainly because it's completely undocumented and each library does [whatever it wants](https://razrfalcon.github.io/resvg-test-suite/svg-support-table.html#filters-feImage).

Fix patch:
diff --git a/crates/resvg/src/filter/mod.rs b/crates/resvg/src/filter/mod.rs
index 4ad198a91..ebeef5058 100644
--- a/crates/resvg/src/filter/mod.rs
+++ b/crates/resvg/src/filter/mod.rs
@@ -972,7 +972,15 @@ fn apply_image(
         }
         usvg::filter::ImageKind::Use(ref node) => {
             let (sx, sy) = ts.get_scale();
-            let transform = tiny_skia::Transform::from_scale(sx, sy);
+
+            let transform = tiny_skia::Transform::from_row(
+                sx,
+                0.0,
+                0.0,
+                sy,
+                subregion.x() as f32,
+                subregion.y() as f32,
+            );
 
             let ctx = crate::render::Context {
                 max_bbox: tiny_skia::IntRect::from_xywh(0, 0, region.width(), region.height())


Test patch:
diff --git a/crates/resvg/tests/integration/render.rs b/crates/resvg/tests/integration/render.rs
index 56c41cf8d..2843f39b7 100644
--- a/crates/resvg/tests/integration/render.rs
+++ b/crates/resvg/tests/integration/render.rs
@@ -196,6 +196,9 @@ use crate::render;
 #[test] fn filters_feImage_with_subregion_3() { assert_eq!(render("tests/filters/feImage/with-subregion-3"), 0); }
 #[test] fn filters_feImage_with_subregion_4() { assert_eq!(render("tests/filters/feImage/with-subregion-4"), 0); }
 #[test] fn filters_feImage_with_subregion_5() { assert_eq!(render("tests/filters/feImage/with-subregion-5"), 0); }
+#[test] fn filters_feImage_with_x_y_and_protruding_subregion_1() { assert_eq!(render("tests/filters/feImage/with-x-y-and-protruding-subregion-1"), 0); }
+#[test] fn filters_feImage_with_x_y_and_protruding_subregion_2() { assert_eq!(render("tests/filters/feImage/with-x-y-and-protruding-subregion-2"), 0); }
+#[test] fn filters_feImage_with_x_y() { assert_eq!(render("tests/filters/feImage/with-x-y"), 0); }
 #[test] fn filters_feMerge_color_interpolation_filters_eq_linearRGB() { assert_eq!(render("tests/filters/feMerge/color-interpolation-filters=linearRGB"), 0); }
 #[test] fn filters_feMerge_color_interpolation_filters_eq_sRGB() { assert_eq!(render("tests/filters/feMerge/color-interpolation-filters=sRGB"), 0); }
 #[test] fn filters_feMerge_complex_transform() { assert_eq!(render("tests/filters/feMerge/complex-transform"), 0); }
diff --git a/crates/resvg/tests/tests/filters/feImage/with-x-y-and-protruding-subregion-1.png b/crates/resvg/tests/tests/filters/feImage/with-x-y-and-protruding-subregion-1.png
new file mode 100644
index 000000000..88e2c6fb1
Binary files /dev/null and b/crates/resvg/tests/tests/filters/feImage/with-x-y-and-protruding-subregion-1.png differ
diff --git a/crates/resvg/tests/tests/filters/feImage/with-x-y-and-protruding-subregion-1.svg b/crates/resvg/tests/tests/filters/feImage/with-x-y-and-protruding-subregion-1.svg
new file mode 100644
index 000000000..43e051132
--- /dev/null
+++ b/crates/resvg/tests/tests/filters/feImage/with-x-y-and-protruding-subregion-1.svg
@@ -0,0 +1,15 @@
+<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>With x and y</title>
+    <defs>
+        <rect fill="green" id="rect3" width="100" height="100"/>
+        <filter id="filter1" >
+            <feImage xlink:href="#rect3" x="60" y="90"/>
+        </filter>
+    </defs>
+    <rect id="rect2" x="50" y="50" width="100" height="100" fill="#1e1705" filter="url(#filter1)"/>
+    <rect id="rect1" x="50" y="50" width="100" height="100" stroke="#00f" fill="none"/>
+
+    <!-- image frame -->
+    <rect id="frame" x="1" y="1" width="198" height="198" fill="none" stroke="black"/>
+</svg>
\ No newline at end of file
diff --git a/crates/resvg/tests/tests/filters/feImage/with-x-y-and-protruding-subregion-2.png b/crates/resvg/tests/tests/filters/feImage/with-x-y-and-protruding-subregion-2.png
new file mode 100644
index 000000000..5f16e4bca
Binary files /dev/null and b/crates/resvg/tests/tests/filters/feImage/with-x-y-and-protruding-subregion-2.png differ
diff --git a/crates/resvg/tests/tests/filters/feImage/with-x-y-and-protruding-subregion-2.svg b/crates/resvg/tests/tests/filters/feImage/with-x-y-and-protruding-subregion-2.svg
new file mode 100644
index 000000000..4ac91da77
--- /dev/null
+++ b/crates/resvg/tests/tests/filters/feImage/with-x-y-and-protruding-subregion-2.svg
@@ -0,0 +1,15 @@
+<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>With x and y</title>
+    <defs>
+        <rect fill="green" id="rect3" width="100" height="100"/>
+        <filter id="filter1" >
+            <feImage xlink:href="#rect3" x="20" y="0"/>
+        </filter>
+    </defs>
+    <rect id="rect2" x="50" y="50" width="100" height="100" fill="#1e1705" filter="url(#filter1)"/>
+    <rect id="rect1" x="50" y="50" width="100" height="100" stroke="#00f" fill="none"/>
+
+    <!-- image frame -->
+    <rect id="frame" x="1" y="1" width="198" height="198" fill="none" stroke="black"/>
+</svg>
\ No newline at end of file
diff --git a/crates/resvg/tests/tests/filters/feImage/with-x-y.png b/crates/resvg/tests/tests/filters/feImage/with-x-y.png
new file mode 100644
index 000000000..e4a462a74
Binary files /dev/null and b/crates/resvg/tests/tests/filters/feImage/with-x-y.png differ
diff --git a/crates/resvg/tests/tests/filters/feImage/with-x-y.svg b/crates/resvg/tests/tests/filters/feImage/with-x-y.svg
new file mode 100644
index 000000000..494ae12cd
--- /dev/null
+++ b/crates/resvg/tests/tests/filters/feImage/with-x-y.svg
@@ -0,0 +1,15 @@
+<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>With x and y</title>
+    <defs>
+        <rect fill="green" id="rect3" width="100" height="100"/>
+        <filter id="filter1" >
+            <feImage xlink:href="#rect3" x="50" y="50"/>
+        </filter>
+    </defs>
+    <rect id="rect2" x="50" y="50" width="100" height="100" fill="#1e1705" filter="url(#filter1)"/>
+    <rect id="rect1" x="50" y="50" width="100" height="100" stroke="#00f" fill="none"/>
+
+    <!-- image frame -->
+    <rect id="frame" x="1" y="1" width="198" height="198" fill="none" stroke="black"/>
+</svg>
\ No newline at end of file