use cranpose_ui_graphics::{BrushSampler, Color, Point, whole};
#[doc(hidden)]
pub fn color_to_rgba(color: Color) -> [f32; 4] {
[
color.0.clamp(0.0, 1.0),
color.1.clamp(0.0, 1.0),
color.2.clamp(0.0, 1.0),
color.3.clamp(0.0, 1.0),
]
}
const LEVEL: f32 = 1.0 / 255.0;
pub fn gradient_dither_offset(x: f32, y: f32) -> f32 {
dither_offset(dither_cell(x), dither_cell(y))
}
fn dither_cell(coordinate: f32) -> u32 {
whole::floor(coordinate).max(0.0) as u32 + 1
}
fn dither_offset(column: u32, row: u32) -> f32 {
let m = ((row & 1) << 3) | ((column & 1) << 2) | (row & 2) | ((column & 2) >> 1);
m as f32 * (1.0 / 16.0) - (15.0 / 32.0)
}
fn dither_by(rgba: [f32; 4], offset: f32) -> [f32; 4] {
if rgba[3] <= 0.0 {
return rgba;
}
let offset = offset * LEVEL;
[
(rgba[0] + offset).clamp(0.0, 1.0),
(rgba[1] + offset).clamp(0.0, 1.0),
(rgba[2] + offset).clamp(0.0, 1.0),
rgba[3],
]
}
#[doc(hidden)]
pub fn sample_brush_rgba(
brush: &BrushSampler<'_>,
x: f32,
y: f32,
dither_origin: Point,
) -> [f32; 4] {
let rgba = color_to_rgba(brush.color_at(Point::new(x, y)));
if brush.is_solid() {
return rgba;
}
dither_by(
rgba,
gradient_dither_offset(x - dither_origin.x, y - dither_origin.y),
)
}
#[doc(hidden)]
pub struct DitheredLine {
by_cell: [[f32; 4]; 4],
}
impl DitheredLine {
pub fn row(brush: &BrushSampler<'_>, x: f32, y: f32, dither_origin: Point) -> Self {
let row = dither_cell(y - dither_origin.y);
Self::dithered(brush, Point::new(x, y), |column| dither_offset(column, row))
}
pub fn column(brush: &BrushSampler<'_>, x: f32, y: f32, dither_origin: Point) -> Self {
let column = dither_cell(x - dither_origin.x);
Self::dithered(brush, Point::new(x, y), |row| dither_offset(column, row))
}
fn dithered(brush: &BrushSampler<'_>, at: Point, offset: impl Fn(u32) -> f32) -> Self {
let rgba = color_to_rgba(brush.color_at(at));
if brush.is_solid() {
return Self { by_cell: [rgba; 4] };
}
Self {
by_cell: [0, 1, 2, 3].map(|cell| dither_by(rgba, offset(cell))),
}
}
pub fn colors(&self) -> &[[f32; 4]; 4] {
&self.by_cell
}
}
#[doc(hidden)]
#[derive(Clone, Copy)]
pub struct DitherAxis {
origin: f32,
whole_origin: Option<i32>,
}
impl DitherAxis {
pub fn new(origin: f32) -> Self {
Self {
origin,
whole_origin: (origin.fract() == 0.0 && origin.abs() < WHOLE_COORDINATES)
.then_some(origin as i32),
}
}
pub fn cell(self, pixel: i32) -> usize {
let cell = match self.whole_origin {
Some(origin) if pixel.abs() < WHOLE_PIXELS => (pixel - origin).max(0) as u32 + 1,
_ => dither_cell(pixel as f32 + 0.5 - self.origin),
};
(cell & 3) as usize
}
pub fn stepping_cell(self, start: i32, end: i32) -> Option<usize> {
let origin = self.whole_origin?;
(start >= origin && start > -WHOLE_PIXELS && end <= WHOLE_PIXELS).then(|| self.cell(start))
}
}
const WHOLE_COORDINATES: f32 = 4_194_304.0;
const WHOLE_PIXELS: i32 = 4_194_304;
#[cfg(test)]
#[path = "tests/brush_sampling_tests.rs"]
mod tests;