mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Add the Image Color Palette node (#1311)
* Add image color palette node * Add max size of palette * Code review cleanup --------- Co-authored-by: 0hypercube <0hypercube@gmail.com> Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -617,6 +617,26 @@ impl GraphicElementRendered for Option<Color> {
|
||||
fn add_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
|
||||
}
|
||||
|
||||
impl GraphicElementRendered for Vec<Color> {
|
||||
fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) {
|
||||
for (index, &color) in self.iter().enumerate() {
|
||||
render.leaf_tag("rect", |attributes| {
|
||||
attributes.push("width", "100");
|
||||
attributes.push("height", "100");
|
||||
attributes.push("x", (index * 120).to_string());
|
||||
attributes.push("y", "40");
|
||||
attributes.push("fill", format!("#{}", color.rgba_hex()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_box(&self, _transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
None
|
||||
}
|
||||
|
||||
fn add_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
|
||||
}
|
||||
|
||||
/// A segment of an svg string to allow for embedding blob urls
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SvgSegment {
|
||||
|
||||
@@ -1128,4 +1128,12 @@ mod index_node {
|
||||
ImageFrame::empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[node_macro::node_impl(IndexNode)]
|
||||
pub fn index_node(input: Vec<Color>, index: u32) -> Option<Color> {
|
||||
if index as usize >= input.len() {
|
||||
warn!("Index of colors is out of range: index is {index} and length is {}", input.len());
|
||||
}
|
||||
input.into_iter().nth(index as usize)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ pub enum TaggedValue {
|
||||
SurfaceFrame(graphene_core::SurfaceFrame),
|
||||
Footprint(graphene_core::transform::Footprint),
|
||||
RenderOutput(RenderOutput),
|
||||
Palette(Vec<Color>),
|
||||
}
|
||||
|
||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||
@@ -138,6 +139,7 @@ impl Hash for TaggedValue {
|
||||
Self::SurfaceFrame(surface_id) => surface_id.hash(state),
|
||||
Self::Footprint(footprint) => footprint.hash(state),
|
||||
Self::RenderOutput(render_output) => render_output.hash(state),
|
||||
Self::Palette(palette) => palette.hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,6 +198,7 @@ impl<'a> TaggedValue {
|
||||
TaggedValue::SurfaceFrame(x) => Box::new(x),
|
||||
TaggedValue::Footprint(x) => Box::new(x),
|
||||
TaggedValue::RenderOutput(x) => Box::new(x),
|
||||
TaggedValue::Palette(x) => Box::new(x),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,6 +268,7 @@ impl<'a> TaggedValue {
|
||||
TaggedValue::SurfaceFrame(_) => concrete!(graphene_core::SurfaceFrame),
|
||||
TaggedValue::Footprint(_) => concrete!(graphene_core::transform::Footprint),
|
||||
TaggedValue::RenderOutput(_) => concrete!(RenderOutput),
|
||||
TaggedValue::Palette(_) => concrete!(Vec<Color>),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,6 +330,7 @@ impl<'a> TaggedValue {
|
||||
Ok(TaggedValue::SurfaceFrame(frame.into()))
|
||||
}
|
||||
x if x == TypeId::of::<graphene_core::transform::Footprint>() => Ok(TaggedValue::Footprint(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<Vec<Color>>() => Ok(TaggedValue::Palette(*downcast(input).unwrap())),
|
||||
_ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))),
|
||||
}
|
||||
}
|
||||
|
||||
83
node-graph/gstd/src/image_color_palette.rs
Normal file
83
node-graph/gstd/src/image_color_palette.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use graphene_core::raster::ImageFrame;
|
||||
use graphene_core::Color;
|
||||
use graphene_core::Node;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ImageColorPaletteNode<MaxSize> {
|
||||
max_size: MaxSize,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(ImageColorPaletteNode)]
|
||||
fn image_color_palette(frame: ImageFrame<Color>, max_size: u32) -> Vec<Color> {
|
||||
const GRID: f32 = 3.0;
|
||||
|
||||
let bins = GRID * GRID * GRID;
|
||||
|
||||
let mut histogram: Vec<usize> = vec![0; (bins + 1.0) as usize];
|
||||
let mut colors: Vec<Vec<Color>> = vec![vec![]; (bins + 1.0) as usize];
|
||||
|
||||
for pixel in frame.image.data.iter() {
|
||||
let r = pixel.r() * GRID;
|
||||
let g = pixel.g() * GRID;
|
||||
let b = pixel.b() * GRID;
|
||||
|
||||
let bin = (r * GRID + g * GRID + b * GRID) as usize;
|
||||
|
||||
histogram[bin] += 1;
|
||||
colors[bin].push(pixel.to_gamma_srgb());
|
||||
}
|
||||
|
||||
let shorted = histogram.iter().enumerate().filter(|(_, &count)| count > 0).map(|(i, _)| i).collect::<Vec<usize>>();
|
||||
|
||||
let mut palette = vec![];
|
||||
|
||||
for i in shorted.iter().take(max_size as usize) {
|
||||
let list = colors[*i].clone();
|
||||
|
||||
let mut r = 0.0;
|
||||
let mut g = 0.0;
|
||||
let mut b = 0.0;
|
||||
let mut a = 0.0;
|
||||
|
||||
for color in list.iter() {
|
||||
r += color.r();
|
||||
g += color.g();
|
||||
b += color.b();
|
||||
a += color.a();
|
||||
}
|
||||
|
||||
r /= list.len() as f32;
|
||||
g /= list.len() as f32;
|
||||
b /= list.len() as f32;
|
||||
a /= list.len() as f32;
|
||||
|
||||
let color = Color::from_rgbaf32(r, g, b, a).unwrap();
|
||||
|
||||
palette.push(color);
|
||||
}
|
||||
|
||||
return palette;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use graphene_core::{raster::Image, value::CopiedNode};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_image_color_palette() {
|
||||
assert_eq!(
|
||||
ImageColorPaletteNode { max_size: CopiedNode(1u32) }.eval(ImageFrame {
|
||||
image: Image {
|
||||
width: 100,
|
||||
height: 100,
|
||||
data: vec![Color::from_rgbaf32(0.0, 0.0, 0.0, 1.0).unwrap(); 10000],
|
||||
},
|
||||
..Default::default()
|
||||
}),
|
||||
[Color::from_rgbaf32(0.0, 0.0, 0.0, 1.0).unwrap()]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,8 @@ pub use graphene_core::*;
|
||||
|
||||
pub mod image_segmentation;
|
||||
|
||||
pub mod image_color_palette;
|
||||
|
||||
pub mod brush;
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
|
||||
@@ -442,9 +442,11 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||
raster_node!(graphene_core::raster::ExtractOpaqueNode<>, params: []),
|
||||
raster_node!(graphene_core::raster::LevelsNode<_, _, _, _, _>, params: [f32, f32, f32, f32, f32]),
|
||||
register_node!(graphene_std::image_segmentation::ImageSegmentationNode<_>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
|
||||
register_node!(graphene_std::image_color_palette::ImageColorPaletteNode<_>, input: ImageFrame<Color>, params: [u32]),
|
||||
register_node!(graphene_core::raster::IndexNode<_>, input: Vec<ImageFrame<Color>>, params: [u32]),
|
||||
register_node!(graphene_core::raster::adjustments::ColorFillNode<_>, input: ImageFrame<Color>, params: [Color]),
|
||||
register_node!(graphene_core::raster::adjustments::ColorOverlayNode<_, _, _>, input: ImageFrame<Color>, params: [Color, BlendMode, f32]),
|
||||
register_node!(graphene_core::raster::IndexNode<_>, input: Vec<Color>, params: [u32]),
|
||||
vec![(
|
||||
ProtoNodeIdentifier::new("graphene_core::raster::BlendNode<_, _, _, _>"),
|
||||
|args| {
|
||||
@@ -561,8 +563,11 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [bool]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [String]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [Option<Color>]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [Vec<Color>]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => VectorData]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => ImageFrame<Color>]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Option<Color>]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Vec<Color>]),
|
||||
async_node!(
|
||||
graphene_core::memo::EndLetNode<_, _>,
|
||||
input: WasmEditorApi,
|
||||
@@ -666,6 +671,9 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => f64, () => Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => String, () => Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => Option<Color>, () => Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Option<Color>, () => Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => Vec<Color>, () => Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Vec<Color>, () => Arc<WasmSurfaceHandle>]),
|
||||
//register_node!(graphene_core::transform::TranformNode<_, _, _, _, _, _>, input: , output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc<WasmSurfaceHandle>]),
|
||||
vec![
|
||||
(
|
||||
|
||||
Reference in New Issue
Block a user