use cranpose_render_common::graph_scene::RenderDiagnostics;
use cranpose_ui_graphics::{BlendMode, Point, Rect, whole};
use super::{ClipBounds, Target, UNIT, clip_rect_to_bounds, snap_delta_for_anchor};
use crate::{pixel::Pixel, scene::ImageDraw};
pub(super) fn image_rect(draw: &ImageDraw) -> Rect {
if let Some(transform) = draw.transform {
return transform.bounds_for_rect(draw.rect);
}
let snap_delta = draw
.snap_anchor
.map(snap_delta_for_anchor)
.unwrap_or_default();
draw.rect.translate(snap_delta.x, snap_delta.y)
}
#[inline(never)]
pub(super) fn draw_image<P: Pixel>(
target: &mut Target<'_, P>,
draw: &ImageDraw,
diagnostics: &RenderDiagnostics,
) {
let rect = image_rect(draw);
if draw.alpha <= 0.0 || rect.width <= 0.0 || rect.height <= 0.0 {
return;
}
let Some(clip_bounds) = clip_rect_to_bounds(rect, draw.clip, target) else {
return;
};
let Some(sampler) = ImageSampler::of(draw) else {
return;
};
let to_image = match draw.transform {
Some(transform) => match transform.inverse() {
Some(inverse) => Some(inverse),
None => return,
},
None => None,
};
let image = draw.rect;
let local = |x: f32, y: f32| match to_image {
Some(inverse) => {
let point = inverse.map_point_in_front(Point::new(x, y))?;
image.contains(point.x, point.y).then(|| {
(
(point.x - image.x) / image.width,
(point.y - image.y) / image.height,
)
})
}
None => Some((
((x - rect.x) / rect.width).clamp(0.0, 1.0),
((y - rect.y) / rect.height).clamp(0.0, 1.0),
)),
};
for py in clip_bounds.min_y..clip_bounds.max_y {
let sample_y = py as f32 + 0.5;
let row = target.row(py, clip_bounds.min_x, clip_bounds.max_x);
for (px, dst) in (clip_bounds.min_x..).zip(row) {
let Some((u, v)) = local(px as f32 + 0.5, sample_y) else {
continue;
};
let sample = sampler.sample(u, v);
if sample[3] > 0.0 {
dst.blend_paint(P::paint(sample, draw.blend_mode), 1.0, diagnostics);
}
}
}
}
struct ImageSampler<'a> {
draw: &'a ImageDraw,
source: Rect,
}
impl<'a> ImageSampler<'a> {
fn of(draw: &'a ImageDraw) -> Option<Self> {
let (width, height) = (draw.image.width(), draw.image.height());
if width == 0
|| height == 0
|| draw.image.format() == cranpose_ui_graphics::ImagePixelFormat::External
{
return None;
}
let source = draw.src_rect.unwrap_or(Rect {
x: 0.0,
y: 0.0,
width: width as f32,
height: height as f32,
});
Some(Self { draw, source })
}
fn sample(&self, u: f32, v: f32) -> [f32; 4] {
let Self { draw, source } = self;
let image = &draw.image;
let x = source.x + u * source.width;
let y = source.y + v * source.height;
let mut sample = match draw.sampling {
cranpose_ui_graphics::ImageSampling::Nearest => {
let src_x = (whole::floor(x) as i32).clamp(0, image.width() as i32 - 1);
let src_y = (whole::floor(y) as i32).clamp(0, image.height() as i32 - 1);
sample_image_nearest(image, src_x as u32, src_y as u32)
}
cranpose_ui_graphics::ImageSampling::Linear => {
sample_image_linear(image, x - 0.5, y - 0.5)
}
};
if let Some(filter) = &draw.color_filter {
sample = filter.apply_rgba(sample);
}
sample[3] *= draw.alpha.clamp(0.0, 1.0);
sample
}
}
fn sample_image_nearest(
image: &cranpose_ui_graphics::ImageBitmap,
src_x: u32,
src_y: u32,
) -> [f32; 4] {
image
.rgba8_pixel(src_y as usize * image.width() as usize + src_x as usize)
.map(|channel| UNIT[usize::from(channel)])
}
fn sample_image_linear(image: &cranpose_ui_graphics::ImageBitmap, x: f32, y: f32) -> [f32; 4] {
let img_width = image.width();
let img_height = image.height();
let x = x.clamp(0.0, img_width.saturating_sub(1) as f32);
let y = y.clamp(0.0, img_height.saturating_sub(1) as f32);
let x0 = whole::floor(x);
let y0 = whole::floor(y);
let tx = x - x0;
let ty = y - y0;
let x0 = (x0 as i32).clamp(0, img_width as i32 - 1) as u32;
let y0 = (y0 as i32).clamp(0, img_height as i32 - 1) as u32;
let x1 = (x0 + 1).min(img_width - 1);
let y1 = (y0 + 1).min(img_height - 1);
let top_left = sample_image_nearest(image, x0, y0);
let top_right = sample_image_nearest(image, x1, y0);
let bottom_left = sample_image_nearest(image, x0, y1);
let bottom_right = sample_image_nearest(image, x1, y1);
let mut out = [0.0; 4];
for channel in 0..4 {
let top = top_left[channel] + (top_right[channel] - top_left[channel]) * tx;
let bottom = bottom_left[channel] + (bottom_right[channel] - bottom_left[channel]) * tx;
out[channel] = top + (bottom - top) * ty;
}
out
}
pub(super) fn blit_image_between_pixels<P: Pixel>(
target: &mut Target<'_, P>,
rect: Rect,
clip_bounds: ClipBounds,
image: &cranpose_ui_graphics::ImageBitmap,
diagnostics: &RenderDiagnostics,
) {
for py in clip_bounds.min_y..clip_bounds.max_y {
let image_y = py as f32 - rect.y;
let row = target.row(py, clip_bounds.min_x, clip_bounds.max_x);
for (px, dst) in (clip_bounds.min_x..).zip(row) {
let src = sample_image_linear(image, px as f32 - rect.x, image_y);
if src[3] <= 0.0 {
continue;
}
dst.blend_paint(P::paint(src, BlendMode::SrcOver), 1.0, diagnostics);
}
}
}