Replace the Color type with Table<Color> everywhere (#3048)
Some checks are pending
Editor: Dev & CI / build (push) Waiting to run
Editor: Dev & CI / cargo-deny (push) Waiting to run

This commit is contained in:
Keavon Chambers 2025-08-12 00:38:23 -07:00 committed by GitHub
parent 437fc70500
commit 1b351aca76
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 306 additions and 386 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -163,8 +163,6 @@ fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'st
Table<Raster<CPU>>,
Table<Raster<GPU>>,
Table<Color>,
Color,
Option<Color>,
f64,
u32,
u64,
@ -487,28 +485,6 @@ impl TableRowLayout for Color {
}
}
impl TableRowLayout for Option<Color> {
fn type_name() -> &'static str {
"Option<Color>"
}
fn identifier(&self) -> String {
format!(
"Option<Color> (#{})",
if let Some(color) = self { color.to_linear_srgb().to_rgba_hex_srgb() } else { "None".to_string() }
)
}
fn element_widget(&self, _index: usize) -> WidgetHolder {
ColorInput::new(if let Some(color) = self { FillChoice::Solid(*color) } else { FillChoice::None })
.disabled(true)
.menu_direction(Some(MenuDirection::Top))
.widget_holder()
}
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
let widgets = vec![self.element_widget(0)];
vec![LayoutGroup::Row { widgets }]
}
}
impl TableRowLayout for f64 {
fn type_name() -> &'static str {
"Number (f64)"

View file

@ -135,7 +135,7 @@ impl<'a> ModifyInputsContext<'a> {
Some(NodeInput::value(TaggedValue::Graphic(Default::default()), true)),
Some(NodeInput::value(TaggedValue::DVec2(artboard.location.into()), false)),
Some(NodeInput::value(TaggedValue::DVec2(artboard.dimensions.into()), false)),
Some(NodeInput::value(TaggedValue::Color(artboard.background), false)),
Some(NodeInput::value(TaggedValue::Color(Table::new_from_element(artboard.background)), false)),
Some(NodeInput::value(TaggedValue::Bool(artboard.clip), false)),
]);
self.network_interface.insert_node(new_id, artboard_node_template, &[]);
@ -329,11 +329,11 @@ impl<'a> ModifyInputsContext<'a> {
match &fill {
Fill::None => {
let input_connector = InputConnector::node(fill_node_id, backup_color_index);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::OptionalColor(None), false), true);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(Table::new()), false), true);
}
Fill::Solid(color) => {
let input_connector = InputConnector::node(fill_node_id, backup_color_index);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::OptionalColor(Some(*color)), false), true);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(Table::new_from_element(*color)), false), true);
}
Fill::Gradient(gradient) => {
let input_connector = InputConnector::node(fill_node_id, backup_gradient_index);
@ -372,8 +372,10 @@ impl<'a> ModifyInputsContext<'a> {
pub fn stroke_set(&mut self, stroke: Stroke) {
let Some(stroke_node_id) = self.existing_node_id("Stroke", true) else { return };
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::ColorInput::<Option<graphene_std::Color>>::INDEX);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::OptionalColor(stroke.color), false), true);
let stroke_color = if let Some(color) = stroke.color { Table::new_from_element(color) } else { Table::new() };
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::ColorInput::INDEX);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(stroke_color), false), true);
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::WeightInput::INDEX);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.weight), false), true);
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::AlignInput::INDEX);

View file

@ -399,7 +399,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::value(TaggedValue::Graphic(Default::default()), true),
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
NodeInput::value(TaggedValue::DVec2(DVec2::new(1920., 1080.)), false),
NodeInput::value(TaggedValue::Color(Color::WHITE), false),
NodeInput::value(TaggedValue::Color(Table::new_from_element(Color::WHITE)), false),
NodeInput::value(TaggedValue::Bool(false), false),
],
..Default::default()

View file

