init
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "hcie-protocol"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
hcie-blend = { path = "../hcie-blend" }
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib"]
|
||||
|
||||
[dev-dependencies]
|
||||
rstest = "0.23"
|
||||
proptest = "1.5"
|
||||
approx = "0.5"
|
||||
@@ -0,0 +1,192 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ContourCurve {
|
||||
pub points: Vec<(f32, f32)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct GradientDef {
|
||||
pub color_stops: Vec<(u32, [u8; 4])>,
|
||||
pub transparency_stops: Vec<(u32, u8)>,
|
||||
pub midpoint: f32,
|
||||
pub angle: f32,
|
||||
pub scale: f32,
|
||||
pub gradient_type: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum BevelStyle {
|
||||
InnerBevel,
|
||||
OuterBevel,
|
||||
Emboss,
|
||||
PillowEmboss,
|
||||
StrokeEmboss,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum Technique {
|
||||
Smooth,
|
||||
ChiselHard,
|
||||
ChiselSoft,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum Direction {
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum StrokePosition {
|
||||
Inside,
|
||||
Center,
|
||||
Outside,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum StrokeFillType {
|
||||
Color,
|
||||
Gradient,
|
||||
Pattern,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum LayerEffect {
|
||||
DropShadow {
|
||||
enabled: bool,
|
||||
blend_mode: String,
|
||||
color: [u8; 4],
|
||||
opacity: f32,
|
||||
angle: f32,
|
||||
distance: f32,
|
||||
spread: f32,
|
||||
size: f32,
|
||||
noise: f32,
|
||||
contour: Option<ContourCurve>,
|
||||
},
|
||||
InnerShadow {
|
||||
enabled: bool,
|
||||
blend_mode: String,
|
||||
color: [u8; 4],
|
||||
opacity: f32,
|
||||
angle: f32,
|
||||
distance: f32,
|
||||
choke: f32,
|
||||
size: f32,
|
||||
noise: f32,
|
||||
contour: Option<ContourCurve>,
|
||||
},
|
||||
OuterGlow {
|
||||
enabled: bool,
|
||||
blend_mode: String,
|
||||
color: [u8; 4],
|
||||
opacity: f32,
|
||||
spread: f32,
|
||||
size: f32,
|
||||
noise: f32,
|
||||
contour: Option<ContourCurve>,
|
||||
},
|
||||
InnerGlow {
|
||||
enabled: bool,
|
||||
blend_mode: String,
|
||||
color: [u8; 4],
|
||||
opacity: f32,
|
||||
choke: f32,
|
||||
size: f32,
|
||||
noise: f32,
|
||||
contour: Option<ContourCurve>,
|
||||
source: u32,
|
||||
},
|
||||
BevelEmboss {
|
||||
enabled: bool,
|
||||
style: BevelStyle,
|
||||
technique: Technique,
|
||||
depth: f32,
|
||||
direction: Direction,
|
||||
size: f32,
|
||||
soften: f32,
|
||||
angle: f32,
|
||||
altitude: f32,
|
||||
highlight_blend: String,
|
||||
highlight_color: [u8; 4],
|
||||
highlight_opacity: f32,
|
||||
shadow_blend: String,
|
||||
shadow_color: [u8; 4],
|
||||
shadow_opacity: f32,
|
||||
contour: Option<ContourCurve>,
|
||||
},
|
||||
Satin {
|
||||
enabled: bool,
|
||||
blend_mode: String,
|
||||
color: [u8; 4],
|
||||
opacity: f32,
|
||||
angle: f32,
|
||||
distance: f32,
|
||||
size: f32,
|
||||
invert: bool,
|
||||
contour: Option<ContourCurve>,
|
||||
},
|
||||
ColorOverlay {
|
||||
enabled: bool,
|
||||
blend_mode: String,
|
||||
color: [u8; 4],
|
||||
opacity: f32,
|
||||
},
|
||||
GradientOverlay {
|
||||
enabled: bool,
|
||||
blend_mode: String,
|
||||
opacity: f32,
|
||||
angle: f32,
|
||||
scale: f32,
|
||||
gradient: GradientDef,
|
||||
},
|
||||
PatternOverlay {
|
||||
enabled: bool,
|
||||
blend_mode: String,
|
||||
opacity: f32,
|
||||
scale: f32,
|
||||
pattern_id: String,
|
||||
},
|
||||
Stroke {
|
||||
enabled: bool,
|
||||
position: StrokePosition,
|
||||
fill_type: StrokeFillType,
|
||||
blend_mode: String,
|
||||
opacity: f32,
|
||||
size: f32,
|
||||
color: [u8; 4],
|
||||
},
|
||||
}
|
||||
|
||||
impl LayerEffect {
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
match self {
|
||||
Self::DropShadow { enabled, .. }
|
||||
| Self::InnerShadow { enabled, .. }
|
||||
| Self::OuterGlow { enabled, .. }
|
||||
| Self::InnerGlow { enabled, .. }
|
||||
| Self::BevelEmboss { enabled, .. }
|
||||
| Self::Satin { enabled, .. }
|
||||
| Self::ColorOverlay { enabled, .. }
|
||||
| Self::GradientOverlay { enabled, .. }
|
||||
| Self::PatternOverlay { enabled, .. }
|
||||
| Self::Stroke { enabled, .. } => *enabled,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn effect_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::DropShadow { .. } => "Drop Shadow",
|
||||
Self::InnerShadow { .. } => "Inner Shadow",
|
||||
Self::OuterGlow { .. } => "Outer Glow",
|
||||
Self::InnerGlow { .. } => "Inner Glow",
|
||||
Self::BevelEmboss { .. } => "Bevel & Emboss",
|
||||
Self::Satin { .. } => "Satin",
|
||||
Self::ColorOverlay { .. } => "Color Overlay",
|
||||
Self::GradientOverlay { .. } => "Gradient Overlay",
|
||||
Self::PatternOverlay { .. } => "Pattern Overlay",
|
||||
Self::Stroke { .. } => "Stroke",
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,767 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tool — Application tools
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
|
||||
pub enum Tool {
|
||||
#[default]
|
||||
Pen,
|
||||
Brush,
|
||||
Eraser,
|
||||
Spray,
|
||||
FloodFill,
|
||||
Eyedropper,
|
||||
VectorLine,
|
||||
VectorRect,
|
||||
VectorCircle,
|
||||
VectorArrow,
|
||||
VectorStar,
|
||||
VectorPolygon,
|
||||
VectorRhombus,
|
||||
VectorCylinder,
|
||||
VectorHeart,
|
||||
VectorBubble,
|
||||
VectorGear,
|
||||
VectorCross,
|
||||
VectorCrescent,
|
||||
VectorBolt,
|
||||
VectorArrow4,
|
||||
VectorSelect,
|
||||
Text,
|
||||
Select,
|
||||
MagicWand,
|
||||
Lasso,
|
||||
PolygonSelect,
|
||||
Gradient,
|
||||
Move,
|
||||
Crop,
|
||||
RedEyeRemoval,
|
||||
SpotRemoval,
|
||||
SmartPatch,
|
||||
AiObjectRemoval,
|
||||
SmartSelect,
|
||||
VisionSelect,
|
||||
}
|
||||
|
||||
impl Tool {
|
||||
pub const ALL: &[Tool] = &[
|
||||
Tool::Pen, Tool::Brush, Tool::Eraser, Tool::Spray,
|
||||
Tool::FloodFill, Tool::Eyedropper,
|
||||
Tool::VectorLine, Tool::VectorRect, Tool::VectorCircle,
|
||||
Tool::VectorArrow, Tool::VectorStar, Tool::VectorPolygon,
|
||||
Tool::VectorRhombus, Tool::VectorCylinder, Tool::VectorHeart,
|
||||
Tool::VectorBubble, Tool::VectorGear, Tool::VectorCross,
|
||||
Tool::VectorCrescent, Tool::VectorBolt, Tool::VectorArrow4,
|
||||
Tool::VectorSelect,
|
||||
Tool::Text,
|
||||
Tool::Select, Tool::MagicWand, Tool::Lasso, Tool::PolygonSelect,
|
||||
Tool::Gradient, Tool::Move, Tool::Crop,
|
||||
Tool::RedEyeRemoval, Tool::SpotRemoval, Tool::SmartPatch,
|
||||
Tool::AiObjectRemoval, Tool::SmartSelect, Tool::VisionSelect,
|
||||
];
|
||||
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
Tool::Pen => "Pen",
|
||||
Tool::Brush => "Brush",
|
||||
Tool::Eraser => "Eraser",
|
||||
Tool::Spray => "Spray",
|
||||
Tool::FloodFill => "Flood Fill",
|
||||
Tool::Eyedropper => "Eyedropper",
|
||||
Tool::VectorLine => "Line",
|
||||
Tool::VectorRect => "Rectangle",
|
||||
Tool::VectorCircle => "Circle",
|
||||
Tool::VectorArrow => "Arrow",
|
||||
Tool::VectorStar => "Star",
|
||||
Tool::VectorPolygon => "Polygon",
|
||||
Tool::VectorRhombus => "Rhombus",
|
||||
Tool::VectorCylinder => "Cylinder",
|
||||
Tool::VectorHeart => "Heart",
|
||||
Tool::VectorBubble => "Speech Bubble",
|
||||
Tool::VectorGear => "Gear",
|
||||
Tool::VectorCross => "Cross",
|
||||
Tool::VectorCrescent => "Crescent",
|
||||
Tool::VectorBolt => "Bolt",
|
||||
Tool::VectorArrow4 => "4-Way Arrow",
|
||||
Tool::VectorSelect => "Vector Select",
|
||||
Tool::Text => "Text",
|
||||
Tool::Select => "Select",
|
||||
Tool::MagicWand => "Magic Wand",
|
||||
Tool::Lasso => "Lasso",
|
||||
Tool::PolygonSelect => "Polygon Select",
|
||||
Tool::Gradient => "Gradient",
|
||||
Tool::Move => "Move",
|
||||
Tool::Crop => "Crop",
|
||||
Tool::RedEyeRemoval => "Red-eye Removal",
|
||||
Tool::SpotRemoval => "Spot Removal",
|
||||
Tool::SmartPatch => "Smart Patch",
|
||||
Tool::AiObjectRemoval => "AI Object Removal",
|
||||
Tool::SmartSelect => "Smart Select",
|
||||
Tool::VisionSelect => "Vision Tools",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icon(self) -> &'static str {
|
||||
match self {
|
||||
Tool::Pen => "✏",
|
||||
Tool::Brush => "🖌",
|
||||
Tool::Eraser => "◻",
|
||||
Tool::Spray => "💨",
|
||||
Tool::FloodFill => "🪣",
|
||||
Tool::Eyedropper => "🔍",
|
||||
Tool::VectorLine => "/",
|
||||
Tool::VectorRect => "□",
|
||||
Tool::VectorCircle => "○",
|
||||
Tool::VectorArrow => "↗",
|
||||
Tool::VectorStar => "⭐",
|
||||
Tool::VectorPolygon => "⬡",
|
||||
Tool::VectorRhombus => "◇",
|
||||
Tool::VectorCylinder => "⛃",
|
||||
Tool::VectorHeart => "❤",
|
||||
Tool::VectorBubble => "💬",
|
||||
Tool::VectorGear => "⚙",
|
||||
Tool::VectorCross => "✚",
|
||||
Tool::VectorCrescent => "🌙",
|
||||
Tool::VectorBolt => "⚡",
|
||||
Tool::VectorArrow4 => "☩",
|
||||
Tool::VectorSelect => "↖",
|
||||
Tool::Text => "T",
|
||||
Tool::Select => "▣",
|
||||
Tool::MagicWand => "🪄",
|
||||
Tool::Lasso => "➰",
|
||||
Tool::PolygonSelect => "polygon",
|
||||
Tool::Gradient => "🌈",
|
||||
Tool::Move => "✥",
|
||||
Tool::Crop => "⊡",
|
||||
Tool::RedEyeRemoval => "👁",
|
||||
Tool::SpotRemoval => "🩹",
|
||||
Tool::SmartPatch => "✨",
|
||||
Tool::AiObjectRemoval => "🪄",
|
||||
Tool::SmartSelect => "🎯",
|
||||
Tool::VisionSelect => "👁",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_raster(self) -> bool {
|
||||
matches!(self, Tool::Pen | Tool::Brush | Tool::Eraser | Tool::FloodFill | Tool::Spray)
|
||||
}
|
||||
|
||||
pub fn is_ai_tool(self) -> bool {
|
||||
matches!(self, Tool::SmartPatch | Tool::AiObjectRemoval | Tool::SmartSelect | Tool::VisionSelect)
|
||||
}
|
||||
|
||||
pub fn is_raster_compatible(self) -> bool {
|
||||
self.is_raster() || self.is_ai_tool() || matches!(self, Tool::SpotRemoval | Tool::RedEyeRemoval)
|
||||
}
|
||||
|
||||
pub fn allowed_layer_type(self) -> Option<super::LayerType> {
|
||||
if self.is_raster() || self == Tool::Gradient || self.is_ai_tool() {
|
||||
Some(super::LayerType::Raster)
|
||||
} else if self.is_shape_tool() {
|
||||
Some(super::LayerType::Vector)
|
||||
} else if self == Tool::Text {
|
||||
Some(super::LayerType::Text)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shows_pen_tips(self) -> bool {
|
||||
matches!(self, Tool::Pen | Tool::Brush | Tool::Eraser | Tool::Spray)
|
||||
}
|
||||
|
||||
pub fn is_selection_tool(self) -> bool {
|
||||
matches!(self, Tool::Select | Tool::MagicWand | Tool::Lasso | Tool::SmartSelect | Tool::VisionSelect)
|
||||
}
|
||||
|
||||
pub fn is_vector_select(self) -> bool {
|
||||
self == Tool::VectorSelect
|
||||
}
|
||||
|
||||
pub fn is_shape_tool(self) -> bool {
|
||||
matches!(self,
|
||||
Tool::VectorLine | Tool::VectorRect | Tool::VectorCircle |
|
||||
Tool::VectorArrow | Tool::VectorStar |
|
||||
Tool::VectorPolygon | Tool::VectorRhombus | Tool::VectorCylinder |
|
||||
Tool::VectorHeart | Tool::VectorBubble | Tool::VectorGear |
|
||||
Tool::VectorCross | Tool::VectorCrescent | Tool::VectorBolt |
|
||||
Tool::VectorArrow4
|
||||
)
|
||||
}
|
||||
|
||||
pub fn has_separator_before(self) -> bool {
|
||||
matches!(self, Tool::FloodFill | Tool::VectorLine | Tool::Text | Tool::Select | Tool::Gradient | Tool::RedEyeRemoval | Tool::SmartPatch)
|
||||
}
|
||||
|
||||
pub fn is_vector(self) -> bool {
|
||||
self.is_shape_tool() || self == Tool::VectorSelect
|
||||
}
|
||||
|
||||
pub fn has_size(self) -> bool {
|
||||
matches!(self, Tool::Pen | Tool::Brush | Tool::Eraser | Tool::Spray | Tool::SpotRemoval)
|
||||
}
|
||||
|
||||
pub fn has_opacity(self) -> bool {
|
||||
matches!(self, Tool::Pen | Tool::Brush | Tool::Eraser | Tool::Spray | Tool::Gradient)
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// BrushStyle
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
|
||||
pub enum BrushStyle {
|
||||
#[default]
|
||||
Default,
|
||||
Round,
|
||||
Square,
|
||||
HardRound,
|
||||
SoftRound,
|
||||
Noise,
|
||||
Texture,
|
||||
Spray,
|
||||
Pen,
|
||||
Oil,
|
||||
Charcoal,
|
||||
Leaf,
|
||||
Rock,
|
||||
Meadow,
|
||||
Wood,
|
||||
Watercolor,
|
||||
Calligraphy,
|
||||
Marker,
|
||||
Sketch,
|
||||
Hatch,
|
||||
Star,
|
||||
Glow,
|
||||
Airbrush,
|
||||
Pencil,
|
||||
Crayon,
|
||||
WetPaint,
|
||||
InkPen,
|
||||
Clouds,
|
||||
Dirt,
|
||||
Tree,
|
||||
Bristle,
|
||||
Mixer,
|
||||
Blender,
|
||||
Bitmap,
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// FilterType
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum FilterType {
|
||||
BoxBlur,
|
||||
GaussianBlur,
|
||||
MotionBlur,
|
||||
UnsharpMask,
|
||||
Mosaic,
|
||||
Pinch,
|
||||
Twirl,
|
||||
OilPaint,
|
||||
Crystallize,
|
||||
Levels,
|
||||
Vibrance,
|
||||
Exposure,
|
||||
Posterize,
|
||||
Threshold,
|
||||
ChannelMixer,
|
||||
BlackAndWhite,
|
||||
SelectiveColor,
|
||||
GammaCorrection,
|
||||
ExtractChannel,
|
||||
GradientMap,
|
||||
GaussianBlurGamma,
|
||||
BoxBlurGamma,
|
||||
MedianFilter,
|
||||
Dehaze,
|
||||
NoisePattern,
|
||||
}
|
||||
|
||||
impl Default for FilterType {
|
||||
fn default() -> Self { FilterType::GaussianBlur }
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Config structs
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PenConfig {
|
||||
pub size: f32,
|
||||
pub opacity: f32,
|
||||
pub hardness: f32,
|
||||
pub blend_mode: super::BlendMode,
|
||||
}
|
||||
|
||||
impl Default for PenConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
size: 10.0,
|
||||
opacity: 1.0,
|
||||
hardness: 1.0,
|
||||
blend_mode: super::BlendMode::Normal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BrushConfig {
|
||||
pub size: f32,
|
||||
pub opacity: f32,
|
||||
pub hardness: f32,
|
||||
pub flow: f32,
|
||||
pub spacing: f32,
|
||||
pub style: BrushStyle,
|
||||
pub blend_mode: super::BlendMode,
|
||||
pub cyclic_color: bool,
|
||||
pub cyclic_speed: f32,
|
||||
pub color_variant: bool,
|
||||
pub variant_amount: f32,
|
||||
pub density: f32,
|
||||
pub jitter_amount: f32,
|
||||
pub scatter_amount: f32,
|
||||
pub angle: f32,
|
||||
pub roundness: f32,
|
||||
pub drawing_angle: bool,
|
||||
pub rotation_random: f32,
|
||||
pub time_enabled: bool,
|
||||
pub time_size_start: f32,
|
||||
pub time_size_end: f32,
|
||||
pub time_opacity_start: f32,
|
||||
pub time_opacity_end: f32,
|
||||
pub time_angle_start: f32,
|
||||
pub time_angle_end: f32,
|
||||
}
|
||||
|
||||
impl Default for BrushConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
size: 20.0,
|
||||
opacity: 1.0,
|
||||
hardness: 0.8,
|
||||
flow: 1.0,
|
||||
spacing: 0.25,
|
||||
style: BrushStyle::Default,
|
||||
blend_mode: super::BlendMode::Normal,
|
||||
cyclic_color: false,
|
||||
cyclic_speed: 0.5,
|
||||
color_variant: false,
|
||||
variant_amount: 0.0,
|
||||
density: 1.0,
|
||||
jitter_amount: 0.0,
|
||||
scatter_amount: 0.0,
|
||||
angle: 0.0,
|
||||
roundness: 1.0,
|
||||
drawing_angle: false,
|
||||
rotation_random: 0.0,
|
||||
time_enabled: false,
|
||||
time_size_start: 1.0,
|
||||
time_size_end: 1.0,
|
||||
time_opacity_start: 1.0,
|
||||
time_opacity_end: 1.0,
|
||||
time_angle_start: 0.0,
|
||||
time_angle_end: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct EraserConfig {
|
||||
pub size: f32,
|
||||
pub opacity: f32,
|
||||
pub hardness: f32,
|
||||
}
|
||||
|
||||
impl Default for EraserConfig {
|
||||
fn default() -> Self {
|
||||
Self { size: 20.0, opacity: 1.0, hardness: 1.0 }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SprayConfig {
|
||||
pub radius: f32,
|
||||
pub dot_size: f32,
|
||||
pub density: u32,
|
||||
pub opacity: f32,
|
||||
pub hardness: f32,
|
||||
}
|
||||
|
||||
impl Default for SprayConfig {
|
||||
fn default() -> Self {
|
||||
Self { radius: 50.0, dot_size: 2.0, density: 100, opacity: 1.0, hardness: 0.3 }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FloodFillConfig {
|
||||
pub tolerance: u8,
|
||||
}
|
||||
|
||||
impl Default for FloodFillConfig {
|
||||
fn default() -> Self { Self { tolerance: 32 } }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MagicWandConfig {
|
||||
pub tolerance: u8,
|
||||
}
|
||||
|
||||
impl Default for MagicWandConfig {
|
||||
fn default() -> Self { Self { tolerance: 32 } }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VectorConfig {
|
||||
pub stroke_size: f32,
|
||||
pub opacity: f32,
|
||||
pub fill_shape: bool,
|
||||
pub round_rect_radius: f32,
|
||||
pub hardness: f32,
|
||||
pub blend_mode: super::BlendMode,
|
||||
pub line_cap: super::LineCap,
|
||||
pub star_points: u32,
|
||||
pub star_inner_radius: f32,
|
||||
pub polygon_sides: u32,
|
||||
}
|
||||
|
||||
impl Default for VectorConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
stroke_size: 2.0,
|
||||
opacity: 1.0,
|
||||
fill_shape: false,
|
||||
round_rect_radius: 0.0,
|
||||
hardness: 0.8,
|
||||
blend_mode: super::BlendMode::Normal,
|
||||
line_cap: super::LineCap::Round,
|
||||
star_points: 5,
|
||||
star_inner_radius: 0.5,
|
||||
polygon_sides: 6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Horizontal alignment of a text layer.
|
||||
///
|
||||
/// Determines how multi-line text is anchored relative to the layer origin.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub enum TextAlignment {
|
||||
#[default]
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
Justify,
|
||||
}
|
||||
|
||||
impl TextAlignment {
|
||||
pub const ALL: &[TextAlignment] = &[
|
||||
TextAlignment::Left,
|
||||
TextAlignment::Center,
|
||||
TextAlignment::Right,
|
||||
TextAlignment::Justify,
|
||||
];
|
||||
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
TextAlignment::Left => "Left",
|
||||
TextAlignment::Center => "Center",
|
||||
TextAlignment::Right => "Right",
|
||||
TextAlignment::Justify => "Justify",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Text orientation controls whether glyphs are laid out horizontally
|
||||
/// or stacked vertically.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub enum TextOrientation {
|
||||
#[default]
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
impl TextOrientation {
|
||||
pub const ALL: &[TextOrientation] = &[
|
||||
TextOrientation::Horizontal,
|
||||
TextOrientation::Vertical,
|
||||
];
|
||||
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
TextOrientation::Horizontal => "Horizontal",
|
||||
TextOrientation::Vertical => "Vertical",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Non-destructive effect that can be applied to a text layer.
|
||||
///
|
||||
/// These effects are stored as editable parameters and rasterized only
|
||||
/// during compositing, so the original text, font and size remain intact.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum TextEffect {
|
||||
/// 2D rotation around the layer origin in degrees.
|
||||
Rotation { angle: f32 },
|
||||
/// Simple planar warp: kind is a stable identifier (e.g. "arc",
|
||||
/// "bulge", "flag") and amount controls intensity.
|
||||
Warp { kind: String, amount: f32 },
|
||||
/// 3D extrusion parameters. Depth and light direction are preserved
|
||||
/// as metadata so the effect can be re-rendered with different values.
|
||||
Extrude3D { depth: f32, angle: f32, light: [f32; 3] },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TextToolConfig {
|
||||
pub size: f32,
|
||||
pub font: String,
|
||||
pub color: [u8; 4],
|
||||
pub angle: f32,
|
||||
pub alignment: TextAlignment,
|
||||
pub orientation: TextOrientation,
|
||||
}
|
||||
|
||||
impl Default for TextToolConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
size: 24.0,
|
||||
font: "default".to_string(),
|
||||
color: [0, 0, 0, 255],
|
||||
angle: 0.0,
|
||||
alignment: TextAlignment::default(),
|
||||
orientation: TextOrientation::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub enum GradientType {
|
||||
#[default]
|
||||
Linear,
|
||||
Radial,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct GradientConfig {
|
||||
pub gradient_type: GradientType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FilterConfig {
|
||||
pub filter_type: FilterType,
|
||||
pub radius: f32,
|
||||
pub sigma: f32,
|
||||
pub amount: f32,
|
||||
pub angle: f32,
|
||||
pub distance: u32,
|
||||
pub cell_size: u32,
|
||||
}
|
||||
|
||||
impl Default for FilterConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
filter_type: FilterType::GaussianBlur,
|
||||
radius: 3.0,
|
||||
sigma: 2.0,
|
||||
amount: 1.0,
|
||||
angle: 0.0,
|
||||
distance: 10,
|
||||
cell_size: 10,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum AdjTab { HueSat, BrightnessContrast }
|
||||
|
||||
impl Default for AdjTab {
|
||||
fn default() -> Self { AdjTab::HueSat }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AdjustmentConfig {
|
||||
pub hue_shift: f32,
|
||||
pub saturation: f32,
|
||||
pub lightness: f32,
|
||||
pub brightness: f32,
|
||||
pub contrast: f32,
|
||||
pub tab: AdjTab,
|
||||
}
|
||||
|
||||
impl Default for AdjustmentConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
hue_shift: 0.0,
|
||||
saturation: 1.0,
|
||||
lightness: 0.0,
|
||||
brightness: 0.0,
|
||||
contrast: 1.0,
|
||||
tab: AdjTab::HueSat,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SmartSelectConfig {
|
||||
pub use_ai: bool,
|
||||
pub tolerance: i32,
|
||||
}
|
||||
|
||||
impl Default for SmartSelectConfig {
|
||||
fn default() -> Self { Self { use_ai: false, tolerance: 32 } }
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// ToolConfigs — per-tool isolated configuration
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct ToolConfigs {
|
||||
pub pen: PenConfig,
|
||||
pub brush: BrushConfig,
|
||||
pub eraser: EraserConfig,
|
||||
pub spray: SprayConfig,
|
||||
pub flood_fill: FloodFillConfig,
|
||||
pub magic_wand: MagicWandConfig,
|
||||
pub vector: VectorConfig,
|
||||
pub text: TextToolConfig,
|
||||
pub gradient: GradientConfig,
|
||||
pub smart_select: SmartSelectConfig,
|
||||
pub filter: FilterConfig,
|
||||
pub adjustment: AdjustmentConfig,
|
||||
pub shared: SharedConfig,
|
||||
}
|
||||
|
||||
/// Shared tool configuration (chain/link mechanism).
|
||||
///
|
||||
/// Purpose: Allows linking size, opacity, and hardness across tools.
|
||||
/// When a tool is in the corresponding HashSet, that tool reads/writes
|
||||
/// the shared global value instead of its own per-tool config value.
|
||||
///
|
||||
/// Logic: Each HashSet contains Tool variants that are "linked" for
|
||||
/// that property. UI toggle buttons (🔗/🔓) add/remove from these sets.
|
||||
/// Accessor methods (active_size, active_opacity, active_hardness)
|
||||
/// check the set membership and route to shared or per-tool value.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SharedConfig {
|
||||
pub size: f32,
|
||||
pub opacity: f32,
|
||||
pub hardness: f32,
|
||||
pub use_shared_size: std::collections::HashSet<Tool>,
|
||||
pub use_shared_opacity: std::collections::HashSet<Tool>,
|
||||
pub use_shared_hardness: std::collections::HashSet<Tool>,
|
||||
}
|
||||
|
||||
impl Default for SharedConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
size: 20.0,
|
||||
opacity: 1.0,
|
||||
hardness: 1.0,
|
||||
use_shared_size: std::collections::HashSet::new(),
|
||||
use_shared_opacity: std::collections::HashSet::new(),
|
||||
use_shared_hardness: std::collections::HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolConfigs {
|
||||
/// Get the active size for the current tool, considering shared config.
|
||||
pub fn active_size(&self, tool: Tool) -> f32 {
|
||||
if self.shared.use_shared_size.contains(&tool) {
|
||||
return self.shared.size;
|
||||
}
|
||||
match tool {
|
||||
Tool::Pen => self.pen.size,
|
||||
Tool::Brush => self.brush.size,
|
||||
Tool::Eraser => self.eraser.size,
|
||||
Tool::Spray => self.spray.radius,
|
||||
t if t.is_shape_tool() => self.vector.stroke_size,
|
||||
Tool::Text => self.text.size,
|
||||
_ => self.brush.size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get mutable reference to the active size, writing through to shared if linked.
|
||||
pub fn active_size_mut(&mut self, tool: Tool) -> &mut f32 {
|
||||
if self.shared.use_shared_size.contains(&tool) {
|
||||
return &mut self.shared.size;
|
||||
}
|
||||
match tool {
|
||||
Tool::Pen => &mut self.pen.size,
|
||||
Tool::Brush => &mut self.brush.size,
|
||||
Tool::Eraser => &mut self.eraser.size,
|
||||
Tool::Spray => &mut self.spray.radius,
|
||||
t if t.is_shape_tool() => &mut self.vector.stroke_size,
|
||||
Tool::Text => &mut self.text.size,
|
||||
_ => &mut self.brush.size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the active opacity for the current tool, considering shared config.
|
||||
pub fn active_opacity(&self, tool: Tool) -> f32 {
|
||||
if self.shared.use_shared_opacity.contains(&tool) {
|
||||
return self.shared.opacity;
|
||||
}
|
||||
match tool {
|
||||
Tool::Pen => self.pen.opacity,
|
||||
Tool::Brush => self.brush.opacity,
|
||||
Tool::Eraser => self.eraser.opacity,
|
||||
Tool::Spray => self.spray.opacity,
|
||||
t if t.is_shape_tool() => self.vector.opacity,
|
||||
_ => self.brush.opacity,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get mutable reference to the active opacity, writing through to shared if linked.
|
||||
pub fn active_opacity_mut(&mut self, tool: Tool) -> &mut f32 {
|
||||
if self.shared.use_shared_opacity.contains(&tool) {
|
||||
return &mut self.shared.opacity;
|
||||
}
|
||||
match tool {
|
||||
Tool::Pen => &mut self.pen.opacity,
|
||||
Tool::Brush => &mut self.brush.opacity,
|
||||
Tool::Eraser => &mut self.eraser.opacity,
|
||||
Tool::Spray => &mut self.spray.opacity,
|
||||
t if t.is_shape_tool() => &mut self.vector.opacity,
|
||||
_ => &mut self.brush.opacity,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the active hardness for the current tool, considering shared config.
|
||||
pub fn active_hardness(&self, tool: Tool) -> f32 {
|
||||
if self.shared.use_shared_hardness.contains(&tool) {
|
||||
return self.shared.hardness;
|
||||
}
|
||||
match tool {
|
||||
Tool::Pen => self.pen.hardness,
|
||||
Tool::Brush => self.brush.hardness,
|
||||
Tool::Eraser => self.eraser.hardness,
|
||||
Tool::Spray => self.spray.hardness,
|
||||
t if t.is_shape_tool() => self.vector.hardness,
|
||||
_ => self.brush.hardness,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get mutable reference to the active hardness, writing through to shared if linked.
|
||||
pub fn active_hardness_mut(&mut self, tool: Tool) -> &mut f32 {
|
||||
if self.shared.use_shared_hardness.contains(&tool) {
|
||||
return &mut self.shared.hardness;
|
||||
}
|
||||
match tool {
|
||||
Tool::Pen => &mut self.pen.hardness,
|
||||
Tool::Brush => &mut self.brush.hardness,
|
||||
Tool::Eraser => &mut self.eraser.hardness,
|
||||
Tool::Spray => &mut self.spray.hardness,
|
||||
t if t.is_shape_tool() => &mut self.vector.hardness,
|
||||
_ => &mut self.brush.hardness,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
use hcie_protocol::{BlendMode, Tool};
|
||||
|
||||
#[test]
|
||||
fn test_to_u32_from_u32_roundtrip() {
|
||||
let colors = [
|
||||
[0, 0, 0, 0],
|
||||
[255, 255, 255, 255],
|
||||
[128, 64, 32, 200],
|
||||
[1, 2, 3, 4],
|
||||
[255, 0, 0, 128],
|
||||
];
|
||||
for &c in &colors {
|
||||
let packed = hcie_protocol::colors::to_u32(c);
|
||||
let unpacked = hcie_protocol::colors::from_u32(packed);
|
||||
assert_eq!(c, unpacked, "round-trip failed for {:?}", c);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_color_constants() {
|
||||
assert_eq!(hcie_protocol::colors::TRANSPARENT, [0, 0, 0, 0]);
|
||||
assert_eq!(hcie_protocol::colors::BLACK, [0, 0, 0, 255]);
|
||||
assert_eq!(hcie_protocol::colors::WHITE, [255, 255, 255, 255]);
|
||||
assert_eq!(hcie_protocol::colors::RED, [255, 0, 0, 255]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lerp_boundaries() {
|
||||
assert_eq!(hcie_protocol::lerp(0.0, 100.0, 0.0), 0.0);
|
||||
assert_eq!(hcie_protocol::lerp(0.0, 100.0, 1.0), 100.0);
|
||||
assert_eq!(hcie_protocol::lerp(0.0, 100.0, 0.5), 50.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_color_lerp() {
|
||||
let a = [0, 0, 0, 0];
|
||||
let b = [100, 200, 50, 255];
|
||||
let mid = hcie_protocol::colors::lerp(a, b, 0.5);
|
||||
assert_eq!(mid[0], 50);
|
||||
assert_eq!(mid[1], 100);
|
||||
assert_eq!(mid[2], 25);
|
||||
assert_eq!(mid[3], 128);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lerp_clamp() {
|
||||
assert_eq!(hcie_protocol::lerp(0.0, 100.0, -0.5), 0.0);
|
||||
assert_eq!(hcie_protocol::lerp(0.0, 100.0, 1.5), 100.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clamp() {
|
||||
assert_eq!(hcie_protocol::clamp(5.0, 0.0, 10.0), 5.0);
|
||||
assert_eq!(hcie_protocol::clamp(-5.0, 0.0, 10.0), 0.0);
|
||||
assert_eq!(hcie_protocol::clamp(15.0, 0.0, 10.0), 10.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_distance() {
|
||||
assert_eq!(hcie_protocol::distance(0.0, 0.0, 3.0, 4.0), 5.0);
|
||||
assert_eq!(hcie_protocol::distance(1.0, 1.0, 1.0, 1.0), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_point_in_rect() {
|
||||
assert!(hcie_protocol::point_in_rect(5.0, 5.0, 0.0, 0.0, 10.0, 10.0));
|
||||
assert!(!hcie_protocol::point_in_rect(15.0, 5.0, 0.0, 0.0, 10.0, 10.0));
|
||||
assert!(hcie_protocol::point_in_rect(5.0, 5.0, 10.0, 10.0, 0.0, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_thumbnail_nearest() {
|
||||
let input = vec![
|
||||
255, 0, 0, 255, 0, 255, 0, 255,
|
||||
0, 0, 255, 255, 255, 255, 0, 255,
|
||||
];
|
||||
let thumb = hcie_protocol::thumbnail_nearest(&input, 2, 1, 1, 1);
|
||||
assert_eq!(thumb.len(), 4, "should produce 1x1 RGBA");
|
||||
assert_eq!(thumb[3], 255, "alpha should be 255");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_selection_rect_from_mask() {
|
||||
let mask = vec![
|
||||
0, 0, 0, 0,
|
||||
0, 255, 255, 0,
|
||||
0, 255, 255, 0,
|
||||
0, 0, 0, 0,
|
||||
];
|
||||
let rect = hcie_protocol::selection_rect_from_mask(&mask, 4, 4);
|
||||
assert_eq!(rect, Some([1, 1, 2, 2]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blank_mask_no_selection() {
|
||||
let mask = vec![0u8; 16];
|
||||
assert!(hcie_protocol::selection_rect_from_mask(&mask, 4, 4).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blend_mode_all_contains_all() {
|
||||
assert_eq!(BlendMode::ALL.len(), 28, "should have 28 blend modes");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_tools_listed() {
|
||||
assert_eq!(Tool::ALL.len(), 36, "should have 36 tools");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expand_dirty_bounds() {
|
||||
let mut bounds = None;
|
||||
hcie_protocol::expand_dirty_bounds(&mut bounds, 10, 10, 5.0, 100, 100);
|
||||
assert_eq!(bounds, Some([5, 5, 16, 16]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_version_string_format() {
|
||||
let v = hcie_protocol::version_string();
|
||||
assert!(v.starts_with("3."), "version should start with 3.x.x");
|
||||
}
|
||||
|
||||
proptest::proptest! {
|
||||
#[test]
|
||||
fn from_u32_to_u32_roundtrip(r: u8, g: u8, b: u8, a: u8) {
|
||||
let rgba = [r, g, b, a];
|
||||
let packed = hcie_protocol::colors::to_u32(rgba);
|
||||
let unpacked = hcie_protocol::colors::from_u32(packed);
|
||||
proptest::prop_assert_eq!(rgba, unpacked);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user