Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions api/node/rust/types/image_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ impl SlintImageData {
(self.width() * self.height()) as usize,
));
}
SharedImageBuffer::Gray8(buffer) => {
let rgba = buffer
.as_bytes()
.iter()
.flat_map(|g| [*g, *g, *g, 255])
.collect::<Vec<_>>();
return Buffer::from(rgba);
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions internal/backends/qt/qt_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1434,6 +1434,9 @@ fn shared_image_buffer_to_pixmap(buffer: &SharedImageBuffer) -> Option<qttypes::
SharedImageBuffer::RGB8(img) => {
(qttypes::ImageFormat::RGB888, img.width() * 3, img.as_bytes().as_ptr())
}
SharedImageBuffer::Gray8(img) => {
(qttypes::ImageFormat::Grayscale8, img.width(), img.as_bytes().as_ptr())
}
};
let width: i32 = buffer.width() as _;
let height: i32 = buffer.height() as _;
Expand Down
3 changes: 3 additions & 0 deletions internal/backends/winit/winitwindowadapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ fn icon_to_winit(
.chain(std::iter::once(alpha as u8))
})
.collect(),
SharedImageBuffer::Gray8(pixels) => {
pixels.as_bytes().iter().flat_map(|g| [*g, *g, *g, 255]).collect()
}
};

winit::window::Icon::from_rgba(rgba_pixels, pixel_buffer.width(), pixel_buffer.height()).ok()
Expand Down
2 changes: 2 additions & 0 deletions internal/compiler/embedded_resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub enum PixelFormat {
RgbaPremultiplied,
// 8bit alpha map with a given color
AlphaMap([u8; 3]),
// 8bit grayscale. Each pixel is a luminance value.
Gray8,
}

#[cfg(feature = "software-renderer")]
Expand Down
1 change: 1 addition & 0 deletions internal/compiler/generator/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ impl quote::ToTokens for crate::embedded_resources::PixelFormat {
quote!(sp::TexturePixelFormat::RgbaPremultiplied)
}
AlphaMap(_) => quote!(sp::TexturePixelFormat::AlphaMap),
Gray8 => quote!(sp::TexturePixelFormat::Gray8),
};
tokens.extend(tks);
}
Expand Down
7 changes: 7 additions & 0 deletions internal/compiler/passes/embed_images.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ fn generate_texture(
assert!(right > left); // otherwise we would have a transparent image
}
let mut is_opaque = true;
let mut is_grayscale = true;
enum ColorState {
Unset,
Different,
Expand All @@ -284,6 +285,9 @@ fn generate_texture(
if alpha == 0 {
continue;
}
if p[0] != p[1] || p[1] != p[2] {
is_grayscale = false;
}
let get_pixel = || match source_format {
SourceFormat::RgbaPremultiplied => <[u8; 3]>::try_from(&p.0[0..3])
.unwrap()
Expand Down Expand Up @@ -311,6 +315,8 @@ fn generate_texture(

let format = if let ColorState::Rgb(c) = color {
PixelFormat::AlphaMap(c)
} else if is_opaque && is_grayscale {
PixelFormat::Gray8
} else if is_opaque {
PixelFormat::Rgb
} else {
Expand Down Expand Up @@ -364,6 +370,7 @@ fn convert_image(
})
.collect(),
(_, PixelFormat::AlphaMap(_)) => i.pixels().map(|(_, _, p)| p[3]).collect(),
(_, PixelFormat::Gray8) => i.pixels().map(|(_, _, p)| p[0]).collect(),
}
}

Expand Down
2 changes: 1 addition & 1 deletion internal/core/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use alloc::string::String;
#[cfg(target_has_atomic = "ptr")]
pub use crate::future::*;
pub use crate::graphics::{
Brush, Color, Image, LoadImageError, OklchColor, Rgb8Pixel, Rgba8Pixel, RgbaColor,
Brush, Color, Gray8Pixel, Image, LoadImageError, OklchColor, Rgb8Pixel, Rgba8Pixel, RgbaColor,
SharedPixelBuffer,
};
pub use crate::input::Keys;
Expand Down
65 changes: 65 additions & 0 deletions internal/core/graphics/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ pub type Rgb8Pixel = rgb::RGB8;
/// Convenience alias for a pixel with four color channels (red, green, blue and alpha), each
/// encoded as u8.
pub type Rgba8Pixel = rgb::RGBA8;
/// Convenience alias for a single-channel grayscale pixel encoded as u8.
pub type Gray8Pixel = rgb::Gray<u8>;

/// SharedImageBuffer is a container for images that are stored in CPU accessible memory.
///
Expand All @@ -173,6 +175,9 @@ pub enum SharedImageBuffer {
/// Only construct this format if you know that your pixels are encoded this way. It is more efficient
/// for rendering.
RGBA8Premultiplied(SharedPixelBuffer<Rgba8Pixel>),
/// This variant holds the data for a grayscale image where each pixel is a single luminance
/// channel encoded as unsigned byte.
Gray8(SharedPixelBuffer<Gray8Pixel>),
}

impl SharedImageBuffer {
Expand All @@ -183,6 +188,7 @@ impl SharedImageBuffer {
Self::RGB8(buffer) => buffer.width(),
Self::RGBA8(buffer) => buffer.width(),
Self::RGBA8Premultiplied(buffer) => buffer.width(),
Self::Gray8(buffer) => buffer.width(),
}
}

Expand All @@ -193,6 +199,7 @@ impl SharedImageBuffer {
Self::RGB8(buffer) => buffer.height(),
Self::RGBA8(buffer) => buffer.height(),
Self::RGBA8Premultiplied(buffer) => buffer.height(),
Self::Gray8(buffer) => buffer.height(),
}
}

Expand All @@ -203,6 +210,7 @@ impl SharedImageBuffer {
Self::RGB8(buffer) => buffer.size(),
Self::RGBA8(buffer) => buffer.size(),
Self::RGBA8Premultiplied(buffer) => buffer.size(),
Self::Gray8(buffer) => buffer.size(),
}
}
}
Expand All @@ -219,6 +227,9 @@ impl PartialEq for SharedImageBuffer {
Self::RGBA8Premultiplied(lhs_buffer) => {
matches!(other, Self::RGBA8Premultiplied(rhs_buffer) if lhs_buffer.data.as_ptr().eq(&rhs_buffer.data.as_ptr()))
}
Self::Gray8(lhs_buffer) => {
matches!(other, Self::Gray8(rhs_buffer) if lhs_buffer.data.as_ptr().eq(&rhs_buffer.data.as_ptr()))
}
}
}
}
Expand All @@ -240,6 +251,8 @@ pub enum TexturePixelFormat {
/// and i8::MAX corresponds to 3 pixels inside the shape.
/// The array must be width * height +1 bytes long. (the extra bit is read but never used)
SignedDistanceField,
/// Grayscale. 8bits. Each pixel is a luminance value rendered as (v, v, v, 255).
Gray8,
}

