use cranpose_render_common::{graph::ProjectiveTransform, graph_scene::RenderDiagnostics};
use cranpose_ui_graphics::{ImageBitmap, Rect, whole};
use crate::{
draw::{Target, TextRaster, draw_item},
scene::RasterScene,
};
const MAX_SURFACE: f32 = 4096.0;
const MARGIN: f32 = 1.0;
const AXIS_TOLERANCE: f32 = 1.0e-5;
pub(crate) fn scales_and_moves(transform: &ProjectiveTransform) -> bool {
let [[a, b, _], [c, d, _], perspective] = transform.matrix();
let near = |value: f32, target: f32| (value - target).abs() <= AXIS_TOLERANCE;
near(b, 0.0)
&& near(c, 0.0)
&& a > 0.0
&& d > 0.0
&& near(perspective[0], 0.0)
&& near(perspective[1], 0.0)
&& near(perspective[2], 1.0)
}
pub(crate) struct SurfacePlacement {
pub to_surface: ProjectiveTransform,
pub width: u32,
pub height: u32,
pub rect: Rect,
pub to_device: Option<ProjectiveTransform>,
}
impl SurfacePlacement {
pub(crate) fn of(bounds: Rect, transform: &ProjectiveTransform) -> Option<Self> {
if bounds.width <= 0.0 || bounds.height <= 0.0 {
return None;
}
if scales_and_moves(transform) {
let device = transform.bounds_for_rect(bounds);
let x = whole::floor(device.x) - MARGIN;
let y = whole::floor(device.y) - MARGIN;
let width = surface_extent(whole::ceil(device.x + device.width) + MARGIN - x)?;
let height = surface_extent(whole::ceil(device.y + device.height) + MARGIN - y)?;
return Some(Self {
to_surface: transform.then(ProjectiveTransform::translation(-x, -y)),
width,
height,
rect: Rect {
x,
y,
width: width as f32,
height: height as f32,
},
to_device: None,
});
}
let [[a, b, _], [c, d, _], _] = transform.matrix();
let scale = (a * d - b * c).abs().sqrt();
if !scale.is_normal() {
return None;
}
let width = surface_extent(whole::ceil(bounds.width * scale) + 2.0 * MARGIN)?;
let height = surface_extent(whole::ceil(bounds.height * scale) + 2.0 * MARGIN)?;
let to_surface = ProjectiveTransform::translation(-bounds.x, -bounds.y)
.then(ProjectiveTransform::uniform_scale(scale))
.then(ProjectiveTransform::translation(MARGIN, MARGIN));
let to_device = to_surface.inverse()?.then(*transform);
Some(Self {
to_surface,
width,
height,
rect: Rect {
x: 0.0,
y: 0.0,
width: width as f32,
height: height as f32,
},
to_device: Some(to_device),
})
}
}
fn surface_extent(pixels: f32) -> Option<u32> {
(pixels.is_finite() && pixels >= 1.0).then(|| pixels.min(MAX_SURFACE) as u32)
}
pub(crate) fn rasterize_surface(
items: &RasterScene,
width: u32,
height: u32,
diagnostics: &RenderDiagnostics,
text: &TextRaster,
) -> Option<ImageBitmap> {
let mut pixels = vec![0u8; width as usize * height as usize * 4];
let mut target = Target {
pixels: pixels.as_chunks_mut::<4>().0,
left: 0,
top: 0,
width,
height,
};
for &item in &items.order {
draw_item(&mut target, items, item, diagnostics, text);
}
for pixel in pixels.as_chunks_mut::<4>().0 {
let alpha = u32::from(pixel[3]);
if alpha > 0 && alpha < 255 {
for channel in &mut pixel[..3] {
*channel = ((u32::from(*channel) * 255 + alpha / 2) / alpha).min(255) as u8;
}
}
}
ImageBitmap::from_rgba8(width, height, pixels).ok()
}