[librsvg] (#471): Fix blurry semi-opaque objects when rendering with a scaled transformation



commit 6dfee76bc239147e358796eb141a8c8cae129493
Author: Federico Mena Quintero <federico gnome org>
Date:   Tue Jun 18 09:57:30 2019 -0500

    (#471): Fix blurry semi-opaque objects when rendering with a scaled transformation
    
    The temporary surface was being created at a size relative to the
    current transformation.  However, when the initial transformation was
    scaled not to 1:1, the temporary surface would in effect get created
    at a different pixel size than what is needed for compositing at 1:1.
    
    So, we now create the temporary surface in what is effectively device
    pixels.  This way, when initial transform *is* scaled, the temporary
    surface will get scaled up or down to the "natural" size with 1:1
    pixels.
    
    Fixes https://gitlab.gnome.org/GNOME/librsvg/issues/471

 librsvg_crate/tests/primitives.rs | 50 +++++++++++++++++++++++++++++++++++++++
 rsvg_internals/src/drawing_ctx.rs | 29 ++++++++++++++---------
 2 files changed, 68 insertions(+), 11 deletions(-)
---
diff --git a/librsvg_crate/tests/primitives.rs b/librsvg_crate/tests/primitives.rs
index 4f4a270a..13f6f15a 100644
--- a/librsvg_crate/tests/primitives.rs
+++ b/librsvg_crate/tests/primitives.rs
@@ -96,6 +96,56 @@ fn simple_opacity_with_offset_viewport() {
     );
 }
 
+#[test]
+// https://gitlab.gnome.org/GNOME/librsvg/issues/471
+fn simple_opacity_with_scale() {
+    let svg = load_svg(
+        br#"<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"; width="50" height="50">
+  <g opacity="0.5">
+    <rect x="10" y="10" width="30" height="30" fill="blue"/>
+  </g>
+</svg>
+"#,
+    );
+
+    let output_surf = render_to_viewport(
+        &svg,
+        SurfaceSize(500, 500),
+        |cr| {
+            cr.translate(50.0, 50.0);
+            cr.scale(8.0, 8.0);
+        },
+        cairo::Rectangle {
+            x: 0.0,
+            y: 0.0,
+            width: 50.0,
+            height: 50.0,
+        },
+    )
+    .unwrap();
+
+    let reference_surf = cairo::ImageSurface::create(cairo::Format::ARgb32, 500, 500).unwrap();
+
+    {
+        let cr = cairo::Context::new(&reference_surf);
+
+        cr.translate(50.0, 50.0);
+        cr.scale(8.0, 8.0);
+        cr.rectangle(10.0, 10.0, 30.0, 30.0);
+        cr.set_source_rgba(0.0, 0.0, 1.0, 0.5);
+        cr.fill();
+    }
+
+    let reference_surf = SharedImageSurface::new(reference_surf, SurfaceType::SRgb).unwrap();
+
+    compare_to_surface(
+        &output_surf,
+        &reference_surf,
+        "simple_opacity_with_scale",
+    );
+}
+
 #[test]
 fn opacity_inside_transformed_group() {
     let svg = load_svg(
diff --git a/rsvg_internals/src/drawing_ctx.rs b/rsvg_internals/src/drawing_ctx.rs
index ec7f4ca9..ce0a6874 100644
--- a/rsvg_internals/src/drawing_ctx.rs
+++ b/rsvg_internals/src/drawing_ctx.rs
@@ -225,10 +225,17 @@ impl DrawingCtx {
     }
 
     fn size_for_temporary_surface(&self) -> (i32, i32) {
+        let (viewport_width, viewport_height) = (self.rect.width, self.rect.height);
+
+        let (scaled_width, scaled_height) = self.initial_affine_with_offset().transform_distance(
+            viewport_width,
+            viewport_height,
+        );
+
         // We need a size in whole pixels, so use ceil() to ensure the whole viewport fits
         // into the temporary surface.
-        let width = self.rect.width.ceil() as i32;
-        let height = self.rect.height.ceil() as i32;
+        let width = scaled_width.ceil() as i32;
+        let height = scaled_height.ceil() as i32;
 
         (width, height)
     }
@@ -896,26 +903,26 @@ impl CompositingAffines {
             cairo::Matrix::multiply(&current, &initial_inverse)
         };
 
+        let (scale_x, scale_y) = initial.transform_distance(1.0, 1.0);
+
         let for_temporary_surface = if is_topmost_temporary_surface {
             let untransformed = cairo::Matrix::multiply(&current, &initial_inverse);
-            untransformed
+            let mut scaled_to_temp_surface = untransformed;
+            scaled_to_temp_surface.scale(scale_x, scale_y);
+            scaled_to_temp_surface
         } else {
             current
         };
 
         let compositing = if is_topmost_temporary_surface {
-            initial
+            let mut scaled = initial;
+            scaled.scale(1.0 / scale_x, 1.0 / scale_y);
+            scaled
         } else {
             cairo::Matrix::identity()
         };
 
-        // This is the inverse of "compositing"; we do it this way
-        // instead of inverting that one to preserve accuracy.
-        let for_snapshot = if is_topmost_temporary_surface {
-            initial_inverse
-        } else {
-            cairo::Matrix::identity()
-        };
+        let for_snapshot = compositing.try_invert().unwrap();
 
         CompositingAffines {
             outside_temporary_surface,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]