impl TexturePixelFormat {
Expand All @@ -251,6 +264,7 @@ impl TexturePixelFormat {
TexturePixelFormat::RgbaPremultiplied => 4,
TexturePixelFormat::AlphaMap => 1,
TexturePixelFormat::SignedDistanceField => 1,
TexturePixelFormat::Gray8 => 1,
}
}
}
Expand Down Expand Up @@ -514,6 +528,15 @@ impl ImageInner {
});
slice.fill_with(|| iter.next().unwrap());
}
TexturePixelFormat::Gray8 => {
let mut iter = source.iter().map(|v| Rgba8Pixel {
r: *v,
g: *v,
b: *v,
a: 255,
});
slice.fill_with(|| iter.next().unwrap());
}
TexturePixelFormat::SignedDistanceField => {
todo!("converting from a signed distance field to an image")
}
Expand Down Expand Up @@ -743,6 +766,15 @@ impl Image {
})
}

/// Creates a new Image from the specified shared pixel buffer, where each pixel is a single
/// grayscale luminance value encoded as u8.
pub fn from_gray8(buffer: SharedPixelBuffer<Gray8Pixel>) -> Self {
Image(ImageInner::EmbeddedImage {
cache_key: ImageCacheKey::Invalid,
buffer: SharedImageBuffer::Gray8(buffer),
})
}

/// Returns the pixel buffer for the Image if available in RGB format without alpha.
/// Returns None if the pixels cannot be obtained, for example when the image was created from borrowed OpenGL textures.
pub fn to_rgb8(&self) -> Option<SharedPixelBuffer<Rgb8Pixel>> {
Expand All @@ -767,6 +799,18 @@ impl Image {
height: buffer.height,
data: buffer.data.into_iter().map(Image::premultiplied_rgba_to_rgba).collect(),
},
SharedImageBuffer::Gray8(buffer) => SharedPixelBuffer::<Rgba8Pixel> {
width: buffer.width,
height: buffer.height,
data: buffer
.data
.into_iter()
.map(|g| {
let v = g.value();
Rgba8Pixel::new(v, v, v, 255)
})
.collect(),
},
})
}

Expand All @@ -786,6 +830,18 @@ impl Image {
data: buffer.data.into_iter().map(Image::rgba_to_premultiplied_rgba).collect(),
},
SharedImageBuffer::RGBA8Premultiplied(buffer) => buffer,
SharedImageBuffer::Gray8(buffer) => SharedPixelBuffer::<Rgba8Pixel> {
width: buffer.width,
height: buffer.height,
data: buffer
.data
.into_iter()
.map(|g| {
let v = g.value();
Rgba8Pixel::new(v, v, v, 255)
})
.collect(),
},
})
}

