Fix the rendering size of svg

- On the web, to return the image size, we need to use the natural size
   of the image, and not its dom size, as the later get modified since
   commit  b727aba4a0

 - The target size did not take in account the image fit, that's because
   former version of resvg could only render by respecting the aspect
   ratio. But since the web don't have this limitation, we now need to
   take it into account. And new version of resvg can also scale with
   any aspect ratio
This commit is contained in:
Olivier Goffart 2022-10-21 14:59:48 +02:00 committed by Olivier Goffart
parent 1fcbae5309
commit 2e08b7dd1e
8 changed files with 56 additions and 31 deletions

View file

@ -1150,25 +1150,32 @@ impl QtItemRenderer<'_> {
debug_assert!(target_height.get() > 0.);
let pixmap: qttypes::QPixmap = self.cache.get_or_update_cache_entry(item_rc, || {
// Query target_width/height here again to ensure that changes will invalidate the item rendering cache.
let target_width = target_width.get() as f64;
let target_height = target_height.get() as f64;
let source = source_property.get();
let origin = source.size();
let source: &ImageInner = (&source).into();
let has_source_clipping = source_rect.map_or(false, |rect| {
rect.is_valid()
&& (rect.x != 0.
|| rect.y != 0.
|| !rect.width.approx_eq(&target_width)
|| !rect.height.approx_eq(&target_height))
});
let source_size = if !has_source_clipping {
Some(euclid::size2(target_width as u32, target_height as u32))
// Query target_width/height here again to ensure that changes will invalidate the item rendering cache.
let t = euclid::size2(target_width.get(), target_height.get()).cast::<f64>();
let source_size = if source.is_svg() {
let has_source_clipping = source_rect.map_or(false, |rect| {
rect.is_valid()
&& (rect.x != 0.
|| rect.y != 0.
|| !rect.width.approx_eq(&t.width)
|| !rect.height.approx_eq(&t.height))
});
if has_source_clipping {
// Source size & clipping is not implemented yet
None
} else {
Some(i_slint_core::graphics::fit_size(image_fit, t.cast(), origin).cast())
}
} else {
// Source size & clipping is not implemented yet
None
};
image_to_pixmap((&source_property.get()).into(), source_size).map_or_else(
image_to_pixmap(source, source_size).map_or_else(
Default::default,
|mut pixmap: qttypes::QPixmap| {
let colorize = colorize_property.map_or(Brush::default(), |c| c.get());

View file

@ -1178,11 +1178,12 @@ impl<'a> GLItemRenderer<'a> {
let target_size_for_scalable_source = image_inner.is_svg().then(|| {
// get the scale factor as a property again, to ensure the cache is invalidated when the scale factor changes
let scale_factor = ScaleFactor::new(self.window.scale_factor());
PhysicalSize::from_lengths(
LogicalLength::new(target_width.get()) * scale_factor,
LogicalLength::new(target_height.get()) * scale_factor,
)
.cast()
let t = LogicalSize::from_lengths(
LogicalLength::new(target_width.get()),
LogicalLength::new(target_height.get()),
) * scale_factor;
i_slint_core::graphics::fit_size(image_fit, t, image.size()).cast()
});
TextureCacheKey::new(image_inner, target_size_for_scalable_source, image_rendering)

View file

@ -5,6 +5,7 @@ use i_slint_core::graphics::{
cache as core_cache, Image, ImageCacheKey, ImageInner, IntSize, OpaqueImage, OpaqueImageVTable,
SharedImageBuffer,
};
use i_slint_core::items::ImageFit;
use i_slint_core::lengths::{LogicalSize, ScaleFactor};
struct SkiaCachedImage {
@ -30,6 +31,7 @@ pub(crate) fn as_skia_image(
image: Image,
target_width: std::pin::Pin<&i_slint_core::Property<f32>>,
target_height: std::pin::Pin<&i_slint_core::Property<f32>>,
image_fit: ImageFit,
scale_factor: ScaleFactor,
) -> Option<skia_safe::Image> {
let image_inner: &ImageInner = (&image).into();
@ -51,6 +53,7 @@ pub(crate) fn as_skia_image(
// Query target_width/height here again to ensure that changes will invalidate the item rendering cache.
let target_size =
LogicalSize::new(target_width.get(), target_height.get()) * scale_factor;
let target_size = i_slint_core::graphics::fit_size(image_fit, target_size, svg.size());
let pixels = match svg.render(target_size.cast()).ok()? {
SharedImageBuffer::RGB8(_) => unreachable!(),
SharedImageBuffer::RGBA8(_) => unreachable!(),

View file

@ -153,6 +153,7 @@ impl<'a> SkiaRenderer<'a> {
image,
target_width,
target_height,
image_fit,
self.scale_factor,
)
.and_then(|skia_image| {

View file

@ -10,6 +10,7 @@ use crate::slice::Slice;
use crate::{SharedString, SharedVector};
use super::{IntRect, IntSize};
use crate::items::ImageFit;
#[cfg(feature = "image-decoders")]
pub mod cache;
@ -664,6 +665,21 @@ fn test_image_size_from_buffer_without_backend() {
}
}
/// Return an size that can be used to render an image in a buffer that matches a given ImageFit
pub fn fit_size(
image_fit: ImageFit,
target: euclid::Size2D<f32, PhysicalPx>,
origin: IntSize,
) -> euclid::Size2D<f32, PhysicalPx> {
let o = origin.cast::<f32>();
let ratio = match image_fit {
ImageFit::Fill => return target,
ImageFit::Contain => f32::min(target.width / o.width, target.height / o.height),
ImageFit::Cover => f32::max(target.width / o.width, target.height / o.height),
};
euclid::Size2D::from_untyped(o * ratio)
}
#[cfg(feature = "ffi")]
pub(crate) mod ffi {
#![allow(unsafe_code)]

View file

@ -46,7 +46,10 @@ impl HTMLImage {
pub fn size(&self) -> Option<IntSize> {
match self.image_load_pending.as_ref().get() {
true => None,
false => Some(IntSize::new(self.dom_element.width(), self.dom_element.height())),
false => Some(IntSize::new(
self.dom_element.natural_width(),
self.dom_element.natural_height(),
)),
}
}

View file

@ -45,9 +45,7 @@ impl ParsedSVG {
size: euclid::Size2D<u32, PhysicalPx>,
) -> Result<SharedImageBuffer, usvg::Error> {
let tree = &self.svg_tree;
// resvg doesn't support scaling to width/height, just fit to width.
// FIXME: the fit should actually depends on the image-fit property
let fit = usvg::FitTo::Width(size.width);
let fit = usvg::FitTo::Size(size.width, size.height);
let size =
fit.fit_to(tree.svg_node().size.to_screen_size()).ok_or(usvg::Error::InvalidSize)?;
let mut buffer = SharedPixelBuffer::new(size.width(), size.height());

View file

@ -939,13 +939,9 @@ impl<'a, T: ProcessScene> SceneBuilder<'a, T> {
}
}
_ => {
let img_src_size = source.size().cast::<f32>();
let img_src_size = source.size();
if let Some(buffer) = image_inner.render_to_buffer(Some(
euclid::size2(
phys_size.width * img_src_size.width / size.width as f32,
phys_size.height * img_src_size.height / size.height as f32,
)
.cast(),
crate::graphics::fit_size(image_fit, phys_size, img_src_size).cast(),
)) {
if let Some(clipped_relative_source_rect) = renderer_clip_in_source_rect_space
.intersection(&euclid::rect(
@ -971,8 +967,8 @@ impl<'a, T: ProcessScene> SceneBuilder<'a, T> {
.to_vector(),
)
.scale(
buf_size.width / img_src_size.width,
buf_size.height / img_src_size.height,
buf_size.width / img_src_size.width as f32,
buf_size.height / img_src_size.height as f32,
)
.cast(),
colorize,