"Final" image polish (#3342)

* Improve the Image API a bit

* Improve image view demo

* Better names

* calculate -> calc
This commit is contained in:
Emil Ernerfeldt 2023-09-15 10:13:50 +02:00 committed by GitHub
parent d7d222d3f6
commit 2bbceb856b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 53 additions and 37 deletions

View File

@ -187,7 +187,7 @@ impl Widget for Button<'_> {
let image_size = if let Some(image) = &image { let image_size = if let Some(image) = &image {
image image
.load_and_calculate_size(ui, space_available_for_image) .load_and_calc_size(ui, space_available_for_image)
.unwrap_or(space_available_for_image) .unwrap_or(space_available_for_image)
} else { } else {
Vec2::ZERO Vec2::ZERO
@ -276,7 +276,7 @@ impl Widget for Button<'_> {
image_size, image_size,
); );
cursor_x += image_size.x; cursor_x += image_size.x;
let tlr = image.load(ui); let tlr = image.load_for_size(ui.ctx(), image_size);
widgets::image::paint_texture_load_result( widgets::image::paint_texture_load_result(
ui, ui,
&tlr, &tlr,
@ -605,11 +605,12 @@ impl<'a> Widget for ImageButton<'a> {
Vec2::ZERO Vec2::ZERO
}; };
let tlr = self.image.load(ui); let available_size_for_image = ui.available_size() - 2.0 * padding;
let texture_size = tlr.as_ref().ok().and_then(|t| t.size()); 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_size = self let image_size = self
.image .image
.calculate_size(ui.available_size() - 2.0 * padding, texture_size); .calc_size(available_size_for_image, original_image_size);
let padded_size = image_size + 2.0 * padding; let padded_size = image_size + 2.0 * padding;
let (rect, response) = ui.allocate_exact_size(padded_size, self.sense); let (rect, response) = ui.allocate_exact_size(padded_size, self.sense);

View File

@ -239,14 +239,14 @@ impl<'a, T: Into<ImageSource<'a>>> From<T> for Image<'a> {
impl<'a> Image<'a> { impl<'a> Image<'a> {
/// Returns the size the image will occupy in the final UI. /// Returns the size the image will occupy in the final UI.
#[inline] #[inline]
pub fn calculate_size(&self, available_size: Vec2, image_size: Option<Vec2>) -> Vec2 { pub fn calc_size(&self, available_size: Vec2, original_image_size: Option<Vec2>) -> Vec2 {
let image_size = image_size.unwrap_or(Vec2::splat(24.0)); // Fallback for still-loading textures, or failure to load. let original_image_size = original_image_size.unwrap_or(Vec2::splat(24.0)); // Fallback for still-loading textures, or failure to load.
self.size.get(available_size, image_size) self.size.calc_size(available_size, original_image_size)
} }
pub fn load_and_calculate_size(&self, ui: &mut Ui, available_size: Vec2) -> Option<Vec2> { pub fn load_and_calc_size(&self, ui: &mut Ui, available_size: Vec2) -> Option<Vec2> {
let image_size = self.load(ui).ok()?.size()?; let image_size = self.load_for_size(ui.ctx(), available_size).ok()?.size()?;
Some(self.size.get(available_size, image_size)) Some(self.size.calc_size(available_size, image_size))
} }
#[inline] #[inline]
@ -269,13 +269,15 @@ impl<'a> Image<'a> {
/// Load the image from its [`Image::source`], returning the resulting [`SizedTexture`]. /// Load the image from its [`Image::source`], returning the resulting [`SizedTexture`].
/// ///
/// The `available_size` is used as a hint when e.g. rendering an svg.
///
/// # Errors /// # Errors
/// May fail if they underlying [`Context::try_load_texture`] call fails. /// May fail if they underlying [`Context::try_load_texture`] call fails.
pub fn load(&self, ui: &Ui) -> TextureLoadResult { pub fn load_for_size(&self, ctx: &Context, available_size: Vec2) -> TextureLoadResult {
let size_hint = self.size.hint(ui.available_size()); let size_hint = self.size.hint(available_size);
self.source self.source
.clone() .clone()
.load(ui.ctx(), self.texture_options, size_hint) .load(ctx, self.texture_options, size_hint)
} }
/// Paint the image in the given rectangle. /// Paint the image in the given rectangle.
@ -283,7 +285,7 @@ impl<'a> Image<'a> {
pub fn paint_at(&self, ui: &mut Ui, rect: Rect) { pub fn paint_at(&self, ui: &mut Ui, rect: Rect) {
paint_texture_load_result( paint_texture_load_result(
ui, ui,
&self.load(ui), &self.load_for_size(ui.ctx(), rect.size()),
rect, rect,
self.show_loading_spinner, self.show_loading_spinner,
&self.image_options, &self.image_options,
@ -293,9 +295,9 @@ impl<'a> Image<'a> {
impl<'a> Widget for Image<'a> { impl<'a> Widget for Image<'a> {
fn ui(self, ui: &mut Ui) -> Response { fn ui(self, ui: &mut Ui) -> Response {
let tlr = self.load(ui); let tlr = self.load_for_size(ui.ctx(), ui.available_size());
let texture_size = tlr.as_ref().ok().and_then(|t| t.size()); let original_image_size = tlr.as_ref().ok().and_then(|t| t.size());
let ui_size = self.calculate_size(ui.available_size(), texture_size); let ui_size = self.calc_size(ui.available_size(), original_image_size);
let (rect, response) = ui.allocate_exact_size(ui_size, self.sense); let (rect, response) = ui.allocate_exact_size(ui_size, self.sense);
paint_texture_load_result( paint_texture_load_result(
@ -364,29 +366,29 @@ impl ImageFit {
} }
impl ImageSize { impl ImageSize {
/// Size hint for e.g. rasterizing an svg.
pub fn hint(&self, available_size: Vec2) -> SizeHint { pub fn hint(&self, available_size: Vec2) -> SizeHint {
if self.maintain_aspect_ratio { let size = match self.fit {
return SizeHint::Scale(1.0.ord());
};
let fit = match self.fit {
ImageFit::Original { scale } => return SizeHint::Scale(scale.ord()), ImageFit::Original { scale } => return SizeHint::Scale(scale.ord()),
ImageFit::Fraction(fract) => available_size * fract, ImageFit::Fraction(fract) => available_size * fract,
ImageFit::Exact(size) => size, ImageFit::Exact(size) => size,
}; };
let fit = fit.min(self.max_size); let size = size.min(self.max_size);
// TODO(emilk): take pixels_per_point into account here!
// `inf` on an axis means "any value" // `inf` on an axis means "any value"
match (fit.x.is_finite(), fit.y.is_finite()) { match (size.x.is_finite(), size.y.is_finite()) {
(true, true) => SizeHint::Size(fit.x.round() as u32, fit.y.round() as u32), (true, true) => SizeHint::Size(size.x.round() as u32, size.y.round() as u32),
(true, false) => SizeHint::Width(fit.x.round() as u32), (true, false) => SizeHint::Width(size.x.round() as u32),
(false, true) => SizeHint::Height(fit.y.round() as u32), (false, true) => SizeHint::Height(size.y.round() as u32),
(false, false) => SizeHint::Scale(1.0.ord()), (false, false) => SizeHint::Scale(1.0.ord()),
} }
} }
pub fn get(&self, available_size: Vec2, image_size: Vec2) -> Vec2 { /// Calculate the final on-screen size in points.
pub fn calc_size(&self, available_size: Vec2, original_image_size: Vec2) -> Vec2 {
let Self { let Self {
maintain_aspect_ratio, maintain_aspect_ratio,
max_size, max_size,
@ -394,7 +396,7 @@ impl ImageSize {
} = *self; } = *self;
match fit { match fit {
ImageFit::Original { scale } => { ImageFit::Original { scale } => {
let image_size = image_size * scale; let image_size = original_image_size * scale;
if image_size.x <= max_size.x && image_size.y <= max_size.y { if image_size.x <= max_size.x && image_size.y <= max_size.y {
image_size image_size
} else { } else {
@ -403,11 +405,11 @@ impl ImageSize {
} }
ImageFit::Fraction(fract) => { ImageFit::Fraction(fract) => {
let scale_to_size = (available_size * fract).min(max_size); let scale_to_size = (available_size * fract).min(max_size);
scale_to_fit(image_size, scale_to_size, maintain_aspect_ratio) scale_to_fit(original_image_size, scale_to_size, maintain_aspect_ratio)
} }
ImageFit::Exact(size) => { ImageFit::Exact(size) => {
let scale_to_size = size.min(max_size); let scale_to_size = size.min(max_size);
scale_to_fit(image_size, scale_to_size, maintain_aspect_ratio) scale_to_fit(original_image_size, scale_to_size, maintain_aspect_ratio)
} }
} }
} }
@ -483,6 +485,15 @@ impl<'a> std::fmt::Debug for ImageSource<'a> {
} }
impl<'a> ImageSource<'a> { impl<'a> ImageSource<'a> {
/// Size of the texture, if known.
#[inline]
pub fn texture_size(&self) -> Option<Vec2> {
match self {
ImageSource::Texture(texture) => Some(texture.size),
ImageSource::Uri(_) | ImageSource::Bytes(_, _) => None,
}
}
/// # Errors /// # Errors
/// Failure to load the texture. /// Failure to load the texture.
pub fn load( pub fn load(

View File

@ -105,13 +105,17 @@ impl eframe::App for ImageViewer {
// bg_fill // bg_fill
ui.add_space(2.0); ui.add_space(2.0);
ui.label("Background color"); ui.horizontal(|ui| {
ui.color_edit_button_srgba(&mut self.image_options.bg_fill); ui.color_edit_button_srgba(&mut self.image_options.bg_fill);
ui.label("Background color");
});
// tint // tint
ui.add_space(2.0); ui.add_space(2.0);
ui.label("Tint"); ui.horizontal(|ui| {
ui.color_edit_button_srgba(&mut self.image_options.tint); ui.color_edit_button_srgba(&mut self.image_options.tint);
ui.label("Tint");
});
// fit // fit
ui.add_space(10.0); ui.add_space(10.0);

View File

@ -212,7 +212,7 @@ impl WrapApp {
), ),
#[cfg(feature = "image_viewer")] #[cfg(feature = "image_viewer")]
( (
"🖼 Image Viewer", "🖼 Image Viewer",
Anchor::ImageViewer, Anchor::ImageViewer,
&mut self.state.image_viewer as &mut dyn eframe::App, &mut self.state.image_viewer as &mut dyn eframe::App,
), ),