Skip to content

Commit 2cf6a3a

Browse files
authored
Track original SVG size (#7098)
This fixes bugs related to how an `Image` follows the size of an SVG. We track the "source size" of each image, i.e. the original width/height of the SVG, which can be different from whatever it was rasterized as.
1 parent da67465 commit 2cf6a3a

File tree

12 files changed

+180
-96
lines changed

12 files changed

+180
-96
lines changed

crates/egui-wgpu/src/capture.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -227,10 +227,10 @@ impl CaptureState {
227227
tx.send((
228228
viewport_id,
229229
data,
230-
ColorImage {
231-
size: [tex_extent.width as usize, tex_extent.height as usize],
230+
ColorImage::new(
231+
[tex_extent.width as usize, tex_extent.height as usize],
232232
pixels,
233-
},
233+
),
234234
))
235235
.ok();
236236
ctx.request_repaint();

crates/egui/src/load.rs

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,16 @@ pub type Result<T, E = LoadError> = std::result::Result<T, E>;
143143
/// Given as a hint for image loading requests.
144144
///
145145
/// Used mostly for rendering SVG:s to a good size.
146-
/// The size is measured in texels, with the pixels per point already factored in.
147-
///
148-
/// All variants will preserve the original aspect ratio.
146+
/// The [`SizeHint`] determines at what resolution the image should be rasterized.
149147
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
150148
pub enum SizeHint {
151-
/// Scale original size by some factor.
149+
/// Scale original size by some factor, keeping the original aspect ratio.
150+
///
151+
/// The original size of the image is usually its texel resolution,
152+
/// but for an SVG it's the point size of the SVG.
153+
///
154+
/// For instance, setting `Scale(2.0)` will rasterize SVG:s to twice their original size,
155+
/// which is useful for high-DPI displays.
152156
Scale(OrderedFloat<f32>),
153157

154158
/// Scale to exactly this pixel width, keeping the original aspect ratio.
@@ -168,6 +172,26 @@ pub enum SizeHint {
168172
},
169173
}
170174

175+
impl SizeHint {
176+
/// Multiply size hint by a factor.
177+
pub fn scale_by(self, factor: f32) -> Self {
178+
match self {
179+
Self::Scale(scale) => Self::Scale(OrderedFloat(factor * scale.0)),
180+
Self::Width(width) => Self::Width((factor * width as f32).round() as _),
181+
Self::Height(height) => Self::Height((factor * height as f32).round() as _),
182+
Self::Size {
183+
width,
184+
height,
185+
maintain_aspect_ratio,
186+
} => Self::Size {
187+
width: (factor * width as f32).round() as _,
188+
height: (factor * height as f32).round() as _,
189+
maintain_aspect_ratio,
190+
},
191+
}
192+
}
193+
}
194+
171195
impl Default for SizeHint {
172196
#[inline]
173197
fn default() -> Self {
@@ -249,12 +273,16 @@ impl Deref for Bytes {
249273
pub enum BytesPoll {
250274
/// Bytes are being loaded.
251275
Pending {
276+
/// Point size of the image.
277+
///
252278
/// Set if known (e.g. from a HTTP header, or by parsing the image file header).
253279
size: Option<Vec2>,
254280
},
255281

256282
/// Bytes are loaded.
257283
Ready {
284+
/// Point size of the image.
285+
///
258286
/// Set if known (e.g. from a HTTP header, or by parsing the image file header).
259287
size: Option<Vec2>,
260288

@@ -344,6 +372,8 @@ pub trait BytesLoader {
344372
pub enum ImagePoll {
345373
/// Image is loading.
346374
Pending {
375+
/// Point size of the image.
376+
///
347377
/// Set if known (e.g. from a HTTP header, or by parsing the image file header).
348378
size: Option<Vec2>,
349379
},
@@ -414,7 +444,7 @@ pub trait ImageLoader {
414444
pub struct SizedTexture {
415445
pub id: TextureId,
416446

417-
/// Size in logical ui points.
447+
/// Point size of the original SVG, or the size of the image in texels.
418448
pub size: Vec2,
419449
}
420450

@@ -460,6 +490,8 @@ impl<'a> From<&'a TextureHandle> for SizedTexture {
460490
pub enum TexturePoll {
461491
/// Texture is loading.
462492
Pending {
493+
/// Point size of the image.
494+
///
463495
/// Set if known (e.g. from a HTTP header, or by parsing the image file header).
464496
size: Option<Vec2>,
465497
},
@@ -469,6 +501,7 @@ pub enum TexturePoll {
469501
}
470502

471503
impl TexturePoll {
504+
/// Point size of the original SVG, or the size of the image in texels.
472505
#[inline]
473506
pub fn size(&self) -> Option<Vec2> {
474507
match self {

crates/egui/src/load/texture_loader.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
22

3+
use emath::Vec2;
4+
35
use super::{
46
BytesLoader as _, Context, HashMap, ImagePoll, Mutex, SizeHint, SizedTexture, TextureHandle,
57
TextureLoadResult, TextureLoader, TextureOptions, TexturePoll,
@@ -16,6 +18,10 @@ type Bucket = HashMap<Option<SizeHint>, Entry>;
1618

1719
struct Entry {
1820
last_used: AtomicU64,
21+
22+
/// Size of the original SVG, if any, or the texel size of the image if not an SVG.
23+
source_size: Vec2,
24+
1925
handle: TextureHandle,
2026
}
2127

@@ -61,18 +67,20 @@ impl TextureLoader for DefaultTextureLoader {
6167
texture
6268
.last_used
6369
.store(self.pass_index.load(Relaxed), Relaxed);
64-
let texture = SizedTexture::from_handle(&texture.handle);
70+
let texture = SizedTexture::new(texture.handle.id(), texture.source_size);
6571
Ok(TexturePoll::Ready { texture })
6672
} else {
6773
match ctx.try_load_image(uri, size_hint)? {
6874
ImagePoll::Pending { size } => Ok(TexturePoll::Pending { size }),
6975
ImagePoll::Ready { image } => {
76+
let source_size = image.source_size;
7077
let handle = ctx.load_texture(uri, image, texture_options);
71-
let texture = SizedTexture::from_handle(&handle);
78+
let texture = SizedTexture::new(handle.id(), source_size);
7279
bucket.insert(
7380
svg_size_hint,
7481
Entry {
7582
last_used: AtomicU64::new(self.pass_index.load(Relaxed)),
83+
source_size,
7684
handle,
7785
},
7886
);

crates/egui/src/widgets/image.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ impl<'a> Image<'a> {
156156

157157
/// Fit the image to its original size with some scaling.
158158
///
159+
/// The texel size of the source image will be multiplied by the `scale` factor,
160+
/// and then become the _ui_ size of the [`Image`].
161+
///
159162
/// This will cause the image to overflow if it is larger than the available space.
160163
///
161164
/// If [`Image::max_size`] is set, this is guaranteed to never exceed that limit.
@@ -291,9 +294,9 @@ impl<'a, T: Into<ImageSource<'a>>> From<T> for Image<'a> {
291294
impl<'a> Image<'a> {
292295
/// Returns the size the image will occupy in the final UI.
293296
#[inline]
294-
pub fn calc_size(&self, available_size: Vec2, original_image_size: Option<Vec2>) -> Vec2 {
295-
let original_image_size = original_image_size.unwrap_or(Vec2::splat(24.0)); // Fallback for still-loading textures, or failure to load.
296-
self.size.calc_size(available_size, original_image_size)
297+
pub fn calc_size(&self, available_size: Vec2, image_source_size: Option<Vec2>) -> Vec2 {
298+
let image_source_size = image_source_size.unwrap_or(Vec2::splat(24.0)); // Fallback for still-loading textures, or failure to load.
299+
self.size.calc_size(available_size, image_source_size)
297300
}
298301

299302
pub fn load_and_calc_size(&self, ui: &Ui, available_size: Vec2) -> Option<Vec2> {
@@ -405,8 +408,8 @@ impl<'a> Image<'a> {
405408
impl Widget for Image<'_> {
406409
fn ui(self, ui: &mut Ui) -> Response {
407410
let tlr = self.load_for_size(ui.ctx(), ui.available_size());
408-
let original_image_size = tlr.as_ref().ok().and_then(|t| t.size());
409-
let ui_size = self.calc_size(ui.available_size(), original_image_size);
411+
let image_source_size = tlr.as_ref().ok().and_then(|t| t.size());
412+
let ui_size = self.calc_size(ui.available_size(), image_source_size);
410413

411414
let (rect, response) = ui.allocate_exact_size(ui_size, self.sense);
412415
response.widget_info(|| {
@@ -458,7 +461,10 @@ pub struct ImageSize {
458461
#[derive(Debug, Clone, Copy)]
459462
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
460463
pub enum ImageFit {
461-
/// Fit the image to its original size, scaled by some factor.
464+
/// Fit the image to its original srce size, scaled by some factor.
465+
///
466+
/// The original size of the image is usually its texel resolution,
467+
/// but for an SVG it's the point size of the SVG.
462468
///
463469
/// Ignores how much space is actually available in the ui.
464470
Original { scale: f32 },
@@ -516,15 +522,15 @@ impl ImageSize {
516522
}
517523

518524
/// Calculate the final on-screen size in points.
519-
pub fn calc_size(&self, available_size: Vec2, original_image_size: Vec2) -> Vec2 {
525+
pub fn calc_size(&self, available_size: Vec2, image_source_size: Vec2) -> Vec2 {
520526
let Self {
521527
maintain_aspect_ratio,
522528
max_size,
523529
fit,
524530
} = *self;
525531
match fit {
526532
ImageFit::Original { scale } => {
527-
let image_size = original_image_size * scale;
533+
let image_size = scale * image_source_size;
528534
if image_size.x <= max_size.x && image_size.y <= max_size.y {
529535
image_size
530536
} else {
@@ -533,11 +539,11 @@ impl ImageSize {
533539
}
534540
ImageFit::Fraction(fract) => {
535541
let scale_to_size = (available_size * fract).min(max_size);
536-
scale_to_fit(original_image_size, scale_to_size, maintain_aspect_ratio)
542+
scale_to_fit(image_source_size, scale_to_size, maintain_aspect_ratio)
537543
}
538544
ImageFit::Exact(size) => {
539545
let scale_to_size = size.min(max_size);
540-
scale_to_fit(original_image_size, scale_to_size, maintain_aspect_ratio)
546+
scale_to_fit(image_source_size, scale_to_size, maintain_aspect_ratio)
541547
}
542548
}
543549
}

crates/egui/src/widgets/image_button.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,10 @@ impl Widget for ImageButton<'_> {
9393

9494
let available_size_for_image = ui.available_size() - 2.0 * padding;
9595
let tlr = self.image.load_for_size(ui.ctx(), available_size_for_image);
96-
let original_image_size = tlr.as_ref().ok().and_then(|t| t.size());
96+
let image_source_size = tlr.as_ref().ok().and_then(|t| t.size());
9797
let image_size = self
9898
.image
99-
.calc_size(available_size_for_image, original_image_size);
99+
.calc_size(available_size_for_image, image_source_size);
100100

101101
let padded_size = image_size + 2.0 * padding;
102102
let (rect, response) = ui.allocate_exact_size(padded_size, self.sense);

crates/egui_demo_lib/src/demo/tests/svg_test.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ impl crate::View for SvgTest {
2929
ui.color_edit_button_srgba(color);
3030
let img_src = egui::include_image!("../../../data/peace.svg");
3131

32-
// First paint a small version…
33-
ui.add_sized(
34-
egui::Vec2 { x: 20.0, y: 20.0 },
35-
egui::Image::new(img_src.clone()).tint(*color),
32+
// First paint a small version, sized the same as the source…
33+
ui.add(
34+
egui::Image::new(img_src.clone())
35+
.fit_to_original_size(1.0)
36+
.tint(*color),
3637
);
3738

3839
// …then a big one, to make sure they are both crisp

crates/egui_demo_lib/src/rendering_test.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -419,10 +419,7 @@ impl TextureManager {
419419
let height = 1;
420420
ctx.load_texture(
421421
"color_test_gradient",
422-
epaint::ColorImage {
423-
size: [width, height],
424-
pixels,
425-
},
422+
epaint::ColorImage::new([width, height], pixels),
426423
TextureOptions::LINEAR,
427424
)
428425
})
Lines changed: 2 additions & 2 deletions
Loading

0 commit comments

Comments
 (0)