Remove dead code for Imaginate

This commit is contained in:
Keavon Chambers 2025-06-26 18:33:00 -07:00
parent 1a4d7aa23c
commit 1875779b0a
32 changed files with 23 additions and 2022 deletions

View file

@ -55,53 +55,3 @@ pub fn commit_info_localized(localized_commit_date: &str) -> String {
localized_commit_date
)
}
// #[cfg(test)]
// mod test {
// use crate::messages::input_mapper::utility_types::input_mouse::ViewportBounds;
// use crate::messages::prelude::*;
// // TODO: Fix and reenable
// #[ignore]
// #[test]
// fn debug_ub() {
// use super::Message;
// let mut editor = super::Editor::new();
// let mut responses = Vec::new();
// let messages: Vec<Message> = vec![
// Message::Init,
// Message::Preferences(PreferencesMessage::Load {
// preferences: r#"{ "imaginate_server_hostname": "http://localhost:7860/", "imaginate_refresh_frequency": 1, "zoom_with_scroll": false }"#.to_string(),
// }),
// PortfolioMessage::OpenDocumentFileWithId {
// document_id: DocumentId(0),
// document_name: "".into(),
// document_is_auto_saved: true,
// document_is_saved: true,
// document_serialized_content: r#" [removed until test is reenabled] "#.into(),
// to_front: false,
// }
// .into(),
// InputPreprocessorMessage::BoundsOfViewports {
// bounds_of_viewports: vec![ViewportBounds::from_slice(&[0., 0., 1920., 1080.])],
// }
// .into(),
// ];
// use futures::executor::block_on;
// for message in messages {
// block_on(crate::node_graph_executor::run_node_graph());
// let mut res = VecDeque::new();
// editor.poll_node_graph_evaluation(&mut res).expect("poll_node_graph_evaluation failed");
// let res = editor.handle_message(message);
// responses.push(res);
// }
// let responses = responses.pop().unwrap();
// // let trigger_message = responses[responses.len() - 2].clone();
// println!("responses: {responses:#?}");
// }
// }

View file

@ -204,27 +204,6 @@ impl PreferencesDialogMessageHandler {
.widget_holder(),
];
// TODO: Reenable when Imaginate is restored
// let imaginate_server_hostname = vec![
// TextLabel::new("Imaginate").min_width(60).italic(true).widget_holder(),
// TextLabel::new("Server Hostname").table_align(true).widget_holder(),
// TextInput::new(&preferences.imaginate_server_hostname)
// .min_width(200)
// .on_update(|text_input: &TextInput| PreferencesMessage::ImaginateServerHostname { hostname: text_input.value.clone() }.into())
// .widget_holder(),
// ];
// let imaginate_refresh_frequency = vec![
// TextLabel::new("").min_width(60).widget_holder(),
// TextLabel::new("Refresh Frequency").table_align(true).widget_holder(),
// NumberInput::new(Some(preferences.imaginate_refresh_frequency))
// .unit(" seconds")
// .min(0.)
// .max((1_u64 << f64::MANTISSA_DIGITS) as f64)
// .min_width(200)
// .on_update(|number_input: &NumberInput| PreferencesMessage::ImaginateRefreshFrequency { seconds: number_input.value.unwrap() }.into())
// .widget_holder(),
// ];
Layout::WidgetLayout(WidgetLayout::new(vec![
LayoutGroup::Row { widgets: navigation_header },
LayoutGroup::Row { widgets: zoom_rate_label },
@ -238,8 +217,6 @@ impl PreferencesDialogMessageHandler {
LayoutGroup::Row { widgets: graph_wire_style },
LayoutGroup::Row { widgets: use_vello },
LayoutGroup::Row { widgets: vector_meshes },
// LayoutGroup::Row { widgets: imaginate_server_hostname },
// LayoutGroup::Row { widgets: imaginate_refresh_frequency },
]))
}

View file

@ -206,13 +206,6 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(ArrowUp); action_dispatch=ShapeToolMessage::IncreaseSides),
entry!(KeyDown(ArrowDown); action_dispatch=ShapeToolMessage::DecreaseSides),
//
// ImaginateToolMessage
// entry!(KeyDown(MouseLeft); action_dispatch=ImaginateToolMessage::DragStart),
// entry!(KeyUp(MouseLeft); action_dispatch=ImaginateToolMessage::DragStop),
// entry!(KeyDown(MouseRight); action_dispatch=ImaginateToolMessage::Abort),
// entry!(KeyDown(Escape); action_dispatch=ImaginateToolMessage::Abort),
// entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=ImaginateToolMessage::Resize { center: Alt, lock_ratio: Shift }),
//
// PathToolMessage
entry!(KeyDown(Delete); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath),
entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath),

View file

@ -73,13 +73,6 @@ pub enum DocumentMessage {
GroupSelectedLayers {
group_folder_type: GroupFolderType,
},
// ImaginateGenerate {
// imaginate_node: Vec<NodeId>,
// },
// ImaginateRandom {
// imaginate_node: Vec<NodeId>,
// then_generate: bool,
// },
MoveSelectedLayersTo {
parent: LayerNodeIdentifier,
insert_index: usize,

View file

@ -613,37 +613,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: new_folders });
}
}
// DocumentMessage::ImaginateGenerate { imaginate_node } => {
// let random_value = generate_uuid();
// responses.add(NodeGraphMessage::SetInputValue {
// node_id: *imaginate_node.last().unwrap(),
// // Needs to match the index of the seed parameter in `pub const IMAGINATE_NODE: DocumentNodeDefinition` in `document_node_type.rs`
// input_index: 17,
// value: graph_craft::document::value::TaggedValue::U64(random_value),
// });
// responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false });
// }
// DocumentMessage::ImaginateRandom { imaginate_node, then_generate } => {
// // Generate a random seed. We only want values between -2^53 and 2^53, because integer values
// // outside of this range can get rounded in f64
// let random_bits = generate_uuid();
// let random_value = ((random_bits >> 11) as f64).copysign(f64::from_bits(random_bits & (1 << 63)));
// responses.add(DocumentMessage::AddTransaction);
// // Set a random seed input
// responses.add(NodeGraphMessage::SetInputValue {
// node_id: *imaginate_node.last().unwrap(),
// // Needs to match the index of the seed parameter in `pub const IMAGINATE_NODE: DocumentNodeDefinition` in `document_node_type.rs`
// input_index: 3,
// value: graph_craft::document::value::TaggedValue::F64(random_value),
// });
// // Generate the image
// if then_generate {
// responses.add(DocumentMessage::ImaginateGenerate { imaginate_node });
// }
// }
DocumentMessage::MoveSelectedLayersTo { parent, insert_index } => {
if !self.selection_network_path.is_empty() {
log::error!("Moving selected layers is only supported for the Document Network");

View file

@ -1466,7 +1466,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// description: Cow::Borrowed("TODO"),
// properties: None,
// },
// (*IMAGINATE_NODE).clone(),
DocumentNodeDefinition {
identifier: "Path",
category: "Vector",
@ -2119,128 +2118,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node_derive::post_process_nodes(custom)
}
// pub static IMAGINATE_NODE: Lazy<DocumentNodeDefinition> = Lazy::new(|| DocumentNodeDefinition {
// identifier: "Imaginate",
// category: "Raster",
// node_template: NodeTemplate {
// document_node: DocumentNode {
// implementation: DocumentNodeImplementation::Network(NodeNetwork {
// exports: vec![NodeInput::node(NodeId(1), 0)],
// nodes: [
// DocumentNode {
// inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0)],
// implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
// manual_composition: Some(concrete!(Context)),
// skip_deduplication: true,
// ..Default::default()
// },
// DocumentNode {
// inputs: vec![
// NodeInput::node(NodeId(0), 0),
// NodeInput::network(concrete!(&WasmEditorApi), 1),
// NodeInput::network(concrete!(ImaginateController), 2),
// NodeInput::network(concrete!(f64), 3),
// NodeInput::network(concrete!(Option<DVec2>), 4),
// NodeInput::network(concrete!(u32), 5),
// NodeInput::network(concrete!(ImaginateSamplingMethod), 6),
// NodeInput::network(concrete!(f64), 7),
// NodeInput::network(concrete!(String), 8),
// NodeInput::network(concrete!(String), 9),
// NodeInput::network(concrete!(bool), 10),
// NodeInput::network(concrete!(f64), 11),
// NodeInput::network(concrete!(bool), 12),
// NodeInput::network(concrete!(f64), 13),
// NodeInput::network(concrete!(ImaginateMaskStartingFill), 14),
// NodeInput::network(concrete!(bool), 15),
// NodeInput::network(concrete!(bool), 16),
// NodeInput::network(concrete!(u64), 17),
// ],
// implementation: DocumentNodeImplementation::proto("graphene_std::raster::ImaginateNode"),
// ..Default::default()
// },
// ]
// .into_iter()
// .enumerate()
// .map(|(id, node)| (NodeId(id as u64), node))
// .collect(),
// ..Default::default()
// }),
// inputs: vec![
// NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true),
// NodeInput::scope("editor-api"),
// NodeInput::value(TaggedValue::ImaginateController(Default::default()), false),
// NodeInput::value(TaggedValue::F64(0.), false), // Remember to keep index used in `ImaginateRandom` updated with this entry's index
// NodeInput::value(TaggedValue::OptionalDVec2(None), false),
// NodeInput::value(TaggedValue::U32(30), false),
// NodeInput::value(TaggedValue::ImaginateSamplingMethod(ImaginateSamplingMethod::EulerA), false),
// NodeInput::value(TaggedValue::F64(7.5), false),
// NodeInput::value(TaggedValue::String(String::new()), false),
// NodeInput::value(TaggedValue::String(String::new()), false),
// NodeInput::value(TaggedValue::Bool(false), false),
// NodeInput::value(TaggedValue::F64(66.), false),
// NodeInput::value(TaggedValue::Bool(true), false),
// NodeInput::value(TaggedValue::F64(4.), false),
// NodeInput::value(TaggedValue::ImaginateMaskStartingFill(ImaginateMaskStartingFill::Fill), false),
// NodeInput::value(TaggedValue::Bool(false), false),
// NodeInput::value(TaggedValue::Bool(false), false),
// NodeInput::value(TaggedValue::U64(0), false),
// ],
// ..Default::default()
// },
// persistent_node_metadata: DocumentNodePersistentMetadata {
// network_metadata: Some(NodeNetworkMetadata {
// persistent_metadata: NodeNetworkPersistentMetadata {
// node_metadata: [
// DocumentNodeMetadata {
// persistent_metadata: DocumentNodePersistentMetadata {
// display_name: "Monitor".to_string(),
// ..Default::default()
// },
// ..Default::default()
// },
// DocumentNodeMetadata {
// persistent_metadata: DocumentNodePersistentMetadata {
// display_name: "Imaginate".to_string(),
// ..Default::default()
// },
// ..Default::default()
// },
// ]
// .into_iter()
// .enumerate()
// .map(|(id, node)| (NodeId(id as u64), node))
// .collect(),
// ..Default::default()
// },
// ..Default::default()
// }),
// input_properties: vec![
// "Input Image".into(),
// "Editor Api".into(),
// "Controller".into(),
// "Seed".into(),
// "Resolution".into(),
// "Samples".into(),
// "Sampling Method".into(),
// "Prompt Guidance".into(),
// "Prompt".into(),
// "Negative Prompt".into(),
// "Adapt Input Image".into(),
// "Image Creativity".into(),
// "Inpaint".into(),
// "Mask Blur".into(),
// "Mask Starting Fill".into(),
// "Improve Faces".into(),
// "Tiling".into(),
// ],
// output_names: vec!["Image".to_string()],
// ..Default::default()
// },
// },
// description: Cow::Borrowed("TODO"),
// properties: None, // Some(&node_properties::imaginate_properties),
// });
type NodeProperties = HashMap<String, Box<dyn Fn(NodeId, &mut NodePropertiesContext) -> Vec<LayoutGroup> + Send + Sync>>;
pub static NODE_OVERRIDES: once_cell::sync::Lazy<NodeProperties> = once_cell::sync::Lazy::new(static_node_properties);
@ -2975,19 +2852,3 @@ impl DocumentNodeDefinition {
self.node_template_input_override(self.node_template.document_node.inputs.clone().into_iter().map(Some))
}
}
// Previously used by the Imaginate node, but usage was commented out since it did nothing.
// pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetwork {
// let mut network = NodeNetwork { ..Default::default() };
// network.push_node_to_document_network(
// resolve_document_node_type("Input Frame")
// .expect("Input Frame node does not exist")
// .to_document_node_default_inputs([], DocumentNodeMetadata::position((8, 4))),
// );
// network.push_node_to_document_network(
// resolve_document_node_type("Output")
// .expect("Output node does not exist")
// .to_document_node([NodeInput::node(output_node_id, 0)], DocumentNodeMetadata::position((output_offset + 8, 4))),
// );
// network
// }

View file

@ -1396,12 +1396,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
input,
});
responses.add(PropertiesPanelMessage::Refresh);
if (network_interface
.reference(&node_id, selection_network_path)
.is_none_or(|reference| *reference != Some("Imaginate".to_string())) // TODO: Potentially remove the reference to Imaginate
|| input_index == 0)
&& network_interface.connected_to_output(&node_id, selection_network_path)
{
if (network_interface.reference(&node_id, selection_network_path).is_none() || input_index == 0) && network_interface.connected_to_output(&node_id, selection_network_path) {
responses.add(NodeGraphMessage::RunDocumentGraph);
}
}

