use crate::pixmap::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,
},
}
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 may_have_transparency(&self) -> bool {
match self {
Self::Pixmap(p) => p.may_have_transparency(),
Self::OpaqueId {
may_have_transparency,
..
} => *may_have_transparency,
}
}
pub fn from_peniko_image_data(image: &peniko::ImageData) -> Self {
let do_alpha_multiply = image.alpha_type != peniko::ImageAlphaType::AlphaPremultiplied;
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();
#[expect(clippy::cast_possible_truncation, reason = "This cannot overflow.")]
let pixels = image
.data
.data()
.chunks_exact(4)
.map(|pixel| {
let rgba: [u8; 4] = match image.format {
peniko::ImageFormat::Rgba8 => pixel.try_into().unwrap(),
peniko::ImageFormat::Bgra8 => [pixel[2], pixel[1], pixel[0], pixel[3]],
format => unimplemented!("Unsupported image format: {format:?}"),
};
let alpha = u16::from(rgba[3]);
let multiply = |component| ((alpha * u16::from(component)) / 255) as u8;
if do_alpha_multiply {
PremulRgba8 {
r: multiply(rgba[0]),
g: multiply(rgba[1]),
b: multiply(rgba[2]),
a: rgba[3],
}
} else {
PremulRgba8 {
r: rgba[0],
g: rgba[1],
b: rgba[2],
a: rgba[3],
}
}
})
.collect();
let pixmap = Pixmap::from_parts(pixels, width, height);
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
}
}
#[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>;