mirror of
https://github.com/emilk/egui.git
synced 2026-09-02 06:40:06 -04:00
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.
This commit is contained in:
@@ -143,12 +143,16 @@ pub type Result<T, E = LoadError> = std::result::Result<T, E>;
|
||||
/// Given as a hint for image loading requests.
|
||||
///
|
||||
/// Used mostly for rendering SVG:s to a good size.
|
||||
/// The size is measured in texels, with the pixels per point already factored in.
|
||||
///
|
||||
/// All variants will preserve the original aspect ratio.
|
||||
/// The [`SizeHint`] determines at what resolution the image should be rasterized.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum SizeHint {
|
||||
/// Scale original size by some factor.
|
||||
/// Scale original size by some factor, keeping the original aspect ratio.
|
||||
///
|
||||
/// The original size of the image is usually its texel resolution,
|
||||
/// but for an SVG it's the point size of the SVG.
|
||||
///
|
||||
/// For instance, setting `Scale(2.0)` will rasterize SVG:s to twice their original size,
|
||||
/// which is useful for high-DPI displays.
|
||||
Scale(OrderedFloat<f32>),
|
||||
|
||||
/// Scale to exactly this pixel width, keeping the original aspect ratio.
|
||||
@@ -168,6 +172,26 @@ pub enum SizeHint {
|
||||
},
|
||||
}
|
||||
|
||||
impl SizeHint {
|
||||
/// Multiply size hint by a factor.
|
||||
pub fn scale_by(self, factor: f32) -> Self {
|
||||
match self {
|
||||
Self::Scale(scale) => Self::Scale(OrderedFloat(factor * scale.0)),
|
||||
Self::Width(width) => Self::Width((factor * width as f32).round() as _),
|
||||
Self::Height(height) => Self::Height((factor * height as f32).round() as _),
|
||||
Self::Size {
|
||||
width,
|
||||
height,
|
||||
maintain_aspect_ratio,
|
||||
} => Self::Size {
|
||||
width: (factor * width as f32).round() as _,
|
||||
height: (factor * height as f32).round() as _,
|
||||
maintain_aspect_ratio,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SizeHint {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
@@ -249,12 +273,16 @@ impl Deref for Bytes {
|
||||
pub enum BytesPoll {
|
||||
/// Bytes are being loaded.
|
||||
Pending {
|
||||
/// Point size of the image.
|
||||
///
|
||||
/// Set if known (e.g. from a HTTP header, or by parsing the image file header).
|
||||
size: Option<Vec2>,
|
||||
},
|
||||
|
||||
/// Bytes are loaded.
|
||||
Ready {
|
||||
/// Point size of the image.
|
||||
///
|
||||
/// Set if known (e.g. from a HTTP header, or by parsing the image file header).
|
||||
size: Option<Vec2>,
|
||||
|
||||
@@ -344,6 +372,8 @@ pub trait BytesLoader {
|
||||
pub enum ImagePoll {
|
||||
/// Image is loading.
|
||||
Pending {
|
||||
/// Point size of the image.
|
||||
///
|
||||
/// Set if known (e.g. from a HTTP header, or by parsing the image file header).
|
||||
size: Option<Vec2>,
|
||||
},
|
||||
@@ -414,7 +444,7 @@ pub trait ImageLoader {
|
||||
pub struct SizedTexture {
|
||||
pub id: TextureId,
|
||||
|
||||
/// Size in logical ui points.
|
||||
/// Point size of the original SVG, or the size of the image in texels.
|
||||
pub size: Vec2,
|
||||
}
|
||||
|
||||
@@ -460,6 +490,8 @@ impl<'a> From<&'a TextureHandle> for SizedTexture {
|
||||
pub enum TexturePoll {
|
||||
/// Texture is loading.
|
||||
Pending {
|
||||
/// Point size of the image.
|
||||
///
|
||||
/// Set if known (e.g. from a HTTP header, or by parsing the image file header).
|
||||
size: Option<Vec2>,
|
||||
},
|
||||
@@ -469,6 +501,7 @@ pub enum TexturePoll {
|
||||
}
|
||||
|
||||
impl TexturePoll {
|
||||
/// Point size of the original SVG, or the size of the image in texels.
|
||||
#[inline]
|
||||
pub fn size(&self) -> Option<Vec2> {
|
||||
match self {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
|
||||
|
||||
use emath::Vec2;
|
||||
|
||||
use super::{
|
||||
BytesLoader as _, Context, HashMap, ImagePoll, Mutex, SizeHint, SizedTexture, TextureHandle,
|
||||
TextureLoadResult, TextureLoader, TextureOptions, TexturePoll,
|
||||
@@ -16,6 +18,10 @@ type Bucket = HashMap<Option<SizeHint>, Entry>;
|
||||
|
||||
struct Entry {
|
||||
last_used: AtomicU64,
|
||||
|
||||
/// Size of the original SVG, if any, or the texel size of the image if not an SVG.
|
||||
source_size: Vec2,
|
||||
|
||||
handle: TextureHandle,
|
||||
}
|
||||
|
||||
@@ -61,18 +67,20 @@ impl TextureLoader for DefaultTextureLoader {
|
||||
texture
|
||||
.last_used
|
||||
.store(self.pass_index.load(Relaxed), Relaxed);
|
||||
let texture = SizedTexture::from_handle(&texture.handle);
|
||||
let texture = SizedTexture::new(texture.handle.id(), texture.source_size);
|
||||
Ok(TexturePoll::Ready { texture })
|
||||
} else {
|
||||
match ctx.try_load_image(uri, size_hint)? {
|
||||
ImagePoll::Pending { size } => Ok(TexturePoll::Pending { size }),
|
||||
ImagePoll::Ready { image } => {
|
||||
let source_size = image.source_size;
|
||||
let handle = ctx.load_texture(uri, image, texture_options);
|
||||
let texture = SizedTexture::from_handle(&handle);
|
||||
let texture = SizedTexture::new(handle.id(), source_size);
|
||||
bucket.insert(
|
||||
svg_size_hint,
|
||||
Entry {
|
||||
last_used: AtomicU64::new(self.pass_index.load(Relaxed)),
|
||||
source_size,
|
||||
handle,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -156,6 +156,9 @@ impl<'a> Image<'a> {
|
||||
|
||||
/// Fit the image to its original size with some scaling.
|
||||
///
|
||||
/// The texel size of the source image will be multiplied by the `scale` factor,
|
||||
/// and then become the _ui_ size of the [`Image`].
|
||||
///
|
||||
/// This will cause the image to overflow if it is larger than the available space.
|
||||
///
|
||||
/// 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> {
|
||||
impl<'a> Image<'a> {
|
||||
/// Returns the size the image will occupy in the final UI.
|
||||
#[inline]
|
||||
pub fn calc_size(&self, available_size: Vec2, original_image_size: Option<Vec2>) -> Vec2 {
|
||||
let original_image_size = original_image_size.unwrap_or(Vec2::splat(24.0)); // Fallback for still-loading textures, or failure to load.
|
||||
self.size.calc_size(available_size, original_image_size)
|
||||
pub fn calc_size(&self, available_size: Vec2, image_source_size: Option<Vec2>) -> Vec2 {
|
||||
let image_source_size = image_source_size.unwrap_or(Vec2::splat(24.0)); // Fallback for still-loading textures, or failure to load.
|
||||
self.size.calc_size(available_size, image_source_size)
|
||||
}
|
||||
|
||||
pub fn load_and_calc_size(&self, ui: &Ui, available_size: Vec2) -> Option<Vec2> {
|
||||
@@ -405,8 +408,8 @@ impl<'a> Image<'a> {
|
||||
impl Widget for Image<'_> {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let tlr = self.load_for_size(ui.ctx(), ui.available_size());
|
||||
let original_image_size = tlr.as_ref().ok().and_then(|t| t.size());
|
||||
let ui_size = self.calc_size(ui.available_size(), original_image_size);
|
||||
let image_source_size = tlr.as_ref().ok().and_then(|t| t.size());
|
||||
let ui_size = self.calc_size(ui.available_size(), image_source_size);
|
||||
|
||||
let (rect, response) = ui.allocate_exact_size(ui_size, self.sense);
|
||||
response.widget_info(|| {
|
||||
@@ -458,7 +461,10 @@ pub struct ImageSize {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum ImageFit {
|
||||
/// Fit the image to its original size, scaled by some factor.
|
||||
/// Fit the image to its original srce size, scaled by some factor.
|
||||
///
|
||||
/// The original size of the image is usually its texel resolution,
|
||||
/// but for an SVG it's the point size of the SVG.
|
||||
///
|
||||
/// Ignores how much space is actually available in the ui.
|
||||
Original { scale: f32 },
|
||||
@@ -516,7 +522,7 @@ impl ImageSize {
|
||||
}
|
||||
|
||||
/// Calculate the final on-screen size in points.
|
||||
pub fn calc_size(&self, available_size: Vec2, original_image_size: Vec2) -> Vec2 {
|
||||
pub fn calc_size(&self, available_size: Vec2, image_source_size: Vec2) -> Vec2 {
|
||||
let Self {
|
||||
maintain_aspect_ratio,
|
||||
max_size,
|
||||
@@ -524,7 +530,7 @@ impl ImageSize {
|
||||
} = *self;
|
||||
match fit {
|
||||
ImageFit::Original { scale } => {
|
||||
let image_size = original_image_size * scale;
|
||||
let image_size = scale * image_source_size;
|
||||
if image_size.x <= max_size.x && image_size.y <= max_size.y {
|
||||
image_size
|
||||
} else {
|
||||
@@ -533,11 +539,11 @@ impl ImageSize {
|
||||
}
|
||||
ImageFit::Fraction(fract) => {
|
||||
let scale_to_size = (available_size * fract).min(max_size);
|
||||
scale_to_fit(original_image_size, scale_to_size, maintain_aspect_ratio)
|
||||
scale_to_fit(image_source_size, scale_to_size, maintain_aspect_ratio)
|
||||
}
|
||||
ImageFit::Exact(size) => {
|
||||
let scale_to_size = size.min(max_size);
|
||||
scale_to_fit(original_image_size, scale_to_size, maintain_aspect_ratio)
|
||||
scale_to_fit(image_source_size, scale_to_size, maintain_aspect_ratio)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,10 +93,10 @@ impl Widget for ImageButton<'_> {
|
||||
|
||||
let available_size_for_image = ui.available_size() - 2.0 * padding;
|
||||
let tlr = self.image.load_for_size(ui.ctx(), available_size_for_image);
|
||||
let original_image_size = tlr.as_ref().ok().and_then(|t| t.size());
|
||||
let image_source_size = tlr.as_ref().ok().and_then(|t| t.size());
|
||||
let image_size = self
|
||||
.image
|
||||
.calc_size(available_size_for_image, original_image_size);
|
||||
.calc_size(available_size_for_image, image_source_size);
|
||||
|
||||
let padded_size = image_size + 2.0 * padding;
|
||||
let (rect, response) = ui.allocate_exact_size(padded_size, self.sense);
|
||||
|
||||
Reference in New Issue
Block a user