mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-21 22:54:11 -04:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
164e76015e | ||
|
|
bb6afdddd7 |
No files matched your search
@@ -563,6 +563,8 @@ fn routed(id: &str) -> bool {
|
||||
|| id.starts_with("image.adjustments.")
|
||||
|| matches!(id, "paint.stroke" | "paint.pencil" | "paint.bucket" | "paint.gradient" | "paint.mixerBrush" | "edit.fill" | "image.applyImage")
|
||||
|| crate::fill_key_cmds::IDS.contains(&id)
|
||||
// Pasting into a targeted mask or channel (#1035).
|
||||
|| matches!(id, "edit.paste" | "edit.pasteSpecial.pasteInPlace" | "edit.pasteSpecial.pasteInto" | "edit.pasteSpecial.pasteOutside")
|
||||
|| matches!(
|
||||
id,
|
||||
"paint.cloneStamp"
|
||||
|
||||
+171
-12
@@ -11,6 +11,14 @@ use serde_json::{Value, json};
|
||||
use crate::commands::CommandSpec;
|
||||
use crate::{EngineError, Result, Session};
|
||||
|
||||
/// The paste commands' `"target"` param (for `concat!` into their params docs).
|
||||
macro_rules! paste_target {
|
||||
() => {
|
||||
r#""target":"pixels"|"mask"|"quickMask"|{"channel":i}=Channels panel target (not pixels: the luminosity pasted into that mask or channel)"#
|
||||
};
|
||||
}
|
||||
pub(crate) use paste_target;
|
||||
|
||||
/// Pixels on the session clipboard, with where they came from (for Paste in Place).
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Clip {
|
||||
@@ -142,7 +150,7 @@ pub(crate) fn clear_area(doc: &mut Document, id: LayerId, area: Rect, sel: Optio
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Paste as a new layer. `in_place` keeps the original position; otherwise the pixels are centred
|
||||
/// Paste as a new layer (or into the targeted mask or channel: [`paste_to_target`]). `in_place` keeps the original position; otherwise the pixels are centred
|
||||
/// on `center` (the view centre from the UI) or the canvas, unless they already overlap the canvas.
|
||||
fn paste(s: &mut Session, p: &Value, in_place: bool) -> Result<Value> {
|
||||
let clip = s.clipboard.clone().ok_or(EngineError::Other("the clipboard is empty".into()))?;
|
||||
@@ -150,17 +158,13 @@ fn paste(s: &mut Session, p: &Value, in_place: bool) -> Result<Value> {
|
||||
// Nothing open to paste into: the clipboard becomes a document of its own (#368).
|
||||
return new_from_clipboard(s);
|
||||
};
|
||||
if crate::channel_cmds::target_of(p) != crate::channel_cmds::Target::Pixels {
|
||||
return paste_to_target(s, p, in_place, None, "Paste");
|
||||
}
|
||||
let canvas = d.doc.bounds();
|
||||
let fmt = d.doc.pixel_format();
|
||||
let (dx, dy) = if in_place || (clip.bounds.intersect(&canvas) == clip.bounds && p.get("center").is_none()) {
|
||||
(0, 0)
|
||||
} else {
|
||||
let c = p.get("center").and_then(Value::as_array).filter(|a| a.len() >= 2).map(|a| (a[0].as_f64().unwrap_or(0.0), a[1].as_f64().unwrap_or(0.0)));
|
||||
let (cx, cy) = c.unwrap_or(((canvas.x0 + canvas.x1) as f64 / 2.0, (canvas.y0 + canvas.y1) as f64 / 2.0));
|
||||
let b = clip.bounds;
|
||||
((cx - (b.x0 + b.x1) as f64 / 2.0).round() as i32, (cy - (b.y0 + b.y1) as f64 / 2.0).round() as i32)
|
||||
};
|
||||
let moved = if dx == 0 && dy == 0 { clip.surface.clone() } else { photocraft_algo::resample::translate_surface(&clip.surface, dx, dy) };
|
||||
let (dx, dy) = paste_offset(&clip, canvas, p, in_place);
|
||||
let moved = shifted(&clip.surface, dx, dy);
|
||||
let target = PixelFormat::new(fmt.mode, fmt.sample, true);
|
||||
let surf = if moved.format() == target { moved } else { moved.convert(target) };
|
||||
let id = s.edit("Paste", |doc, active| {
|
||||
@@ -174,6 +178,45 @@ fn paste(s: &mut Session, p: &Value, in_place: bool) -> Result<Value> {
|
||||
Ok(json!({"layer": id.0, "offset": [dx, dy]}))
|
||||
}
|
||||
|
||||
/// How far a paste moves the clipboard pixels: not at all in place (or when they lie on the
|
||||
/// canvas and no `center` is given), else onto `center` (the view centre from the UI) or the
|
||||
/// canvas centre.
|
||||
fn paste_offset(clip: &Clip, canvas: Rect, p: &Value, in_place: bool) -> (i32, i32) {
|
||||
if in_place || (clip.bounds.intersect(&canvas) == clip.bounds && p.get("center").is_none()) {
|
||||
return (0, 0);
|
||||
}
|
||||
let c = p.get("center").and_then(Value::as_array).filter(|a| a.len() >= 2).map(|a| (a[0].as_f64().unwrap_or(0.0), a[1].as_f64().unwrap_or(0.0)));
|
||||
let (cx, cy) = c.unwrap_or(((canvas.x0 + canvas.x1) as f64 / 2.0, (canvas.y0 + canvas.y1) as f64 / 2.0));
|
||||
let b = clip.bounds;
|
||||
((cx - (b.x0 + b.x1) as f64 / 2.0).round() as i32, (cy - (b.y0 + b.y1) as f64 / 2.0).round() as i32)
|
||||
}
|
||||
|
||||
fn shifted(surface: &Surface, dx: i32, dy: i32) -> Surface {
|
||||
if dx == 0 && dy == 0 { surface.clone() } else { photocraft_algo::resample::translate_surface(surface, dx, dy) }
|
||||
}
|
||||
|
||||
/// Paste into the targeted layer mask, alpha channel or Quick Mask (`"target"`) instead of as a
|
||||
/// new layer (Photoshop, #1035): the pasted pixels' luminosity, placed as a paste places them,
|
||||
/// over what was there. Their transparency, and `limit` (Paste Into / Outside), let it show
|
||||
/// through. One history step.
|
||||
pub(crate) fn paste_to_target(s: &mut Session, p: &Value, in_place: bool, limit: Option<&Surface>, label: &str) -> Result<Value> {
|
||||
let clip = s.clipboard.clone().ok_or(EngineError::Other("the clipboard is empty".into()))?;
|
||||
let d = s.active().ok_or(EngineError::NoDocument)?;
|
||||
let canvas = d.doc.bounds();
|
||||
let layer = d.active_layer;
|
||||
let (dx, dy) = paste_offset(&clip, canvas, p, in_place);
|
||||
let src = shifted(&clip.surface, dx, dy);
|
||||
let area = src.content_bounds().intersect(&canvas);
|
||||
s.edit(label, |doc, _| {
|
||||
let (surf, _) = crate::channel_cmds::target_surface(doc, layer, p)?;
|
||||
crate::fill_cmds::composite_over(surf, &src, area, limit);
|
||||
surf.prune();
|
||||
doc.selection = None;
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(json!({"target": p.get("target"), "offset": [dx, dy]}))
|
||||
}
|
||||
|
||||
/// A new document the size of the clipboard image, holding it as its one layer, in the pixel
|
||||
/// format it was copied in (#368).
|
||||
fn new_from_clipboard(s: &mut Session) -> Result<Value> {
|
||||
@@ -507,7 +550,11 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Paste",
|
||||
&["Edit"],
|
||||
Some("Cmd+V"),
|
||||
r##"{"center":[x,y]? (view centre; default keeps the position when it overlaps the canvas)} (with no document open: a new document from the clipboard)"##,
|
||||
concat!(
|
||||
r#"{"center":[x,y]? (view centre; default keeps the position when it overlaps the canvas),"#,
|
||||
paste_target!(),
|
||||
"} (with no document open: a new document from the clipboard)"
|
||||
),
|
||||
has_clip_only,
|
||||
|s, p| paste(s, p, false)
|
||||
),
|
||||
@@ -520,7 +567,15 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_clip_only,
|
||||
|s, _| new_from_clipboard(s)
|
||||
),
|
||||
spec!("edit.pasteSpecial.pasteInPlace", "Paste in Place", &["Edit", "Paste Special"], Some("Cmd+Shift+V"), "{}", has_clip, |s, p| paste(s, p, true)),
|
||||
spec!(
|
||||
"edit.pasteSpecial.pasteInPlace",
|
||||
"Paste in Place",
|
||||
&["Edit", "Paste Special"],
|
||||
Some("Cmd+Shift+V"),
|
||||
concat!("{", paste_target!(), "}"),
|
||||
has_clip,
|
||||
|s, p| paste(s, p, true)
|
||||
),
|
||||
spec!("layer.new.layerViaCopy", "Layer via Copy", &["Layer", "New"], Some("Cmd+J"), "{}", has_doc, |s, _| layer_via(s, false)),
|
||||
spec!("layer.new.layerViaCut", "Layer via Cut", &["Layer", "New"], Some("Cmd+Shift+J"), "{}", has_pixels, |s, _| layer_via(s, true)),
|
||||
spec!("layer.mergeVisible", "Merge Visible", &["Layer"], Some("Cmd+Shift+E"), "{}", has_doc, |s, _| merge_visible(s)),
|
||||
@@ -651,6 +706,110 @@ mod tests {
|
||||
assert!(px[0][0] > 0.9 && px[0][1] < 0.1 && px[0][2] < 0.1, "pasted content is red: {:?}", px[0]);
|
||||
}
|
||||
|
||||
/// #1035: a `depth`-bit document with a blue-grey square copied from layer `src` (at
|
||||
/// (5,5)-(15,15)) and an active layer `m` above it with a hide-all mask.
|
||||
fn mask_target_session(depth: u32) -> (Session, LayerId, LayerId) {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", json!({"width": 40, "height": 40, "depth": depth})).unwrap();
|
||||
let src = LayerId(s.execute("layer.new.layer", json!({"name": "src"})).unwrap()["layer"].as_u64().unwrap());
|
||||
s.execute("select.rect", json!({"x": 5, "y": 5, "width": 10, "height": 10})).unwrap();
|
||||
s.execute("edit.fill", json!({"color": "#3366cc"})).unwrap();
|
||||
s.execute("edit.copy", json!({})).unwrap();
|
||||
s.execute("select.deselect", json!({})).unwrap();
|
||||
let m = LayerId(s.execute("layer.new.layer", json!({"name": "m"})).unwrap()["layer"].as_u64().unwrap());
|
||||
s.execute("layer.layerMask.hideAll", json!({})).unwrap();
|
||||
(s, src, m)
|
||||
}
|
||||
|
||||
/// The luminosity of `src`'s pixel at (`x`, `y`): what a paste into a mask writes there.
|
||||
fn luminosity(s: &Session, src: LayerId, x: i32, y: i32) -> f32 {
|
||||
let p = s.active().unwrap().doc.layer(src).unwrap().surface().unwrap().rgba(x, y);
|
||||
photocraft_color::convert::rgb_to_gray([p[0], p[1], p[2]])
|
||||
}
|
||||
|
||||
fn mask_at(s: &Session, id: LayerId, x: i32, y: i32) -> f32 {
|
||||
s.active().unwrap().doc.layer(id).unwrap().mask.as_ref().unwrap().value(x, y)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paste_into_a_targeted_mask_writes_the_luminosity_in_one_step() {
|
||||
for depth in [8, 16, 32] {
|
||||
let (mut s, src, m) = mask_target_session(depth);
|
||||
let lum = luminosity(&s, src, 10, 10);
|
||||
assert!(lum > 0.05 && lum < 0.95, "{depth}-bit: a mid grey ({lum})");
|
||||
let (layers, steps) = (s.active().unwrap().doc.layer_count(), s.active().unwrap().history.past_len());
|
||||
s.execute("edit.pasteSpecial.pasteInPlace", json!({"target": "mask"})).unwrap();
|
||||
let st = s.active().unwrap();
|
||||
assert_eq!(st.doc.layer_count(), layers, "{depth}-bit: no new layer");
|
||||
assert_eq!(st.history.past_len(), steps + 1, "{depth}-bit: one history step");
|
||||
assert_eq!(st.active_layer, Some(m));
|
||||
assert!((mask_at(&s, m, 10, 10) - lum).abs() < 2.0 / 255.0, "{depth}-bit: {} vs {lum}", mask_at(&s, m, 10, 10));
|
||||
assert_eq!(mask_at(&s, m, 20, 20), 0.0, "{depth}-bit: the mask outside the paste is unchanged");
|
||||
assert!(st.doc.layer(m).unwrap().surface().unwrap().content_bounds().is_empty(), "{depth}-bit: the pixels are untouched");
|
||||
s.execute("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(mask_at(&s, m, 10, 10), 0.0, "{depth}-bit: undo restores the mask");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paste_follows_the_mask_view_and_is_placed_like_a_paste() {
|
||||
let (mut s, src, m) = mask_target_session(8);
|
||||
let lum = luminosity(&s, src, 10, 10);
|
||||
// ⌥-click the mask thumbnail: the canvas shows the mask, and pasting targets it.
|
||||
s.execute(crate::mask_view_cmds::ID, json!({"layer": m.0, "mode": "gray"})).unwrap();
|
||||
let r = s.execute("edit.paste", json!({"center": [30, 30]})).unwrap();
|
||||
assert_eq!(r["offset"], json!([20, 20]));
|
||||
assert!((mask_at(&s, m, 30, 30) - lum).abs() < 2.0 / 255.0);
|
||||
assert_eq!(mask_at(&s, m, 10, 10), 0.0);
|
||||
assert_eq!(s.active().unwrap().doc.layers.len(), 3, "Background, src, m");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paste_into_and_outside_a_targeted_mask_keep_to_the_selection() {
|
||||
let (mut s, src, m) = mask_target_session(16);
|
||||
let lum = luminosity(&s, src, 10, 10);
|
||||
// The paste is centred on the selection, so lands in place: only the selection takes it.
|
||||
s.execute("select.rect", json!({"x": 8, "y": 8, "width": 4, "height": 4})).unwrap();
|
||||
s.execute("edit.pasteSpecial.pasteInto", json!({"target": "mask"})).unwrap();
|
||||
assert!((mask_at(&s, m, 9, 9) - lum).abs() < 2.0 / 255.0);
|
||||
assert_eq!(mask_at(&s, m, 7, 7), 0.0, "pasted, but outside the selection");
|
||||
assert!(s.active().unwrap().doc.selection.is_none());
|
||||
assert_eq!(s.active().unwrap().doc.layers.len(), 3, "no new layer");
|
||||
s.execute("layer.layerMask.hideAll", json!({})).unwrap();
|
||||
s.execute("select.rect", json!({"x": 8, "y": 8, "width": 4, "height": 4})).unwrap();
|
||||
s.execute("edit.pasteSpecial.pasteOutside", json!({"target": "mask"})).unwrap();
|
||||
assert_eq!(mask_at(&s, m, 9, 9), 0.0, "Paste Outside leaves the selection alone");
|
||||
assert!((mask_at(&s, m, 7, 7) - lum).abs() < 2.0 / 255.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paste_follows_a_targeted_alpha_channel() {
|
||||
let (mut s, src, _) = mask_target_session(8);
|
||||
let lum = luminosity(&s, src, 10, 10);
|
||||
s.execute("channel.new", json!({})).unwrap();
|
||||
s.execute("edit.pasteSpecial.pasteInPlace", json!({"target": {"channel": 0}})).unwrap();
|
||||
let c = &s.active().unwrap().doc.channels[0].surface;
|
||||
assert!((c.sample_channel(10, 10, 0) - lum).abs() < 2.0 / 255.0);
|
||||
assert_eq!(s.active().unwrap().doc.layers.len(), 3, "no new layer");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paste_into_a_missing_mask_or_channel_is_an_error() {
|
||||
let (mut s, _, m) = mask_target_session(8);
|
||||
s.edit("drop mask", |doc, _| {
|
||||
doc.layer_mut(m).unwrap().mask = None;
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
let layers = s.active().unwrap().doc.layer_count();
|
||||
for target in [json!("mask"), json!({"channel": 7}), json!("quickMask")] {
|
||||
for id in ["edit.paste", "edit.pasteSpecial.pasteInPlace"] {
|
||||
assert!(s.execute(id, json!({ "target": target })).is_err(), "{id} {target}");
|
||||
}
|
||||
}
|
||||
assert_eq!(s.active().unwrap().doc.layer_count(), layers, "nothing pasted as a layer");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paste_between_different_color_modes() {
|
||||
// Copy from an RGB document, paste into a Grayscale one: the clipboard converts to the
|
||||
|
||||
@@ -298,30 +298,39 @@ fn can_reselect(s: &Session) -> std::result::Result<(), String> {
|
||||
fn paste_into(s: &mut Session, p: &Value, outside: bool) -> Result<Value> {
|
||||
let d = s.active().ok_or(EngineError::NoDocument)?;
|
||||
let selection = d.doc.selection.clone().ok_or(EngineError::Other("Paste Into needs a selection".into()))?;
|
||||
let canvas = d.doc.bounds();
|
||||
let b = selection.content_bounds();
|
||||
let key = step_key(s, "pasteInto");
|
||||
let mut params = json!({"center": [(b.x0 + b.x1) as f64 / 2.0, (b.y0 + b.y1) as f64 / 2.0], "coalesce": key});
|
||||
// Where the paste shows: the selection, or everything but it (default reveal, the
|
||||
// selection's area inverted).
|
||||
let limit = if outside {
|
||||
let area = b.intersect(&canvas);
|
||||
let inv: Vec<f32> = sel::mask_from_surface(Some(&selection), area).iter().map(|v| 1.0 - v).collect();
|
||||
let mut m = photocraft_raster::Surface::with_default(photocraft_color::PixelFormat::GRAY8, &[1.0]);
|
||||
m.write_region(area, &inv);
|
||||
m
|
||||
} else {
|
||||
selection
|
||||
};
|
||||
let label = if outside { "Paste Outside" } else { "Paste Into" };
|
||||
let mut params = json!({"center": [(b.x0 + b.x1) as f64 / 2.0, (b.y0 + b.y1) as f64 / 2.0]});
|
||||
if let Some(c) = p.get("center") {
|
||||
params["center"] = c.clone();
|
||||
}
|
||||
if crate::channel_cmds::target_of(p) != crate::channel_cmds::Target::Pixels {
|
||||
// A targeted mask or channel (#1035): the paste goes into it, only where `limit` allows.
|
||||
params["target"] = p.get("target").cloned().unwrap_or_default();
|
||||
return crate::edit_cmds::paste_to_target(s, ¶ms, false, Some(&limit), label);
|
||||
}
|
||||
let key = step_key(s, "pasteInto");
|
||||
params["coalesce"] = json!(key);
|
||||
params["target"] = json!("pixels");
|
||||
let r = s.execute("edit.paste", params)?;
|
||||
let id = layer_param(s, &Value::Null)?;
|
||||
s.execute("layer.layerMask.revealAll", json!({"layer": id.0, "coalesce": key}))?;
|
||||
s.coalesce_request = Some(key);
|
||||
let r2 = s.edit(if outside { "Paste Outside" } else { "Paste Into" }, |doc, _| {
|
||||
let canvas = doc.bounds();
|
||||
let r2 = s.edit(label, |doc, _| {
|
||||
let l = doc.layer_mut(id).ok_or(EngineError::NoLayer(id))?;
|
||||
let surface = if outside {
|
||||
// Everything but the selection: default reveal, the selection's area inverted.
|
||||
let area = b.intersect(&canvas);
|
||||
let inv: Vec<f32> = sel::mask_from_surface(Some(&selection), area).iter().map(|v| 1.0 - v).collect();
|
||||
let mut m = photocraft_raster::Surface::with_default(photocraft_color::PixelFormat::GRAY8, &[1.0]);
|
||||
m.write_region(area, &inv);
|
||||
m
|
||||
} else {
|
||||
selection
|
||||
};
|
||||
l.mask = Some(LayerMask { surface, linked: false, ..LayerMask::reveal_all() });
|
||||
l.mask = Some(LayerMask { surface: limit, linked: false, ..LayerMask::reveal_all() });
|
||||
Ok(())
|
||||
});
|
||||
s.coalesce_request = None;
|
||||
@@ -619,13 +628,19 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Paste Into",
|
||||
&["Edit", "Paste Special"],
|
||||
Some("Cmd+Alt+Shift+V"),
|
||||
r##"{"center":[x,y]?}"##,
|
||||
concat!(r#"{"center":[x,y]?,"#, crate::edit_cmds::paste_target!(), "}"),
|
||||
has_clip_and_selection,
|
||||
|s, p| paste_into(s, p, false)
|
||||
),
|
||||
spec!("edit.pasteSpecial.pasteOutside", "Paste Outside", &["Edit", "Paste Special"], None, r##"{"center":[x,y]?}"##, has_clip_and_selection, |s, p| {
|
||||
paste_into(s, p, true)
|
||||
}),
|
||||
spec!(
|
||||
"edit.pasteSpecial.pasteOutside",
|
||||
"Paste Outside",
|
||||
&["Edit", "Paste Special"],
|
||||
None,
|
||||
concat!(r#"{"center":[x,y]?,"#, crate::edit_cmds::paste_target!(), "}"),
|
||||
has_clip_and_selection,
|
||||
|s, p| paste_into(s, p, true)
|
||||
),
|
||||
spec!("select.reselect", "Reselect", &["Select"], Some("Cmd+Shift+D"), "{}", can_reselect, |s, _| {
|
||||
let m = reselect_target(s).ok_or(EngineError::Other("there is no selection to restore".into()))?;
|
||||
s.edit("Reselect", |doc, _| {
|
||||
|
||||
@@ -161,6 +161,13 @@ fn read_rgba(src: &Surface, area: Rect) -> Vec<[f32; 4]> {
|
||||
px
|
||||
}
|
||||
|
||||
/// `src` over `surf` across `area` (Normal, 100 %), each pixel weighted by `limit` when given:
|
||||
/// a paste into a layer mask or channel, which keeps the pasted pixels' luminosity (#1035).
|
||||
pub(crate) fn composite_over(surf: &mut Surface, src: &Surface, area: Rect, limit: Option<&Surface>) {
|
||||
let source = Source::Pixels(area, read_rgba(src, area));
|
||||
blend_into(surf, area, &source, limit, Blend { mode: BlendMode::Normal, opacity: 1.0, keep_alpha: false, restore: false });
|
||||
}
|
||||
|
||||
/// The selection filled from its surroundings (`photocraft_algo::content_aware`), as straight
|
||||
/// RGBA over the sampling window. Only the selected pixels are used.
|
||||
fn content_aware(surf: &Surface, sel: &Surface, canvas: Rect, color_adaptation: bool, seed: u64) -> Result<Source> {
|
||||
|
||||
@@ -1606,4 +1606,24 @@ mod clipboard_tests {
|
||||
assert_eq!(reads.load(std::sync::atomic::Ordering::SeqCst), 0, "the OS clipboard is read only on an explicit paste");
|
||||
assert!(h.state().session.clipboard.is_none());
|
||||
}
|
||||
|
||||
/// #1035: with the layer mask targeted (its thumbnail clicked), ⌘V pastes the clipboard's
|
||||
/// luminosity into the mask instead of making a layer.
|
||||
#[test]
|
||||
fn paste_with_the_mask_targeted_goes_into_the_mask() {
|
||||
let (os, reads) = (OsClip::default(), Arc::default());
|
||||
let mut app = app_with_os_clipboard(&os, &reads);
|
||||
app.run("layer.new.layer", serde_json::json!({})).unwrap();
|
||||
app.run("layer.layerMask.hideAll", serde_json::json!({})).unwrap();
|
||||
app.ui.mask_target = true;
|
||||
app.ui.views[0].center = [32.0, 32.0];
|
||||
*os.lock().unwrap() = Some((4, 4, [128u8, 128, 128, 255].repeat(16)));
|
||||
let layers = app.session.active().unwrap().doc.layer_count();
|
||||
crate::menus::invoke(&mut app, &egui::Context::default(), "edit.paste", serde_json::json!({})).unwrap();
|
||||
let st = app.session.active().unwrap();
|
||||
assert_eq!(st.doc.layer_count(), layers, "no new layer");
|
||||
let mask = st.doc.layer(st.active_layer.unwrap()).unwrap().mask.as_ref().unwrap();
|
||||
assert!((mask.value(32, 32) - 128.0 / 255.0).abs() < 2.0 / 255.0, "the grey, centred in the view: {}", mask.value(32, 32));
|
||||
assert_eq!(mask.value(0, 0), 0.0, "the rest of the mask is unchanged");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user