@ -89,7 +89,7 @@ pub struct NodeGraphMessageHandler {
reordering_import: Option<usize>,
/// The index of the export that is being moved
reordering_export: Option<usize>,
/// The end index of the moved port
/// The end index of the moved connector
end_index: Option<usize>,
/// Used to keep track of what nodes are sent to the front end so that only visible ones are sent to the frontend
frontend_nodes: Vec<NodeId>,

View file

@ -20,6 +20,7 @@ use graphene_std::raster::{
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
SelectiveColorChoice,
};
use graphene_std::table::{Table, TableRow};
use graphene_std::text::{Font, TextAlign};
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
use graphene_std::vector::misc::{ArcType, CentroidType, GridType, MergeByDistanceAlgorithm, PointSpacingType};
@ -180,8 +181,7 @@ pub(crate) fn property_from_type(
// ============
// STRUCT TYPES
// ============
Some(x) if x == TypeId::of::<Color>() => color_widget(default_info, ColorInput::default().allow_none(false)),
Some(x) if x == TypeId::of::<Option<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)),
Some(x) if x == TypeId::of::<Table<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)),
Some(x) if x == TypeId::of::<GradientStops>() => color_widget(default_info, ColorInput::default().allow_none(false)),
Some(x) if x == TypeId::of::<Font>() => font_widget(default_info),
Some(x) if x == TypeId::of::<Curve>() => curve_widget(default_info),
@ -908,28 +908,25 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
// Add the color input
match &**tagged_value {
TaggedValue::Color(color) => widgets.push(
TaggedValue::Color(color_table) => widgets.push(
color_button
.value(FillChoice::Solid(*color))
.on_update(update_value(|x: &ColorInput| TaggedValue::Color(x.value.as_solid().unwrap_or_default()), node_id, index))
.on_commit(commit_value)
.widget_holder(),
),
TaggedValue::OptionalColor(color) => widgets.push(
color_button
.value(match color {
Some(color) => FillChoice::Solid(*color),
.value(match color_table.iter().next() {
Some(color) => FillChoice::Solid(*color.element),
None => FillChoice::None,
})
.on_update(update_value(|x: &ColorInput| TaggedValue::OptionalColor(x.value.as_solid()), node_id, index))
.on_update(update_value(
|input: &ColorInput| TaggedValue::Color(input.value.as_solid().iter().map(|&color| TableRow::new_from_element(color)).collect()),
node_id,
index,
))
.on_commit(commit_value)
.widget_holder(),
),
TaggedValue::GradientStops(x) => widgets.push(
TaggedValue::GradientStops(gradient_stops) => widgets.push(
color_button
.value(FillChoice::Gradient(x.clone()))
.value(FillChoice::Gradient(gradient_stops.clone()))
.on_update(update_value(
|x: &ColorInput| TaggedValue::GradientStops(x.value.as_gradient().cloned().unwrap_or_default()),
|input: &ColorInput| TaggedValue::GradientStops(input.value.as_gradient().cloned().unwrap_or_default()),
node_id,
index,
))
@ -1559,7 +1556,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
}
};
let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), &Some(&TaggedValue::OptionalColor(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = (
let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), Some(TaggedValue::Color(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = (
&document_node.inputs[FillInput::<Color>::INDEX].as_value(),
&document_node.inputs[BackupColorInput::INDEX].as_value(),
&document_node.inputs[BackupGradientInput::INDEX].as_value(),
@ -1569,7 +1566,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
return vec![LayoutGroup::Row { widgets: widgets_first_row }];
};
let fill2 = fill.clone();
let backup_color_fill: Fill = backup_color.into();
let backup_color_fill: Fill = backup_color.clone().into();
let backup_gradient_fill: Fill = backup_gradient.clone().into();
widgets_first_row.push(Separator::new(SeparatorType::Unrelated).widget_holder());
@ -1582,13 +1579,13 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
Fill::None => NodeGraphMessage::SetInputValue {
node_id,
input_index: BackupColorInput::INDEX,
value: TaggedValue::OptionalColor(None),
value: TaggedValue::Color(Table::new()),
}
.into(),
Fill::Solid(color) => NodeGraphMessage::SetInputValue {
node_id,
input_index: BackupColorInput::INDEX,
value: TaggedValue::OptionalColor(Some(*color)),
value: TaggedValue::Color(Table::new_from_element(*color)),
}
.into(),
Fill::Gradient(gradient) => NodeGraphMessage::SetInputValue {
@ -1750,7 +1747,7 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
let miter_limit_disabled = join_value != &StrokeJoin::Miter;
let color = color_widget(
ParameterWidgetsInfo::new(node_id, ColorInput::<Option<Color>>::INDEX, true, context),
ParameterWidgetsInfo::new(node_id, ColorInput::INDEX, true, context),
crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(),
);
let weight = number_widget(ParameterWidgetsInfo::new(node_id, WeightInput::INDEX, true, context), NumberInput::default().unit(" px").min(0.));

View file

@ -31,7 +31,7 @@ impl FrontendGraphDataType {
TaggedValue::Graphic(_) => Self::Graphic,
TaggedValue::Raster(_) => Self::Raster,
TaggedValue::Vector(_) => Self::Vector,
TaggedValue::ColorTable(_) | TaggedValue::Color(_) | TaggedValue::OptionalColor(_) => Self::Color,
TaggedValue::Color(_) => Self::Color,
_ => Self::General,
}
}
@ -179,8 +179,8 @@ pub struct FrontendClickTargets {
pub node_click_targets: Vec<String>,
#[serde(rename = "layerClickTargets")]
pub layer_click_targets: Vec<String>,
#[serde(rename = "portClickTargets")]
pub port_click_targets: Vec<String>,
#[serde(rename = "connectorClickTargets")]
pub connector_click_targets: Vec<String>,
#[serde(rename = "iconClickTargets")]
pub icon_click_targets: Vec<String>,
#[serde(rename = "allNodesBoundingBox")]

View file

@ -1287,7 +1287,7 @@ impl NodeNetworkInterface {
let artboard = self.document_node(&artboard_node_identifier.to_node(), &[]);
let clip_input = artboard.unwrap().inputs.get(5).unwrap();
if let NodeInput::Value { tagged_value, .. } = clip_input {
if tagged_value.to_primitive_string() == "true" {
if tagged_value.clone().into_inner() == TaggedValue::Bool(true) {
return Some(Quad::clip(
self.document_metadata.bounding_box_document(layer).unwrap_or_default(),
self.document_metadata.bounding_box_document(artboard_node_identifier).unwrap_or_default(),
@ -3048,7 +3048,7 @@ impl NodeNetworkInterface {
pub fn collect_frontend_click_targets(&mut self, network_path: &[NodeId]) -> FrontendClickTargets {
let mut all_node_click_targets = Vec::new();
let mut port_click_targets = Vec::new();
let mut connector_click_targets = Vec::new();
let mut icon_click_targets = Vec::new();
let Some(network_metadata) = self.network_metadata(network_path) else {
log::error!("Could not get nested network_metadata in collect_frontend_click_targets");
@ -3066,7 +3066,7 @@ impl NodeNetworkInterface {
if let ClickTargetType::Subpath(subpath) = port.target_type() {
let mut port_path = String::new();
let _ = subpath.subpath_to_svg(&mut port_path, DAffine2::IDENTITY);
port_click_targets.push(port_path);
connector_click_targets.push(port_path);
}
}
if let NodeTypeClickTargets::Layer(layer_metadata) = &node_click_targets.node_type_metadata {
@ -3145,7 +3145,7 @@ impl NodeNetworkInterface {
FrontendClickTargets {
node_click_targets,
layer_click_targets,
port_click_targets,
connector_click_targets,
icon_click_targets,
all_nodes_bounding_box,
import_exports_bounding_box,

View file

@ -300,10 +300,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
"graphene_core::raster::BlendNode",
],
},
NodeReplacement {
node: graphene_std::raster_nodes::blending_nodes::blend_color_pair::IDENTIFIER,
aliases: &["graphene_raster_nodes::adjustments::BlendColorPairNode", "graphene_core::raster::BlendColorPairNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::blending_nodes::color_overlay::IDENTIFIER,
aliases: &[
@ -637,9 +633,9 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
};
let vector = Vector::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false));
// Retrieve the output connectors linked to the "Spline" node's output port
// Retrieve the output connectors linked to the "Spline" node's output connector
let Some(spline_outputs) = document.network_interface.outward_wires(network_path)?.get(&OutputConnector::node(*node_id, 0)).cloned() else {
log::error!("Vec of InputConnector Spline node is connected to its output port 0.");
log::error!("Vec of InputConnector Spline node is connected to its output connector 0.");
return None;
};
@ -684,7 +680,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
// Reposition the new "Path" node with an offset relative to the original "Spline" node's position
document.network_interface.shift_node(&new_path_id, node_position + IVec2::new(-7, 0), network_path);
// Redirect each output connection from the old node to the new "Spline" node's output port
// Redirect each output connection from the old node to the new "Spline" node's output connector
for input_connector in spline_outputs {
document.network_interface.set_input(&input_connector, NodeInput::node(new_spline_id, 0), network_path);
}

View file

@ -15,7 +15,7 @@ use graphene_std::raster_types::{CPU, GPU, Raster};
use graphene_std::table::Table;
use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::misc::ManipulatorPointId;
use graphene_std::vector::style::Gradient;
use graphene_std::vector::style::{Fill, Gradient};
use graphene_std::vector::{PointId, SegmentId, VectorModificationType};
use std::collections::VecDeque;
@ -273,7 +273,7 @@ pub fn get_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkI
let fill_index = 1;
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Fill")?;
let TaggedValue::Fill(graphene_std::vector::style::Fill::Gradient(gradient)) = inputs.get(fill_index)?.as_value()? else {
let TaggedValue::Fill(Fill::Gradient(gradient)) = inputs.get(fill_index)?.as_value()? else {
return None;
};
Some(gradient.clone())
@ -284,7 +284,7 @@ pub fn get_fill_color(layer: LayerNodeIdentifier, network_interface: &NodeNetwor
let fill_index = 1;
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Fill")?;
let TaggedValue::Fill(graphene_std::vector::style::Fill::Solid(color)) = inputs.get(fill_index)?.as_value()? else {
let TaggedValue::Fill(Fill::Solid(color)) = inputs.get(fill_index)?.as_value()? else {
return None;
};
Some(color.to_linear_srgb())

View file

@ -387,8 +387,6 @@ impl NodeGraphExecutor {
TaggedValue::F64(render_object) => Self::debug_render(render_object, footprint, responses),
TaggedValue::DVec2(render_object) => Self::debug_render(render_object, footprint, responses),
TaggedValue::String(render_object) => Self::debug_render(render_object, footprint, responses),
TaggedValue::OptionalColor(render_object) => Self::debug_render(render_object, footprint, responses),
TaggedValue::Palette(render_object) => Self::debug_render(render_object, footprint, responses),
_ => return Err(format!("Invalid node graph output type: {node_graph_output:#?}")),
};

View file

@ -117,10 +117,10 @@
--color-data-number-dim: #886b60;
--color-data-artboard: #fbf9eb;
--color-data-artboard-dim: #b9b9a9;
--color-data-graphic: #66b195;
--color-data-graphic-dim: #3d725e;
--color-data-graphic: #68c587;
--color-data-graphic-dim: #37754c;
--color-data-raster: #e4bb72;
--color-data-raster-dim: #8b7752;
--color-data-raster-dim: #9a7b43;
--color-data-vector: #65bbe5;
--color-data-vector-dim: #417892;
--color-data-color: #af81eb;

View file

@ -730,6 +730,7 @@
width: 36px;
height: 24px;
border-radius: 2px;
overflow: hidden;
flex: 0 0 auto;
background-image: var(--color-transparent-checkered-background);
background-size: var(--color-transparent-checkered-background-size-mini);
@ -737,9 +738,8 @@
background-repeat: var(--color-transparent-checkered-background-repeat);
svg {
width: calc(100% - 4px);
height: calc(100% - 4px);
margin: 2px;
width: 100%;
height: 100%;
}
}

View file

@ -32,7 +32,7 @@
// Imports/Export are stored at a key value of 0
$: gridSpacing = calculateGridSpacing($nodeGraph.transform.scale);
$: dotRadius = 1 + Math.floor($nodeGraph.transform.scale - 0.5 + 0.001) / 2;
$: gridDotRadius = 1 + Math.floor($nodeGraph.transform.scale - 0.5 + 0.001) / 2;
let inputElement: HTMLInputElement;
let hoveringImportIndex: number | undefined = undefined;
@ -192,7 +192,7 @@
return output.connectedTo
.map((inputConnector) => {
if ((inputConnector as Node).nodeId === undefined) return `Connected to export index ${inputConnector.index}`;
return `Connected to ${(inputConnector as Node).nodeId}, port index ${inputConnector.index}`;
return `Connected to ${(inputConnector as Node).nodeId}, connector index ${inputConnector.index}`;
})
.join("\n");
}
@ -200,7 +200,7 @@
function inputConnectedToText(input: FrontendGraphInput): string {
if (input.connectedTo === undefined) return "Connected to nothing";
if ((input.connectedTo as Node).nodeId === undefined) return `Connected to import index ${input.connectedTo.index}`;
return `Connected to ${(input.connectedTo as Node).nodeId}, port index ${input.connectedTo.index}`;
return `Connected to ${(input.connectedTo as Node).nodeId}, connector index ${input.connectedTo.index}`;
}
function primaryOutputConnectedToLayer(node: FrontendNode): boolean {
@ -238,7 +238,7 @@
style:--grid-spacing={`${gridSpacing}px`}
style:--grid-offset-x={`${$nodeGraph.transform.x}px`}
style:--grid-offset-y={`${$nodeGraph.transform.y}px`}
style:--dot-radius={`${dotRadius}px`}
style:--grid-dot-radius={`${gridDotRadius}px`}
data-node-graph
>
<!-- Right click menu for adding nodes -->
@ -298,8 +298,8 @@
{#each $nodeGraph.clickTargets.layerClickTargets as pathString}
<path class="layer" d={pathString} />
{/each}
{#each $nodeGraph.clickTargets.portClickTargets as pathString}
<path class="port" d={pathString} />
{#each $nodeGraph.clickTargets.connectorClickTargets as pathString}
<path class="connector" d={pathString} />
{/each}
{#each $nodeGraph.clickTargets.iconClickTargets as pathString}
<path class="visibility" d={pathString} />
@ -332,14 +332,14 @@
</svg>
</div>
<!-- Import and Export ports -->
<!-- Import and Export connectors -->
<div class="imports-and-exports" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
{#each $nodeGraph.imports as { outputMetadata, position }, index}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 8 8"
class="port"
data-port="output"
class="connector"
data-connector="output"
data-datatype={outputMetadata.dataType}
style:--data-color={`var(--color-data-${outputMetadata.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${outputMetadata.dataType.toLowerCase()}-dim)`}
@ -411,8 +411,8 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 8 8"
class="port"
data-port="input"
class="connector"
data-connector="input"
data-datatype={inputMetadata.dataType}
style:--data-color={`var(--color-data-${inputMetadata.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${inputMetadata.dataType.toLowerCase()}-dim)`}
@ -521,8 +521,8 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 8 12"
class="port top"
data-port="output"
class="connector top"
data-connector="output"
data-datatype={node.primaryOutput.dataType}
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
@ -542,8 +542,8 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 8 12"
class="port bottom"
data-port="input"
class="connector bottom"
data-connector="input"
data-datatype={node.primaryInput?.dataType}
style:--data-color={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()}-dim)`}
@ -561,14 +561,14 @@
{/if}
</svg>
</div>
<!-- Layer input port (from left) -->
<!-- Layer input connector (from left) -->
{#if node.exposedInputs.length > 0}
<div class="input ports">
<div class="input connectors">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 8 8"
class="port"
data-port="input"
class="connector"
data-connector="input"
data-datatype={stackDataInput.dataType}
style:--data-color={`var(--color-data-${stackDataInput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`}
@ -679,14 +679,14 @@
{/each}
</div>
{/if}
<!-- Input ports -->
<div class="input ports">
<!-- Input connectors -->
<div class="input connectors">
{#if node.primaryInput?.dataType}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 8 8"
class="port primary-port"
data-port="input"
class="connector primary-connector"
data-connector="input"
data-datatype={node.primaryInput?.dataType}
style:--data-color={`var(--color-data-${node.primaryInput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`}
@ -704,8 +704,8 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 8 8"
class="port"
data-port="input"
class="connector"
data-connector="input"
data-datatype={secondary.dataType}
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
@ -720,14 +720,14 @@
{/if}
{/each}
</div>
<!-- Output ports -->
<div class="output ports">
<!-- Output connectors -->
<div class="output connectors">
{#if node.primaryOutput}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 8 8"
class="port primary-port"
data-port="output"
class="connector primary-connector"
data-connector="output"
data-datatype={node.primaryOutput.dataType}
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
@ -744,8 +744,8 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 8 8"
class="port"
data-port="output"
class="connector"
data-connector="output"
data-datatype={secondary.dataType}
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
@ -801,8 +801,8 @@
width: 100%;
height: 100%;
background-size: var(--grid-spacing) var(--grid-spacing);
background-position: calc(var(--grid-offset-x) - var(--dot-radius)) calc(var(--grid-offset-y) - var(--dot-radius));
background-image: radial-gradient(circle at var(--dot-radius) var(--dot-radius), var(--color-3-darkgray) var(--dot-radius), transparent 0);
background-position: calc(var(--grid-offset-x) - var(--grid-dot-radius)) calc(var(--grid-offset-y) - var(--grid-dot-radius));
background-image: radial-gradient(circle at var(--grid-dot-radius) var(--grid-dot-radius), var(--color-3-darkgray) var(--grid-dot-radius), transparent 0);
background-repeat: repeat;
image-rendering: pixelated;
mix-blend-mode: screen;
@ -859,7 +859,7 @@
stroke: blue;
}
.port {
.connector {
stroke: green;
}
@ -898,11 +898,13 @@
}
.imports-and-exports {
position: absolute;
width: 100%;
height: 100%;
position: absolute;
// Keeps the connectors above the wires
z-index: 1;
.port {
.connector {
position: absolute;
width: 8px;
height: 8px;
@ -1088,8 +1090,10 @@
border: 1px dashed var(--data-color);
}
.ports {
.connectors {
position: absolute;
// Keeps the connectors above the wires
z-index: 1;
&.input {
left: -3px;
@ -1100,7 +1104,7 @@
}
}
.port {
.connector {
// Double the intended value because of margin collapsing, but for the first and last we divide it by two as intended
margin: calc(24px - 8px) 0;
width: 8px;
@ -1117,7 +1121,7 @@
border-radius: 8px;
--extra-width-to-reach-grid-multiple: 8px;
--node-chain-area-left-extension: 0;
// Keep this equation in sync with the equivalent one in the Svelte template `<clipPath><path d="layerBorderMask(...)" /></clipPath>` above, as well as the `left` port offset CSS rule above in `.ports.input` above.
// Keep this equation in sync with the equivalent one in the Svelte template `<clipPath><path d="layerBorderMask(...)" /></clipPath>` above, as well as the `left` connector offset CSS rule above in `.connectors.input` above.
width: calc((var(--layer-area-width) - 0.5) * 24px);
padding-left: calc(var(--node-chain-area-left-extension) * 24px);
margin-left: calc((0.5 - var(--node-chain-area-left-extension)) * 24px);
@ -1141,8 +1145,10 @@
border-radius: 2px;
position: relative;
box-sizing: border-box;
width: 72px;
height: 48px;
// We shorten the width by 1px on the left and right so the inner thumbnail graphic maintains a perfect 3:2 aspect ratio
width: calc(72px - 2px);
margin: 0 1px;
&::before {
content: "";
@ -1153,7 +1159,7 @@
}
&::before,
svg:not(.port) {
svg:not(.connector) {
pointer-events: none;
position: absolute;
margin: auto;
@ -1163,7 +1169,7 @@
height: calc(100% - 2px);
}
.port {
.connector {
position: absolute;
margin: 0 auto;
left: 0;
@ -1211,21 +1217,21 @@
right: -12px;
}
.input.ports {
.input.connectors {
left: calc(-3px + var(--node-chain-area-left-extension) * 24px - 36px);
}
.solo-drag-grip,
.visibility,
.input.ports,
.input.ports .port {
.input.connectors,
.input.connectors .connector {
position: absolute;
margin: auto 0;
top: 0;
bottom: 0;
}
.input.ports .port {
.input.connectors .connector {
left: 24px;
}
}
@ -1259,11 +1265,11 @@
}
}
.port {
.connector {
&:first-of-type {
margin-top: calc((24px - 8px) / 2);
&:not(.primary-port) {
&:not(.primary-connector) {
margin-top: calc((24px - 8px) / 2 + 24px);
}
}

View file

@ -180,7 +180,7 @@ export class Box {
export type FrontendClickTargets = {
readonly nodeClickTargets: string[];
readonly layerClickTargets: string[];
readonly portClickTargets: string[];
readonly connectorClickTargets: string[];
readonly iconClickTargets: string[];
readonly allNodesBoundingBox: string;
readonly importExportsBoundingBox: string;

View file

@ -130,7 +130,7 @@ where
pub async fn create_brush_texture(brush_style: &BrushStyle) -> Raster<CPU> {
let stamp = brush_stamp_generator(brush_style.diameter, brush_style.color, brush_style.hardness, brush_style.flow);
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.));
let blank_texture = empty_image((), transform, Color::TRANSPARENT).into_iter().next().unwrap_or_default();
let blank_texture = empty_image((), transform, Table::new_from_element(Color::TRANSPARENT)).into_iter().next().unwrap_or_default();
let image = blend_stamp_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.));
image.element
@ -230,7 +230,6 @@ async fn brush(_: impl Ctx, mut image_frame_table: Table<Raster<CPU>>, strokes:
let stroke_origin_in_layer = bbox.start - snap_offset - DVec2::splat(stroke.style.diameter / 2.);
let stroke_to_layer = DAffine2::from_translation(stroke_origin_in_layer) * DAffine2::from_scale(stroke_size);
// let normal_blend = BlendColorPairNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal)), ValueNode::new(CopiedNode::new(100.)));
let normal_blend = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Normal, 1.));
let blit_node = BlitNode::new(
FutureWrapperNode::new(ClonedNode::new(brush_texture)),
@ -241,7 +240,7 @@ async fn brush(_: impl Ctx, mut image_frame_table: Table<Raster<CPU>>, strokes:
let target = core::mem::take(&mut brush_plan.first_stroke_texture);
extend_image_to_bounds((), Table::new_from_row(target), stroke_to_layer)
} else {
empty_image((), stroke_to_layer, Color::TRANSPARENT)
empty_image((), stroke_to_layer, Table::new_from_element(Color::TRANSPARENT))
// EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(())
};

View file

@ -104,7 +104,7 @@ async fn create_artboard<T: Into<Table<Graphic>> + 'n>(
label: String,
location: DVec2,
dimensions: DVec2,
background: Color,
background: Table<Color>,
clip: bool,
) -> Table<Artboard> {
let location = location.as_ivec2();
@ -123,6 +123,9 @@ async fn create_artboard<T: Into<Table<Graphic>> + 'n>(
let dimensions = dimensions.abs();
let background: Option<Color> = background.into();
let background = background.unwrap_or(Color::WHITE);
Table::new_from_element(Artboard {
content,
label,

View file

@ -22,14 +22,11 @@ macro_rules! none_impl {
}
};
}
none_impl!(String);
none_impl!(bool);
none_impl!(f32);
none_impl!(f64);
none_impl!(DVec2);
none_impl!(Option<Color>); // TODO: Remove this?
none_impl!(Vec<Color>); // TODO: Remove this?
none_impl!(String);
impl BoundingBox for Color {
fn bounding_box(&self, _transform: DAffine2, _include_stroke: bool) -> RenderBoundingBox {

View file

@ -1,11 +1,10 @@
use crate::Ctx;
use crate::raster_types::{CPU, Raster};
use crate::table::Table;
use crate::vector::Vector;
use crate::{Color, Ctx};
use glam::{DAffine2, DVec2};
#[node_macro::node(category("Debug"), name("Log to Console"))]
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, Table<Vector>, DAffine2, Color, Option<Color>)] value: T) -> T {
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(bool, f64, u32, u64, DVec2, DAffine2, String)] value: T) -> T {
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
log::debug!("{value:#?}");
value
@ -19,13 +18,13 @@ fn size_of(_: impl Ctx, ty: crate::Type) -> Option<usize> {
/// Meant for debugging purposes, not general use. Wraps the input value in the Some variant of an Option.
#[node_macro::node(category("Debug"))]
fn some<T>(_: impl Ctx, #[implementations(f64, f32, u32, u64, String, Color)] input: T) -> Option<T> {
fn some<T>(_: impl Ctx, #[implementations(f64, f32, u32, u64, String)] input: T) -> Option<T> {
Some(input)
}
/// Meant for debugging purposes, not general use. Unwraps the input value from an Option, returning the default value if the input is None.
#[node_macro::node(category("Debug"))]
fn unwrap_option<T: Default>(_: impl Ctx, #[implementations(Option<f64>, Option<u32>, Option<u64>, Option<String>, Option<Color>)] input: Option<T>) -> T {
fn unwrap_option<T: Default>(_: impl Ctx, #[implementations(Option<f64>, Option<u32>, Option<u64>, Option<String>)] input: Option<T>) -> T {
input.unwrap_or_default()
}

View file

@ -139,6 +139,11 @@ impl From<Option<Color>> for Table<Graphic> {
}
}
}
impl From<Table<Color>> for Option<Color> {
fn from(color: Table<Color>) -> Self {
color.into_iter().next().map(|row| row.element)
}
}
// DAffine2
impl From<DAffine2> for Graphic {
@ -297,8 +302,6 @@ async fn wrap_graphic<T: Into<Graphic> + 'n>(
Table<Raster<CPU>>,
Table<Raster<GPU>>,
Table<Color>,
Color,
Option<Color>,
DAffine2,
)]
content: T,
@ -317,8 +320,6 @@ async fn to_graphic<T: Into<Table<Graphic>> + 'n>(
Table<Raster<CPU>>,
Table<Raster<GPU>>,
Table<Color>,
Color,
Option<Color>,
)]
content: T,
) -> Table<Graphic> {
@ -416,13 +417,16 @@ fn index<T: AtIndex + Clone + Default>(
_: impl Ctx,
/// The collection of data, such as a list or table.
#[implementations(
Vec<Color>,
Vec<Option<Color>>,
Vec<f64>, Vec<u64>,
Vec<f64>,
Vec<u32>,
Vec<u64>,
Vec<DVec2>,
Table<Artboard>,
Table<Graphic>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Graphic>,
Table<Raster<GPU>>,
Table<Color>,
)]
collection: T,
/// The index of the item to retrieve, starting from 0 for the first item.

View file

@ -10,14 +10,14 @@ use crate::{Context, Ctx};
use glam::{DAffine2, DVec2};
#[node_macro::node(category("Type Conversion"))]
fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Table<Vector>)] value: T) -> String {
format!("{:?}", value)
fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(bool, f64, u32, u64, DVec2, DAffine2, String)] value: T) -> String {
format!("{value:?}")
}
#[node_macro::node(category("Text"))]
fn serialize<T: serde::Serialize>(
_: impl Ctx,
#[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Color, Option<Color>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>)] value: T,
#[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>)] value: T,
) -> String {
serde_json::to_string(&value).unwrap_or_else(|_| "Serialization Error".to_string())
}
@ -64,8 +64,7 @@ async fn switch<T, C: Send + 'n + Clone>(
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> Color,
Context -> Option<Color>,
Context -> Table<Color>,
Context -> GradientStops,
)]
if_true: impl Node<C, Output = T>,
@ -84,8 +83,7 @@ async fn switch<T, C: Send + 'n + Clone>(
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> Color,
Context -> Option<Color>,
Context -> Table<Color>,
Context -> GradientStops,
)]
if_false: impl Node<C, Output = T>,

View file

@ -60,3 +60,30 @@ impl Clampable for DVec2 {
self.min(DVec2::splat(max))
}
}
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_color<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<crate::table::Table<graphene_core_shaders::color::Color>, D::Error> {
use crate::table::Table;
use graphene_core_shaders::color::Color;
use serde::Deserialize;
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum EitherFormat {
Color(Color),
OptionalColor(Option<Color>),
ColorTable(Table<Color>),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::Color(color) => Table::new_from_element(color),
EitherFormat::OptionalColor(color) => {
if let Some(color) = color {
Table::new_from_element(color)
} else {
Table::new()
}
}
EitherFormat::ColorTable(color_table) => color_table,
})
}

View file

@ -64,5 +64,3 @@ impl RenderComplexity for bool {}
impl RenderComplexity for f32 {}
impl RenderComplexity for f64 {}
impl RenderComplexity for DVec2 {}
impl RenderComplexity for Option<Color> {}
impl RenderComplexity for Vec<Color> {}

View file

@ -252,6 +252,15 @@ pub struct TableRow<T> {
}
impl<T> TableRow<T> {
pub fn new_from_element(element: T) -> Self {
Self {
element,
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(),
source_node_id: None,
}
}
pub fn as_ref(&self) -> TableRowRef<'_, T> {
TableRowRef {
element: &self.element,

View file

@ -2,6 +2,7 @@
use crate::Color;
pub use crate::gradient::*;
use crate::table::Table;
use dyn_any::DynAny;
use glam::DAffine2;
@ -120,6 +121,12 @@ impl From<Option<Color>> for Fill {
}
}
impl From<Table<Color>> for Fill {
fn from(color: Table<Color>) -> Fill {
Fill::solid_or_none(color.into())
}
}
impl From<Gradient> for Fill {
fn from(gradient: Gradient) -> Fill {
Fill::Gradient(gradient)
@ -309,17 +316,6 @@ impl std::hash::Hash for Stroke {
}
}
impl From<Color> for Stroke {
fn from(color: Color) -> Self {
Self::new(Some(color), 1.)
}
}
impl From<Option<Color>> for Stroke {
fn from(color: Option<Color>) -> Self {
Self::new(color, 1.)
}
}
impl Stroke {
pub const fn new(color: Option<Color>, weight: f64) -> Self {
Self {

View file

@ -48,28 +48,28 @@ impl VectorTableIterMut for Table<Vector> {
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
async fn assign_colors<T>(
_: impl Ctx,
/// The content with vector paths to apply the fill and/or stroke style to.
#[implementations(Table<Graphic>, Table<Vector>)]
#[widget(ParsedWidgetOverride::Hidden)]
/// The content with vector paths to apply the fill and/or stroke style to.
mut content: T,
#[default(true)]
/// Whether to style the fill.
#[default(true)]
fill: bool,
/// Whether to style the stroke.
stroke: bool,
#[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")]
/// The range of colors to select from.
#[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")]
gradient: GradientStops,
/// Whether to reverse the gradient.
reverse: bool,
/// Whether to randomize the color selection for each element from throughout the gradient.
randomize: bool,
#[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")]
/// The seed used for randomization.
/// Seed to determine unique variations on the randomized color selection.
#[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")]
seed: SeedValue,
#[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")]
/// The number of elements to span across the gradient before repeating. A 0 value will span the entire gradient once.
#[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")]
repeat_every: u32,
) -> T
where
@ -108,32 +108,28 @@ where
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))]
async fn fill<F: Into<Fill> + 'n + Send, V: VectorTableIterMut + 'n + Send>(
_: impl Ctx,
/// The content with vector paths to apply the fill style to.
#[implementations(
Table<Vector>,
Table<Vector>,
Table<Vector>,
Table<Vector>,
Table<Graphic>,
Table<Graphic>,
Table<Graphic>,
Table<Graphic>,
)]
/// The content with vector paths to apply the fill style to.
mut content: V,
/// The fill to paint the path with.
#[implementations(
Fill,
Option<Color>,
Color,
Table<Color>,
Gradient,
Fill,
Option<Color>,
Color,
Table<Color>,
Gradient,
)]
#[default(Color::BLACK)]
/// The fill to paint the path with.
fill: F,
_backup_color: Option<Color>,
_backup_color: Table<Color>,
_backup_gradient: Gradient,
) -> V {
let fill: Fill = fill.into();
@ -150,23 +146,17 @@ async fn fill<F: Into<Fill> + 'n + Send, V: VectorTableIterMut + 'n + Send>(
/// Applies a stroke style to the vector contained in the input.
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("stroke_properties"))]
async fn stroke<C: Into<Option<Color>> + 'n + Send, V>(
async fn stroke<V>(
_: impl Ctx,
#[implementations(Table<Vector>, Table<Vector>, Table<Graphic>, Table<Graphic>)]
/// The content with vector paths to apply the stroke style to.
#[implementations(Table<Vector>, Table<Graphic>)]
mut content: Table<V>,
#[implementations(
Option<Color>,
Color,
Option<Color>,
Color,
)]
#[default(Color::BLACK)]
/// The stroke color.
color: C,
#[default(Color::BLACK)]
color: Table<Color>,
/// The stroke weight.
#[unit(" px")]
#[default(2.)]
/// The stroke weight.
weight: f64,
/// The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.
align: StrokeAlign,
@ -174,8 +164,8 @@ async fn stroke<C: Into<Option<Color>> + 'n + Send, V>(
cap: StrokeCap,
/// The curvature of the bent stroke at sharp corners.
join: StrokeJoin,
#[default(4.)]
/// The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.
#[default(4.)]
miter_limit: f64,
/// The order to paint the stroke on top of the fill, or the fill on top of the stroke.
/// <https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>
@ -286,8 +276,8 @@ async fn circular_repeat<I: 'n + Send + Clone>(
async fn copy_to_points<I: 'n + Send + Clone>(
_: impl Ctx,
points: Table<Vector>,
#[expose]
/// Artwork to be copied and placed at each point.
#[expose]
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>)]
instance: Table<I>,
/// Minimum range of randomized sizes given to each instance.

View file

@ -1,6 +1,7 @@
use glam::{DAffine2, DVec2};
use graphene_core::gradient::GradientStops;
use graphene_core::registry::types::{Fraction, Percentage, PixelSize, TextArea};
use graphene_core::table::Table;
use graphene_core::transform::Footprint;
use graphene_core::{Color, Ctx, num_traits};
use log::warn;
@ -659,15 +660,16 @@ fn vec2_value(_: impl Ctx, _primary: (), x: f64, y: f64) -> DVec2 {
/// Constructs a color value which may be set to any color, or no color.
#[node_macro::node(category("Value"))]
fn color_value(_: impl Ctx, _primary: (), #[default(Color::BLACK)] color: Option<Color>) -> Option<Color> {
fn color_value(_: impl Ctx, _primary: (), #[default(Color::RED)] color: Table<Color>) -> Table<Color> {
color
}
/// Gets the color at the specified position along the gradient, given a position from 0 (left) to 1 (right).
#[node_macro::node(category("Color"))]
fn sample_gradient(_: impl Ctx, _primary: (), gradient: GradientStops, position: Fraction) -> Color {
fn sample_gradient(_: impl Ctx, _primary: (), gradient: GradientStops, position: Fraction) -> Table<Color> {
let position = position.clamp(0., 1.);
gradient.evaluate(position)
let color = gradient.evaluate(position);
Table::new_from_element(color)
}
/// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors.

View file

@ -193,16 +193,15 @@ tagged_value! {
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::artboard::migrate_artboard"))] // TODO: Eventually remove this migration document upgrade code
#[serde(alias = "ArtboardGroup")]
Artboard(Table<Artboard>),
ColorTable(Table<Color>), // TODO: Rename to Color
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::misc::migrate_color"))] // TODO: Eventually remove this migration document upgrade code
#[serde(alias = "ColorTable", alias = "OptionalColor")]
Color(Table<Color>),
// ============
// STRUCT TYPES
// ============
#[serde(alias = "IVec2", alias = "UVec2")]
DVec2(DVec2),
DAffine2(DAffine2),
Color(Color),
OptionalColor(Option<Color>),
Palette(Vec<Color>),
Stroke(graphene_core::vector::style::Stroke),
Gradient(graphene_core::vector::style::Gradient),
#[serde(alias = "GradientPositions")] // TODO: Eventually remove this alias document upgrade code
@ -259,7 +258,6 @@ impl TaggedValue {
TaggedValue::F64(x) => x.to_string() + "_f64",
TaggedValue::Bool(x) => x.to_string(),
TaggedValue::BlendMode(x) => "BlendMode::".to_string() + &x.to_string(),
TaggedValue::Color(x) => format!("Color {x:?}"),
_ => panic!("Cannot convert to primitive string"),
}
}
@ -280,7 +278,7 @@ impl TaggedValue {
6 => return Color::from_rgb_str(color),
8 => return Color::from_rgba_str(color),
_ => {
log::error!("Invalid default value color string: {}", input);
log::error!("Invalid default value color string: {input}");
return None;
}
}
@ -352,8 +350,7 @@ impl TaggedValue {
x if x == TypeId::of::<u32>() => FromStr::from_str(string).map(TaggedValue::U32).ok()?,
x if x == TypeId::of::<DVec2>() => to_dvec2(string).map(TaggedValue::DVec2)?,
x if x == TypeId::of::<bool>() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?,
x if x == TypeId::of::<Color>() => to_color(string).map(TaggedValue::Color)?,
x if x == TypeId::of::<Option<Color>>() => to_color(string).map(|color| TaggedValue::OptionalColor(Some(color)))?,
x if x == TypeId::of::<Table<Color>>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?,
x if x == TypeId::of::<Fill>() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?,
x if x == TypeId::of::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?,
_ => return None,

View file

@ -119,7 +119,6 @@ impl Hash for ConstructionArgs {
}
impl ConstructionArgs {
// TODO: what? Used in the gpu_compiler crate for something.
pub fn new_function_args(&self) -> Vec<String> {
match self {
ConstructionArgs::Nodes(nodes) => nodes.iter().map(|(n, _)| format!("n{:0x}", n.0)).collect(),

View file

@ -8,13 +8,6 @@ impl Adjust<Color> for Color {
*self = map_fn(self);
}
}
impl Adjust<Color> for Option<Color> {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
if let Some(color) = self {
*color = map_fn(color)
}
}
}
#[cfg(feature = "std")]
mod adjust_std {
@ -23,13 +16,6 @@ mod adjust_std {
use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
impl Adjust<Color> for GradientStops {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
for (_, color) in self.iter_mut() {
*color = map_fn(color);
}
}
}
impl Adjust<Color> for Table<Raster<CPU>> {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
for row in self.iter_mut() {
@ -39,4 +25,18 @@ mod adjust_std {
}
}
}
impl Adjust<Color> for Table<Color> {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
for color in self.iter_mut() {
*color.element = map_fn(color.element);
}
}
}
impl Adjust<Color> for GradientStops {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
for (_, color) in self.iter_mut() {
*color = map_fn(color);
}
}
}
}

View file

@ -44,8 +44,8 @@ pub enum LuminanceCalculation {
fn luminance<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
Table<Raster<CPU>>,
Table<Color>,
GradientStops,
)]
mut input: T,
@ -68,8 +68,8 @@ fn luminance<T: Adjust<Color>>(
fn gamma_correction<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
Table<Raster<CPU>>,
Table<Color>,
GradientStops,
)]
mut input: T,
@ -88,8 +88,8 @@ fn gamma_correction<T: Adjust<Color>>(
fn extract_channel<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
Table<Raster<CPU>>,
Table<Color>,
GradientStops,
)]
mut input: T,
@ -111,8 +111,8 @@ fn extract_channel<T: Adjust<Color>>(
fn make_opaque<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
Table<Raster<CPU>>,
Table<Color>,
GradientStops,
)]
mut input: T,
@ -136,8 +136,8 @@ fn make_opaque<T: Adjust<Color>>(
fn brightness_contrast<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
Table<Raster<CPU>>,
Table<Color>,
GradientStops,
)]
mut input: T,
@ -225,8 +225,8 @@ fn brightness_contrast<T: Adjust<Color>>(
fn levels<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
Table<Raster<CPU>>,
Table<Color>,
GradientStops,
)]
mut image: T,
@ -292,12 +292,12 @@ fn levels<T: Adjust<Color>>(
async fn black_and_white<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
Table<Raster<CPU>>,
Table<Color>,
GradientStops,
)]
mut image: T,
#[default(Color::BLACK)] tint: Color,
#[default(Color::BLACK)] tint: Table<Color>,
#[default(40.)]
#[range((-200., 300.))]
reds: Percentage,
@ -317,6 +317,9 @@ async fn black_and_white<T: Adjust<Color>>(
#[range((-200., 300.))]
magentas: Percentage,
) -> T {
let tint: Option<Color> = tint.into();
let tint = tint.unwrap_or(Color::BLACK);
image.adjust(|color| {
let color = color.to_gamma_srgb();
@ -364,8 +367,8 @@ async fn black_and_white<T: Adjust<Color>>(
async fn hue_saturation<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
Table<Raster<CPU>>,
Table<Color>,
GradientStops,
)]
mut input: T,
@ -398,8 +401,8 @@ async fn hue_saturation<T: Adjust<Color>>(
async fn invert<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
Table<Raster<CPU>>,
Table<Color>,
GradientStops,
)]
mut input: T,
@ -420,8 +423,8 @@ async fn invert<T: Adjust<Color>>(
async fn threshold<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
Table<Raster<CPU>>,
Table<Color>,
GradientStops,
)]
mut image: T,
@ -465,8 +468,8 @@ async fn threshold<T: Adjust<Color>>(
async fn vibrance<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
Table<Raster<CPU>>,
Table<Color>,
GradientStops,
)]
mut image: T,
@ -630,8 +633,8 @@ pub enum DomainWarpType {
async fn channel_mixer<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
Table<Raster<CPU>>,
Table<Color>,
GradientStops,
)]
mut image: T,
@ -758,8 +761,8 @@ pub enum SelectiveColorChoice {
async fn selective_color<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
Table<Raster<CPU>>,
Table<Color>,
GradientStops,
)]
mut image: T,
@ -900,8 +903,8 @@ async fn selective_color<T: Adjust<Color>>(
async fn posterize<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
Table<Raster<CPU>>,
Table<Color>,
GradientStops,
)]
mut input: T,
@ -933,8 +936,8 @@ async fn posterize<T: Adjust<Color>>(
async fn exposure<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
Table<Raster<CPU>>,
Table<Color>,
GradientStops,
)]
mut input: T,

View file

@ -17,15 +17,6 @@ impl Blend<Color> for Color {
blend_fn(*self, *under)
}
}
impl Blend<Color> for Option<Color> {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
match (self, under) {
(Some(a), Some(b)) => Some(blend_fn(*a, *b)),
(a, None) => *a,
(None, b) => *b,
}
}
}
#[cfg(feature = "std")]
mod blend_std {
@ -51,6 +42,15 @@ mod blend_std {
result_table
}
}
impl Blend<Color> for Table<Color> {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let mut result_table = self.clone();
for (over, under) in result_table.iter_mut().zip(under.iter()) {
*over.element = blend_fn(*over.element, *under.element);
}
result_table
}
}
impl Blend<Color> for GradientStops {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let mut combined_stops = self.iter().map(|(position, _)| position).chain(under.iter().map(|(position, _)| position)).collect::<Vec<_>>();
@ -126,15 +126,15 @@ pub fn apply_blend_mode(foreground: Color, background: Color, blend_mode: BlendM
async fn blend<T: Blend<Color> + Send>(
_: impl Ctx,
#[implementations(
Color,
Table<Raster<CPU>>,
Table<Color>,
GradientStops,
)]
over: T,
#[expose]
#[implementations(
Color,
Table<Raster<CPU>>,
Table<Color>,
GradientStops,
)]
under: T,
@ -148,17 +148,20 @@ async fn blend<T: Blend<Color> + Send>(
fn color_overlay<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
Table<Raster<CPU>>,
Table<Color>,
GradientStops,
)]
mut image: T,
#[default(Color::BLACK)] color: Color,
#[default(Color::BLACK)] color: Table<Color>,
blend_mode: BlendMode,
#[default(100.)] opacity: Percentage,
) -> T {
let opacity = (opacity as f32 / 100.).clamp(0., 1.);
let color: Option<Color> = color.into();
let color = color.unwrap_or(Color::BLACK);
image.adjust(|pixel| {
let image = pixel.map_rgb(|channel| channel * (1. - opacity));
@ -171,18 +174,6 @@ fn color_overlay<T: Adjust<Color>>(
image
}
#[cfg(feature = "std")]
#[node_macro::node(category(""), skip_impl)]
fn blend_color_pair<BlendModeNode, OpacityNode>(input: (Color, Color), blend_mode: &'n BlendModeNode, opacity: &'n OpacityNode) -> Color
where
BlendModeNode: graphene_core::Node<'n, (), Output = BlendMode> + 'n,
OpacityNode: graphene_core::Node<'n, (), Output = Percentage> + 'n,
{
let blend_mode = blend_mode.eval(());
let opacity = opacity.eval(());
blend_colors(input.0, input.1, blend_mode, opacity / 100.)
}
#[cfg(all(feature = "std", test))]
mod test {
use graphene_core::blending::BlendMode;
@ -202,7 +193,13 @@ mod test {
// 100% of the output should come from the multiplied value
let opacity = 100_f64;
let result = super::color_overlay((), Table::new_from_element(Raster::new_cpu(image.clone())), overlay_color, BlendMode::Multiply, opacity);
let result = super::color_overlay(
(),
Table::new_from_element(Raster::new_cpu(image.clone())),
Table::new_from_element(overlay_color),
BlendMode::Multiply,
opacity,
);
let result = result.iter().next().unwrap().element;
// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0)

View file

@ -13,8 +13,8 @@ use graphene_core::{Color, Ctx};
async fn gradient_map<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Color,
Table<Raster<CPU>>,
Table<Color>,
GradientStops,
)]
mut image: T,

View file

@ -1,7 +1,7 @@
use graphene_core::color::Color;
use graphene_core::context::Ctx;
use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
use graphene_core::table::{Table, TableRow};
#[node_macro::node(category("Color"))]
async fn image_color_palette(
@ -10,13 +10,13 @@ async fn image_color_palette(
#[hard_min(1.)]
#[soft_max(28.)]
max_size: u32,
) -> Vec<Color> {
) -> Table<Color> {
const GRID: f32 = 3.;
let bins = GRID * GRID * GRID;
let mut histogram: Vec<usize> = vec![0; (bins + 1.) as usize];
let mut colors: Vec<Vec<Color>> = vec![vec![]; (bins + 1.) as usize];
let mut histogram = vec![0; (bins + 1.) as usize];
let mut color_bins = vec![Vec::new(); (bins + 1.) as usize];
for row in image.iter() {
for pixel in row.element.data.iter() {
@ -27,40 +27,38 @@ async fn image_color_palette(
let bin = (r * GRID + g * GRID + b * GRID) as usize;
histogram[bin] += 1;
colors[bin].push(pixel.to_gamma_srgb());
color_bins[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![];
shorted
.iter()
.take(max_size as usize)
.flat_map(|&i| {
let list = &color_bins[i];
for i in shorted.iter().take(max_size as usize) {
let list = colors[*i].clone();
let mut r = 0.;
let mut g = 0.;
let mut b = 0.;
let mut a = 0.;
let mut r = 0.;
let mut g = 0.;
let mut b = 0.;
let mut a = 0.;
for color in list.iter() {
r += color.r();
g += color.g();
b += color.b();
a += color.a();
}
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;
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);
}
palette
Color::from_rgbaf32(r, g, b, a).map(TableRow::new_from_element).into_iter()
})
.collect()
}
#[cfg(test)]
@ -81,6 +79,6 @@ mod test {
})),
1,
);
assert_eq!(futures::executor::block_on(result), [Color::from_rgbaf32(0., 0., 0., 1.).unwrap()]);
assert_eq!(futures::executor::block_on(result), Table::new_from_element(Color::from_rgbaf32(0., 0., 0., 1.).unwrap()));
}
}

View file

@ -246,7 +246,7 @@ pub fn extend_image_to_bounds(_: impl Ctx, image: Table<Raster<CPU>>, bounds: DA
let image_data = &row.element.data;
let (image_width, image_height) = (row.element.width, row.element.height);
if image_width == 0 || image_height == 0 {
return empty_image((), bounds, Color::TRANSPARENT).into_iter().next().unwrap();
return empty_image((), bounds, Table::new_from_element(Color::TRANSPARENT)).into_iter().next().unwrap();
}
let orig_image_scale = DVec2::new(image_width as f64, image_height as f64);
@ -280,11 +280,12 @@ pub fn extend_image_to_bounds(_: impl Ctx, image: Table<Raster<CPU>>, bounds: DA
}
#[node_macro::node(category("Debug: Raster"))]
pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> Table<Raster<CPU>> {
pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Table<Color>) -> Table<Raster<CPU>> {
let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32;
let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32;
let image = Image::new(width, height, color);
let color: Option<Color> = color.into();
let image = Image::new(width, height, color.unwrap_or(Color::WHITE));
let mut result_table = Table::new_from_element(Raster::new_cpu(image));
let row = result_table.get_mut(0).unwrap();

View file

@ -291,8 +291,6 @@ async fn render<'a: 'n, T: 'n + Render + WasmNotSend>(
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Color>,
Context -> Option<Color>,
Context -> Vec<Color>,
Context -> bool,
Context -> f32,
Context -> f64,

View file

@ -1309,13 +1309,12 @@ impl Render for Table<Color> {
}
}
/// Used to stop rust complaining about upstream traits adding display implementations to `Option<Color>`. This would not be an issue as we control that crate.
trait Primitive: std::fmt::Display + BoundingBox + RenderComplexity {}
impl Primitive for String {}
impl Primitive for bool {}
impl Primitive for f32 {}
impl Primitive for f64 {}
impl Primitive for DVec2 {}
impl Primitive for String {}
fn text_attributes(attributes: &mut SvgRenderAttrs) {
attributes.push("fill", "white");
@ -1332,73 +1331,6 @@ impl<P: Primitive> Render for P {
fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2, _context: &mut RenderContext, _render_params: &RenderParams) {}
}
impl Render for Option<Color> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
if let Some(color) = self {
color.render_svg(render, render_params);
}
}
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
if let Some(color) = self {
color.render_to_vello(scene, parent_transform, _context, render_params);
}
}
}
impl Render for Color {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
render.leaf_tag("rect", |attributes| {
attributes.push("width", render_params.footprint.resolution.x.to_string());
attributes.push("height", render_params.footprint.resolution.y.to_string());
let matrix = format_transform_matrix(render_params.footprint.transform.inverse());
if !matrix.is_empty() {
attributes.push("transform", matrix);
}
attributes.push("fill", format!("#{}", self.to_rgb_hex_srgb_from_gamma()));
if self.a() < 1. {
attributes.push("fill-opacity", ((self.a() * 1000.).round() / 1000.).to_string());
}
});
}
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
let transform = parent_transform * render_params.footprint.transform.inverse();
let vello_color = peniko::Color::new([self.r(), self.g(), self.b(), self.a()]);
let rect = kurbo::Rect::from_origin_size(
kurbo::Point::ZERO,
kurbo::Size::new(render_params.footprint.resolution.x as f64, render_params.footprint.resolution.y as f64),
);
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(transform.to_cols_array()), vello_color, None, &rect);
}
}
impl Render 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.to_rgb_hex_srgb_from_gamma()));
if color.a() < 1. {
attributes.push("fill-opacity", ((color.a() * 1000.).round() / 1000.).to_string());
}
});
}
}
#[cfg(feature = "vello")]
fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2, _context: &mut RenderContext, _render_params: &RenderParams) {}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SvgSegment {
Slice(&'static str),

View file

@ -63,8 +63,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Color>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Color]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Option<Color>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => String]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => IVec2]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => DVec2]),
@ -95,7 +93,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => [f64; 4]]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<NodeId>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Graphic]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<Color>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_core::text::Font]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<BrushStroke>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => BrushCache]),

View file

@ -1271,15 +1271,16 @@ mod tests {
let tuples = quote_spanned!(problem_span=> () ());
let input = quote! {
fn test_node(
#[implementations((), #tuples, Footprint)] footprint: F,
#[implementations((), #tuples, Footprint)]
footprint: F,
#[implementations(
() -> Color,
() -> Table<Raster<CPU>>,
() -> GradientStops,
Footprint -> Color,
Footprint -> Table<Raster<CPU>>,
Footprint -> GradientStops,
)]
() -> Table<Raster<CPU>>,
() -> Table<Color>,
() -> GradientStops,
Footprint -> Table<Raster<CPU>>,
Footprint -> Table<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
) -> T {
// Implementation details...