View file

@ -1,547 +0,0 @@
//! This has all been copied out of node_properties.rs to avoid leaving hundreds of lines of commented out code in that file. It's left here instead for future reference.
// pub fn imaginate_sampling_method(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {
// let ParameterWidgetsInfo { node_id, index, .. } = parameter_widgets_info;
// vec![
// DropdownInput::new(
// ImaginateSamplingMethod::list()
// .into_iter()
// .map(|method| {
// vec![
// MenuListEntry::new(format!("{:?}", method))
// .label(method.to_string())
// .on_update(update_value(move |_| TaggedValue::ImaginateSamplingMethod(method), node_id, index)),
// ]
// })
// .collect(),
// )
// .widget_holder(),
// ]
// .into()
// }
// pub fn imaginate_mask_starting_fill(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {
// let ParameterWidgetsInfo { node_id, index, .. } = parameter_widgets_info;
// vec![
// DropdownInput::new(
// ImaginateMaskStartingFill::list()
// .into_iter()
// .map(|fill| {
// vec![
// MenuListEntry::new(format!("{:?}", fill))
// .label(fill.to_string())
// .on_update(update_value(move |_| TaggedValue::ImaginateMaskStartingFill(fill), node_id, index)),
// ]
// })
// .collect(),
// )
// .widget_holder(),
// ]
// .into()
// }
// pub(crate) fn imaginate_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
// let imaginate_node = [context.selection_network_path, &[node_id]].concat();
// let resolve_input = |name: &str| {
// IMAGINATE_NODE
// .default_node_template()
// .persistent_node_metadata
// .input_properties
// .iter()
// .position(|row| row.input_name.as_str() == name)
// .unwrap_or_else(|| panic!("Input {name} not found"))
// };
// let seed_index = resolve_input("Seed");
// let resolution_index = resolve_input("Resolution");
// let samples_index = resolve_input("Samples");
// let sampling_method_index = resolve_input("Sampling Method");
// let text_guidance_index = resolve_input("Prompt Guidance");
// let text_index = resolve_input("Prompt");
// let neg_index = resolve_input("Negative Prompt");
// let base_img_index = resolve_input("Adapt Input Image");
// let img_creativity_index = resolve_input("Image Creativity");
// // let mask_index = resolve_input("Masking Layer");
// // let inpaint_index = resolve_input("Inpaint");
// // let mask_blur_index = resolve_input("Mask Blur");
// // let mask_fill_index = resolve_input("Mask Starting Fill");
// let faces_index = resolve_input("Improve Faces");
// let tiling_index = resolve_input("Tiling");
// let document_node = match get_document_node(node_id, context) {
// Ok(document_node) => document_node,
// Err(err) => {
// log::error!("Could not get document node in imaginate_properties: {err}");
// return Vec::new();
// }
// };
// let controller = &document_node.inputs[resolve_input("Controller")];
// let server_status = {
// let server_status = context.persistent_data.imaginate.server_status();
// let status_text = server_status.to_text();
// let mut widgets = vec![
// TextLabel::new("Server").widget_holder(),
// Separator::new(SeparatorType::Unrelated).widget_holder(),
// IconButton::new("Settings", 24)
// .tooltip("Preferences: Imaginate")
// .on_update(|_| DialogMessage::RequestPreferencesDialog.into())
// .widget_holder(),
// Separator::new(SeparatorType::Unrelated).widget_holder(),
// TextLabel::new(status_text).bold(true).widget_holder(),
// Separator::new(SeparatorType::Related).widget_holder(),
// IconButton::new("Reload", 24)
// .tooltip("Refresh connection status")
// .on_update(|_| PortfolioMessage::ImaginateCheckServerStatus.into())
// .widget_holder(),
// ];
// if let ImaginateServerStatus::Unavailable | ImaginateServerStatus::Failed(_) = server_status {
// widgets.extend([
// Separator::new(SeparatorType::Unrelated).widget_holder(),
// TextButton::new("Server Help")
// .tooltip("Learn how to connect Imaginate to an image generation server")
// .on_update(|_| {
// FrontendMessage::TriggerVisitLink {
// url: "https://github.com/GraphiteEditor/Graphite/discussions/1089".to_string(),
// }
// .into()
// })
// .widget_holder(),
// ]);
// }
// LayoutGroup::Row { widgets }.with_tooltip("Connection status to the server that computes generated images")
// };
// let Some(TaggedValue::ImaginateController(controller)) = controller.as_value() else {
// panic!("Invalid output status input")
// };
// let imaginate_status = controller.get_status();
// let use_base_image = if let Some(&TaggedValue::Bool(use_base_image)) = &document_node.inputs[base_img_index].as_value() {
// use_base_image
// } else {
// true
// };
// let transform_not_connected = false;
// let progress = {
// let mut widgets = vec![TextLabel::new("Progress").widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder()];
// add_blank_assist(&mut widgets);
// let status = imaginate_status.to_text();
// widgets.push(TextLabel::new(status.as_ref()).bold(true).widget_holder());
// LayoutGroup::Row { widgets }.with_tooltip(match imaginate_status {
// ImaginateStatus::Failed(_) => status.as_ref(),
// _ => "When generating, the percentage represents how many sampling steps have so far been processed out of the target number",
// })
// };
// let image_controls = {
// let mut widgets = vec![TextLabel::new("Image").widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder()];
// match &imaginate_status {
// ImaginateStatus::Beginning | ImaginateStatus::Uploading => {
// add_blank_assist(&mut widgets);
// widgets.push(TextButton::new("Beginning...").tooltip("Sending image generation request to the server").disabled(true).widget_holder());
// }
// ImaginateStatus::Generating(_) => {
// add_blank_assist(&mut widgets);
// widgets.push(
// TextButton::new("Terminate")
// .tooltip("Cancel the in-progress image generation and keep the latest progress")
// .on_update({
// let controller = controller.clone();
// move |_| {
// controller.request_termination();
// Message::NoOp
// }
// })
// .widget_holder(),
// );
// }
// ImaginateStatus::Terminating => {
// add_blank_assist(&mut widgets);
// widgets.push(
// TextButton::new("Terminating...")
// .tooltip("Waiting on the final image generated after termination")
// .disabled(true)
// .widget_holder(),
// );
// }
// ImaginateStatus::Ready | ImaginateStatus::ReadyDone | ImaginateStatus::Terminated | ImaginateStatus::Failed(_) => widgets.extend_from_slice(&[
// IconButton::new("Random", 24)
// .tooltip("Generate with a new random seed")
// .on_update({
// let imaginate_node = imaginate_node.clone();
// let controller = controller.clone();
// move |_| {
// controller.trigger_regenerate();
// DocumentMessage::ImaginateRandom {
// imaginate_node: imaginate_node.clone(),
// then_generate: true,
// }
// .into()
// }
// })
// .widget_holder(),
// Separator::new(SeparatorType::Unrelated).widget_holder(),
// TextButton::new("Generate")
// .tooltip("Fill layer frame by generating a new image")
// .on_update({
// let controller = controller.clone();
// let imaginate_node = imaginate_node.clone();
// move |_| {
// controller.trigger_regenerate();
// DocumentMessage::ImaginateGenerate {
// imaginate_node: imaginate_node.clone(),
// }
// .into()
// }
// })
// .widget_holder(),
// Separator::new(SeparatorType::Related).widget_holder(),
// TextButton::new("Clear")
// .tooltip("Remove generated image from the layer frame")
// .disabled(!matches!(imaginate_status, ImaginateStatus::ReadyDone))
// .on_update({
// let controller = controller.clone();
// let imaginate_node = imaginate_node.clone();
// move |_| {
// controller.set_status(ImaginateStatus::Ready);
// DocumentMessage::ImaginateGenerate {
// imaginate_node: imaginate_node.clone(),
// }
// .into()
// }
// })
// .widget_holder(),
// ]),
// }
// LayoutGroup::Row { widgets }.with_tooltip("Buttons that control the image generation process")
// };
// // Requires custom layout for the regenerate button
// let seed = {
// let mut widgets = start_widgets(document_node, node_id, seed_index, "Seed", FrontendGraphDataType::Number, false);
// let Some(input) = document_node.inputs.get(seed_index) else {
// log::warn!("A widget failed to be built because its node's input index is invalid.");
// return vec![];
// };
// if let Some(&TaggedValue::F64(seed)) = &input.as_non_exposed_value() {
// widgets.extend_from_slice(&[
// Separator::new(SeparatorType::Unrelated).widget_holder(),
// IconButton::new("Resync", 24)
// .tooltip("Set a new random seed")
// .on_update({
// let imaginate_node = imaginate_node.clone();
// move |_| {
// DocumentMessage::ImaginateRandom {
// imaginate_node: imaginate_node.clone(),
// then_generate: false,
// }
// .into()
// }
// })
// .widget_holder(),
// Separator::new(SeparatorType::Unrelated).widget_holder(),
// NumberInput::new(Some(seed))
// .int()
// .min(-((1_u64 << f64::MANTISSA_DIGITS) as f64))
// .max((1_u64 << f64::MANTISSA_DIGITS) as f64)
// .on_update(update_value(move |input: &NumberInput| TaggedValue::F64(input.value.unwrap()), node_id, seed_index))
// .on_commit(commit_value)
// .mode(NumberInputMode::Increment)
// .widget_holder(),
// ])
// }
// // Note: Limited by f64. You cannot even have all the possible u64 values :)
// LayoutGroup::Row { widgets }.with_tooltip("Seed determines the random outcome, enabling limitless unique variations")
// };
// // let transform = context
// // .executor
// // .introspect_node_in_network(context.network, &imaginate_node, |network| network.inputs.first().copied(), |frame: &RasterData<Color>| frame.transform)
// // .unwrap_or_default();
// let image_size = context
// .executor
// .introspect_node_in_network(
// context.network_interface.document_network().unwrap(),
// &imaginate_node,
// |network| {
// network
// .nodes
// .iter()
// .find(|node| {
// node.1
// .inputs
// .iter()
// .any(|node_input| if let NodeInput::Network { import_index, .. } = node_input { *import_index == 0 } else { false })
// })
// .map(|(node_id, _)| node_id)
// .copied()
// },
// |frame: &IORecord<(), RasterData<Color>>| (frame.output.image.width, frame.output.image.height),
// )
// .unwrap_or_default();
// let document_node = match get_document_node(node_id, context) {
// Ok(document_node) => document_node,
// Err(err) => {
// log::error!("Could not get document node in imaginate_properties: {err}");
// return Vec::new();
// }
// };
// let resolution = {
// let mut widgets = start_widgets(document_node, node_id, resolution_index, "Resolution", FrontendGraphDataType::Number, false);
// let round = |size: DVec2| {
// let (x, y) = graphene_std::imaginate::pick_safe_imaginate_resolution(size.into());
// DVec2::new(x as f64, y as f64)
// };
// let Some(input) = document_node.inputs.get(resolution_index) else {
// log::warn!("A widget failed to be built because its node's input index is invalid.");
// return vec![];
// };
// if let Some(&TaggedValue::OptionalDVec2(vec2)) = &input.as_non_exposed_value() {
// let dimensions_is_auto = vec2.is_none();
// let vec2 = vec2.unwrap_or_else(|| round((image_size.0 as f64, image_size.1 as f64).into()));
// widgets.extend_from_slice(&[
// Separator::new(SeparatorType::Unrelated).widget_holder(),
// IconButton::new("FrameAll", 24)
// .tooltip("Set the layer dimensions to this resolution")
// .on_update(move |_| DialogMessage::RequestComingSoonDialog { issue: None }.into())
// .widget_holder(),
// Separator::new(SeparatorType::Unrelated).widget_holder(),
// CheckboxInput::new(!dimensions_is_auto || transform_not_connected)
// .icon("Edit12px")
// .tooltip({
// let message = "Set a custom resolution instead of using the input's dimensions (rounded to the nearest 64)";
// let manual_message = "Set a custom resolution instead of using the input's dimensions (rounded to the nearest 64).\n\
// \n\
// (Resolution must be set manually while the 'Transform' input is disconnected.)";
// if transform_not_connected {
// manual_message
// } else {
// message
// }
// })
// .disabled(transform_not_connected)
// .on_update(update_value(
// move |checkbox_input: &CheckboxInput| TaggedValue::OptionalDVec2(if checkbox_input.checked { Some(vec2) } else { None }),
// node_id,
// resolution_index,
// ))
// .on_commit(commit_value)
// .for_label(checkbox_id.clone())
// .widget_holder(),
// Separator::new(SeparatorType::Related).widget_holder(),
// NumberInput::new(Some(vec2.x))
// .label("W")
// .min(64.)
// .step(64.)
// .unit(" px")
// .disabled(dimensions_is_auto && !transform_not_connected)
// .on_update(update_value(
// move |number_input: &NumberInput| TaggedValue::OptionalDVec2(Some(round(DVec2::new(number_input.value.unwrap(), vec2.y)))),
// node_id,
// resolution_index,
// ))
// .on_commit(commit_value)
// .widget_holder(),
// Separator::new(SeparatorType::Related).widget_holder(),
// NumberInput::new(Some(vec2.y))
// .label("H")
// .min(64.)
// .step(64.)
// .unit(" px")
// .disabled(dimensions_is_auto && !transform_not_connected)
// .on_update(update_value(
// move |number_input: &NumberInput| TaggedValue::OptionalDVec2(Some(round(DVec2::new(vec2.x, number_input.value.unwrap())))),
// node_id,
// resolution_index,
// ))
// .on_commit(commit_value)
// .widget_holder(),
// ])
// }
// LayoutGroup::Row { widgets }.with_tooltip(
// "Width and height of the image that will be generated. Larger resolutions take longer to compute.\n\
// \n\
// 512x512 yields optimal results because the AI is trained to understand that scale best. Larger sizes may tend to integrate the prompt's subject more than once. Small sizes are often incoherent.\n\
// \n\
// Dimensions must be a multiple of 64, so these are set by rounding the layer dimensions. A resolution exceeding 1 megapixel is reduced below that limit because larger sizes may exceed available GPU memory on the server.")
// };
// let sampling_steps = {
// let widgets = number_widget(document_node, node_id, samples_index, "Sampling Steps", NumberInput::default().min(0.).max(150.).int(), true);
// LayoutGroup::Row { widgets }.with_tooltip("Number of iterations to improve the image generation quality, with diminishing returns around 40 when using the Euler A sampling method")
// };
// let sampling_method = {
// let mut widgets = start_widgets(document_node, node_id, sampling_method_index, "Sampling Method", FrontendGraphDataType::General, true);
// let Some(input) = document_node.inputs.get(sampling_method_index) else {
// log::warn!("A widget failed to be built because its node's input index is invalid.");
// return vec![];
// };
// if let Some(&TaggedValue::ImaginateSamplingMethod(sampling_method)) = &input.as_non_exposed_value() {
// let sampling_methods = ImaginateSamplingMethod::list();
// let mut entries = Vec::with_capacity(sampling_methods.len());
// for method in sampling_methods {
// entries.push(
// MenuListEntry::new(format!("{method:?}"))
// .label(method.to_string())
// .on_update(update_value(move |_| TaggedValue::ImaginateSamplingMethod(method), node_id, sampling_method_index))
// .on_commit(commit_value),
// );
// }
// let entries = vec![entries];
// widgets.extend_from_slice(&[
// Separator::new(SeparatorType::Unrelated).widget_holder(),
// DropdownInput::new(entries).selected_index(Some(sampling_method as u32)).widget_holder(),
// ]);
// }
// LayoutGroup::Row { widgets }.with_tooltip("Algorithm used to generate the image during each sampling step")
// };
// let text_guidance = {
// let widgets = number_widget(document_node, node_id, text_guidance_index, "Prompt Guidance", NumberInput::default().min(0.).max(30.), true);
// LayoutGroup::Row { widgets }.with_tooltip(
// "Amplification of the text prompt's influence over the outcome. At 0, the prompt is entirely ignored.\n\
// \n\
// Lower values are more creative and exploratory. Higher values are more literal and uninspired.\n\
// \n\
// This parameter is otherwise known as CFG (classifier-free guidance).",
// )
// };
// let text_prompt = {
// let widgets = text_area_widget(document_node, node_id, text_index, "Prompt", true);
// LayoutGroup::Row { widgets }.with_tooltip(
// "Description of the desired image subject and style.\n\
// \n\
// Include an artist name like \"Rembrandt\" or art medium like \"watercolor\" or \"photography\" to influence the look. List multiple to meld styles.\n\
// \n\
// To boost (or lessen) the importance of a word or phrase, wrap it in parentheses ending with a colon and a multiplier, for example:\n\
// \"Colorless green ideas (sleep:1.3) furiously\"",
// )
// };
// let negative_prompt = {
// let widgets = text_area_widget(document_node, node_id, neg_index, "Negative Prompt", true);
// LayoutGroup::Row { widgets }.with_tooltip("A negative text prompt can be used to list things like objects or colors to avoid")
// };
// let base_image = {
// let widgets = bool_widget(document_node, node_id, base_img_index, "Adapt Input Image", CheckboxInput::default().for_label(checkbox_id.clone()), true);
// LayoutGroup::Row { widgets }.with_tooltip("Generate an image based upon the bitmap data plugged into this node")
// };
// let image_creativity = {
// let props = NumberInput::default().percentage().disabled(!use_base_image);
// let widgets = number_widget(document_node, node_id, img_creativity_index, "Image Creativity", props, true);
// LayoutGroup::Row { widgets }.with_tooltip(
// "Strength of the artistic liberties allowing changes from the input image. The image is unchanged at 0% and completely different at 100%.\n\
// \n\
// This parameter is otherwise known as denoising strength.",
// )
// };
// let mut layout = vec![
// server_status,
// progress,
// image_controls,
// seed,
// resolution,
// sampling_steps,
// sampling_method,
// text_guidance,
// text_prompt,
// negative_prompt,
// base_image,
// image_creativity,
// // layer_mask,
// ];
// // if use_base_image && layer_reference_input_layer_is_some {
// // let in_paint = {
// // let mut widgets = start_widgets(document_node, node_id, inpaint_index, "Inpaint", FrontendGraphDataType::Boolean, true);
// // if let Some(& TaggedValue::Bool(in_paint)
// //)/ } = &document_node.inputs[inpaint_index].as_non_exposed_value()
// // {
// // widgets.extend_from_slice(&[
// // Separator::new(SeparatorType::Unrelated).widget_holder(),
// // RadioInput::new(
// // [(true, "Inpaint"), (false, "Outpaint")]
// // .into_iter()
// // .map(|(paint, name)| RadioEntryData::new(name).label(name).on_update(update_value(move |_| TaggedValue::Bool(paint), node_id, inpaint_index)))
// // .collect(),
// // )
// // .selected_index(Some(1 - in_paint as u32))
// // .widget_holder(),
// // ]);
// // }
// // LayoutGroup::Row { widgets }.with_tooltip(
// // "Constrain image generation to the interior (inpaint) or exterior (outpaint) of the mask, while referencing the other unchanged parts as context imagery.\n\
// // \n\
// // An unwanted part of an image can be replaced by drawing around it with a black shape and inpainting with that mask layer.\n\
// // \n\
// // An image can be uncropped by resizing the Imaginate layer to the target bounds and outpainting with a black rectangle mask matching the original image bounds.",
// // )
// // };
// // let blur_radius = {
// // let number_props = NumberInput::default().unit(" px").min(0.).max(25.).int();
// // let widgets = number_widget(document_node, node_id, mask_blur_index, "Mask Blur", number_props, true);
// // LayoutGroup::Row { widgets }.with_tooltip("Blur radius for the mask. Useful for softening sharp edges to blend the masked area with the rest of the image.")
// // };
// // let mask_starting_fill = {
// // let mut widgets = start_widgets(document_node, node_id, mask_fill_index, "Mask Starting Fill", FrontendGraphDataType::General, true);
// // if let Some(& TaggedValue::ImaginateMaskStartingFill(starting_fill)
// //)/ } = &document_node.inputs[mask_fill_index].as_non_exposed_value()
// // {
// // let mask_fill_content_modes = ImaginateMaskStartingFill::list();
// // let mut entries = Vec::with_capacity(mask_fill_content_modes.len());
// // for mode in mask_fill_content_modes {
// // entries.push(MenuListEntry::new(format!("{mode:?}")).label(mode.to_string()).on_update(update_value(move |_| TaggedValue::ImaginateMaskStartingFill(mode), node_id, mask_fill_index)));
// // }
// // let entries = vec![entries];
// // widgets.extend_from_slice(&[
// // Separator::new(SeparatorType::Unrelated).widget_holder(),
// // DropdownInput::new(entries).selected_index(Some(starting_fill as u32)).widget_holder(),
// // ]);
// // }
// // LayoutGroup::Row { widgets }.with_tooltip(
// // "Begin in/outpainting the masked areas using this fill content as the starting input image.\n\
// // \n\
// // Each option can be visualized by generating with 'Sampling Steps' set to 0.",
// // )
// // };
// // layout.extend_from_slice(&[in_paint, blur_radius, mask_starting_fill]);
// // }
// let improve_faces = {
// let widgets = bool_widget(document_node, node_id, faces_index, "Improve Faces", CheckboxInput::default().for_label(checkbox_id.clone()), true);
// LayoutGroup::Row { widgets }.with_tooltip(
// "Postprocess human (or human-like) faces to look subtly less distorted.\n\
// \n\
// This filter can be used on its own by enabling 'Adapt Input Image' and setting 'Sampling Steps' to 0.",
// )
// };
// let tiling = {
// let widgets = bool_widget(document_node, node_id, tiling_index, "Tiling", CheckboxInput::default().for_label(checkbox_id.clone()), true);
// LayoutGroup::Row { widgets }.with_tooltip("Generate the image so its edges loop seamlessly to make repeatable patterns or textures")
// };
// layout.extend_from_slice(&[improve_faces, tiling]);
// layout
// }

