From 44b828629d7ef397a9c802ea71aecd30a996b95f Mon Sep 17 00:00:00 2001 From: Don Omondi <282319807+mockydevs@users.noreply.github.com> Date: Wed, 4 Nov 2026 23:30:24 -0500 Subject: [PATCH] Rasterize: Color Model is a dropdown of the document's model, Grayscale and Bitmap (#5660) --- crates/ui-egui/src/dialogs/command.rs | 72 ++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/src/dialogs/command.rs b/crates/ui-egui/src/dialogs/command.rs index ef781fb43..0a722cbcf 100644 --- a/crates/ui-egui/src/dialogs/command.rs +++ b/crates/ui-egui/src/dialogs/command.rs @@ -21,7 +21,9 @@ pub(super) const SPEC: DialogSpec = DialogSpec { // Area Type Options: a Row Gutter still as it opened, on rows without a gutter of their own, // follows the Gutter as it is changed. let linked = d.fields.get("__rowGutterLinked").is_some() && d.fields.get("__rowGutterLinked") == d.fields.get("rowGutter"); - form::param_fields(ui, d, &|k| lengths.contains(&k), &|k| choices(&command, k), &rank, app.session.general_unit()); + let cmyk = app.session.active().is_some_and(|s| s.doc.color_mode == vectorcraft_doc::ColorMode::Cmyk); + let choices = |k: &str| rasterize_color_models(&command, k, cmyk).or_else(|| choices(&command, k)); + form::param_fields(ui, d, &|k| lengths.contains(&k), &choices, &rank, app.session.general_unit()); if linked && d.fields.get("rowGutter") == d.fields.get("__rowGutterLinked") && let Some(g) = d.fields.get("gutter").cloned() @@ -138,6 +140,19 @@ fn choices(command: &str, key: &str) -> Option { } } +/// Object › Rasterize › Color Model: the values `object.rasterize` takes, the document's own +/// colour model first (#5660). +fn rasterize_color_models(command: &str, key: &str, cmyk: bool) -> Option { + match (command, key) { + ("object.rasterize", "colorModel") => Some(if cmyk { RASTERIZE_CMYK } else { RASTERIZE_RGB }), + _ => None, + } +} + +/// Object › Rasterize › Color Model in an RGB and in a CMYK document. +const RASTERIZE_RGB: form::Choices = &[("RGB", "rgb"), ("Grayscale", "grayscale"), ("Bitmap", "bitmap")]; +const RASTERIZE_CMYK: form::Choices = &[("CMYK", "cmyk"), ("Grayscale", "grayscale"), ("Bitmap", "bitmap")]; + /// Type on a Path Options › Align to Path. const PATH_ALIGN: form::Choices = &[("Ascender", "ascender"), ("Descender", "descender"), ("Center", "center"), ("Baseline", "baseline")]; @@ -287,6 +302,61 @@ mod tests { assert_eq!(t.area.flow, vectorcraft_doc::TextFlow::Rows); } + /// Object › Rasterize › Color Model is a dropdown of the models `object.rasterize` takes, the + /// document's own first (#5660): every value runs, and picking Grayscale rasterizes in grays. + #[test] + fn rasterize_color_model_is_a_dropdown_of_the_models_the_command_takes() { + use egui::accesskit::Role; + use egui_kittest::kittest::Queryable; + for (mode, own) in [("rgb", "RGB"), ("cmyk", "CMYK")] { + let models = rasterize_color_models("object.rasterize", "colorModel", mode == "cmyk").unwrap(); + assert_eq!(models.first(), Some(&(own, mode)), "the document's own model first"); + for (_, value) in models { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 100, "height": 100, "colorMode": mode})).unwrap(); + s.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 20, "height": 20})).unwrap(); + s.execute("object.rasterize", &json!({"ppi": 72, "colorModel": value})).unwrap_or_else(|e| panic!("{mode} {value}: {e}")); + } + } + assert!(rasterize_color_models("object.rasterize", "background", false).is_none()); + assert!(rasterize_color_models("document.rasterEffectsSettings", "colorModel", false).is_none()); + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 200, "height": 200})).unwrap(); + let id = app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 20, "height": 20})).unwrap()["id"].as_u64().unwrap(); + app.run("paint.setFill", json!({"color": "#ff0000", "ids": [id]})).unwrap(); + app.run("select.set", json!({"ids": [id]})).unwrap(); + crate::menus::invoke(&mut app, "object.rasterize", json!({})); + assert_eq!(app.ui.dialog.as_ref().unwrap().str("colorModel"), "rgb"); + let mut fonts = false; + let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1100.0, 900.0)).build_ui_state( + move |ui, app: &mut VectorcraftApp| { + if !std::mem::replace(&mut fonts, true) { + crate::theme::install_fonts(ui.ctx()); + return; + } + crate::dialogs::show(app, ui.ctx()); + }, + app, + ); + h.run_steps(5); + assert!(h.query_by_role_and_label(Role::TextInput, "Color Model:").is_none(), "not a text field"); + h.get_by_role_and_label(Role::ComboBox, "Color Model:").click(); + h.run_steps(3); + h.get_by_label("Grayscale").click(); + h.run_steps(3); + assert_eq!(h.state().ui.dialog.as_ref().unwrap().str("colorModel"), "grayscale"); + let app = h.state_mut(); + crate::dialogs::confirm(app).unwrap(); + let st = app.session.active().unwrap(); + let key = st.selection.objects.iter().find_map(|id| match st.doc.node(*id).map(|n| &n.kind) { + Some(NodeKind::Image(i)) => Some(i.key.clone()), + _ => None, + }); + let img = image::load_from_memory(&st.doc.images[&key.expect("the rectangle became an image")].bytes).unwrap().to_rgba8(); + let p = img.get_pixel(img.width() / 2, img.height() / 2).0; + assert!(p[0] == p[1] && p[1] == p[2] && p[0] < 128, "red in grayscale: {p:?}"); + } + /// Graph Type › Value Axis is a dropdown of the values `graph.setType` takes, and OK puts the /// value axis on the side picked. #[test]