diff --git a/assets/ocad.pgp b/assets/ocad.pgp index 54f24f703..6c6a6b92c 100644 --- a/assets/ocad.pgp +++ b/assets/ocad.pgp @@ -74,6 +74,7 @@ XR, *XREF -XR, *-XREF XC, *XCLIP ICL, *IMAGECLIP +IAD, *IMAGEADJUST ADC, *ADCENTER W, *WBLOCK diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index a4c3d8d3d..7136bbfb1 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -6514,3 +6514,18 @@ ref-edit = .redefined = تمت إعادة تعريف __ocs_fmt_0__. .ok = موافق .nested = متداخلة + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index 068d07c73..1ac16535b 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -6502,3 +6502,18 @@ ref-edit = .redefined = __ocs_fmt_0__ е предефиниран. .ok = Ок .nested = Вложени + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 11d89aeb8..104c6e138 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -6499,3 +6499,18 @@ ref-edit = .redefined = __ocs_fmt_0__ předefinován. .ok = Ok .nested = Vnořené + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index d977c6fb8..446ac53cd 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -6500,3 +6500,18 @@ ref-edit = .redefined = __ocs_fmt_0__ neu definiert. .ok = Ok .nested = Verschachtelt + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index 6355853a7..c61864000 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -6502,3 +6502,18 @@ ref-edit = .redefined = Το __ocs_fmt_0__ επαναορίστηκε. .ok = Ok .nested = Ένθετα + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index cc3a6dd29..254be731f 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -6501,3 +6501,18 @@ ref-edit = .redefined = __ocs_fmt_0__ redefined. .ok = Ok .nested = Nested + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 136e8ac6e..7891c3899 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -6502,3 +6502,18 @@ ref-edit = .redefined = __ocs_fmt_0__ redefinido. .ok = Aceptar .nested = Anidados + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index ba3fd03be..6f5a4859c 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -6499,3 +6499,18 @@ ref-edit = .redefined = __ocs_fmt_0__ määritetty uudelleen. .ok = Ok .nested = Sisäkkäiset + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 3403ade30..d06265334 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -6502,3 +6502,18 @@ ref-edit = .redefined = __ocs_fmt_0__ redéfini. .ok = Ok .nested = Imbriqués + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index 4d7a49907..64b6f0b50 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -6490,3 +6490,18 @@ ref-edit = .redefined = __ocs_fmt_0__ पुनः परिभाषित किया गया। .ok = ठीक .nested = नेस्टेड + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 5e313d26b..b2bcd0ce3 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -6486,3 +6486,18 @@ ref-edit = .redefined = __ocs_fmt_0__ újradefiniálva. .ok = Ok .nested = Beágyazott + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index e4840f666..5201a29fa 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -6501,3 +6501,18 @@ ref-edit = .redefined = __ocs_fmt_0__ ridefinito. .ok = Ok .nested = Nidificati + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 978be1188..89b11b6ef 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -6490,3 +6490,18 @@ ref-edit = .redefined = __ocs_fmt_0__ を再定義しました。 .ok = OK .nested = ネスト + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index a02a5f742..732c0064c 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -6501,3 +6501,18 @@ ref-edit = .redefined = __ocs_fmt_0__이(가) 재정의되었습니다. .ok = 확인 .nested = 내포 + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/lo-LA/opencadstudio.ftl b/locales/lo-LA/opencadstudio.ftl index 5e1b081d3..b4be679b7 100644 --- a/locales/lo-LA/opencadstudio.ftl +++ b/locales/lo-LA/opencadstudio.ftl @@ -6502,3 +6502,18 @@ ref-edit = .redefined = ນິຍາມ __ocs_fmt_0__ ໃໝ່ແລ້ວ. .ok = ຕົກລົງ .nested = ຊ້ອນ + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index c5766c4b3..214b11351 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -6500,3 +6500,18 @@ ref-edit = .redefined = __ocs_fmt_0__ opnieuw gedefinieerd. .ok = Ok .nested = Genest + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index e09fba7d0..aad21bd42 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -6501,3 +6501,18 @@ ref-edit = .redefined = Przedefiniowano __ocs_fmt_0__. .ok = Ok .nested = Zagnieżdżone + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 8af49667b..9021e4b97 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -6502,3 +6502,18 @@ ref-edit = .redefined = __ocs_fmt_0__ redefinido. .ok = Ok .nested = Aninhados + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index 4bac0c524..32e842d25 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -6503,3 +6503,18 @@ ref-edit = .redefined = __ocs_fmt_0__ переопределен. .ok = Ок .nested = Вложенные + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 812d81026..3fd25bdaa 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -6423,3 +6423,18 @@ ref-edit = .redefined = __ocs_fmt_0__ yeniden tanımlandı. .ok = Tamam .nested = İç içe + +image-adjust = + .title = Görüntü Ayarı + .preview = Önizleme + .before = Önce + .after = Sonra + .no-preview = görüntü yüklenemedi + .adjust = Ayar + .brightness = Parlaklık + .contrast = Karşıtlık + .fade = Solukluk + .hint = Çubuğa tıklayın veya sürükleyin; − / + bir adım, kutuya yazılan 0–100 tamsayı. + .reset = Varsayılana dön + .apply = Uygula + .help = Seçilen raster görüntülerin parlaklığını, karşıtlığını ve soluklığını ayarlar. diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index c91e42953..6a35418eb 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -6485,3 +6485,18 @@ ref-edit = .redefined = 已重定义 __ocs_fmt_0__。 .ok = 确定 .nested = 嵌套 + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index 53b433d98..b5e7e36e7 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -6497,3 +6497,18 @@ ref-edit = .redefined = 已重新定義 __ocs_fmt_0__。 .ok = 確定 .nested = 巢狀 + +image-adjust = + .title = Image Adjust + .preview = Preview + .before = Before + .after = After + .no-preview = image could not be loaded + .adjust = Adjust + .brightness = Brightness + .contrast = Contrast + .fade = Fade + .hint = Click or drag a bar; − / + is one step, a box takes a whole number 0–100. + .reset = Restore defaults + .apply = Apply + .help = Sets the brightness, contrast and fade of the selected raster images. diff --git a/src/app/alias.rs b/src/app/alias.rs index 09112798a..acc50ee5e 100644 --- a/src/app/alias.rs +++ b/src/app/alias.rs @@ -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")], _ => &[], } } diff --git a/src/app/commands/display.rs b/src/app/commands/display.rs index ebfdbbea4..fb37ba4ac 100644 --- a/src/app/commands/display.rs +++ b/src/app/commands/display.rs @@ -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 = 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 { + 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::() + .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)] diff --git a/src/app/commands/pdf_dialogs.rs b/src/app/commands/pdf_dialogs.rs index 4d7669a30..ebdcf0aa1 100644 --- a/src/app/commands/pdf_dialogs.rs +++ b/src/app/commands/pdf_dialogs.rs @@ -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(_) diff --git a/src/app/control/actions.rs b/src/app/control/actions.rs index 6fa262f35..6a4d48deb 100644 --- a/src/app/control/actions.rs +++ b/src/app/control/actions.rs @@ -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 { diff --git a/src/app/mod.rs b/src/app/mod.rs index 9b39e5aee..e7a768374 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -584,6 +584,8 @@ pub(super) struct OpenCADStudio { pc_section: Option, /// -VIEW sEttings Background dialog. view_background: Option, + /// IMAGEADJUST dialog. + image_adjust: Option, underlay_layers: Option, pdf_import_settings: Option, pdf_import_file: Option, @@ -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, diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index e9295a3fb..d82b32e60 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -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(); diff --git a/src/app/view/modal.rs b/src/app/view/modal.rs index b9a9b8ba6..96beed812 100644 --- a/src/app/view/modal.rs +++ b/src/app/view/modal.rs @@ -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| { diff --git a/src/modules/insert/xadjust.rs b/src/modules/insert/xadjust.rs index 096f934a9..debf67a5e 100644 --- a/src/modules/insert/xadjust.rs +++ b/src/modules/insert/xadjust.rs @@ -359,5 +359,5 @@ impl CadCommand for AdjustCommand { } inventory::submit!(crate::command::CommandRegistration { - names: &["-IMAGEADJUST", "PDFADJUST", "DWFADJUST", "DGNADJUST"] + names: &["IMAGEADJUST", "-IMAGEADJUST", "PDFADJUST", "DWFADJUST", "DGNADJUST"] }); diff --git a/src/ui/window/pdf_dialogs.rs b/src/ui/window/pdf_dialogs.rs index cc11a8f44..5acaaf136 100644 --- a/src/ui/window/pdf_dialogs.rs +++ b/src/ui/window/pdf_dialogs.rs @@ -130,6 +130,16 @@ pub enum PdfDialogMsg { /// Offset 0, scale 1. BgReset, BgApply, + // Image Adjust: rows 0 brightness, 1 contrast, 2 fade. + /// A row's value from its bar. + IaValue(u8, u8), + /// A row's box, as typed. + IaText(u8, String), + /// One step down (-1) or up (+1). + IaStep(u8, i8), + /// Brightness 50, contrast 50, fade 0. + IaReset, + IaApply, } fn msg(m: PdfDialogMsg) -> Message { @@ -2617,3 +2627,237 @@ fn adjust_card<'a>(state: &'a ViewBackgroundState) -> Element<'a, Message> { .style(card_style) .into() } + +// ── Image Adjust (IMAGEADJUST) ────────────────────────────────────────── + +/// The Image Adjust dialog: brightness, contrast and fade (0–100) of the +/// chosen raster images, with a before / after preview of the first. +pub struct ImageAdjustState { + pub handles: Vec, + /// The first image's file name, shown over the preview. + pub name: String, + /// Brightness, contrast, fade. + pub values: [u8; 3], + /// The boxes as shown; a box may be empty while it is typed in. + pub texts: [String; 3], + /// The first image, at most 512 pixels a side, as RGBA. + pub source: Option<(u32, u32, Vec)>, + /// The drawing area colour the image fades into. + pub canvas: [u8; 3], + /// Left half as it is, right half adjusted, over the drawing area colour. + pub preview: Option, +} + +/// Brightness 50, contrast 50, fade 0. +pub const IMAGE_ADJUST_DEFAULTS: [u8; 3] = [50, 50, 0]; + +impl ImageAdjustState { + pub fn new(handles: Vec, path: &str, values: [u8; 3], canvas: [u8; 3]) -> Self { + let name = std::path::Path::new(path.trim()) + .file_name() + .map_or_else(|| path.trim().to_string(), |n| n.to_string_lossy().into_owned()); + let source = crate::scene::model::image_model::resolve_image(path).and_then(|decoded| { + let full = ::image::RgbaImage::from_raw(decoded.width, decoded.height, decoded.pixels.to_vec())?; + let small = if full.width().max(full.height()) > 512 { + ::image::imageops::thumbnail(&full, 512, 512) + } else { + full + }; + Some((small.width(), small.height(), small.into_raw())) + }); + let mut state = Self { + handles, + name, + values: [0; 3], + texts: Default::default(), + source, + canvas, + preview: None, + }; + state.set_all(values); + state + } + + pub fn set(&mut self, row: usize, value: u8) { + let mut values = self.values; + values[row] = value.min(100); + self.set_all(values); + } + + pub fn set_all(&mut self, values: [u8; 3]) { + self.values = values.map(|v| v.min(100)); + self.texts = self.values.map(|v| v.to_string()); + self.preview = self.source.as_ref().map(|(w, h, pixels)| { + // As the drawing shows it: brightness then contrast per channel, + // fade as the image's opacity over the drawing area. + let lut = crate::scene::model::image_model::brightness_contrast_lut(self.values[0], self.values[1]); + let opacity = 1.0 - f32::from(self.values[2]) / 100.0; + let mut out = Vec::with_capacity(pixels.len()); + for (k, px) in pixels.chunks_exact(4).enumerate() { + let after = k as u32 % w >= w / 2; + let alpha = f32::from(px[3]) / 255.0 * if after { opacity } else { 1.0 }; + for c in 0..3 { + let value = if after { lut[px[c] as usize] } else { px[c] }; + let bg = f32::from(self.canvas[c]); + out.push((bg + (f32::from(value) - bg) * alpha).round() as u8); + } + out.push(255); + } + image::Handle::from_rgba(*w, *h, out) + }); + } +} + +pub fn image_adjust<'a>( + state: &'a ImageAdjustState, + sizing: crate::ui::modal::ModalSizing, +) -> Element<'a, Message> { + let badge = |label: String, accent: bool| { + container(text(label).size(10)) + .padding([2, 8]) + .style(move |theme: &Theme| container::Style { + background: Some(Background::Color(if accent { + theme.palette().primary.base.color.scale_alpha(0.2) + } else { + iced::Color::from_rgba8(0x1e, 0x1e, 0x1e, 0.85) + })), + border: Border { + width: 1.0, + radius: 10.0.into(), + color: if accent { + theme.palette().primary.base.color + } else { + iced::Color::from_rgb8(0x4a, 0x4a, 0x4a) + }, + }, + text_color: Some(iced::Color::WHITE), + ..Default::default() + }) + }; + let canvas = iced::Color::from_rgb8(state.canvas[0], state.canvas[1], state.canvas[2]); + let picture: Element<'a, Message> = match &state.preview { + Some(handle) => image(handle.clone()) + .content_fit(iced::ContentFit::Cover) + .width(Fill) + .height(Fill) + .into(), + None => container(text(crate::tr!("image-adjust", "no-preview")).size(11).style(muted_style)) + .center(Fill) + .into(), + }; + let divider = container( + container(Space::new().width(2.0).height(Fill)).style(|theme: &Theme| container::Style { + background: Some(Background::Color(theme.palette().primary.base.color)), + ..Default::default() + }), + ) + .center_x(Fill) + .height(Fill); + let badges = row![ + badge(crate::tr!("image-adjust", "before"), false), + Space::new().width(Fill), + badge(crate::tr!("image-adjust", "after"), true), + ] + .padding(8); + let mut layers = iced::widget::Stack::new().width(Fill).height(Fill).push(picture); + if state.preview.is_some() { + layers = layers.push(divider).push(badges); + } + let preview = card( + format!("{} · {}", crate::tr!("image-adjust", "preview"), state.name), + container(layers) + .width(Fill) + .height(Length::Fixed(150.0)) + .clip(true) + .style(move |_: &Theme| container::Style { + background: Some(Background::Color(canvas)), + border: Border { radius: 5.0.into(), ..Default::default() }, + ..Default::default() + }), + ); + let ramp = |row: u8| -> Background { + let (from, to) = match row { + // Contrast: grey, the flat image its zero gives. + 1 => (iced::Color::from_rgb8(0x77, 0x77, 0x77), iced::Color::from_rgb8(0x77, 0x77, 0x77)), + _ => (iced::Color::from_rgb8(0x22, 0x22, 0x22), iced::Color::from_rgb8(0xee, 0xee, 0xee)), + }; + Background::Gradient(iced::Gradient::Linear( + iced::gradient::Linear::new(iced::Radians(std::f32::consts::FRAC_PI_2)) + .add_stop(0.0, from) + .add_stop(1.0, to), + )) + }; + let line = |row: u8, label: String| -> Element<'a, Message> { + let k = row as usize; + let bar = container(container(Space::new()).width(Fill).height(8.0).style(move |_: &Theme| { + container::Style { + background: Some(ramp(row)), + border: Border { radius: 4.0.into(), ..Default::default() }, + ..Default::default() + } + })) + .center_y(16.0) + .width(Fill); + let slide = iced::widget::slider(0..=100u8, state.values[k], move |v| msg(PdfDialogMsg::IaValue(row, v))) + .height(16.0) + .style(|theme: &Theme, _| iced::widget::slider::Style { + rail: iced::widget::slider::Rail { + backgrounds: (iced::Color::TRANSPARENT.into(), iced::Color::TRANSPARENT.into()), + width: 0.0, + border: Border::default(), + }, + handle: iced::widget::slider::Handle { + shape: iced::widget::slider::HandleShape::Rectangle { width: 4, border_radius: 2.0.into() }, + background: theme.palette().primary.base.color.into(), + border_width: 0.0, + border_color: iced::Color::TRANSPARENT, + }, + }); + let step = |dir: i8| { + button(text(if dir < 0 { "\u{2212}" } else { "+" }).size(12)) + .on_press(msg(PdfDialogMsg::IaStep(row, dir))) + .style(button_style(false)) + .padding([2, 8]) + }; + let value = text_input("", &state.texts[k]) + .on_input(move |v| msg(PdfDialogMsg::IaText(row, v))) + .size(11) + .padding([3, 4]) + .width(Length::Fixed(40.0)) + .align_x(iced::alignment::Horizontal::Right) + .style(field_style); + row![ + text(label).size(11).width(Length::Fixed(72.0)), + iced::widget::Stack::new().width(Fill).push(bar).push(slide), + container(row![step(-1), value, step(1)].spacing(2).align_y(iced::Center)) + .padding(2) + .style(well_style), + ] + .spacing(10) + .align_y(iced::Center) + .into() + }; + let adjust = card( + crate::tr!("image-adjust", "adjust"), + column![ + line(0, crate::tr!("image-adjust", "brightness")), + line(1, crate::tr!("image-adjust", "contrast")), + line(2, crate::tr!("image-adjust", "fade")), + text(crate::tr!("image-adjust", "hint")).size(10).style(muted_style), + ] + .spacing(10), + ); + let reset = button(text(crate::tr!("image-adjust", "reset")).size(11).style(accent_text)) + .on_press(msg(PdfDialogMsg::IaReset)) + .style(button::text) + .padding([5, 4]); + column![ + preview, + adjust, + footer("imageadjust", Some(reset.into()), crate::tr!("image-adjust", "apply").into(), PdfDialogMsg::IaApply), + ] + .spacing(10) + .padding([10, 12]) + .width(sizing.width) + .into() +}