View file

@ -54,9 +54,6 @@ pub enum PortfolioMessage {
preview_url: String,
data: Vec<u8>,
},
// ImaginateCheckServerStatus,
// ImaginatePollServerStatus,
// ImaginateServerHostname,
Import,
LoadDocumentResources {
document_id: DocumentId,

View file

@ -328,35 +328,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
responses.add(NodeGraphMessage::RunDocumentGraph);
}
}
// PortfolioMessage::ImaginateCheckServerStatus => {
// let server_status = self.persistent_data.imaginate.server_status().clone();
// self.persistent_data.imaginate.poll_server_check();
// #[cfg(target_arch = "wasm32")]
// if let Some(fut) = self.persistent_data.imaginate.initiate_server_check() {
// wasm_bindgen_futures::spawn_local(async move {
// let () = fut.await;
// use wasm_bindgen::prelude::*;
// #[wasm_bindgen(module = "/../frontend/src/editor.ts")]
// extern "C" {
// #[wasm_bindgen(js_name = injectImaginatePollServerStatus)]
// fn inject();
// }
// inject();
// })
// }
// if &server_status != self.persistent_data.imaginate.server_status() {
// responses.add(PropertiesPanelMessage::Refresh);
// }
// }
// PortfolioMessage::ImaginatePollServerStatus => {
// self.persistent_data.imaginate.poll_server_check();
// responses.add(PropertiesPanelMessage::Refresh);
// }
PortfolioMessage::EditorPreferences => self.executor.update_editor_preferences(preferences.editor_preferences()),
// PortfolioMessage::ImaginateServerHostname => {
// self.persistent_data.imaginate.set_host_name(&preferences.imaginate_server_hostname);
// }
PortfolioMessage::Import => {
// This portfolio message wraps the frontend message so it can be listed as an action, which isn't possible for frontend messages
responses.add(FrontendMessage::TriggerImport);

View file

@ -4,7 +4,6 @@ use graphene_std::text::FontCache;
pub struct PersistentData {
pub font_cache: FontCache,
pub use_vello: bool,
// pub imaginate: ImaginatePersistentData,
}
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]

