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:
parent
da67465a6c
commit
2cf6a3a9a6
|
|
@ -227,10 +227,10 @@ impl CaptureState {
|
|||
tx.send((
|
||||
viewport_id,
|
||||
data,
|
||||
ColorImage {
|
||||
size: [tex_extent.width as usize, tex_extent.height as usize],
|
||||
ColorImage::new(
|
||||
[tex_extent.width as usize, tex_extent.height as usize],
|
||||
pixels,
|
||||
},
|
||||
),
|
||||
))
|
||||
.ok();
|
||||
ctx.request_repaint();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -29,10 +29,11 @@ impl crate::View for SvgTest {
|
|||
ui.color_edit_button_srgba(color);
|
||||
let img_src = egui::include_image!("../../../data/peace.svg");
|
||||
|
||||
// First paint a small version…
|
||||
ui.add_sized(
|
||||
egui::Vec2 { x: 20.0, y: 20.0 },
|
||||
egui::Image::new(img_src.clone()).tint(*color),
|
||||
// First paint a small version, sized the same as the source…
|
||||
ui.add(
|
||||
egui::Image::new(img_src.clone())
|
||||
.fit_to_original_size(1.0)
|
||||
.tint(*color),
|
||||
);
|
||||
|
||||
// …then a big one, to make sure they are both crisp
|
||||
|
|
|
|||
|
|
@ -419,10 +419,7 @@ impl TextureManager {
|
|||
let height = 1;
|
||||
ctx.load_texture(
|
||||
"color_test_gradient",
|
||||
epaint::ColorImage {
|
||||
size: [width, height],
|
||||
pixels,
|
||||
},
|
||||
epaint::ColorImage::new([width, height], pixels),
|
||||
TextureOptions::LINEAR,
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1160361c41ffa9cde6d83cb32eeb9f9b75b275e98b97b625eababee460b69ba9
|
||||
size 24072
|
||||
oid sha256:22515363a812443b65cbe9060e2352e18eed04dc382fc993c33bd8a4b5ddff91
|
||||
size 24817
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#![allow(deprecated)]
|
||||
|
||||
use egui::{mutex::Mutex, TextureOptions};
|
||||
use egui::{load::SizedTexture, mutex::Mutex, ColorImage, TextureOptions, Vec2};
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
use egui::SizeHint;
|
||||
|
|
@ -16,10 +16,16 @@ use egui::SizeHint;
|
|||
pub struct RetainedImage {
|
||||
debug_name: String,
|
||||
|
||||
size: [usize; 2],
|
||||
/// Texel size.
|
||||
///
|
||||
/// Same as [`Self.image`]`.size`
|
||||
texel_size: [usize; 2],
|
||||
|
||||
/// Original SVG size (if this is an SVG), or same as [`Self::texel_size`].
|
||||
source_size: Vec2,
|
||||
|
||||
/// Cleared once [`Self::texture`] has been loaded.
|
||||
image: Mutex<egui::ColorImage>,
|
||||
image: Mutex<ColorImage>,
|
||||
|
||||
/// Lazily loaded when we have an egui context.
|
||||
texture: Mutex<Option<egui::TextureHandle>>,
|
||||
|
|
@ -28,10 +34,11 @@ pub struct RetainedImage {
|
|||
}
|
||||
|
||||
impl RetainedImage {
|
||||
pub fn from_color_image(debug_name: impl Into<String>, image: egui::ColorImage) -> Self {
|
||||
pub fn from_color_image(debug_name: impl Into<String>, image: ColorImage) -> Self {
|
||||
Self {
|
||||
debug_name: debug_name.into(),
|
||||
size: image.size,
|
||||
texel_size: image.size,
|
||||
source_size: image.source_size,
|
||||
image: Mutex::new(image),
|
||||
texture: Default::default(),
|
||||
options: Default::default(),
|
||||
|
|
@ -68,7 +75,7 @@ impl RetainedImage {
|
|||
svg_bytes: &[u8],
|
||||
options: &resvg::usvg::Options<'_>,
|
||||
) -> Result<Self, String> {
|
||||
Self::from_svg_bytes_with_size(debug_name, svg_bytes, None, options)
|
||||
Self::from_svg_bytes_with_size(debug_name, svg_bytes, Default::default(), options)
|
||||
}
|
||||
|
||||
/// Pass in the str of an SVG that you've loaded.
|
||||
|
|
@ -81,11 +88,11 @@ impl RetainedImage {
|
|||
svg_str: &str,
|
||||
options: &resvg::usvg::Options<'_>,
|
||||
) -> Result<Self, String> {
|
||||
Self::from_svg_bytes(debug_name, svg_str.as_bytes(), options)
|
||||
Self::from_svg_bytes_with_size(debug_name, svg_str.as_bytes(), Default::default(), options)
|
||||
}
|
||||
|
||||
/// Pass in the bytes of an SVG that you've loaded
|
||||
/// and the scaling option to resize the SVG with
|
||||
/// and the scaling option to resize the SVG with.
|
||||
///
|
||||
/// # Errors
|
||||
/// On invalid image
|
||||
|
|
@ -93,7 +100,7 @@ impl RetainedImage {
|
|||
pub fn from_svg_bytes_with_size(
|
||||
debug_name: impl Into<String>,
|
||||
svg_bytes: &[u8],
|
||||
size_hint: Option<SizeHint>,
|
||||
size_hint: SizeHint,
|
||||
options: &resvg::usvg::Options<'_>,
|
||||
) -> Result<Self, String> {
|
||||
Ok(Self::from_color_image(
|
||||
|
|
@ -112,10 +119,7 @@ impl RetainedImage {
|
|||
/// # use egui_extras::RetainedImage;
|
||||
/// # use egui::{Color32, epaint::{ColorImage, textures::TextureOptions}};
|
||||
/// # let pixels = vec![Color32::BLACK];
|
||||
/// # let color_image = ColorImage {
|
||||
/// # size: [1, 1],
|
||||
/// # pixels,
|
||||
/// # };
|
||||
/// # let color_image = ColorImage::new([1, 1], pixels);
|
||||
/// #
|
||||
/// // Upload a pixel art image without it getting blurry when resized
|
||||
/// let image = RetainedImage::from_color_image("my_image", color_image)
|
||||
|
|
@ -133,23 +137,39 @@ impl RetainedImage {
|
|||
}
|
||||
|
||||
/// The size of the image data (number of pixels wide/high).
|
||||
pub fn texel_size(&self) -> [usize; 2] {
|
||||
self.texel_size
|
||||
}
|
||||
|
||||
/// The size of the original SVG image (if any).
|
||||
///
|
||||
/// Note that this can differ from [`Self::texel_size`] if the SVG was rasterized at a different
|
||||
/// resolution than the size of the original SVG.
|
||||
pub fn source_size(&self) -> Vec2 {
|
||||
self.source_size
|
||||
}
|
||||
|
||||
#[deprecated = "use `texel_size` or `source_size` instead"]
|
||||
pub fn size(&self) -> [usize; 2] {
|
||||
self.size
|
||||
self.texel_size
|
||||
}
|
||||
|
||||
/// The width of the image.
|
||||
#[deprecated = "use `texel_size` or `source_size` instead"]
|
||||
pub fn width(&self) -> usize {
|
||||
self.size[0]
|
||||
self.texel_size[0]
|
||||
}
|
||||
|
||||
/// The height of the image.
|
||||
#[deprecated = "use `texel_size` or `source_size` instead"]
|
||||
pub fn height(&self) -> usize {
|
||||
self.size[1]
|
||||
self.texel_size[1]
|
||||
}
|
||||
|
||||
/// The size of the image data (number of pixels wide/high).
|
||||
#[deprecated = "use `texel_size` or `source_size` instead"]
|
||||
pub fn size_vec2(&self) -> egui::Vec2 {
|
||||
let [w, h] = self.size();
|
||||
let [w, h] = self.texel_size;
|
||||
egui::vec2(w as f32, h as f32)
|
||||
}
|
||||
|
||||
|
|
@ -163,7 +183,7 @@ impl RetainedImage {
|
|||
self.texture
|
||||
.lock()
|
||||
.get_or_insert_with(|| {
|
||||
let image: &mut egui::ColorImage = &mut self.image.lock();
|
||||
let image: &mut ColorImage = &mut self.image.lock();
|
||||
let image = std::mem::take(image);
|
||||
ctx.load_texture(&self.debug_name, image, self.options)
|
||||
})
|
||||
|
|
@ -172,7 +192,7 @@ impl RetainedImage {
|
|||
|
||||
/// Show the image with the given maximum size.
|
||||
pub fn show_max_size(&self, ui: &mut egui::Ui, max_size: egui::Vec2) -> egui::Response {
|
||||
let mut desired_size = self.size_vec2();
|
||||
let mut desired_size = self.source_size();
|
||||
desired_size *= (max_size.x / desired_size.x).min(1.0);
|
||||
desired_size *= (max_size.y / desired_size.y).min(1.0);
|
||||
self.show_size(ui, desired_size)
|
||||
|
|
@ -180,12 +200,12 @@ impl RetainedImage {
|
|||
|
||||
/// Show the image with the original size (one image pixel = one gui point).
|
||||
pub fn show(&self, ui: &mut egui::Ui) -> egui::Response {
|
||||
self.show_size(ui, self.size_vec2())
|
||||
self.show_size(ui, self.source_size())
|
||||
}
|
||||
|
||||
/// Show the image with the given scale factor (1.0 = original size).
|
||||
pub fn show_scaled(&self, ui: &mut egui::Ui, scale: f32) -> egui::Response {
|
||||
self.show_size(ui, self.size_vec2() * scale)
|
||||
self.show_size(ui, self.source_size() * scale)
|
||||
}
|
||||
|
||||
/// Show the image with the given size.
|
||||
|
|
@ -193,7 +213,7 @@ impl RetainedImage {
|
|||
// We need to convert the SVG to a texture to display it:
|
||||
// Future improvement: tell backend to do mip-mapping of the image to
|
||||
// make it look smoother when downsized.
|
||||
ui.image((self.texture_id(ui.ctx()), desired_size))
|
||||
ui.image(SizedTexture::new(self.texture_id(ui.ctx()), desired_size))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -207,7 +227,7 @@ impl RetainedImage {
|
|||
/// # Errors
|
||||
/// On invalid image or unsupported image format.
|
||||
#[cfg(feature = "image")]
|
||||
pub fn load_image_bytes(image_bytes: &[u8]) -> Result<egui::ColorImage, egui::load::LoadError> {
|
||||
pub fn load_image_bytes(image_bytes: &[u8]) -> Result<ColorImage, egui::load::LoadError> {
|
||||
profiling::function_scope!();
|
||||
let image = image::load_from_memory(image_bytes).map_err(|err| match err {
|
||||
image::ImageError::Unsupported(err) => match err.kind() {
|
||||
|
|
@ -223,10 +243,11 @@ pub fn load_image_bytes(image_bytes: &[u8]) -> Result<egui::ColorImage, egui::lo
|
|||
let size = [image.width() as _, image.height() as _];
|
||||
let image_buffer = image.to_rgba8();
|
||||
let pixels = image_buffer.as_flat_samples();
|
||||
Ok(egui::ColorImage::from_rgba_unmultiplied(
|
||||
size,
|
||||
pixels.as_slice(),
|
||||
))
|
||||
|
||||
// TODO(emilk): if this is a PNG, looks for DPI info to calculate the source size,
|
||||
// e.g. for screenshots taken on a high-DPI/retina display.
|
||||
|
||||
Ok(ColorImage::from_rgba_unmultiplied(size, pixels.as_slice()))
|
||||
}
|
||||
|
||||
/// Load an SVG and rasterize it into an egui image.
|
||||
|
|
@ -239,8 +260,8 @@ pub fn load_image_bytes(image_bytes: &[u8]) -> Result<egui::ColorImage, egui::lo
|
|||
pub fn load_svg_bytes(
|
||||
svg_bytes: &[u8],
|
||||
options: &resvg::usvg::Options<'_>,
|
||||
) -> Result<egui::ColorImage, String> {
|
||||
load_svg_bytes_with_size(svg_bytes, None, options)
|
||||
) -> Result<ColorImage, String> {
|
||||
load_svg_bytes_with_size(svg_bytes, Default::default(), options)
|
||||
}
|
||||
|
||||
/// Load an SVG and rasterize it into an egui image with a scaling parameter.
|
||||
|
|
@ -252,9 +273,9 @@ pub fn load_svg_bytes(
|
|||
#[cfg(feature = "svg")]
|
||||
pub fn load_svg_bytes_with_size(
|
||||
svg_bytes: &[u8],
|
||||
size_hint: Option<SizeHint>,
|
||||
size_hint: SizeHint,
|
||||
options: &resvg::usvg::Options<'_>,
|
||||
) -> Result<egui::ColorImage, String> {
|
||||
) -> Result<ColorImage, String> {
|
||||
use egui::Vec2;
|
||||
use resvg::{
|
||||
tiny_skia::Pixmap,
|
||||
|
|
@ -265,19 +286,18 @@ pub fn load_svg_bytes_with_size(
|
|||
|
||||
let rtree = Tree::from_data(svg_bytes, options).map_err(|err| err.to_string())?;
|
||||
|
||||
let original_size = Vec2::new(rtree.size().width(), rtree.size().height());
|
||||
let source_size = Vec2::new(rtree.size().width(), rtree.size().height());
|
||||
|
||||
let scaled_size = match size_hint {
|
||||
None => original_size,
|
||||
Some(SizeHint::Size {
|
||||
SizeHint::Size {
|
||||
width,
|
||||
height,
|
||||
maintain_aspect_ratio,
|
||||
}) => {
|
||||
} => {
|
||||
if maintain_aspect_ratio {
|
||||
// As large as possible, without exceeding the given size:
|
||||
let mut size = original_size;
|
||||
size *= width as f32 / original_size.x;
|
||||
let mut size = source_size;
|
||||
size *= width as f32 / source_size.x;
|
||||
if size.y > height as f32 {
|
||||
size *= height as f32 / size.y;
|
||||
}
|
||||
|
|
@ -286,9 +306,9 @@ pub fn load_svg_bytes_with_size(
|
|||
Vec2::new(width as _, height as _)
|
||||
}
|
||||
}
|
||||
Some(SizeHint::Height(h)) => original_size * (h as f32 / original_size.y),
|
||||
Some(SizeHint::Width(w)) => original_size * (w as f32 / original_size.x),
|
||||
Some(SizeHint::Scale(scale)) => scale.into_inner() * original_size,
|
||||
SizeHint::Height(h) => source_size * (h as f32 / source_size.y),
|
||||
SizeHint::Width(w) => source_size * (w as f32 / source_size.x),
|
||||
SizeHint::Scale(scale) => scale.into_inner() * source_size,
|
||||
};
|
||||
|
||||
let scaled_size = scaled_size.round();
|
||||
|
|
@ -299,11 +319,12 @@ pub fn load_svg_bytes_with_size(
|
|||
|
||||
resvg::render(
|
||||
&rtree,
|
||||
Transform::from_scale(w as f32 / original_size.x, h as f32 / original_size.y),
|
||||
Transform::from_scale(w as f32 / source_size.x, h as f32 / source_size.y),
|
||||
&mut pixmap.as_mut(),
|
||||
);
|
||||
|
||||
let image = egui::ColorImage::from_rgba_premultiplied([w as _, h as _], pixmap.data());
|
||||
let image = ColorImage::from_rgba_premultiplied([w as _, h as _], pixmap.data())
|
||||
.with_source_size(source_size);
|
||||
|
||||
Ok(image)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,12 +75,9 @@ impl ImageLoader for SvgLoader {
|
|||
match ctx.try_load_bytes(uri) {
|
||||
Ok(BytesPoll::Ready { bytes, .. }) => {
|
||||
log::trace!("Started loading {uri:?}");
|
||||
let result = crate::image::load_svg_bytes_with_size(
|
||||
&bytes,
|
||||
Some(size_hint),
|
||||
&self.options,
|
||||
)
|
||||
.map(Arc::new);
|
||||
let result =
|
||||
crate::image::load_svg_bytes_with_size(&bytes, size_hint, &self.options)
|
||||
.map(Arc::new);
|
||||
|
||||
log::trace!("Finished loading {uri:?}");
|
||||
bucket.insert(
|
||||
|
|
|
|||
|
|
@ -696,10 +696,7 @@ impl Painter {
|
|||
for row in pixels.chunks_exact((w * 4) as usize).rev() {
|
||||
flipped.extend_from_slice(bytemuck::cast_slice(row));
|
||||
}
|
||||
egui::ColorImage {
|
||||
size: [w as usize, h as usize],
|
||||
pixels: flipped,
|
||||
}
|
||||
egui::ColorImage::new([w as usize, h as usize], flipped)
|
||||
}
|
||||
|
||||
pub fn read_screen_rgb(&self, [w, h]: [u32; 2]) -> Vec<u8> {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use emath::Vec2;
|
||||
|
||||
use crate::{textures::TextureOptions, Color32};
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
@ -47,18 +49,36 @@ impl ImageData {
|
|||
#[derive(Clone, Default, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct ColorImage {
|
||||
/// width, height.
|
||||
/// width, height in texels.
|
||||
pub size: [usize; 2],
|
||||
|
||||
/// Size of the original SVG image (if any), or just the texel size of the image.
|
||||
pub source_size: Vec2,
|
||||
|
||||
/// The pixels, row by row, from top to bottom.
|
||||
pub pixels: Vec<Color32>,
|
||||
}
|
||||
|
||||
impl ColorImage {
|
||||
/// Create an image filled with the given color.
|
||||
pub fn new(size: [usize; 2], color: Color32) -> Self {
|
||||
pub fn new(size: [usize; 2], pixels: Vec<Color32>) -> Self {
|
||||
debug_assert!(
|
||||
size[0] * size[1] == pixels.len(),
|
||||
"size: {size:?}, pixels.len(): {}",
|
||||
pixels.len()
|
||||
);
|
||||
Self {
|
||||
size,
|
||||
source_size: Vec2::new(size[0] as f32, size[1] as f32),
|
||||
pixels,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an image filled with the given color.
|
||||
pub fn filled(size: [usize; 2], color: Color32) -> Self {
|
||||
Self {
|
||||
size,
|
||||
source_size: Vec2::new(size[0] as f32, size[1] as f32),
|
||||
pixels: vec![color; size[0] * size[1]],
|
||||
}
|
||||
}
|
||||
|
|
@ -105,7 +125,7 @@ impl ColorImage {
|
|||
.chunks_exact(4)
|
||||
.map(|p| Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
|
||||
.collect();
|
||||
Self { size, pixels }
|
||||
Self::new(size, pixels)
|
||||
}
|
||||
|
||||
pub fn from_rgba_premultiplied(size: [usize; 2], rgba: &[u8]) -> Self {
|
||||
|
|
@ -120,7 +140,7 @@ impl ColorImage {
|
|||
.chunks_exact(4)
|
||||
.map(|p| Color32::from_rgba_premultiplied(p[0], p[1], p[2], p[3]))
|
||||
.collect();
|
||||
Self { size, pixels }
|
||||
Self::new(size, pixels)
|
||||
}
|
||||
|
||||
/// Create a [`ColorImage`] from flat opaque gray data.
|
||||
|
|
@ -135,7 +155,7 @@ impl ColorImage {
|
|||
gray.len()
|
||||
);
|
||||
let pixels = gray.iter().map(|p| Color32::from_gray(*p)).collect();
|
||||
Self { size, pixels }
|
||||
Self::new(size, pixels)
|
||||
}
|
||||
|
||||
/// Alternative method to `from_gray`.
|
||||
|
|
@ -152,7 +172,7 @@ impl ColorImage {
|
|||
size,
|
||||
pixels.len()
|
||||
);
|
||||
Self { size, pixels }
|
||||
Self::new(size, pixels)
|
||||
}
|
||||
|
||||
/// A view of the underlying data as `&[u8]`
|
||||
|
|
@ -185,14 +205,14 @@ impl ColorImage {
|
|||
.chunks_exact(3)
|
||||
.map(|p| Color32::from_rgb(p[0], p[1], p[2]))
|
||||
.collect();
|
||||
Self { size, pixels }
|
||||
Self::new(size, pixels)
|
||||
}
|
||||
|
||||
/// An example color image, useful for tests.
|
||||
pub fn example() -> Self {
|
||||
let width = 128;
|
||||
let height = 64;
|
||||
let mut img = Self::new([width, height], Color32::TRANSPARENT);
|
||||
let mut img = Self::filled([width, height], Color32::TRANSPARENT);
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
let h = x as f32 / width as f32;
|
||||
|
|
@ -205,6 +225,13 @@ impl ColorImage {
|
|||
img
|
||||
}
|
||||
|
||||
/// Set the source size of e.g. the original SVG image.
|
||||
#[inline]
|
||||
pub fn with_source_size(mut self, source_size: Vec2) -> Self {
|
||||
self.source_size = source_size;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn width(&self) -> usize {
|
||||
self.size[0]
|
||||
|
|
@ -242,10 +269,7 @@ impl ColorImage {
|
|||
&self.pixels[row * row_stride + min_x..row * row_stride + max_x],
|
||||
);
|
||||
}
|
||||
Self {
|
||||
size: [width, height],
|
||||
pixels: output,
|
||||
}
|
||||
Self::new([width, height], output)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue