mod color;
pub use crate::pick_clip::pointer_physical_position;
pub use color::parse_css_color;
use bevy::asset::{Assets, RenderAssetUsages};
use bevy::ecs::entity::Entity;
use bevy::image::Image;
use bevy::math::Vec2;
use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat};
use bevy::ui::widget::ImageNode;
use tiny_skia::Pixmap;
pub const MAX_DIM: u32 = 4096;
pub fn node_scale_factor(node: &bevy::ui::ComputedNode) -> f32 {
if node.inverse_scale_factor > 0.0 {
node.inverse_scale_factor.recip()
} else {
1.0
}
}
pub fn clamp_physical_size(size: Vec2) -> (u32, u32) {
(
(size.x.round() as u32).min(MAX_DIM),
(size.y.round() as u32).min(MAX_DIM),
)
}
pub fn blank_image() -> Image {
Image::new_fill(
Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
TextureDimension::D2,
&[0, 0, 0, 0],
TextureFormat::Rgba8UnormSrgb,
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
)
}
pub fn replace_image_pixels(image: &mut Image, w: u32, h: u32, data: Vec<u8>) -> Vec<u8> {
let extent = Extent3d {
width: w,
height: h,
depth_or_array_layers: 1,
};
if image.texture_descriptor.size != extent {
image.texture_descriptor.size = extent;
}
image.data.replace(data).unwrap_or_default()
}
pub fn write_straight_alpha(pixmap: &Pixmap, out: &mut Vec<u8>) {
const BLOCK: usize = 32;
const ALPHA: u32 = u32::from_ne_bytes([0, 0, 0, 0xFF]);
let pixels = pixmap.pixels();
let bytes = pixmap.data();
out.clear();
out.reserve(bytes.len());
for (i, chunk) in bytes.chunks(BLOCK * 4).enumerate() {
let (and, or) = chunk
.as_chunks::<4>()
.0
.iter()
.map(|px| u32::from_ne_bytes(*px))
.fold((u32::MAX, 0u32), |(and, or), w| (and & w, or | w));
if and & ALPHA == ALPHA {
out.extend_from_slice(chunk); } else if or == 0 {
out.resize(out.len() + chunk.len(), 0); } else {
for px in &pixels[i * BLOCK..i * BLOCK + chunk.len() / 4] {
let c = px.demultiply();
out.extend_from_slice(&[c.red(), c.green(), c.blue(), c.alpha()]);
}
}
}
}
pub fn meet_transform(
min: bevy::math::Vec2,
size: bevy::math::Vec2,
w: u32,
h: u32,
) -> tiny_skia::Transform {
let scale = (w as f32 / size.x).min(h as f32 / size.y);
let tx = (w as f32 - size.x * scale) * 0.5 - min.x * scale;
let ty = (h as f32 - size.y * scale) * 0.5 - min.y * scale;
tiny_skia::Transform::from_row(scale, 0.0, 0.0, scale, tx, ty)
}
#[derive(Default)]
pub struct RasterCache {
pixmap: Option<tiny_skia::Pixmap>,
spare: Vec<u8>,
}
impl RasterCache {
pub fn take_pixmap(&mut self, w: u32, h: u32) -> Option<tiny_skia::Pixmap> {
match self.pixmap.take() {
Some(mut pixmap) if pixmap.width() == w && pixmap.height() == h => {
pixmap.fill(tiny_skia::Color::TRANSPARENT);
Some(pixmap)
}
_ => tiny_skia::Pixmap::new(w, h),
}
}
pub fn keep_pixmap(&mut self, pixmap: tiny_skia::Pixmap) {
self.pixmap = Some(pixmap);
}
}
#[allow(clippy::too_many_arguments)]
pub fn upload_pixmap(
entity: Entity,
image_node: &ImageNode,
images: &mut Assets<Image>,
dirt: &mut crate::layer::LayerContentDirt,
w: u32,
h: u32,
pixmap: &tiny_skia::Pixmap,
cache: &mut RasterCache,
) {
dirt.nodes.push(entity);
let Some(mut image) = images.get_mut(&image_node.image) else {
return;
};
let mut data = std::mem::take(&mut cache.spare);
write_straight_alpha(pixmap, &mut data);
cache.spare = replace_image_pixels(&mut image, w, h, data);
}
#[cfg(test)]
mod tests {
use super::write_straight_alpha;
use tiny_skia::Pixmap;
#[test]
fn straight_alpha_matches_per_pixel_demultiply() {
let (w, h) = (64u32, 33u32);
let mut seed = 0x2545_F491_4F6C_DD1Du64;
let mut next = || {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
seed
};
let mut data = Vec::with_capacity((w * h * 4) as usize);
for i in 0..(w * h) as usize {
let r = next();
if (500..700).contains(&i) {
data.extend_from_slice(&[0, 0, 0, 0]); continue;
}
let a = match i % 7 {
_ if (200..400).contains(&i) => 255, 0 => 255,
1 => 0,
2 => 1,
3 => 254,
_ => (r >> 8) as u8,
};
let ch = |shift: u32| {
let v = (r >> shift) as u8;
if i % 11 == 0 { v } else { v.min(a) }
};
data.extend_from_slice(&[ch(16), ch(24), ch(32), a]);
}
let pixmap =
Pixmap::from_vec(data, tiny_skia::IntSize::from_wh(w, h).unwrap()).expect("pixmap");
let mut expected = Vec::new();
for px in pixmap.pixels() {
let c = px.demultiply();
expected.extend_from_slice(&[c.red(), c.green(), c.blue(), c.alpha()]);
}
let mut out = vec![7u8; 13];
write_straight_alpha(&pixmap, &mut out);
assert_eq!(out, expected);
}
}