This commit is contained in:
2026-07-09 02:59:53 +03:00
commit 7ace048d94
817 changed files with 234289 additions and 0 deletions
+282
View File
@@ -0,0 +1,282 @@
#![allow(dead_code)]
pub use hcie_protocol::{Layer, BlendMode};
pub use hcie_brush_engine::{BrushTip, BrushStyle};
use hcie_blend::blend_pixels;
use hcie_brush_engine::{brush_spacing_pixels, jitter_offset};
pub fn draw_brush_stamp(
layer: &mut Layer,
cx: f32, cy: f32,
stamp: &[u8], stamp_diameter: usize,
color: [u8; 4],
mask: Option<&[u8]>,
) {
let radius = (stamp_diameter / 2) as i32;
let start_x = (cx as i32) - radius;
let start_y = (cy as i32) - radius;
for sy in 0..stamp_diameter {
for sx in 0..stamp_diameter {
let px = start_x + sx as i32;
let py = start_y + sy as i32;
if px < 0 || py < 0 || px >= layer.width as i32 || py >= layer.height as i32 {
continue;
}
let px_u = px as u32;
let py_u = py as u32;
if let Some(m) = mask {
let idx = (py_u * layer.width + px_u) as usize;
if m.get(idx).copied().unwrap_or(0) == 0 { continue; }
}
let stamp_alpha = stamp[sy * stamp_diameter + sx] as f32 / 255.0;
if stamp_alpha <= 0.0 { continue; }
let dst = layer.get_pixel(px_u, py_u);
let src_color: [u8; 4] = [
color[0], color[1], color[2],
(color[3] as f32 * stamp_alpha).round() as u8,
];
let out = blend_pixels(dst, src_color, hcie_protocol::BlendMode::Normal.into(), 1.0);
layer.set_pixel(px_u, py_u, out);
}
}
}
pub fn draw_brush_stroke(
layer: &mut Layer,
points: &[(f32, f32, f32)],
color: [u8; 4],
tip: &BrushTip,
is_eraser: bool,
mask: Option<&[u8]>,
mut stroke_mask: Option<&mut [u8]>,
bg_pixels: Option<&[u8]>,
) {
if points.is_empty() { return; }
let spacing = brush_spacing_pixels(tip.size, tip.spacing);
let use_color_variant = tip.color_variant && tip.variant_amount > 0.0;
let mut rng = if use_color_variant {
Some(rand::thread_rng())
} else {
None
};
let mut last_p = points[0];
let (jx0, jy0) = jitter_offset(tip.jitter_amount, tip.size);
let first_x = last_p.0 + jx0;
let first_y = last_p.1 + jy0;
let first_size = tip.size * last_p.2.max(0.1);
let first_opacity = tip.opacity * last_p.2.max(0.0).min(1.0);
let first_color = if let Some(r) = rng.as_mut() {
hcie_brush_engine::vary_color_hsl(color, tip.variant_amount, r)
} else {
color
};
let first_px_color = [
first_color[0], first_color[1], first_color[2],
(first_color[3] as f32 * first_opacity).round() as u8,
];
hcie_brush_engine::draw_dab_capped(
&mut layer.pixels, layer.width, layer.height,
first_x, first_y, first_size, tip.hardness, first_px_color, 1.0, is_eraser, mask,
stroke_mask.as_deref_mut(), bg_pixels, tip.opacity,
);
for &p in &points[1..] {
let dx = p.0 - last_p.0;
let dy = p.1 - last_p.1;
let dist = (dx * dx + dy * dy).sqrt();
if dist == 0.0 { continue; }
let steps = ((dist / spacing).max(1.0)).ceil() as i32;
let step_x = dx / steps as f32;
let step_y = dy / steps as f32;
for i in 1..=steps {
let t = i as f32 / steps as f32;
let pressure = last_p.2 * (1.0 - t) + p.2 * t;
let px = last_p.0 + step_x * i as f32;
let py = last_p.1 + step_y * i as f32;
let (jx, jy) = jitter_offset(tip.jitter_amount, tip.size);
let final_x = px + jx;
let final_y = py + jy;
let effective_size = tip.size * pressure.max(0.1);
let pressure_opacity = tip.opacity * pressure.max(0.0).min(1.0);
let dab_color = if let Some(r) = rng.as_mut() {
hcie_brush_engine::vary_color_hsl(color, tip.variant_amount, r)
} else {
color
};
let px_color = [
dab_color[0], dab_color[1], dab_color[2],
(dab_color[3] as f32 * pressure_opacity).round() as u8,
];
hcie_brush_engine::draw_dab_capped(
&mut layer.pixels, layer.width, layer.height,
final_x, final_y, effective_size, tip.hardness, px_color, 1.0, is_eraser, mask,
stroke_mask.as_deref_mut(), bg_pixels, tip.opacity,
);
}
last_p = p;
}
}
pub fn draw_line(
layer: &mut Layer,
x0: f32, y0: f32, x1: f32, y1: f32,
color: [u8; 4],
width: f32,
mask: Option<&[u8]>,
) {
let dx = x1 - x0;
let dy = y1 - y0;
let dist = (dx * dx + dy * dy).sqrt();
if dist == 0.0 { return; }
let steps = (dist * 2.0).max(1.0).ceil() as i32;
for i in 0..=steps {
let t = i as f32 / steps as f32;
let px = x0 + dx * t;
let py = y0 + dy * t;
draw_filled_circle(layer, px, py, width / 2.0, color, mask);
}
}
pub fn draw_filled_circle(
layer: &mut Layer,
cx: f32, cy: f32, r: f32,
color: [u8; 4],
mask: Option<&[u8]>,
) {
let r_i = r.ceil() as i32;
for dy in -r_i..=r_i {
let py = (cy + dy as f32).round() as i32;
if py < 0 || py >= layer.height as i32 { continue; }
let dx_max = (r * r - dy as f32 * dy as f32).sqrt();
let dx_i = dx_max.ceil() as i32;
for dx in -dx_i..=dx_i {
let px = (cx + dx as f32).round() as i32;
if px < 0 || px >= layer.width as i32 { continue; }
if let Some(m) = mask {
let idx = (py as u32 * layer.width + px as u32) as usize;
if m.get(idx).copied().unwrap_or(0) == 0 { continue; }
}
let dist = ((dx as f32).powi(2) + (dy as f32).powi(2)).sqrt();
let alpha_t = 1.0 - (dist - (r - 1.0)).max(0.0).min(1.0);
if alpha_t <= 0.0 { continue; }
let src = [color[0], color[1], color[2], (color[3] as f32 * alpha_t).round() as u8];
let dst = layer.get_pixel(px as u32, py as u32);
let out = blend_pixels(dst, src, hcie_protocol::BlendMode::Normal.into(), 1.0);
layer.set_pixel(px as u32, py as u32, out);
}
}
}
pub fn draw_filled_rect(
layer: &mut Layer,
x1: f32, y1: f32, x2: f32, y2: f32,
color: [u8; 4],
mask: Option<&[u8]>,
) {
let x_start = x1.min(x2).max(0.0).floor() as u32;
let x_end = x1.max(x2).min(layer.width as f32).ceil() as u32;
let y_start = y1.min(y2).max(0.0).floor() as u32;
let y_end = y1.max(y2).min(layer.height as f32).ceil() as u32;
for y in y_start..y_end {
for x in x_start..x_end {
if let Some(m) = mask {
let idx = (y * layer.width + x) as usize;
if m.get(idx).copied().unwrap_or(0) == 0 { continue; }
}
let dst = layer.get_pixel(x, y);
let out = blend_pixels(dst, color, hcie_protocol::BlendMode::Normal.into(), 1.0);
layer.set_pixel(x, y, out);
}
}
}
pub fn draw_filled_ellipse(
layer: &mut Layer,
cx: f32, cy: f32, rx: f32, ry: f32,
color: [u8; 4],
mask: Option<&[u8]>,
) {
let x_start = (cx - rx).max(0.0).floor() as u32;
let x_end = (cx + rx).min(layer.width as f32).ceil() as u32;
let y_start = (cy - ry).max(0.0).floor() as u32;
let y_end = (cy + ry).min(layer.height as f32).ceil() as u32;
for y in y_start..y_end {
for x in x_start..x_end {
let dx = (x as f32 - cx) / rx;
let dy = (y as f32 - cy) / ry;
let dist = dx * dx + dy * dy;
if dist <= 1.0 {
if let Some(m) = mask {
let idx = (y * layer.width + x) as usize;
if m.get(idx).copied().unwrap_or(0) == 0 { continue; }
}
let dst = layer.get_pixel(x, y);
let out = blend_pixels(dst, color, hcie_protocol::BlendMode::Normal.into(), 1.0);
layer.set_pixel(x, y, out);
}
}
}
}
pub fn flood_fill(
layer: &mut Layer,
x: u32, y: u32,
fill_color: [u8; 4],
tolerance: u8,
mask: Option<&[u8]>,
) {
if let Some(m) = mask {
let idx = (y * layer.width + x) as usize;
if m.get(idx).copied().unwrap_or(0) == 0 { return; }
}
let start_color = layer.get_pixel(x, y);
if color_match(&start_color, &fill_color, tolerance) {
return;
}
let mut stack = vec![(x, y)];
let w = layer.width;
let h = layer.height;
while let Some((px, py)) = stack.pop() {
let c = layer.get_pixel(px, py);
if !color_match(&c, &start_color, tolerance) { continue; }
if let Some(m) = mask {
let idx = (py * w + px) as usize;
if m.get(idx).copied().unwrap_or(0) == 0 { continue; }
}
layer.set_pixel(px, py, fill_color);
if px > 0 { stack.push((px - 1, py)); }
if px + 1 < w { stack.push((px + 1, py)); }
if py > 0 { stack.push((px, py - 1)); }
if py + 1 < h { stack.push((px, py + 1)); }
}
}
fn color_match(a: &[u8; 4], b: &[u8; 4], tolerance: u8) -> bool {
let tol = tolerance as i32;
let diff_r = (a[0] as i32 - b[0] as i32).abs();
let diff_g = (a[1] as i32 - b[1] as i32).abs();
let diff_b = (a[2] as i32 - b[2] as i32).abs();
let diff_a = (a[3] as i32 - b[3] as i32).abs();
diff_r <= tol && diff_g <= tol && diff_b <= tol && diff_a <= tol
}