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:
2026-07-10 01:23:47 +03:00
parent e88a2be508
commit 07a9b35208
14 changed files with 2250 additions and 1523 deletions
+51 -50
View File
@@ -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)
}