forked from 1b-agent/opencadstudio-upstream
IMAGEADJUST: Image Adjust dialog for raster images
IMAGEADJUST (alias IAD) takes the selected raster images or asks "Select image(s):", then opens a dialog with a before/after preview of the first image and brightness, contrast and fade rows (bar plus stepper, whole numbers 0-100). Apply writes the values to every chosen image as one undo step; restore defaults sets 50/50/0. Adds the image_adjust automation action and the image-adjust locale group. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
592289f4fd
commit
2f55c73457
32 files changed
+757
-2
No files matched your search
@@ -74,6 +74,7 @@ XR, *XREF
|
||||
-XR, *-XREF
|
||||
XC, *XCLIP
|
||||
ICL, *IMAGECLIP
|
||||
IAD, *IMAGEADJUST
|
||||
ADC, *ADCENTER
|
||||
W, *WBLOCK
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -6489,3 +6489,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.
|
||||
@@ -6486,3 +6486,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.
|
||||
@@ -6487,3 +6487,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.
|
||||
@@ -6489,3 +6489,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.
|
||||
@@ -6488,3 +6488,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.
|
||||
@@ -6489,3 +6489,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.
|
||||
@@ -6486,3 +6486,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.
|
||||
@@ -6489,3 +6489,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.
|
||||
@@ -6477,3 +6477,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.
|
||||
@@ -6473,3 +6473,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.
|
||||
@@ -6488,3 +6488,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.
|
||||
@@ -6477,3 +6477,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.
|
||||
@@ -6488,3 +6488,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.
|
||||
@@ -6489,3 +6489,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.
|
||||
@@ -6487,3 +6487,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.
|
||||
@@ -6488,3 +6488,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.
|
||||
@@ -6489,3 +6489,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.
|
||||
@@ -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.
|
||||
@@ -6410,3 +6410,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.
|
||||
@@ -6472,3 +6472,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.
|
||||
@@ -6484,3 +6484,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.
|
||||
+2
-1
@@ -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")],
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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(_)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -582,6 +582,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>,
|
||||
@@ -2153,6 +2155,7 @@ pub enum ModalKind {
|
||||
PointCloudColorMap,
|
||||
PcSection,
|
||||
ViewBackground,
|
||||
ImageAdjust,
|
||||
UnderlayLayers,
|
||||
PdfImportSettings,
|
||||
PdfImportFile,
|
||||
@@ -4559,6 +4562,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,
|
||||
|
||||
@@ -9470,6 +9470,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();
|
||||
|
||||
@@ -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(),
|
||||
@@ -528,6 +529,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| {
|
||||
|
||||
@@ -359,5 +359,5 @@ impl CadCommand for AdjustCommand {
|
||||
}
|
||||
|
||||
inventory::submit!(crate::command::CommandRegistration {
|
||||
names: &["-IMAGEADJUST", "PDFADJUST", "DWFADJUST", "DGNADJUST"]
|
||||
names: &["IMAGEADJUST", "-IMAGEADJUST", "PDFADJUST", "DWFADJUST", "DGNADJUST"]
|
||||
});
|
||||
@@ -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<codec::Handle>,
|
||||
/// 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<u8>)>,
|
||||
/// 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<image::Handle>,
|
||||
}
|
||||
|
||||
/// Brightness 50, contrast 50, fade 0.
|
||||
pub const IMAGE_ADJUST_DEFAULTS: [u8; 3] = [50, 50, 0];
|
||||
|
||||
impl ImageAdjustState {
|
||||
pub fn new(handles: Vec<codec::Handle>, 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()
|
||||
}
|
||||
Reference in new issue
Block a user