(#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 #471
parent c8cb94b5
Pipeline #89190 passed with stages
in 36 minutes and 34 seconds
......@@ -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(
......
......@@ -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,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment