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:
Emil Ernerfeldt 2025-05-28 08:33:01 +02:00 committed by GitHub
parent da67465a6c
commit 2cf6a3a9a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 180 additions and 96 deletions

View File

@ -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();

View File

@ -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 {

View File

@ -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,
},
);

View File

@ -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)
}
}
}

View File

@ -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);

View File

@ -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

View File

@ -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,
)
})

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1160361c41ffa9cde6d83cb32eeb9f9b75b275e98b97b625eababee460b69ba9
size 24072
oid sha256:22515363a812443b65cbe9060e2352e18eed04dc382fc993c33bd8a4b5ddff91
size 24817

View File

@ -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)
}

View File

@ -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(

View File

@ -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> {

View File

@ -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)
}
}