Merge pull request #1776 from ramox81/codex/imageadjust-dialog

IMAGEADJUST: Image Adjust dialog for raster images
This commit is contained in:
Hakan Seven committed 2026-10-14 12:01:13 +03:00
commit ff5833c35e
32 files changed
+757 -2

No files matched your search

+2 -1
View File
@@ -80,7 +80,7 @@ const WEB_ALIAS_KEY: &str = "opencadstudio.aliases";
/// migrated forward (see `introduced_at` / `migrate_aliases`). Version 1 shipped
/// the pre-REDRAW table; version 2 introduced the REDRAW-family aliases; version 3
/// introduced HB (HATCHTOBACK); version 4 introduced ER (EXTERNALREFERENCES).
const DEFAULT_ALIASES_VERSION: u32 = 8;
const DEFAULT_ALIASES_VERSION: u32 = 9;
#[cfg(target_arch = "wasm32")]
const WEB_ALIAS_VERSION_KEY: &str = "opencadstudio.aliases.version";
@@ -103,6 +103,7 @@ fn introduced_at(version: u32) -> &'static [(&'static str, &'static str)] {
6 => &[("VV", "QLAYER")],
7 => &[("DV", "DVIEW")],
8 => &[("-XR", "-XREF"), ("XC", "XCLIP"), ("ICL", "IMAGECLIP")],
9 => &[("IAD", "IMAGEADJUST")],
_ => &[],
}
}
+121
View File
@@ -1391,6 +1391,40 @@ impl OpenCADStudio {
return Some(Task::none());
}
// IMAGEADJUST: chosen images (pickfirst first), then the dialog.
"IMAGEADJUST" => {
let keep = crate::modules::insert::xadjust::keeps("I");
let chosen: Vec<codec::Handle> = self.tabs[i]
.scene
.selected_entities()
.into_iter()
.filter(|(_, e)| keep(e))
.map(|(h, _)| h)
.collect();
if chosen.is_empty() {
self.tabs[i].scene.deselect_all();
self.refresh_properties();
let command = crate::modules::draw::select::SelectObjectsCommand::filtered(
"IMAGEADJUST",
"_IMAGEADJUSTSEL",
crate::modules::insert::xadjust::select_prompt("I"),
keep,
);
use crate::command::CadCommand;
self.command_line.push_info(&command.prompt());
self.command_line.set_step_options(command.options());
self.tabs[i].active_cmd = Some(Box::new(command));
} else {
self.tabs[i]
.scene
.replace_selection(chosen.into_iter().collect());
return Some(self.dispatch_command("_IMAGEADJUSTSEL"));
}
}
"_IMAGEADJUSTSEL" => {
self.open_image_adjust(i);
return Some(self.finish_dispatch("IMAGEADJUST"));
}
// ADJUST / -IMAGEADJUST / PDFADJUST / DWFADJUST / DGNADJUST:
// chosen images or underlays (pickfirst first), then the option
// and the value.
@@ -2253,6 +2287,93 @@ impl OpenCADStudio {
self.command_line.set_step_options(command.options());
self.tabs[i].active_cmd = Some(Box::new(command));
}
/// The Image Adjust dialog for the chosen images, starting from the
/// first one's values; nothing opens without an image.
fn open_image_adjust(&mut self, i: usize) {
let keep = crate::modules::insert::xadjust::keeps("I");
let scene = &self.tabs[i].scene;
let images: Vec<(codec::Handle, codec::EntityType)> = scene
.selected_entities()
.into_iter()
.filter(|(h, e)| keep(e) && !scene.is_layer_locked(*h))
.map(|(h, e)| (h, e.clone()))
.collect();
let Some((_, codec::EntityType::RasterImage(first))) = images.first() else {
return;
};
let canvas = scene.bg_color;
let state = crate::ui::window::pdf_dialogs::ImageAdjustState::new(
images.iter().map(|(h, _)| *h).collect(),
&first.file_path,
[first.brightness, first.contrast, first.fade],
[0, 1, 2].map(|k| (canvas[k].clamp(0.0, 1.0) * 255.0).round() as u8),
);
self.image_adjust = Some(state);
self.active_modal = Some(crate::app::ModalKind::ImageAdjust);
}
pub(in crate::app) fn update_image_adjust(
&mut self,
message: crate::ui::window::pdf_dialogs::PdfDialogMsg,
) -> Task<Message> {
use crate::ui::window::pdf_dialogs::{PdfDialogMsg as M, IMAGE_ADJUST_DEFAULTS};
let i = self.active_tab;
let Some(state) = self.image_adjust.as_mut() else {
return Task::none();
};
match message {
M::IaValue(row, value) => state.set(row as usize, value),
M::IaStep(row, dir) => {
let k = row as usize;
state.set(k, state.values[k].saturating_add_signed(dir.signum()));
}
// A box takes whole numbers 0–100 only; anything else is not
// typed in. An emptied box keeps the value until a number comes.
M::IaText(row, typed) => {
let k = row as usize;
let typed = typed.trim().to_string();
if typed.is_empty() {
state.texts[k] = typed;
} else if let Some(value) = typed
.parse::<u8>()
.ok()
.filter(|v| *v <= 100 && typed.chars().all(|c| c.is_ascii_digit()))
{
state.set(k, value);
}
}
M::IaReset => state.set_all(IMAGE_ADJUST_DEFAULTS),
M::IaApply => {
let Some(state) = self.image_adjust.take() else {
return Task::none();
};
self.close_active_modal();
let [brightness, contrast, fade] = state.values;
self.push_undo_snapshot(i, "IMAGEADJUST");
let mut changed = Vec::new();
for h in &state.handles {
if let Some(codec::EntityType::RasterImage(img)) =
self.tabs[i].scene.document.get_entity_mut(*h)
{
img.brightness = brightness;
img.contrast = contrast;
img.fade = fade;
self.tabs[i].scene.reseed_derived_caches(*h);
changed.push((*h, crate::scene::ChangeKind::Modified));
}
}
if !changed.is_empty() {
self.tabs[i].scene.bump_entities(&changed);
self.tabs[i].dirty = true;
}
self.tabs[i].scene.deselect_all();
self.refresh_properties();
}
_ => {}
}
Task::none()
}
}
#[cfg(test)]
+6
View File
@@ -437,6 +437,7 @@ impl OpenCADStudio {
"pointcloud" => crate::t!("Attaches a point cloud scan or project."),
"pcsection" => crate::t!("Traces the points on the kept side of a live section as lines."),
"background" => std::borrow::Cow::Owned(crate::tr!("view-background", "help")),
"imageadjust" => std::borrow::Cow::Owned(crate::tr!("image-adjust", "help")),
"pointcloudcolormap" => {
crate::t!("Sets the color schemes and ranges a point cloud is colored by intensity or elevation.")
}
@@ -584,6 +585,11 @@ impl OpenCADStudio {
| PdfDialogMsg::BgStep(..)
| PdfDialogMsg::BgReset
| PdfDialogMsg::BgApply) => return self.update_view_background(message),
message @ (PdfDialogMsg::IaValue(..)
| PdfDialogMsg::IaText(..)
| PdfDialogMsg::IaStep(..)
| PdfDialogMsg::IaReset
| PdfDialogMsg::IaApply) => return self.update_image_adjust(message),
message @ (PdfDialogMsg::SecPerimeter(_)
| PdfDialogMsg::SecMaxPoints(_)
| PdfDialogMsg::SecLayer(_)
+38
View File
@@ -104,6 +104,7 @@ pub(super) const NAMES: &[&str] = &[
"external_change_cancel",
"pdf_dialog_ok",
"view_background",
"image_adjust",
"pdf_layer_toggle",
"pc_manager_toggle",
"pc_manager",
@@ -1958,6 +1959,43 @@ impl OpenCADStudio {
_ => return Err(bad()),
}
}
// The Image Adjust dialog, one field per call: "brightness=70",
// "contrast=40", "fade=20" (the bar), "type=fade,abc" (typed into
// the box), "step=brightness,+1|-1", "reset", "apply" or "cancel".
"image_adjust" => {
use crate::ui::window::pdf_dialogs::PdfDialogMsg as P;
if self.active_modal != Some(crate::app::ModalKind::ImageAdjust) {
return Err(failure("no_dialog", "The Image Adjust dialog is not open"));
}
let spec = string(req, "value")?;
let (key, value) = spec.split_once('=').unwrap_or((spec, ""));
let bad = || failure("invalid_value", spec);
let row = |name: &str| match name.trim() {
"brightness" => Ok(0u8),
"contrast" => Ok(1),
"fade" => Ok(2),
_ => Err(bad()),
};
match key {
"cancel" => Message::CloseModal,
"apply" => Message::PdfDialog(P::IaApply),
"reset" => Message::PdfDialog(P::IaReset),
"brightness" | "contrast" | "fade" => {
let v: u8 = value.trim().parse().ok().filter(|v| *v <= 100).ok_or_else(bad)?;
Message::PdfDialog(P::IaValue(row(key)?, v))
}
"type" => {
let (name, typed) = value.split_once(',').ok_or_else(bad)?;
Message::PdfDialog(P::IaText(row(name)?, typed.to_string()))
}
"step" => {
let (name, dir) = value.split_once(',').ok_or_else(bad)?;
let dir: i8 = dir.trim().parse().map_err(|_| bad())?;
Message::PdfDialog(P::IaStep(row(name)?, dir))
}
_ => return Err(bad()),
}
}
"pdf_dialog_ok" => {
use crate::ui::window::pdf_dialogs::PdfDialogMsg;
Message::PdfDialog(match self.active_modal {
+4
View File
@@ -584,6 +584,8 @@ pub(super) struct OpenCADStudio {
pc_section: Option<crate::ui::window::pdf_dialogs::PcSectionState>,
/// -VIEW sEttings Background dialog.
view_background: Option<crate::ui::window::pdf_dialogs::ViewBackgroundState>,
/// IMAGEADJUST dialog.
image_adjust: Option<crate::ui::window::pdf_dialogs::ImageAdjustState>,
underlay_layers: Option<crate::ui::window::pdf_dialogs::UnderlayLayersState>,
pdf_import_settings: Option<crate::modules::insert::pdf_import::PdfImportSettings>,
pdf_import_file: Option<crate::ui::window::pdf_dialogs::PdfImportFileState>,
@@ -2155,6 +2157,7 @@ pub enum ModalKind {
PointCloudColorMap,
PcSection,
ViewBackground,
ImageAdjust,
UnderlayLayers,
PdfImportSettings,
PdfImportFile,
@@ -4565,6 +4568,7 @@ impl OpenCADStudio {
point_cloud_color_map: None,
pc_section: None,
view_background: None,
image_adjust: None,
underlay_layers: None,
pdf_import_settings: None,
pdf_import_file: None,
+3
View File
@@ -9483,6 +9483,9 @@ impl OpenCADStudio {
let i = self.active_tab;
return self.run_dash_view("SETTINGS", i);
}
if self.active_modal == Some(super::ModalKind::ImageAdjust) {
self.image_adjust = None;
}
if self.active_modal == Some(super::ModalKind::Field) {
self.close_field_dialog();
return Task::none();
+7
View File
@@ -40,6 +40,7 @@ impl OpenCADStudio {
crate::tr!("view-background", "title"),
self.view_background.as_ref().map_or("", |s| s.view.as_str())
),
Some(K::ImageAdjust) => crate::tr!("image-adjust", "title"),
Some(K::UnderlayLayers) => crate::t!("Underlay Layers").into_owned(),
Some(K::PdfImportSettings) => crate::t!("PDF Import Settings").into_owned(),
Some(K::PdfImportFile) => crate::t!("Import PDF").into_owned(),
@@ -535,6 +536,12 @@ impl OpenCADStudio {
crate::ui::window::pdf_dialogs::view_background(state, flow)
})
}
super::super::ModalKind::ImageAdjust => {
let state = self.image_adjust.as_ref()?;
sized_flow(ex, 520, 560, |flow| {
crate::ui::window::pdf_dialogs::image_adjust(state, flow)
})
}
super::super::ModalKind::UnderlayLayers => {
let state = self.underlay_layers.as_ref()?;
sized_flow(ex, 460, 520, |flow| {