use crate::TextureId;
use crate::geometry::RectU16;
use crate::pixmap::{PixelMetadata, Pixmap};
use alloc::sync::Arc;
pub use peniko::Color;
use peniko::{
Gradient,
color::{AlphaColor, PremulRgba8, Srgb},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IndexedPaint(u32);
impl IndexedPaint {
pub fn new(index: usize) -> Self {
Self(u32::try_from(index).expect("exceeded the maximum number of paints"))
}
pub fn index(&self) -> usize {
usize::try_from(self.0).unwrap()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Paint {
Solid(PremulColor),
Indexed(IndexedPaint),
}
impl From<AlphaColor<Srgb>> for Paint {
fn from(value: AlphaColor<Srgb>) -> Self {
Self::Solid(PremulColor::from_alpha_color(value))
}
}
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
pub struct ImageId(u32);
impl ImageId {
pub fn new(value: u32) -> Self {
Self(value)
}
pub fn as_u32(&self) -> u32 {
self.0
}
}
#[derive(Debug, Clone)]
pub enum ImageSource {
Pixmap(Arc<Pixmap>),
OpaqueId {
id: ImageId,
may_have_transparency: bool,
},
ExternalTexture {
id: TextureId,
source_region: RectU16,
may_have_transparency: bool,
},
}
impl ImageSource {
pub fn opaque_id(id: ImageId) -> Self {
Self::OpaqueId {
id,
may_have_transparency: true,
}
}
pub fn opaque_id_with_transparency_hint(id: ImageId, may_have_transparency: bool) -> Self {
Self::OpaqueId {
id,
may_have_transparency,
}
}
pub fn external_texture(
texture_id: TextureId,
source_region: RectU16,
may_have_transparency: bool,
) -> Self {
assert!(
!source_region.is_empty(),
"external texture source regions must not be empty"
);
Self::ExternalTexture {
id: texture_id,
source_region,
may_have_transparency,
}
}
pub fn may_have_transparency(&self) -> bool {
match self {
Self::Pixmap(p) => p.may_have_transparency(),
Self::OpaqueId {
may_have_transparency,
..
}
| Self::ExternalTexture {
may_have_transparency,
..
} => *may_have_transparency,
}
}
pub fn from_peniko_image_data(image: &peniko::ImageData) -> Self {
assert!(
image.width <= u16::MAX as u32 && image.height <= u16::MAX as u32,
"The image is too big. Its width and height can be no larger than {} pixels.",
u16::MAX,
);
let width = image.width.try_into().unwrap();
let height = image.height.try_into().unwrap();
let mut rgba = image.data.data().to_vec();
match image.format {
peniko::ImageFormat::Rgba8 => {}
peniko::ImageFormat::Bgra8 => {
for pixel in rgba.chunks_exact_mut(4) {
pixel.swap(0, 2);
}
}
format => unimplemented!("Unsupported image format: {format:?}"),
}
let pixmap = Pixmap::from_parts(
rgba,
width,
height,
PixelMetadata::new(image.alpha_type, true),
);
Self::Pixmap(Arc::new(pixmap))
}
}
pub type Image = peniko::ImageBrush<ImageSource>;
pub trait ImageResolver: Send + Sync {
fn resolve(&self, id: ImageId) -> Option<Arc<Pixmap>>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NoOpImageResolver;
impl ImageResolver for NoOpImageResolver {
fn resolve(&self, _id: ImageId) -> Option<Arc<Pixmap>> {
None
}
}
#[derive(Debug, Clone, PartialEq, Copy)]
pub struct PremulColor {
premul_u8: PremulRgba8,
premul_f32: peniko::color::PremulColor<Srgb>,
}
impl PremulColor {
pub fn from_alpha_color(color: AlphaColor<Srgb>) -> Self {
Self::from_premul_color(color.premultiply())
}
pub fn from_premul_color(color: peniko::color::PremulColor<Srgb>) -> Self {
Self {
premul_u8: color.to_rgba8(),
premul_f32: color,
}
}
pub fn as_premul_rgba8(&self) -> PremulRgba8 {
self.premul_u8
}
pub fn as_premul_f32(&self) -> peniko::color::PremulColor<Srgb> {
self.premul_f32
}
pub fn is_opaque(&self) -> bool {
self.premul_f32.components[3] == 1.0
}
pub fn is_transparent(&self) -> bool {
self.premul_f32.components[3] == 0.0
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum TintMode {
AlphaMask = 0,
Multiply = 1,
}
impl TintMode {
pub fn as_u32(self) -> u32 {
self as u32
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Tint {
pub color: Color,
pub mode: TintMode,
}
pub type PaintType = peniko::Brush<Image, Gradient>;
#[cfg(test)]
mod tests {
use super::ImageSource;
use alloc::sync::Arc;
fn image_data(pixels: &[u8], alpha_type: peniko::ImageAlphaType) -> peniko::ImageData {
peniko::ImageData {
data: peniko::Blob::new(Arc::new(pixels.to_vec())),
format: peniko::ImageFormat::Rgba8,
alpha_type,
width: (pixels.len() / 4) as u32,
height: 1,
}
}
#[test]
fn from_peniko_image_data_computes_transparency_hint() {
let opaque = image_data(
&[10, 20, 30, 255, 40, 50, 60, 255],
peniko::ImageAlphaType::Alpha,
);
assert!(!ImageSource::from_peniko_image_data(&opaque).may_have_transparency());
let translucent = image_data(
&[10, 20, 30, 255, 40, 50, 60, 128],
peniko::ImageAlphaType::Alpha,
);
assert!(ImageSource::from_peniko_image_data(&translucent).may_have_transparency());
let premultiplied = image_data(
&[10, 20, 30, 255, 40, 50, 60, 255],
peniko::ImageAlphaType::AlphaPremultiplied,
);
assert!(ImageSource::from_peniko_image_data(&premultiplied).may_have_transparency());
}
}