View file

@ -16,6 +16,4 @@ pub enum PreferencesMessage {
ModifyLayout { zoom_with_scroll: bool },
GraphWireStyle { style: GraphWireStyle },
ViewportZoomWheelRate { rate: f64 },
// ImaginateRefreshFrequency { seconds: f64 },
// ImaginateServerHostname { hostname: String },
}

View file

@ -7,8 +7,6 @@ use graph_craft::wasm_application_io::EditorPreferences;
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct PreferencesMessageHandler {
// pub imaginate_server_hostname: String,
// pub imaginate_refresh_frequency: f64,
pub selection_mode: SelectionMode,
pub zoom_with_scroll: bool,
pub use_vello: bool,
@ -24,7 +22,6 @@ impl PreferencesMessageHandler {
pub fn editor_preferences(&self) -> EditorPreferences {
EditorPreferences {
// imaginate_hostname: self.imaginate_server_hostname.clone(),
use_vello: self.use_vello && self.supports_wgpu(),
}
}
@ -37,8 +34,6 @@ impl PreferencesMessageHandler {
impl Default for PreferencesMessageHandler {
fn default() -> Self {
Self {
// imaginate_server_hostname: EditorPreferences::default().imaginate_hostname,
// imaginate_refresh_frequency: 1.,
selection_mode: SelectionMode::Touched,
zoom_with_scroll: matches!(MappingVariant::default(), MappingVariant::ZoomWithScroll),
use_vello: EditorPreferences::default().use_vello,
@ -57,10 +52,6 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
if let Ok(deserialized_preferences) = serde_json::from_str::<PreferencesMessageHandler>(&preferences) {
*self = deserialized_preferences;
// TODO: Reenable when Imaginate is restored
// responses.add(PortfolioMessage::ImaginateServerHostname);
// responses.add(PortfolioMessage::ImaginateCheckServerStatus);
responses.add(PortfolioMessage::EditorPreferences);
responses.add(PortfolioMessage::UpdateVelloPreference);
responses.add(PreferencesMessage::ModifyLayout {
@ -101,27 +92,6 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
self.viewport_zoom_wheel_rate = rate;
}
}
// TODO: Reenable when Imaginate is restored (and move back up one line since the auto-formatter doesn't like it in that block)
// PreferencesMessage::ImaginateRefreshFrequency { seconds } => {
// self.imaginate_refresh_frequency = seconds;
// responses.add(PortfolioMessage::ImaginateCheckServerStatus);
// responses.add(PortfolioMessage::EditorPreferences);
// }
// PreferencesMessage::ImaginateServerHostname { hostname } => {
// let initial = hostname.clone();
// let has_protocol = hostname.starts_with("http://") || hostname.starts_with("https://");
// let hostname = if has_protocol { hostname } else { "http://".to_string() + &hostname };
// let hostname = if hostname.ends_with('/') { hostname } else { hostname + "/" };
// if hostname != initial {
// refresh_dialog(responses);
// }
// self.imaginate_server_hostname = hostname;
// responses.add(PortfolioMessage::ImaginateServerHostname);
// responses.add(PortfolioMessage::ImaginateCheckServerStatus);
// responses.add(PortfolioMessage::EditorPreferences);
//}
responses.add(FrontendMessage::TriggerSavePreferences { preferences: self.clone() });
}

View file

@ -38,7 +38,6 @@ pub use crate::messages::tool::tool_messages::eyedropper_tool::{EyedropperToolMe
pub use crate::messages::tool::tool_messages::fill_tool::{FillToolMessage, FillToolMessageDiscriminant};
pub use crate::messages::tool::tool_messages::freehand_tool::{FreehandToolMessage, FreehandToolMessageDiscriminant};
pub use crate::messages::tool::tool_messages::gradient_tool::{GradientToolMessage, GradientToolMessageDiscriminant};
// pub use crate::messages::tool::tool_messages::imaginate_tool::{ImaginateToolMessage, ImaginateToolMessageDiscriminant};
pub use crate::messages::tool::tool_messages::navigate_tool::{NavigateToolMessage, NavigateToolMessageDiscriminant};
pub use crate::messages::tool::tool_messages::path_tool::{PathToolMessage, PathToolMessageDiscriminant};
pub use crate::messages::tool::tool_messages::pen_tool::{PenToolMessage, PenToolMessageDiscriminant};

View file

@ -48,17 +48,17 @@ pub enum ToolMessage {
// Relight(RelightToolMessage),
// // #[child]
// Detail(DetailToolMessage),
// #[child]
// Imaginate(ImaginateToolMessage),
// Messages
// General tools
ActivateToolSelect,
ActivateToolArtboard,
ActivateToolNavigate,
ActivateToolEyedropper,
ActivateToolFill,
ActivateToolGradient,
// Vector tools
ActivateToolPath,
ActivateToolPen,
ActivateToolFreehand,
@ -68,10 +68,9 @@ pub enum ToolMessage {
ActivateToolShapeEllipse,
ActivateToolShape,
ActivateToolText,
// Raster tools
ActivateToolBrush,
// ActivateToolImaginate,
//
ActivateTool {
tool_type: ToolType,
},

View file

@ -82,7 +82,6 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
responses.add(ShapeToolMessage::HideShapeTypeWidget(true));
responses.add(ShapeToolMessage::SetShape(shape));
}
// ToolMessage::ActivateToolImaginate => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Imaginate }),
ToolMessage::ActivateTool { tool_type } => {
let tool_data = &mut self.tool_state.tool_data;
let old_tool = tool_data.active_tool_type.get_tool();
@ -340,7 +339,6 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
ActivateToolText,
ActivateToolBrush,
// ActivateToolImaginate,
SelectRandomPrimaryColor,
ResetColors,

View file

@ -1,184 +0,0 @@
use super::tool_prelude::*;
use crate::messages::tool::common_functionality::resize::Resize;
#[derive(Default)]
pub struct ImaginateTool {
fsm_state: ImaginateToolFsmState,
tool_data: ImaginateToolData,
}
#[impl_message(Message, ToolMessage, Imaginate)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum ImaginateToolMessage {
// Standard messages
Abort,
// Tool-specific messages
DragStart,
DragStop,
Resize { center: Key, lock_ratio: Key },
}
impl LayoutHolder for ImaginateTool {
fn layout(&self) -> Layout {
Layout::WidgetLayout(WidgetLayout::default())
}
}
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ImaginateTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
}
fn actions(&self) -> ActionList {
match self.fsm_state {
ImaginateToolFsmState::Ready => actions!(ImaginateToolMessageDiscriminant;
DragStart,
),
ImaginateToolFsmState::Drawing => actions!(ImaginateToolMessageDiscriminant;
DragStop,
Abort,
Resize,
),
}
}
}
impl ToolMetadata for ImaginateTool {
fn icon_name(&self) -> String {
"RasterImaginateTool".into()
}
fn tooltip(&self) -> String {
"Imaginate Tool".into()
}
fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {
ToolType::Imaginate
}
}
impl ToolTransition for ImaginateTool {
fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap {
tool_abort: Some(ImaginateToolMessage::Abort.into()),
..Default::default()
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum ImaginateToolFsmState {
#[default]
Ready,
Drawing,
}
#[derive(Clone, Debug, Default)]
struct ImaginateToolData {
data: Resize,
}
impl Fsm for ImaginateToolFsmState {
type ToolData = ImaginateToolData;
type ToolOptions = ();
fn transition(
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
ToolActionHandlerData { document, input, .. }: &mut ToolActionHandlerData,
_tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
let shape_data = &mut tool_data.data;
let ToolMessage::Imaginate(event) = event else { return self };
match (self, event) {
(ImaginateToolFsmState::Ready, ImaginateToolMessage::DragStart) => {
shape_data.start(document, input);
// responses.add(DocumentMessage::AddTransaction);
//shape_data.layer = Some(LayerNodeIdentifier::new(NodeId::new(), &document.network_interface));
responses.add(DocumentMessage::DeselectAllLayers);
// // Utility function to offset the position of each consecutive node
// let mut pos = 8;
// let mut next_pos = || {
// pos += 8;
// DocumentNodeMetadata::position((pos, 4))
// };
// // Get the node type for the Transform and Imaginate nodes
// let Some(transform_node_type) = resolve_document_node_type("Transform") else {
// warn!("Transform node should be in registry");
// return ImaginateToolFsmState::Drawing;
// };
// let imaginate_node_type = &*IMAGINATE_NODE;
// // Give them a unique ID
// let transform_node_id = NodeId(100);
//let imaginate_node_id = NodeId(101);
// Create the network based on the Input -> Output passthrough default network
// let mut network = new_image_network(16, imaginate_node_id);
// // Insert the nodes into the default network
// network.insert_node(
// transform_node_id,
// transform_node_type.to_document_node_default_inputs([Some(NodeInput::node(NodeId(0), 0))], next_pos()),
// );
// network.insert_node(
// imaginate_node_id,
// imaginate_node_type.to_document_node_default_inputs([Some(NodeInput::node(transform_node_id, 0))], next_pos()),
// );
// responses.add(NodeGraphMessage::ShiftNode { node_id: imaginate_node_id });
// // Add a layer with a frame to the document
// responses.add(Operation::AddFrame {
// path: shape_data.layer.unwrap().to_path(),
// insert_index: -1,
// transform: DAffine2::ZERO.to_cols_array(),
// network,
// });
ImaginateToolFsmState::Drawing
}
(state, ImaginateToolMessage::Resize { center, lock_ratio }) => {
let message = shape_data.calculate_transform(document, input, center, lock_ratio, true);
responses.try_add(message);
state
}
(ImaginateToolFsmState::Drawing, ImaginateToolMessage::DragStop) => {
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
shape_data.cleanup(responses);
ImaginateToolFsmState::Ready
}
(ImaginateToolFsmState::Drawing, ImaginateToolMessage::Abort) => {
responses.add(DocumentMessage::AbortTransaction);
shape_data.cleanup(responses);
ImaginateToolFsmState::Ready
}
(_, ImaginateToolMessage::Abort) => ImaginateToolFsmState::Ready,
_ => self,
}
}
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
ImaginateToolFsmState::Ready => HintData(vec![HintGroup(vec![
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Repaint Frame"),
HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(),
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
])]),
ImaginateToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")])]),
};
responses.add(FrontendMessage::UpdateInputHints { hint_data });
}
fn update_cursor(&self, responses: &mut VecDeque<Message>) {
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair });
}
}

View file

@ -4,7 +4,6 @@ pub mod eyedropper_tool;
pub mod fill_tool;
pub mod freehand_tool;
pub mod gradient_tool;
// pub mod imaginate_tool;
pub mod navigate_tool;
pub mod path_tool;
pub mod pen_tool;

View file

@ -363,7 +363,6 @@ pub enum ToolType {
Patch,
Detail,
Relight,
Imaginate,
Frame,
}
@ -418,9 +417,6 @@ fn list_tools_in_groups() -> Vec<Vec<ToolAvailability>> {
ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Patch, "RasterPatchTool").tooltip("Coming Soon: Patch Tool")),
ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Detail, "RasterDetailTool").tooltip("Coming Soon: Detail Tool (D)")),
ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Relight, "RasterRelightTool").tooltip("Coming Soon: Relight Tool (O)")),
// TODO: Fix and reenable Imaginate tool
// ToolAvailability::Available(Box::<imaginate_tool::ImaginateTool>::default()),
ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Heal, "RasterImaginateTool").tooltip("Coming Soon: Imaginate Tool")),
],
]
}
@ -450,7 +446,6 @@ pub fn tool_message_to_tool_type(tool_message: &ToolMessage) -> ToolType {
// ToolMessage::Patch(_) => ToolType::Patch,
// ToolMessage::Detail(_) => ToolType::Detail,
// ToolMessage::Relight(_) => ToolType::Relight,
// ToolMessage::Imaginate(_) => ToolType::Imaginate,
_ => panic!("Conversion from ToolMessage to ToolType impossible because the given ToolMessage does not have a matching ToolType. Got: {tool_message:?}"),
}
}
@ -483,7 +478,6 @@ pub fn tool_type_to_activate_tool_message(tool_type: ToolType) -> ToolMessageDis
// ToolType::Patch => ToolMessageDiscriminant::ActivateToolPatch,
// ToolType::Detail => ToolMessageDiscriminant::ActivateToolDetail,
// ToolType::Relight => ToolMessageDiscriminant::ActivateToolRelight,
// ToolType::Imaginate => ToolMessageDiscriminant::ActivateToolImaginate,
_ => panic!("Conversion from ToolType to ToolMessage impossible because the given ToolType does not have a matching ToolMessage. Got: {tool_type:?}"),
}
}

View file

@ -299,9 +299,6 @@ impl NodeGraphExecutor {
}
}
}
// NodeGraphUpdate::NodeGraphUpdateMessage(NodeGraphUpdateMessage::ImaginateStatusUpdate) => {
// responses.add(DocumentMessage::PropertiesPanel(PropertiesPanelMessage::Refresh));
// }
NodeGraphUpdate::CompilationResponse(execution_response) => {
let CompilationResponse { node_graph_errors, result } = execution_response;
let type_delta = match result {

View file

@ -47,9 +47,6 @@ export function createEditor(): Editor {
subscriptions.handleJsMessage(messageType, messageData, raw, handle);
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).editorHandle = handle;
// Subscriptions: allows subscribing to messages in JS that are sent from the WASM backend
const subscriptions: SubscriptionRouter = createSubscriptionRouter();
@ -76,12 +73,3 @@ export function createEditor(): Editor {
return { raw, handle, subscriptions };
}
// TODO: Find a better way to do this, since no other code takes this approach.
// TODO: Then, delete the `(window as any).editorHandle = handle;` line above.
// This function is called by an FFI binding within the Rust code directly, rather than using the FrontendMessage system.
// Then, this directly calls the `injectImaginatePollServerStatus` function on the `EditorHandle` object which is a JS binding generated by wasm-bindgen, going straight back into the Rust code.
// export function injectImaginatePollServerStatus() {
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
// (window as any).editorHandle?.injectImaginatePollServerStatus();
// }

View file

@ -193,9 +193,7 @@ pub enum ApplicationError {
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum NodeGraphUpdateMessage {
// ImaginateStatusUpdate,
}
pub enum NodeGraphUpdateMessage {}
pub trait NodeGraphUpdateSender {
fn send(&self, message: NodeGraphUpdateMessage);
@ -208,7 +206,6 @@ impl<T: NodeGraphUpdateSender> NodeGraphUpdateSender for std::sync::Mutex<T> {
}
pub trait GetEditorPreferences {
// fn hostname(&self) -> &str;
fn use_vello(&self) -> bool;
}
@ -250,10 +247,6 @@ impl NodeGraphUpdateSender for Logger {
struct DummyPreferences;
impl GetEditorPreferences for DummyPreferences {
// fn hostname(&self) -> &str {
// "dummy_endpoint"
// }
fn use_vello(&self) -> bool {
false
}

View file

@ -9,7 +9,6 @@ use crate::uuid::{NodeId, generate_uuid};
use crate::vector::style::{Fill, Stroke, StrokeAlign, ViewMode};
use crate::vector::{PointId, VectorDataTable};
use crate::{Artboard, ArtboardGroupTable, Color, GraphicElement, GraphicGroupTable};
use base64::Engine;
use bezier_rs::Subpath;
use dyn_any::DynAny;
use glam::{DAffine2, DMat2, DVec2};
@ -1148,6 +1147,8 @@ impl GraphicElementRendered for RasterDataTable<CPU> {
}
let base64_string = image.base64_string.clone().unwrap_or_else(|| {
use base64::Engine;
let output = image.to_png();
let preamble = "data:image/png;base64,";
let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4);

View file

@ -248,11 +248,6 @@ tagged_value! {
ReferencePoint(graphene_core::transform::ReferencePoint),
CentroidType(graphene_core::vector::misc::CentroidType),
BooleanOperation(graphene_core::vector::misc::BooleanOperation),
// ImaginateCache(ImaginateCache),
// ImaginateSamplingMethod(ImaginateSamplingMethod),
// ImaginateMaskStartingFill(ImaginateMaskStartingFill),
// ImaginateController(ImaginateController),
}
impl TaggedValue {

View file

@ -1,279 +0,0 @@
use dyn_any::DynAny;
use graphene_core::Color;
use std::borrow::Cow;
use std::fmt::Debug;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
#[derive(Default, Debug, Clone, DynAny, specta::Type, serde::Serialize, serde::Deserialize)]
pub struct ImaginateCache(Arc<Mutex<graphene_core::raster::Image<Color>>>);
impl ImaginateCache {
pub fn into_inner(self) -> Arc<Mutex<graphene_core::raster::Image<Color>>> {
self.0
}
}
impl std::cmp::PartialEq for ImaginateCache {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl core::hash::Hash for ImaginateCache {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
let _ = self.0.try_lock().map(|g| g.hash(state)).map_err(|_| "error".hash(state));
}
}
pub trait ImaginateTerminationHandle: Debug + Send + 'static {
fn terminate(&self);
}
#[derive(Default, Debug, specta::Type, serde::Serialize, serde::Deserialize)]
struct InternalImaginateControl {
#[serde(skip)]
status: Mutex<ImaginateStatus>,
trigger_regenerate: AtomicBool,
#[serde(skip)]
#[specta(skip)]
termination_sender: Mutex<Option<Box<dyn ImaginateTerminationHandle>>>,
}
#[derive(Debug, Default, Clone, DynAny, specta::Type, serde::Serialize, serde::Deserialize)]
pub struct ImaginateController(Arc<InternalImaginateControl>);
impl ImaginateController {
pub fn get_status(&self) -> ImaginateStatus {
self.0.status.try_lock().as_deref().cloned().unwrap_or_default()
}
pub fn set_status(&self, status: ImaginateStatus) {
if let Ok(mut lock) = self.0.status.try_lock() {
*lock = status
}
}
pub fn take_regenerate_trigger(&self) -> bool {
self.0.trigger_regenerate.swap(false, Ordering::SeqCst)
}
pub fn trigger_regenerate(&self) {
self.0.trigger_regenerate.store(true, Ordering::SeqCst)
}
pub fn request_termination(&self) {
if let Some(handle) = self.0.termination_sender.try_lock().ok().and_then(|mut lock| lock.take()) {
handle.terminate()
}
}
pub fn set_termination_handle<H: ImaginateTerminationHandle>(&self, handle: Box<H>) {
if let Ok(mut lock) = self.0.termination_sender.try_lock() {
*lock = Some(handle)
}
}
}
impl std::cmp::PartialEq for ImaginateController {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl core::hash::Hash for ImaginateController {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
core::ptr::hash(Arc::as_ptr(&self.0), state)
}
}
#[derive(Default, Debug, Clone, PartialEq, DynAny, specta::Type, serde::Serialize, serde::Deserialize)]
pub enum ImaginateStatus {
#[default]
Ready,
ReadyDone,
Beginning,
Uploading,
Generating(f64),
Terminating,
Terminated,
Failed(String),
}
impl ImaginateStatus {
pub fn to_text(&self) -> Cow<'static, str> {
match self {
Self::Ready => Cow::Borrowed("Ready"),
Self::ReadyDone => Cow::Borrowed("Done"),
Self::Beginning => Cow::Borrowed("Beginning…"),
Self::Uploading => Cow::Borrowed("Downloading Image…"),
Self::Generating(percent) => Cow::Owned(format!("Generating {percent:.0}%")),
Self::Terminating => Cow::Owned("Terminating…".to_string()),
Self::Terminated => Cow::Owned("Terminated".to_string()),
Self::Failed(err) => Cow::Owned(format!("Failed: {err}")),
}
}
}
#[allow(clippy::derived_hash_with_manual_eq)]
impl core::hash::Hash for ImaginateStatus {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
match self {
Self::Ready | Self::ReadyDone | Self::Beginning | Self::Uploading | Self::Terminating | Self::Terminated => (),
Self::Generating(f) => f.to_bits().hash(state),
Self::Failed(err) => err.hash(state),
}
}
}
#[derive(PartialEq, Eq, Clone, Default, Debug)]
pub enum ImaginateServerStatus {
#[default]
Unknown,
Checking,
Connected,
Failed(String),
Unavailable,
}
impl ImaginateServerStatus {
pub fn to_text(&self) -> Cow<'static, str> {
match self {
Self::Unknown | Self::Checking => Cow::Borrowed("Checking..."),
Self::Connected => Cow::Borrowed("Connected"),
Self::Failed(err) => Cow::Owned(err.clone()),
Self::Unavailable => Cow::Borrowed("Unavailable"),
}
}
}
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, specta::Type, Hash, serde::Serialize, serde::Deserialize)]
pub enum ImaginateMaskPaintMode {
#[default]
Inpaint,
Outpaint,
}
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, DynAny, specta::Type, Hash, serde::Serialize, serde::Deserialize)]
pub enum ImaginateMaskStartingFill {
#[default]
Fill,
Original,
LatentNoise,
LatentNothing,
}
impl ImaginateMaskStartingFill {
pub fn list() -> [ImaginateMaskStartingFill; 4] {
[
ImaginateMaskStartingFill::Fill,
ImaginateMaskStartingFill::Original,
ImaginateMaskStartingFill::LatentNoise,
ImaginateMaskStartingFill::LatentNothing,
]
}
}
impl std::fmt::Display for ImaginateMaskStartingFill {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ImaginateMaskStartingFill::Fill => write!(f, "Smeared Surroundings"),
ImaginateMaskStartingFill::Original => write!(f, "Original Input Image"),
ImaginateMaskStartingFill::LatentNoise => write!(f, "Randomness (Latent Noise)"),
ImaginateMaskStartingFill::LatentNothing => write!(f, "Neutral (Latent Nothing)"),
}
}
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, DynAny, specta::Type, Hash, serde::Serialize, serde::Deserialize)]
pub enum ImaginateSamplingMethod {
#[default]
EulerA,
Euler,
LMS,
Heun,
DPM2,
DPM2A,
DPMPlusPlus2sA,
DPMPlusPlus2m,
DPMFast,
DPMAdaptive,
LMSKarras,
DPM2Karras,
DPM2AKarras,
DPMPlusPlus2sAKarras,
DPMPlusPlus2mKarras,
DDIM,
PLMS,
}
impl ImaginateSamplingMethod {
pub fn api_value(&self) -> &str {
match self {
ImaginateSamplingMethod::EulerA => "Euler a",
ImaginateSamplingMethod::Euler => "Euler",
ImaginateSamplingMethod::LMS => "LMS",
ImaginateSamplingMethod::Heun => "Heun",
ImaginateSamplingMethod::DPM2 => "DPM2",
ImaginateSamplingMethod::DPM2A => "DPM2 a",
ImaginateSamplingMethod::DPMPlusPlus2sA => "DPM++ 2S a",
ImaginateSamplingMethod::DPMPlusPlus2m => "DPM++ 2M",
ImaginateSamplingMethod::DPMFast => "DPM fast",
ImaginateSamplingMethod::DPMAdaptive => "DPM adaptive",
ImaginateSamplingMethod::LMSKarras => "LMS Karras",
ImaginateSamplingMethod::DPM2Karras => "DPM2 Karras",
ImaginateSamplingMethod::DPM2AKarras => "DPM2 a Karras",
ImaginateSamplingMethod::DPMPlusPlus2sAKarras => "DPM++ 2S a Karras",
ImaginateSamplingMethod::DPMPlusPlus2mKarras => "DPM++ 2M Karras",
ImaginateSamplingMethod::DDIM => "DDIM",
ImaginateSamplingMethod::PLMS => "PLMS",
}
}
pub fn list() -> [ImaginateSamplingMethod; 17] {
[
ImaginateSamplingMethod::EulerA,
ImaginateSamplingMethod::Euler,
ImaginateSamplingMethod::LMS,
ImaginateSamplingMethod::Heun,
ImaginateSamplingMethod::DPM2,
ImaginateSamplingMethod::DPM2A,
ImaginateSamplingMethod::DPMPlusPlus2sA,
ImaginateSamplingMethod::DPMPlusPlus2m,
ImaginateSamplingMethod::DPMFast,
ImaginateSamplingMethod::DPMAdaptive,
ImaginateSamplingMethod::LMSKarras,
ImaginateSamplingMethod::DPM2Karras,
ImaginateSamplingMethod::DPM2AKarras,
ImaginateSamplingMethod::DPMPlusPlus2sAKarras,
ImaginateSamplingMethod::DPMPlusPlus2mKarras,
ImaginateSamplingMethod::DDIM,
ImaginateSamplingMethod::PLMS,
]
}
}
impl std::fmt::Display for ImaginateSamplingMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ImaginateSamplingMethod::EulerA => write!(f, "Euler A (Recommended)"),
ImaginateSamplingMethod::Euler => write!(f, "Euler"),
ImaginateSamplingMethod::LMS => write!(f, "LMS"),
ImaginateSamplingMethod::Heun => write!(f, "Heun"),
ImaginateSamplingMethod::DPM2 => write!(f, "DPM2"),
ImaginateSamplingMethod::DPM2A => write!(f, "DPM2 A"),
ImaginateSamplingMethod::DPMPlusPlus2sA => write!(f, "DPM++ 2S a"),
ImaginateSamplingMethod::DPMPlusPlus2m => write!(f, "DPM++ 2M"),
ImaginateSamplingMethod::DPMFast => write!(f, "DPM Fast"),
ImaginateSamplingMethod::DPMAdaptive => write!(f, "DPM Adaptive"),
ImaginateSamplingMethod::LMSKarras => write!(f, "LMS Karras"),
ImaginateSamplingMethod::DPM2Karras => write!(f, "DPM2 Karras"),
ImaginateSamplingMethod::DPM2AKarras => write!(f, "DPM2 A Karras"),
ImaginateSamplingMethod::DPMPlusPlus2sAKarras => write!(f, "DPM++ 2S a Karras"),
ImaginateSamplingMethod::DPMPlusPlus2mKarras => write!(f, "DPM++ 2M Karras"),
ImaginateSamplingMethod::DDIM => write!(f, "DDIM"),
ImaginateSamplingMethod::PLMS => write!(f, "PLMS"),
}
}
}