Expand Down Expand Up @@ -1332,6 +1388,15 @@ pub(crate) mod ffi {
a: u8,
}

// Expand Gray8Pixel so that cbindgen can see it. (is in fact rgb::Gray<u8>)
/// Represents a grayscale pixel.
#[cfg(cbindgen)]
#[repr(C)]
struct Gray8Pixel {
/// luminance value (between 0 and 255)
v: u8,
}

#[cfg(feature = "image-decoders")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn slint_image_load_from_path(path: &SharedString, image: *mut Image) {
Expand Down
1 change: 1 addition & 0 deletions internal/core/graphics/image/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ impl clru::WeightScale<ImageCacheKey, ImageInner> for ImageWeightInBytes {
SharedImageBuffer::RGB8(pixels) => pixels.as_bytes().len(),
SharedImageBuffer::RGBA8(pixels) => pixels.as_bytes().len(),
SharedImageBuffer::RGBA8Premultiplied(pixels) => pixels.as_bytes().len(),
SharedImageBuffer::Gray8(pixels) => pixels.as_bytes().len(),
},
#[cfg(feature = "svg")]
ImageInner::Svg(_) => 512, // Don't know how to measure the size of the parsed SVG tree...
Expand Down
7 changes: 7 additions & 0 deletions internal/renderers/femtovg/images.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,13 @@ fn image_buffer_to_image_source(
},
femtovg::ImageFlags::PREMULTIPLIED,
),
SharedImageBuffer::Gray8(buffer) => (
{
imgref::ImgRef::new(buffer.as_slice(), buffer.width() as _, buffer.height() as _)
.into()
},
femtovg::ImageFlags::empty(),
),
}
}

Expand Down
8 changes: 8 additions & 0 deletions internal/renderers/skia/cached_image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ pub(crate) fn as_skia_image(
SharedImageBuffer::RGB8(_) => unreachable!(),
SharedImageBuffer::RGBA8(_) => unreachable!(),
SharedImageBuffer::RGBA8Premultiplied(pixels) => pixels,
SharedImageBuffer::Gray8(_) => unreachable!(),
};

let image_info = skia_safe::ImageInfo::new(
Expand Down Expand Up @@ -140,6 +141,13 @@ fn image_buffer_to_skia_image(buffer: &SharedImageBuffer) -> Option<skia_safe::I
skia_safe::ColorType::RGBA8888,
skia_safe::AlphaType::Premul,
),
SharedImageBuffer::Gray8(pixels) => (
skia_safe::Data::new_copy(pixels.as_bytes()),
pixels.width() as usize,
pixels.size(),
skia_safe::ColorType::Gray8,
skia_safe::AlphaType::Opaque,
),
};
let image_info = skia_safe::ImageInfo::new(
skia_safe::ISize::new(size.width as i32, size.height as i32),
Expand Down
13 changes: 13 additions & 0 deletions internal/renderers/software/draw_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,19 @@ pub(super) fn draw_texture_line(
pix.blend(c);
}
}
TexturePixelFormat::Gray8 => {
for pix in line_buffer {
let pos = pos(1).0;
let v = data[pos];
if alpha == 0xff {
*pix = TargetPixel::from_rgb(v, v, v);
} else {
pix.blend(PremultipliedRgbaColor::premultiply(Color::from_argb_u8(
alpha, v, v, v,
)));
}
}
}
TexturePixelFormat::SignedDistanceField => {
const RANGE: i32 = 6;
let factor = (362 * 256 / delta.0) * RANGE; // 362 ≃ 255 * sqrt(2)
Expand Down
6 changes: 6 additions & 0 deletions internal/renderers/software/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,12 @@ impl SharedBufferCommand {
extra: self.extra,
}
}
SharedBufferData::SharedImage(SharedImageBuffer::Gray8(b)) => SceneTexture {
data: &b.as_bytes()[start..end],
pixel_stride: stride as u16,
format: TexturePixelFormat::Gray8,
extra: self.extra,
},
SharedBufferData::AlphaMap { data, width } => SceneTexture {
data: &data[start..end],
pixel_stride: *width,
Expand Down
6 changes: 6 additions & 0 deletions internal/renderers/software/target_pixel_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ impl DrawTextureArgs {
size,
)
}
SharedBufferData::SharedImage(SharedImageBuffer::Gray8(b)) => TextureData::new(
&b.as_bytes()[start..end],
TexturePixelFormat::Gray8,
stride,
size,
),
SharedBufferData::AlphaMap { data, .. } => TextureData::new(
&data[start..end],
TexturePixelFormat::AlphaMap,
Expand Down
Loading