forked from 1b-agent/opencadstudio-upstream
Raster images: transparency off draws pixels opaque; TRANSPARENCY command
An image's transparency flag decides whether transparent pixels show what is behind the image or draw in their stored colour: the image pass and PDF plots now follow it (it was ignored, so every PNG alpha showed through). TRANSPARENCY selects images and asks for the mode, defaulting to the first image's current one. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
801882314f
commit
5ca7becd97
30 files changed
+237
-6
No files matched your search
@@ -929,6 +929,55 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
}
|
||||
// TRANSPARENCY — raster images show transparent pixels (ON) or
|
||||
// draw every pixel in its colour (OFF).
|
||||
"TRANSPARENCY" => {
|
||||
let c = crate::modules::insert::image_transparency::TransparencyCommand::new();
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
"TRANSPARENCY MODE" | "TRANSPARENCY ON" | "TRANSPARENCY OFF" => {
|
||||
use codec::entities::ImageDisplayFlags;
|
||||
let images: Vec<(codec::Handle, bool)> = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
.iter()
|
||||
.filter(|(handle, _)| !self.tabs[i].scene.is_layer_locked(*handle))
|
||||
.filter_map(|(handle, _)| match self.tabs[i].scene.document.get_entity(*handle) {
|
||||
Some(codec::EntityType::RasterImage(image)) => {
|
||||
Some((*handle, image.flags.contains(ImageDisplayFlags::TRANSPARENCY_ON)))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let Some(&(_, first_on)) = images.first() else {
|
||||
return Some(Task::none());
|
||||
};
|
||||
let handles: Vec<codec::Handle> = images.iter().map(|(handle, _)| *handle).collect();
|
||||
if cmd == "TRANSPARENCY MODE" {
|
||||
let c = crate::modules::insert::image_transparency::TransparencyCommand::mode(
|
||||
handles, first_on,
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
} else {
|
||||
let on = cmd == "TRANSPARENCY ON";
|
||||
self.push_undo_snapshot(i, "TRANSPARENCY");
|
||||
for handle in &handles {
|
||||
if let Some(codec::EntityType::RasterImage(image)) =
|
||||
self.tabs[i].scene.document.get_entity_mut(*handle)
|
||||
{
|
||||
image.flags.set(ImageDisplayFlags::TRANSPARENCY_ON, on);
|
||||
}
|
||||
}
|
||||
let changes: Vec<_> = handles
|
||||
.iter()
|
||||
.map(|handle| (*handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
}
|
||||
// UNDERLAY — edit properties of selected PDF/DWF/DGN underlay entities.
|
||||
// Usage:
|
||||
// UNDERLAY FADE <0-80>
|
||||
|
||||
@@ -714,6 +714,7 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
"IM",
|
||||
"IMAGE",
|
||||
"IMAGEATTACH",
|
||||
"TRANSPARENCY",
|
||||
"IMAGEEMBED",
|
||||
"IMPORTOBJ",
|
||||
"ISOLATEOBJECTS",
|
||||
|
||||
+11
-2
@@ -242,8 +242,10 @@ fn emit_image(
|
||||
{
|
||||
return Err("Cannot plot bitmap: invalid coordinates, clip boundary, or opacity.".into());
|
||||
}
|
||||
// The low bit (free: the pixel buffer is aligned) keeps the opaque copy
|
||||
// of a picture apart from its transparent one.
|
||||
let key = (
|
||||
std::sync::Arc::as_ptr(&image.pixels) as usize,
|
||||
std::sync::Arc::as_ptr(&image.pixels) as usize | usize::from(!image.use_alpha),
|
||||
image.width,
|
||||
image.height,
|
||||
);
|
||||
@@ -255,7 +257,14 @@ fn emit_image(
|
||||
doc.resources.xobjects.map.insert(
|
||||
id.clone(),
|
||||
printpdf::XObject::Image(printpdf::RawImage {
|
||||
pixels: printpdf::RawImageData::U8(image.pixels.as_ref().clone()),
|
||||
pixels: printpdf::RawImageData::U8(if image.use_alpha {
|
||||
image.pixels.as_ref().clone()
|
||||
} else {
|
||||
// Transparency off: every pixel opaque in its colour.
|
||||
let mut opaque = image.pixels.as_ref().clone();
|
||||
opaque.chunks_exact_mut(4).for_each(|px| px[3] = 255);
|
||||
opaque
|
||||
}),
|
||||
width: image.width as usize,
|
||||
height: image.height as usize,
|
||||
data_format: printpdf::RawImageFormat::RGBA8,
|
||||
|
||||
@@ -1197,6 +1197,9 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
||||
"Frames & Print" => Some(("insert", "frames-print")),
|
||||
"Frames Off" => Some(("insert", "frames-off")),
|
||||
"Frames On" => Some(("insert", "frames-on")),
|
||||
"Select image(s):" => Some(("insert", "select-images")),
|
||||
"Enter transparency mode [ON/OFF] <ON>:" => Some(("insert", "transparency-mode-on")),
|
||||
"Enter transparency mode [ON/OFF] <OFF>:" => Some(("insert", "transparency-mode-off")),
|
||||
"Frames On, Not Plotted" => Some(("draw", "frames-on-not-plotted")),
|
||||
"Freeze" => Some(("layers", "freeze")),
|
||||
"From DXF" => Some(("properties", "from-dxf")),
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
// TRANSPARENCY — whether raster images show their transparent pixels as
|
||||
// transparent or draw every pixel in its stored colour.
|
||||
//
|
||||
// Select image(s):
|
||||
// Enter transparency mode [ON/OFF] <OFF>: (the default is the first
|
||||
// image's current mode)
|
||||
|
||||
use codec::Handle;
|
||||
use glam::DVec3;
|
||||
|
||||
use crate::command::{CadCommand, CmdOption, CmdResult};
|
||||
|
||||
pub struct TransparencyCommand {
|
||||
handles: Vec<Handle>,
|
||||
/// `None` while images are being selected; then the default mode.
|
||||
default_on: Option<bool>,
|
||||
}
|
||||
|
||||
impl TransparencyCommand {
|
||||
pub fn new() -> Self {
|
||||
Self { handles: Vec::new(), default_on: None }
|
||||
}
|
||||
|
||||
/// The mode prompt for images already chosen.
|
||||
pub fn mode(handles: Vec<Handle>, default_on: bool) -> Self {
|
||||
Self { handles, default_on: Some(default_on) }
|
||||
}
|
||||
|
||||
fn apply(&mut self, on: bool) -> CmdResult {
|
||||
let command = if on { "TRANSPARENCY ON" } else { "TRANSPARENCY OFF" };
|
||||
CmdResult::Relaunch(command.to_string(), std::mem::take(&mut self.handles))
|
||||
}
|
||||
}
|
||||
|
||||
impl CadCommand for TransparencyCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
"TRANSPARENCY"
|
||||
}
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
match self.default_on {
|
||||
None => crate::t!("Select image(s):").into_owned(),
|
||||
Some(true) => crate::t!("Enter transparency mode [ON/OFF] <ON>:").into_owned(),
|
||||
Some(false) => crate::t!("Enter transparency mode [ON/OFF] <OFF>:").into_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn options(&self) -> Vec<CmdOption> {
|
||||
match self.default_on {
|
||||
None => Vec::new(),
|
||||
Some(_) => vec![CmdOption::new("ON", "ON"), CmdOption::new("OFF", "OFF")],
|
||||
}
|
||||
}
|
||||
|
||||
fn is_selection_gathering(&self) -> bool {
|
||||
self.default_on.is_none()
|
||||
}
|
||||
|
||||
fn wants_text_input(&self) -> bool {
|
||||
self.default_on.is_some()
|
||||
}
|
||||
|
||||
fn on_selection_complete(&mut self, handles: Vec<Handle>) -> CmdResult {
|
||||
self.handles = handles;
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
let default_on = self.default_on?;
|
||||
Some(match text.trim().to_uppercase().as_str() {
|
||||
"" => self.apply(default_on),
|
||||
"ON" => self.apply(true),
|
||||
"OFF" => self.apply(false),
|
||||
_ => CmdResult::ReportError("Invalid option keyword.".to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
fn on_point(&mut self, _pt: DVec3) -> CmdResult {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
match self.default_on {
|
||||
None if self.handles.is_empty() => CmdResult::Cancel,
|
||||
// The app keeps only the images and asks for the mode.
|
||||
None => CmdResult::Relaunch(
|
||||
"TRANSPARENCY MODE".to_string(),
|
||||
std::mem::take(&mut self.handles),
|
||||
),
|
||||
Some(on) => self.apply(on),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ mod content_browser;
|
||||
pub(crate) mod create_block;
|
||||
mod design_center;
|
||||
mod edit_block;
|
||||
pub(crate) mod image_transparency;
|
||||
pub(crate) mod insert_block;
|
||||
mod landxml;
|
||||
pub(crate) mod picker;
|
||||
|
||||
@@ -94,6 +94,9 @@ pub struct ImageModel {
|
||||
/// Magnified pixels stay square (raster images); otherwise they blend
|
||||
/// (PDF pages, OLE pictures).
|
||||
pub pixelated: bool,
|
||||
/// Pixel alpha applies. A raster image whose transparency is off draws
|
||||
/// every pixel opaque in its stored colour.
|
||||
pub use_alpha: bool,
|
||||
}
|
||||
|
||||
impl ImageModel {
|
||||
@@ -165,6 +168,9 @@ impl ImageModel {
|
||||
draw_depth: 0.0,
|
||||
verts,
|
||||
pixelated: true,
|
||||
use_alpha: img
|
||||
.flags
|
||||
.contains(codec::entities::ImageDisplayFlags::TRANSPARENCY_ON),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -274,6 +280,7 @@ impl ImageModel {
|
||||
draw_depth: 0.0,
|
||||
verts,
|
||||
pixelated: false,
|
||||
use_alpha: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -355,6 +362,7 @@ impl ImageModel {
|
||||
draw_depth: 0.0,
|
||||
verts,
|
||||
pixelated: false,
|
||||
use_alpha: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,9 @@ struct ImageParams {
|
||||
/// Signed draw-order depth (-1,1); applied as a clip-z bias in the shader
|
||||
/// so the raster orders against other entity types. 0.0 = neutral.
|
||||
draw_depth: f32,
|
||||
_pad: [f32; 2],
|
||||
/// 1.0 when pixel alpha applies, 0.0 when pixels draw opaque.
|
||||
use_alpha: f32,
|
||||
_pad: f32,
|
||||
} // 16 bytes
|
||||
|
||||
// ── Per-image GPU handle ──────────────────────────────────────────────────
|
||||
@@ -196,7 +198,8 @@ impl ImageGpu {
|
||||
let params = ImageParams {
|
||||
opacity: model.opacity.clamp(0.0, 1.0),
|
||||
draw_depth: 0.0,
|
||||
_pad: [0.0; 2],
|
||||
use_alpha: if model.use_alpha { 1.0 } else { 0.0 },
|
||||
_pad: 0.0,
|
||||
};
|
||||
let params_buf = Arc::new(super::gpu_upload::upload_buffer(
|
||||
device,
|
||||
|
||||
@@ -28,7 +28,7 @@ struct Uniforms {
|
||||
struct ImageParams {
|
||||
opacity: f32,
|
||||
draw_depth: f32, // signed (-1,1) draw-order bias; 0 = neutral
|
||||
_pad1: f32,
|
||||
use_alpha: f32, // 1 = pixel alpha applies, 0 = pixels draw opaque
|
||||
_pad2: f32,
|
||||
};
|
||||
@group(1) @binding(2) var<uniform> img_params: ImageParams;
|
||||
@@ -63,5 +63,6 @@ fn vs_main(in: VertIn) -> VertOut {
|
||||
@fragment
|
||||
fn fs_main(in: VertOut) -> @location(0) vec4<f32> {
|
||||
let col = textureSample(img_texture, img_sampler, in.uv);
|
||||
return vec4<f32>(col.rgb, col.a * img_params.opacity);
|
||||
let alpha = select(1.0, col.a, img_params.use_alpha > 0.5);
|
||||
return vec4<f32>(col.rgb, alpha * img_params.opacity);
|
||||
}
|
||||
Reference in new issue
Block a user