View file

@ -317,14 +317,10 @@ pub type WasmSurfaceHandleFrame = graphene_application_io::SurfaceHandleFrame<wg
#[derive(Clone, Debug, PartialEq, Hash, specta::Type, serde::Serialize, serde::Deserialize)]
pub struct EditorPreferences {
// pub imaginate_hostname: String,
pub use_vello: bool,
}
impl graphene_application_io::GetEditorPreferences for EditorPreferences {
// fn hostname(&self) -> &str {
// &self.imaginate_hostname
// }
fn use_vello(&self) -> bool {
self.use_vello
}
@ -333,7 +329,6 @@ impl graphene_application_io::GetEditorPreferences for EditorPreferences {
impl Default for EditorPreferences {
fn default() -> Self {
Self {
// imaginate_hostname: "http://localhost:7860/".into(),
#[cfg(target_arch = "wasm32")]
use_vello: false,
#[cfg(not(target_arch = "wasm32"))]

View file

@ -7,11 +7,16 @@ authors = ["Graphite Authors <contact@graphite.rs>"]
license = "MIT OR Apache-2.0"
[features]
default = ["wasm", "imaginate"]
default = ["wasm"]
gpu = []
wgpu = ["gpu", "graph-craft/wgpu", "graphene-application-io/wgpu"]
wasm = ["wasm-bindgen", "web-sys", "graphene-application-io/wasm"]
imaginate = ["image/png", "base64", "web-sys", "wasm-bindgen-futures"]
wasm = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"graphene-application-io/wasm",
"image/png",
]
image-compare = []
vello = ["dep:vello", "gpu", "graphene-core/vello"]
resvg = []
@ -39,9 +44,9 @@ rand_chacha = { workspace = true }
rand = { workspace = true }
bytemuck = { workspace = true }
image = { workspace = true }
base64 = { workspace = true }
# Optional workspace dependencies
base64 = { workspace = true, optional = true }
wasm-bindgen = { workspace = true, optional = true }
wasm-bindgen-futures = { workspace = true, optional = true }
tokio = { workspace = true, optional = true }
@ -58,7 +63,7 @@ web-sys = { workspace = true, optional = true, features = [
"ImageBitmapRenderingContext",
] }
# Optional dependencies
# Required dependencies
ndarray = "0.16.1"
[dev-dependencies]

View file

@ -1,526 +0,0 @@
use crate::wasm_application_io::WasmEditorApi;
use core::any::TypeId;
use core::future::Future;
use futures::TryFutureExt;
use futures::future::Either;
use glam::{DVec2, U64Vec2};
use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod, ImaginateServerStatus, ImaginateStatus, ImaginateTerminationHandle};
use graph_craft::wasm_application_io::EditorPreferences;
use graphene_core::application_io::NodeGraphUpdateMessage;
use graphene_core::raster::{Color, Image, Luma, Pixel};
use image::{DynamicImage, ImageBuffer, ImageFormat};
use reqwest::Url;
const PROGRESS_EVERY_N_STEPS: u32 = 5;
const SDAPI_TEXT_TO_IMAGE: &str = "sdapi/v1/txt2img";
const SDAPI_IMAGE_TO_IMAGE: &str = "sdapi/v1/img2img";
const SDAPI_PROGRESS: &str = "sdapi/v1/progress?skip_current_image=true";
const SDAPI_TERMINATE: &str = "sdapi/v1/interrupt";
fn new_client() -> Result<reqwest::Client, Error> {
reqwest::ClientBuilder::new().build().map_err(Error::ClientBuild)
}
fn parse_url(url: &str) -> Result<Url, Error> {
url.try_into().map_err(|err| Error::UrlParse { text: url.into(), err })
}
fn join_url(base_url: &Url, path: &str) -> Result<Url, Error> {
base_url.join(path).map_err(|err| Error::UrlParse { text: base_url.to_string(), err })
}
fn new_get_request<U: reqwest::IntoUrl>(client: &reqwest::Client, url: U) -> Result<reqwest::Request, Error> {
client.get(url).header("Accept", "*/*").build().map_err(Error::RequestBuild)
}
pub struct ImaginatePersistentData {
pending_server_check: Option<futures::channel::oneshot::Receiver<reqwest::Result<reqwest::Response>>>,
host_name: Url,
client: Option<reqwest::Client>,
server_status: ImaginateServerStatus,
}
impl core::fmt::Debug for ImaginatePersistentData {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.debug_struct(core::any::type_name::<Self>())
.field("pending_server_check", &self.pending_server_check.is_some())
.field("host_name", &self.host_name)
.field("status", &self.server_status)
.finish()
}
}
impl Default for ImaginatePersistentData {
fn default() -> Self {
let server_status = ImaginateServerStatus::default();
#[cfg(not(miri))]
let mut server_status = server_status;
#[cfg(not(miri))]
let client = new_client().map_err(|err| server_status = ImaginateServerStatus::Failed(err.to_string())).ok();
#[cfg(miri)]
let client = None;
let EditorPreferences { imaginate_hostname: host_name, .. } = Default::default();
Self {
pending_server_check: None,
host_name: parse_url(&host_name).unwrap(),
client,
server_status,
}
}
}
type ImaginateFuture = core::pin::Pin<Box<dyn Future<Output = ()> + 'static>>;
impl ImaginatePersistentData {
pub fn set_host_name(&mut self, name: &str) {
match parse_url(name) {
Ok(url) => self.host_name = url,
Err(err) => self.server_status = ImaginateServerStatus::Failed(err.to_string()),
}
}
fn initiate_server_check_maybe_fail(&mut self) -> Result<Option<ImaginateFuture>, Error> {
use futures::future::FutureExt;
let Some(client) = &self.client else {
return Ok(None);
};
if self.pending_server_check.is_some() {
return Ok(None);
}
self.server_status = ImaginateServerStatus::Checking;
let url = join_url(&self.host_name, SDAPI_PROGRESS)?;
let request = new_get_request(client, url)?;
let (send, recv) = futures::channel::oneshot::channel();
let response_future = client.execute(request).map(move |r| {
let _ = send.send(r);
});
self.pending_server_check = Some(recv);
Ok(Some(Box::pin(response_future)))
}
pub fn initiate_server_check(&mut self) -> Option<ImaginateFuture> {
match self.initiate_server_check_maybe_fail() {
Ok(f) => f,
Err(err) => {
self.server_status = ImaginateServerStatus::Failed(err.to_string());
None
}
}
}
pub fn poll_server_check(&mut self) {
if let Some(mut check) = self.pending_server_check.take() {
self.server_status = match check.try_recv().map(|r| r.map(|r| r.and_then(reqwest::Response::error_for_status))) {
Ok(Some(Ok(_response))) => ImaginateServerStatus::Connected,
Ok(Some(Err(_))) | Err(_) => ImaginateServerStatus::Unavailable,
Ok(None) => {
self.pending_server_check = Some(check);
ImaginateServerStatus::Checking
}
}
}
}
pub fn server_status(&self) -> &ImaginateServerStatus {
&self.server_status
}
pub fn is_checking(&self) -> bool {
matches!(self.server_status, ImaginateServerStatus::Checking)
}
}
#[derive(Debug)]
struct ImaginateFutureAbortHandle(futures::future::AbortHandle);
impl ImaginateTerminationHandle for ImaginateFutureAbortHandle {
fn terminate(&self) {
self.0.abort()
}
}
#[derive(Debug)]
enum Error {
UrlParse { text: String, err: <&'static str as TryInto<Url>>::Error },
ClientBuild(reqwest::Error),
RequestBuild(reqwest::Error),
Request(reqwest::Error),
ResponseFormat(reqwest::Error),
NoImage,
Base64Decode(base64::DecodeError),
ImageDecode(image::error::ImageError),
ImageEncode(image::error::ImageError),
UnsupportedPixelType(&'static str),
InconsistentImageSize,
Terminated,
TerminationFailed(reqwest::Error),
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
Self::UrlParse { text, err } => write!(f, "invalid url '{text}' ({err})"),
Self::ClientBuild(err) => write!(f, "failed to create a reqwest client ({err})"),
Self::RequestBuild(err) => write!(f, "failed to create a reqwest request ({err})"),
Self::Request(err) => write!(f, "request failed ({err})"),
Self::ResponseFormat(err) => write!(f, "got an invalid API response ({err})"),
Self::NoImage => write!(f, "got an empty API response"),
Self::Base64Decode(err) => write!(f, "failed to decode base64 encoded image ({err})"),
Self::ImageDecode(err) => write!(f, "failed to decode png image ({err})"),
Self::ImageEncode(err) => write!(f, "failed to encode png image ({err})"),
Self::UnsupportedPixelType(ty) => write!(f, "pixel type `{ty}` not supported for imaginate images"),
Self::InconsistentImageSize => write!(f, "image width and height do not match the image byte size"),
Self::Terminated => write!(f, "imaginate request was terminated by the user"),
Self::TerminationFailed(err) => write!(f, "termination failed ({err})"),
}
}
}
impl std::error::Error for Error {}
#[derive(Default, Debug, Clone, serde::Deserialize)]
struct ImageResponse {
images: Vec<String>,
}
#[derive(Default, Debug, Clone, serde::Deserialize)]
struct ProgressResponse {
progress: f64,
}
#[derive(Debug, Clone, Copy, serde::Serialize)]
struct ImaginateTextToImageRequestOverrideSettings {
show_progress_every_n_steps: u32,
}
impl Default for ImaginateTextToImageRequestOverrideSettings {
fn default() -> Self {
Self {
show_progress_every_n_steps: PROGRESS_EVERY_N_STEPS,
}
}
}
#[derive(Debug, Clone, Copy, serde::Serialize)]
struct ImaginateImageToImageRequestOverrideSettings {
show_progress_every_n_steps: u32,
img2img_fix_steps: bool,
}
impl Default for ImaginateImageToImageRequestOverrideSettings {
fn default() -> Self {
Self {
show_progress_every_n_steps: PROGRESS_EVERY_N_STEPS,
img2img_fix_steps: true,
}
}
}
#[derive(Debug, Clone, serde::Serialize)]
struct ImaginateTextToImageRequest<'a> {
#[serde(flatten)]
common: ImaginateCommonImageRequest<'a>,
override_settings: ImaginateTextToImageRequestOverrideSettings,
}
#[derive(Debug, Clone, serde::Serialize)]
struct ImaginateMask {
mask: String,
mask_blur: String,
inpainting_fill: u32,
inpaint_full_res: bool,
inpainting_mask_invert: u32,
}
#[derive(Debug, Clone, serde::Serialize)]
struct ImaginateImageToImageRequest<'a> {
#[serde(flatten)]
common: ImaginateCommonImageRequest<'a>,
override_settings: ImaginateImageToImageRequestOverrideSettings,
init_images: Vec<String>,
denoising_strength: f64,
#[serde(flatten)]
mask: Option<ImaginateMask>,
}
#[derive(Debug, Clone, serde::Serialize)]
struct ImaginateCommonImageRequest<'a> {
prompt: String,
seed: f64,
steps: u32,
cfg_scale: f64,
width: f64,
height: f64,
restore_faces: bool,
tiling: bool,
negative_prompt: String,
sampler_index: &'a str,
}
#[cfg(all(feature = "imaginate", feature = "serde"))]
#[allow(clippy::too_many_arguments)]
pub async fn imaginate<'a, P: Pixel>(
image: Image<P>,
editor_api: impl Future<Output = &'a WasmEditorApi>,
controller: ImaginateController,
seed: impl Future<Output = f64>,
res: impl Future<Output = Option<DVec2>>,
samples: impl Future<Output = u32>,
sampling_method: impl Future<Output = ImaginateSamplingMethod>,
prompt_guidance: impl Future<Output = f64>,
prompt: impl Future<Output = String>,
negative_prompt: impl Future<Output = String>,
adapt_input_image: impl Future<Output = bool>,
image_creativity: impl Future<Output = f64>,
inpaint: impl Future<Output = bool>,
mask_blur: impl Future<Output = f64>,
mask_starting_fill: impl Future<Output = ImaginateMaskStartingFill>,
improve_faces: impl Future<Output = bool>,
tiling: impl Future<Output = bool>,
) -> Image<P> {
let WasmEditorApi {
node_graph_message_sender,
editor_preferences,
..
} = editor_api.await;
let set_progress = |progress: ImaginateStatus| {
controller.set_status(progress);
node_graph_message_sender.send(NodeGraphUpdateMessage::ImaginateStatusUpdate);
};
let host_name = editor_preferences.hostname();
imaginate_maybe_fail(
image,
host_name,
set_progress,
&controller,
seed,
res,
samples,
sampling_method,
prompt_guidance,
prompt,
negative_prompt,
adapt_input_image,
image_creativity,
inpaint,
mask_blur,
mask_starting_fill,
improve_faces,
tiling,
)
.await
.unwrap_or_else(|err| {
match err {
Error::Terminated => {
set_progress(ImaginateStatus::Terminated);
}
err => {
error!("{err}");
set_progress(ImaginateStatus::Failed(err.to_string()));
}
};
Image::default()
})
}
#[cfg(all(feature = "imaginate", feature = "serde"))]
#[allow(clippy::too_many_arguments)]
async fn imaginate_maybe_fail<P: Pixel, F: Fn(ImaginateStatus)>(
image: Image<P>,
host_name: &str,
set_progress: F,
controller: &ImaginateController,
seed: impl Future<Output = f64>,
res: impl Future<Output = Option<DVec2>>,
samples: impl Future<Output = u32>,
sampling_method: impl Future<Output = ImaginateSamplingMethod>,
prompt_guidance: impl Future<Output = f64>,
prompt: impl Future<Output = String>,
negative_prompt: impl Future<Output = String>,
adapt_input_image: impl Future<Output = bool>,
image_creativity: impl Future<Output = f64>,
_inpaint: impl Future<Output = bool>,
_mask_blur: impl Future<Output = f64>,
_mask_starting_fill: impl Future<Output = ImaginateMaskStartingFill>,
improve_faces: impl Future<Output = bool>,
tiling: impl Future<Output = bool>,
) -> Result<Image<P>, Error> {
set_progress(ImaginateStatus::Beginning);
let base_url: Url = parse_url(host_name)?;
let client = new_client()?;
let sampler_index = sampling_method.await;
let sampler_index = sampler_index.api_value();
let res = res.await.unwrap_or_else(|| {
let (width, height) = pick_safe_imaginate_resolution((image.width as _, image.height as _));
DVec2::new(width as _, height as _)
});
let common_request_data = ImaginateCommonImageRequest {
prompt: prompt.await,
seed: seed.await,
steps: samples.await,
cfg_scale: prompt_guidance.await,
width: res.x,
height: res.y,
restore_faces: improve_faces.await,
tiling: tiling.await,
negative_prompt: negative_prompt.await,
sampler_index,
};
let request_builder = if adapt_input_image.await {
let base64_data = image_to_base64(image)?;
let request_data = ImaginateImageToImageRequest {
common: common_request_data,
override_settings: Default::default(),
init_images: vec![base64_data],
denoising_strength: image_creativity.await * 0.01,
mask: None,
};
let url = join_url(&base_url, SDAPI_IMAGE_TO_IMAGE)?;
client.post(url).json(&request_data)
} else {
let request_data = ImaginateTextToImageRequest {
common: common_request_data,
override_settings: Default::default(),
};
let url = join_url(&base_url, SDAPI_TEXT_TO_IMAGE)?;
client.post(url).json(&request_data)
};
let request = request_builder.header("Accept", "*/*").build().map_err(Error::RequestBuild)?;
let (response_future, abort_handle) = futures::future::abortable(client.execute(request));
controller.set_termination_handle(Box::new(ImaginateFutureAbortHandle(abort_handle)));
let progress_url = join_url(&base_url, SDAPI_PROGRESS)?;
futures::pin_mut!(response_future);
let response = loop {
let progress_request = new_get_request(&client, progress_url.clone())?;
let progress_response_future = client.execute(progress_request).and_then(|response| response.json());
futures::pin_mut!(progress_response_future);
response_future = match futures::future::select(response_future, progress_response_future).await {
Either::Left((response, _)) => break response,
Either::Right((progress, response_future)) => {
if let Ok(ProgressResponse { progress }) = progress {
set_progress(ImaginateStatus::Generating(progress * 100.));
}
response_future
}
};
};
let response = match response {
Ok(response) => response.and_then(reqwest::Response::error_for_status).map_err(Error::Request)?,
Err(_aborted) => {
set_progress(ImaginateStatus::Terminating);
let url = join_url(&base_url, SDAPI_TERMINATE)?;
let request = client.post(url).build().map_err(Error::RequestBuild)?;
// The user probably doesn't really care if the server side was really aborted or if there was an network error.
// So we fool them that the request was terminated if the termination request in reality failed.
let _ = client.execute(request).await.and_then(reqwest::Response::error_for_status).map_err(Error::TerminationFailed)?;
return Err(Error::Terminated);
}
};
set_progress(ImaginateStatus::Uploading);
let ImageResponse { images } = response.json().await.map_err(Error::ResponseFormat)?;
let result = images.into_iter().next().ok_or(Error::NoImage).and_then(base64_to_image)?;
set_progress(ImaginateStatus::ReadyDone);
Ok(result)
}
fn image_to_base64<P: Pixel>(image: Image<P>) -> Result<String, Error> {
use base64::prelude::*;
let Image { width, height, data, .. } = image;
fn cast_with_f32<S: Pixel, D: image::Pixel<Subpixel = f32>>(data: Vec<S>, width: u32, height: u32) -> Result<DynamicImage, Error>
where
DynamicImage: From<ImageBuffer<D, Vec<f32>>>,
{
ImageBuffer::<D, Vec<f32>>::from_raw(width, height, bytemuck::cast_vec(data))
.ok_or(Error::InconsistentImageSize)
.map(Into::into)
}
let image: DynamicImage = match TypeId::of::<P>() {
id if id == TypeId::of::<Color>() => cast_with_f32::<_, image::Rgba<f32>>(data, width, height)?
// we need to do this cast, because png does not support rgba32f
.to_rgba16().into(),
id if id == TypeId::of::<Luma>() => cast_with_f32::<_, image::Luma<f32>>(data, width, height)?
// we need to do this cast, because png does not support luma32f
.to_luma16().into(),
_ => return Err(Error::UnsupportedPixelType(core::any::type_name::<P>())),
};
let mut png_data = std::io::Cursor::new(vec![]);
image.write_to(&mut png_data, ImageFormat::Png).map_err(Error::ImageEncode)?;
Ok(BASE64_STANDARD.encode(png_data.into_inner()))
}
fn base64_to_image<D: AsRef<[u8]>, P: Pixel>(base64_data: D) -> Result<Image<P>, Error> {
use base64::prelude::*;
let png_data = BASE64_STANDARD.decode(base64_data).map_err(Error::Base64Decode)?;
let dyn_image = image::load_from_memory_with_format(&png_data, image::ImageFormat::Png).map_err(Error::ImageDecode)?;
let (width, height) = (dyn_image.width(), dyn_image.height());
let result_data: Vec<P> = match TypeId::of::<P>() {
id if id == TypeId::of::<Color>() => bytemuck::cast_vec(dyn_image.into_rgba32f().into_raw()),
id if id == TypeId::of::<Luma>() => bytemuck::cast_vec(dyn_image.to_luma32f().into_raw()),
_ => return Err(Error::UnsupportedPixelType(core::any::type_name::<P>())),
};
Ok(Image {
data: result_data,
width,
height,
base64_string: None,
})
}
pub fn pick_safe_imaginate_resolution((width, height): (f64, f64)) -> (u64, u64) {
const NATIVE_MODEL_RESOLUTION: f64 = 512.;
let size = if width * height == 0. { DVec2::splat(NATIVE_MODEL_RESOLUTION) } else { DVec2::new(width, height) };
const MAX_RESOLUTION: u64 = 1000 * 1000;
// This is the maximum width/height that can be obtained
const MAX_DIMENSION: u64 = (MAX_RESOLUTION / 64) & !63;
// Round the resolution to the nearest multiple of 64
let size = (size.round().clamp(DVec2::ZERO, DVec2::splat(MAX_DIMENSION as _)).as_u64vec2() + U64Vec2::splat(32)).max(U64Vec2::splat(64)) & !U64Vec2::splat(63);
let resolution = size.x * size.y;
if resolution > MAX_RESOLUTION {
// Scale down the image, so it is smaller than MAX_RESOLUTION
let scale = (MAX_RESOLUTION as f64 / resolution as f64).sqrt();
let size = size.as_dvec2() * scale;
if size.x < 64. {
// The image is extremely wide
(64, MAX_DIMENSION)
} else if size.y < 64. {
// The image is extremely high
(MAX_DIMENSION, 64)
} else {
// Round down to a multiple of 64, so that the resolution still is smaller than MAX_RESOLUTION
(size.as_u64vec2() & !U64Vec2::splat(63)).into()
}
} else {
size.into()
}
}

