feat: Add theme and font management for egui GUI
- Introduced `theme.rs` to handle visual themes, font discovery, and application of theme colors. - Implemented system font registration for Linux, macOS, and Windows. - Added `apply_theme` function to configure egui visuals based on selected theme presets. feat: Implement interactive tool state management - Created `tool_state.rs` to manage runtime tool, selection, and transform states. - Defined `ToolState` struct to encapsulate active tool, color settings, drawing states, and AI panel states. - Included functionality for managing brush presets, text editing, and selection transformations. feat: Add utility functions for image and file handling - Developed `utils.rs` with stateless helper functions for color formatting, drawing icons, and cropping images. - Implemented save dialog builders and error handling for file operations. - Added flood fill functionality for composite RGBA pixels. test: Add unit test for bitmap brush stamp functionality - Created `bitmap_brush_stamp.rs` to verify the behavior of bitmap brush stamping in the engine. - Ensured that the painted area matches expected dimensions and characteristics.
This commit is contained in:
@@ -9,56 +9,8 @@ use libloading::Library;
|
||||
/// 0 = CPU, 1 = GPU. Stored here so it can be set before the plugin is loaded.
|
||||
static VISION_BACKEND: AtomicU8 = AtomicU8::new(0);
|
||||
|
||||
pub fn blend_pixels(dst: [u8; 4], src: [u8; 4], mode: hcie_protocol::BlendMode, opacity: f32) -> [u8; 4] {
|
||||
hcie_blend::blend_pixels(dst, src, mode.into(), opacity)
|
||||
}
|
||||
pub fn blend_buffers(dst: &mut [u8], src: &[u8], mode: hcie_protocol::BlendMode, opacity: f32) {
|
||||
hcie_blend::blend_buffers(dst, src, mode.into(), opacity)
|
||||
}
|
||||
|
||||
pub fn apply_filter(filter_id: &str, params: &serde_json::Value, pixels: &mut [u8], width: u32, height: u32) -> Result<(), String> {
|
||||
let mut fl = hcie_protocol::Layer::new_transparent("", width, height);
|
||||
fl.pixels.copy_from_slice(pixels);
|
||||
hcie_filter::apply_filter(&mut fl, filter_id, params);
|
||||
let len = pixels.len().min(fl.pixels.len());
|
||||
pixels[..len].copy_from_slice(&fl.pixels[..len]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw_line(layer: &mut hcie_protocol::Layer, x0: f32, y0: f32, x1: f32, y1: f32, color: [u8; 4], stroke_size: f32, mask: Option<&[u8]>) {
|
||||
hcie_draw::draw_line(layer, x0, y0, x1, y1, color, stroke_size, mask); layer.dirty = true;
|
||||
}
|
||||
pub fn draw_filled_rect(layer: &mut hcie_protocol::Layer, x1: f32, y1: f32, x2: f32, y2: f32, color: [u8; 4], mask: Option<&[u8]>) {
|
||||
hcie_draw::draw_filled_rect(layer, x1, y1, x2, y2, color, mask); layer.dirty = true;
|
||||
}
|
||||
pub fn draw_filled_ellipse(layer: &mut hcie_protocol::Layer, cx: f32, cy: f32, rx: f32, ry: f32, color: [u8; 4], mask: Option<&[u8]>) {
|
||||
hcie_draw::draw_filled_ellipse(layer, cx, cy, rx, ry, color, mask); layer.dirty = true;
|
||||
}
|
||||
pub fn flood_fill(layer: &mut hcie_protocol::Layer, x: u32, y: u32, color: [u8; 4], tolerance: u8, mask: Option<&[u8]>) {
|
||||
hcie_draw::flood_fill(layer, x, y, color, tolerance, mask); layer.dirty = true;
|
||||
}
|
||||
pub fn draw_brush_stroke(layer: &mut hcie_protocol::Layer, points: &[(f32, f32, f32)], color: [u8; 4], tip: &hcie_protocol::BrushTip, is_eraser: bool, mask: Option<&[u8]>, stroke_mask: Option<&mut [u8]>, bg_pixels: Option<&[u8]>) {
|
||||
let style = if tip.style == hcie_protocol::BrushStyle::Default {
|
||||
hcie_brush_engine::BrushStyle::Round
|
||||
} else {
|
||||
unsafe { std::mem::transmute::<i32, hcie_brush_engine::BrushStyle>(tip.style as i32) }
|
||||
};
|
||||
let ct = hcie_brush_engine::BrushTip {
|
||||
style,
|
||||
size: tip.size, opacity: tip.opacity, hardness: tip.hardness,
|
||||
spacing: tip.spacing, flow: tip.flow,
|
||||
jitter_amount: tip.jitter_amount, scatter_amount: tip.scatter_amount,
|
||||
angle: tip.angle, roundness: tip.roundness,
|
||||
spray_particle_size: tip.spray_particle_size, spray_density: tip.spray_density,
|
||||
bitmap_pixels: tip.bitmap_pixels.clone(), bitmap_w: tip.bitmap_w, bitmap_h: tip.bitmap_h,
|
||||
color_variant: tip.color_variant, variant_amount: tip.variant_amount, density: tip.density,
|
||||
drawing_angle: false,
|
||||
rotation_random: 0.0,
|
||||
};
|
||||
hcie_draw::draw_brush_stroke(layer, points, color, &ct, is_eraser, mask, stroke_mask, bg_pixels); layer.dirty = true;
|
||||
}
|
||||
pub fn draw_specialized_stroke(pixels: &mut [u8], width: u32, height: u32, points: &[(f32, f32, f32)], brush_style: hcie_protocol::BrushStyle, size: f32, hardness: f32, color: [u8; 4], opacity: f32, spacing_ratio: f32, is_eraser: bool, sketch_history: Option<&mut Vec<(f32, f32)>>, spray_particle_size: f32, spray_density: u32, mask: Option<&[u8]>, stroke_mask: Option<&mut [u8]>, bg_pixels: Option<&[u8]>, color_variant: bool, variant_amount: f32, density: f32) {
|
||||
let style = match brush_style {
|
||||
fn map_brush_style(style: hcie_protocol::BrushStyle) -> hcie_brush_engine::BrushStyle {
|
||||
match style {
|
||||
hcie_protocol::BrushStyle::Round => hcie_brush_engine::BrushStyle::Round,
|
||||
hcie_protocol::BrushStyle::Square => hcie_brush_engine::BrushStyle::Square,
|
||||
hcie_protocol::BrushStyle::HardRound => hcie_brush_engine::BrushStyle::HardRound,
|
||||
@@ -93,7 +45,56 @@ pub fn draw_brush_stroke(layer: &mut hcie_protocol::Layer, points: &[(f32, f32,
|
||||
hcie_protocol::BrushStyle::Blender => hcie_brush_engine::BrushStyle::Blender,
|
||||
hcie_protocol::BrushStyle::Default => hcie_brush_engine::BrushStyle::Round,
|
||||
hcie_protocol::BrushStyle::Bitmap => hcie_brush_engine::BrushStyle::Bitmap,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blend_pixels(dst: [u8; 4], src: [u8; 4], mode: hcie_protocol::BlendMode, opacity: f32) -> [u8; 4] {
|
||||
|
||||
hcie_blend::blend_pixels(dst, src, mode.into(), opacity)
|
||||
}
|
||||
pub fn blend_buffers(dst: &mut [u8], src: &[u8], mode: hcie_protocol::BlendMode, opacity: f32) {
|
||||
hcie_blend::blend_buffers(dst, src, mode.into(), opacity)
|
||||
}
|
||||
|
||||
pub fn apply_filter(filter_id: &str, params: &serde_json::Value, pixels: &mut [u8], width: u32, height: u32) -> Result<(), String> {
|
||||
let mut fl = hcie_protocol::Layer::new_transparent("", width, height);
|
||||
fl.pixels.copy_from_slice(pixels);
|
||||
hcie_filter::apply_filter(&mut fl, filter_id, params);
|
||||
let len = pixels.len().min(fl.pixels.len());
|
||||
pixels[..len].copy_from_slice(&fl.pixels[..len]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw_line(layer: &mut hcie_protocol::Layer, x0: f32, y0: f32, x1: f32, y1: f32, color: [u8; 4], stroke_size: f32, mask: Option<&[u8]>) {
|
||||
hcie_draw::draw_line(layer, x0, y0, x1, y1, color, stroke_size, mask); layer.dirty = true;
|
||||
}
|
||||
pub fn draw_filled_rect(layer: &mut hcie_protocol::Layer, x1: f32, y1: f32, x2: f32, y2: f32, color: [u8; 4], mask: Option<&[u8]>) {
|
||||
hcie_draw::draw_filled_rect(layer, x1, y1, x2, y2, color, mask); layer.dirty = true;
|
||||
}
|
||||
pub fn draw_filled_ellipse(layer: &mut hcie_protocol::Layer, cx: f32, cy: f32, rx: f32, ry: f32, color: [u8; 4], mask: Option<&[u8]>) {
|
||||
hcie_draw::draw_filled_ellipse(layer, cx, cy, rx, ry, color, mask); layer.dirty = true;
|
||||
}
|
||||
pub fn flood_fill(layer: &mut hcie_protocol::Layer, x: u32, y: u32, color: [u8; 4], tolerance: u8, mask: Option<&[u8]>) {
|
||||
hcie_draw::flood_fill(layer, x, y, color, tolerance, mask); layer.dirty = true;
|
||||
}
|
||||
pub fn draw_brush_stroke(layer: &mut hcie_protocol::Layer, points: &[(f32, f32, f32)], color: [u8; 4], tip: &hcie_protocol::BrushTip, is_eraser: bool, mask: Option<&[u8]>, stroke_mask: Option<&mut [u8]>, bg_pixels: Option<&[u8]>) {
|
||||
let style = map_brush_style(tip.style);
|
||||
let ct = hcie_brush_engine::BrushTip {
|
||||
style,
|
||||
size: tip.size, opacity: tip.opacity, hardness: tip.hardness,
|
||||
spacing: tip.spacing, flow: tip.flow,
|
||||
jitter_amount: tip.jitter_amount, scatter_amount: tip.scatter_amount,
|
||||
angle: tip.angle, roundness: tip.roundness,
|
||||
spray_particle_size: tip.spray_particle_size, spray_density: tip.spray_density,
|
||||
bitmap_pixels: tip.bitmap_pixels.clone(), bitmap_w: tip.bitmap_w, bitmap_h: tip.bitmap_h,
|
||||
color_variant: tip.color_variant, variant_amount: tip.variant_amount, density: tip.density,
|
||||
drawing_angle: false,
|
||||
rotation_random: 0.0,
|
||||
};
|
||||
hcie_draw::draw_brush_stroke(layer, points, color, &ct, is_eraser, mask, stroke_mask, bg_pixels); layer.dirty = true;
|
||||
}
|
||||
pub fn draw_specialized_stroke(pixels: &mut [u8], width: u32, height: u32, points: &[(f32, f32, f32)], brush_style: hcie_protocol::BrushStyle, size: f32, hardness: f32, color: [u8; 4], opacity: f32, spacing_ratio: f32, is_eraser: bool, sketch_history: Option<&mut Vec<(f32, f32)>>, spray_particle_size: f32, spray_density: u32, mask: Option<&[u8]>, stroke_mask: Option<&mut [u8]>, bg_pixels: Option<&[u8]>, color_variant: bool, variant_amount: f32, density: f32) {
|
||||
let style = map_brush_style(brush_style);
|
||||
hcie_brush_engine::draw_specialized_stroke(pixels, width, height, points, style, size, hardness, color, opacity, spacing_ratio, is_eraser, sketch_history, spray_particle_size, spray_density, mask, stroke_mask, bg_pixels, color_variant, variant_amount, density)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
use hcie_engine_api::{Engine, BrushTip, BrushStyle};
|
||||
|
||||
#[test]
|
||||
fn bitmap_brush_stamp_shape() {
|
||||
let mut engine = Engine::new(64, 64);
|
||||
// 4x4 solid white square bitmap stamp
|
||||
let mut pixels = vec![0u8; 16];
|
||||
for i in 0..16 { pixels[i] = 255; }
|
||||
let brush = BrushTip {
|
||||
style: BrushStyle::Bitmap,
|
||||
size: 8.0,
|
||||
opacity: 1.0,
|
||||
hardness: 1.0,
|
||||
spacing: 1.0,
|
||||
bitmap_pixels: pixels,
|
||||
bitmap_w: 4,
|
||||
bitmap_h: 4,
|
||||
..Default::default()
|
||||
};
|
||||
engine.set_brush_tip(brush);
|
||||
engine.begin_stroke(engine.active_layer_id(), 20.0, 20.0);
|
||||
engine.stroke_to(engine.active_layer_id(), 20.0, 20.0, 1.0);
|
||||
engine.end_stroke(engine.active_layer_id());
|
||||
let output = engine.get_composite_pixels();
|
||||
|
||||
// Check that the painted area is roughly 4x4, not a circular blob.
|
||||
// Count alpha > 0 pixels and bounding box.
|
||||
let mut min_x = 63; let mut max_x = 0; let mut min_y = 63; let mut max_y = 0;
|
||||
let mut count = 0;
|
||||
for y in 0..64 {
|
||||
for x in 0..64 {
|
||||
let a = output[(y*64+x)*4+3];
|
||||
if a > 0 {
|
||||
count += 1;
|
||||
if x < min_x { min_x = x; }
|
||||
if x > max_x { max_x = x; }
|
||||
if y < min_y { min_y = y; }
|
||||
if y > max_y { max_y = y; }
|
||||
}
|
||||
}
|
||||
}
|
||||
let w = (max_x - min_x + 1) as i32;
|
||||
let h = (max_y - min_y + 1) as i32;
|
||||
// Should fit inside ~8x8 because size=8 and stamp 4x4 scaled up
|
||||
assert!(w <= 8 && h <= 8, "bitmap dab bounding box {}x{} too large for 4x4 stamp", w, h);
|
||||
assert!(count >= 8, "bitmap dab should paint some pixels");
|
||||
// Square-ish: aspect ratio not too far from 1 (was very low for circle vs square?)
|
||||
assert!(w.abs_diff(h) <= 2, "bitmap dab bounding box should be roughly square, got {}x{}", w, h);
|
||||
}
|
||||
Reference in New Issue
Block a user