Make `Image::paint_at` pixel-perfect crisp for SVG images (#7078)

This commit is contained in:
Emil Ernerfeldt 2025-05-23 10:15:17 +02:00 committed by GitHub
parent b8334f365b
commit ec8b41f7ec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 36 additions and 12 deletions

View File

@ -3114,6 +3114,8 @@ impl Context {
));
let max_preview_size = vec2(48.0, 32.0);
let pixels_per_point = self.pixels_per_point();
ui.group(|ui| {
ScrollArea::vertical()
.max_height(300.0)
@ -3128,15 +3130,16 @@ impl Context {
.show(ui, |ui| {
for (&texture_id, meta) in textures {
let [w, h] = meta.size;
let point_size = vec2(w as f32, h as f32) / pixels_per_point;
let mut size = vec2(w as f32, h as f32);
let mut size = point_size;
size *= (max_preview_size.x / size.x).min(1.0);
size *= (max_preview_size.y / size.y).min(1.0);
ui.image(SizedTexture::new(texture_id, size))
.on_hover_ui(|ui| {
// show larger on hover
let max_size = 0.5 * ui.ctx().screen_rect().size();
let mut size = vec2(w as f32, h as f32);
let mut size = point_size;
size *= max_size.x / size.x.max(max_size.x);
size *= max_size.y / size.y.max(max_size.y);
ui.image(SizedTexture::new(texture_id, size));

View File

@ -413,6 +413,8 @@ pub trait ImageLoader {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SizedTexture {
pub id: TextureId,
/// Size in logical ui points.
pub size: Vec2,
}

View File

@ -1,6 +1,6 @@
use std::{borrow::Cow, slice::Iter, sync::Arc, time::Duration};
use emath::{Align, Float as _, Rot2};
use emath::{Align, Float as _, GuiRounding as _, NumExt as _, Rot2};
use epaint::{
text::{LayoutJob, TextFormat, TextWrapping},
RectShape,
@ -373,9 +373,23 @@ impl<'a> Image<'a> {
/// ```
#[inline]
pub fn paint_at(&self, ui: &Ui, rect: Rect) {
let pixels_per_point = ui.pixels_per_point();
let rect = rect.round_to_pixels(pixels_per_point);
// Load exactly the size of the rectangle we are painting to.
// This is important for getting crisp SVG:s.
let pixel_size = (pixels_per_point * rect.size()).round();
let texture = self.source(ui.ctx()).clone().load(
ui.ctx(),
self.texture_options,
SizeHint::Size(pixel_size.x as _, pixel_size.y as _),
);
paint_texture_load_result(
ui,
&self.load_for_size(ui.ctx(), rect.size()),
&texture,
rect,
self.show_loading_spinner,
&self.image_options,
@ -467,19 +481,24 @@ impl ImageFit {
impl ImageSize {
/// Size hint for e.g. rasterizing an svg.
pub fn hint(&self, available_size: Vec2, pixels_per_point: f32) -> SizeHint {
let size = match self.fit {
ImageFit::Original { scale } => return SizeHint::Scale(scale.ord()),
let point_size = match self.fit {
ImageFit::Original { scale } => {
return SizeHint::Scale((pixels_per_point * scale).ord())
}
ImageFit::Fraction(fract) => available_size * fract,
ImageFit::Exact(size) => size,
};
let size = size.min(self.max_size);
let size = size * pixels_per_point;
let point_size = point_size.at_most(self.max_size);
let pixel_size = pixels_per_point * point_size;
// `inf` on an axis means "any value"
match (size.x.is_finite(), size.y.is_finite()) {
(true, true) => SizeHint::Size(size.x.round() as u32, size.y.round() as u32),
(true, false) => SizeHint::Width(size.x.round() as u32),
(false, true) => SizeHint::Height(size.y.round() as u32),
match (pixel_size.x.is_finite(), pixel_size.y.is_finite()) {
(true, true) => {
SizeHint::Size(pixel_size.x.round() as u32, pixel_size.y.round() as u32)
}
(true, false) => SizeHint::Width(pixel_size.x.round() as u32),
(false, true) => SizeHint::Height(pixel_size.y.round() as u32),
(false, false) => SizeHint::Scale(pixels_per_point.ord()),
}
}