View file

@ -305,103 +305,6 @@ fn image_value(_: impl Ctx, _primary: (), image: RasterDataTable<CPU>) -> Raster
image
}
// macro_rules! generate_imaginate_node {
// ($($val:ident: $t:ident: $o:ty,)*) => {
// pub struct ImaginateNode<P: Pixel, E, C, G, $($t,)*> {
// editor_api: E,
// controller: C,
// generation_id: G,
// $($val: $t,)*
// cache: std::sync::Arc<std::sync::Mutex<HashMap<u64, Image<P>>>>,
// last_generation: std::sync::atomic::AtomicU64,
// }
// impl<'e, P: Pixel, E, C, G, $($t,)*> ImaginateNode<P, E, C, G, $($t,)*>
// where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)*
// E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, &'e WasmEditorApi>>,
// C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>,
// G: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, u64>>,
// {
// #[allow(clippy::too_many_arguments)]
// pub fn new(editor_api: E, controller: C, $($val: $t,)* generation_id: G ) -> Self {
// Self { editor_api, controller, generation_id, $($val,)* cache: Default::default(), last_generation: std::sync::atomic::AtomicU64::new(u64::MAX) }
// }
// }
// impl<'i, 'e: 'i, P: Pixel + 'i + Hash + Default + Send, E: 'i, C: 'i, G: 'i, $($t: 'i,)*> Node<'i, RasterData<P>> for ImaginateNode<P, E, C, G, $($t,)*>
// where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)*
// E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, &'e WasmEditorApi>>,
// C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>,
// G: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, u64>>,
// {
// type Output = DynFuture<'i, RasterData<P>>;
// fn eval(&'i self, frame: RasterData<P>) -> Self::Output {
// let controller = self.controller.eval(());
// $(let $val = self.$val.eval(());)*
// use std::hash::Hasher;
// let mut hasher = rustc_hash::FxHasher::default();
// frame.image.hash(&mut hasher);
// let hash = hasher.finish();
// let editor_api = self.editor_api.eval(());
// let cache = self.cache.clone();
// let generation_future = self.generation_id.eval(());
// let last_generation = &self.last_generation;
// Box::pin(async move {
// let controller: ImaginateController = controller.await;
// let generation_id = generation_future.await;
// if generation_id != last_generation.swap(generation_id, std::sync::atomic::Ordering::SeqCst) {
// let image = super::imaginate::imaginate(frame.image, editor_api, controller, $($val,)*).await;
// cache.lock().unwrap().insert(hash, image.clone());
// return wrap_image_frame(image, frame.transform);
// }
// let image = cache.lock().unwrap().get(&hash).cloned().unwrap_or_default();
// return wrap_image_frame(image, frame.transform);
// })
// }
// }
// }
// }
// fn wrap_image_frame<P: Pixel>(image: Image<P>, transform: DAffine2) -> RasterData<P> {
// if !transform.decompose_scale().abs_diff_eq(DVec2::ZERO, 0.00001) {
// RasterData {
// image,
// transform,
// alpha_blending: AlphaBlending::default(),
// }
// } else {
// let resolution = DVec2::new(image.height as f64, image.width as f64);
// RasterData {
// image,
// transform: DAffine2::from_scale_angle_translation(resolution, 0., transform.translation),
// alpha_blending: AlphaBlending::default(),
// }
// }
// }
// generate_imaginate_node! {
// seed: Seed: f64,
// res: Res: Option<DVec2>,
// samples: Samples: u32,
// sampling_method: SamplingMethod: ImaginateSamplingMethod,
// prompt_guidance: PromptGuidance: f64,
// prompt: Prompt: String,
// negative_prompt: NegativePrompt: String,
// adapt_input_image: AdaptInputImage: bool,
// image_creativity: ImageCreativity: f64,
// inpaint: Inpaint: bool,
// mask_blur: MaskBlur: f64,
// mask_starting_fill: MaskStartingFill: ImaginateMaskStartingFill,
// improve_faces: ImproveFaces: bool,
// tiling: Tiling: bool,
// }
#[node_macro::node(category("Raster: Pattern"))]
#[allow(clippy::too_many_arguments)]
fn noise_pattern(

View file

@ -117,10 +117,6 @@ Always on the bleeding edge and built to last— Graphite is written on a robust
<img class="atlas" style="--atlas-index: 8" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
<span>Procedural vector editing and usability</span>
</div>
<!-- <div class="feature-icon ongoing" title="Development Ongoing">
<img class="atlas" style="--atlas-index: 0" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
<span>Imaginate tool</span>
</div> -->
<!-- Alpha 4 -->
<div class="feature-icon ongoing heading" title="Began February 2025" data-year="2025">
<h3>— Alpha 4 —</h3>
@ -209,6 +205,10 @@ Always on the bleeding edge and built to last— Graphite is written on a robust
<img class="atlas" style="--atlas-index: 17" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
<span>Stable document format</span>
</div>
<!-- <div class="feature-icon ongoing" title="Development Ongoing">
<img class="atlas" style="--atlas-index: 0" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
<span>Imaginate tool</span>
</div> -->
<!-- Beta -->
<div class="feature-icon heading">
<h3>— Beta —</h3>

View file

@ -57,8 +57,6 @@ Raster image editing is a growing capability that will develop over time into th
A prototype <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/brush-tool-icon.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="" style="vertical-align: bottom" /> Brush tool exists letting you draw simple doodles and sketches. However it is very limited in its capabilities and there are multiple bugs and performance issues with the feature. It can be used in a basic capacity, but don't expect to paint anything too impressive using raster brushes quite yet. The tool will be fully rewritten in the future.
<!-- The raster-based Imaginate feature enables you to synthesize artwork using generative AI based on text descriptions. With it, you can also nondestructively modify your vector art and imported images. You can inpaint (or outpaint) the content in a specific masked part of an image or use it to touch up quick-and-dirty compositions. This feature is temporarily out of comission but will be resurrected, and further improved upon, in the near future. -->
## Status and limitations
Please make yourself aware of these factors to better understand and work around the rough edges in today's Graphite editor.

View file

@ -163,7 +163,6 @@ messages
│   │   ├── fill_tool.rs
│   │   ├── freehand_tool.rs
│   │   ├── gradient_tool.rs
│   │   ├── imaginate_tool.rs
│   │   ├── line_tool.rs
│   │   ├── navigate_tool.rs
│   │   ├── path_tool.rs