mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-10 20:32:31 +00:00
This commit is contained in:
commit
17e3490142
2 files changed
+66
-1
No files matched your search
@@ -171,6 +171,12 @@ fn image_size(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let d = s.active().ok_or(EngineError::NoDocument)?;
|
||||
let (ow, oh) = (d.doc.size.width, d.doc.size.height);
|
||||
let resample = parse_resample(p.get("resample").and_then(Value::as_str).unwrap_or("bicubic"));
|
||||
// An Indexed Color document holds only its colour table's colours, and a save quantises to
|
||||
// them: interpolation showed blends that the saved file didn't have (#6019).
|
||||
let resample = match resample {
|
||||
Method::Filter(_) | Method::Automatic if d.doc.mode == ColorMode::Indexed => Method::Filter(Resample::Nearest),
|
||||
m => m,
|
||||
};
|
||||
let mut pw = p.get("width").and_then(Value::as_f64);
|
||||
let mut ph = p.get("height").and_then(Value::as_f64);
|
||||
// `percent` scales both sides (what a Photoshop action records as "200%"); an explicit side wins.
|
||||
@@ -943,6 +949,27 @@ mod tests {
|
||||
/// #1544: 64 x 48 to 300000 x 300000 (90 billion pixels) started allocating resampled bands
|
||||
/// without a budget. It is refused before any allocation and the document is untouched; a
|
||||
/// change of resolution only, and an ordinary resize, still work.
|
||||
#[test]
|
||||
fn image_size_resamples_indexed_color_with_nearest_neighbor() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", json!({"width": 2, "height": 1})).unwrap();
|
||||
s.execute("edit.fill", json!({"color": "#ff0000"})).unwrap();
|
||||
s.execute("select.rect", json!({"x": 1, "y": 0, "width": 1, "height": 1})).unwrap();
|
||||
s.execute("edit.fill", json!({"color": "#0000ff"})).unwrap();
|
||||
s.execute("select.deselect", json!({})).unwrap();
|
||||
s.execute("image.mode.indexedColor", json!({})).unwrap();
|
||||
for resample in ["bicubic", "automatic", "bilinear", "lanczos", "preserveDetails"] {
|
||||
s.execute("image.imageSize", json!({"width": 8, "resample": resample})).unwrap();
|
||||
let st = s.active().unwrap();
|
||||
let surf = st.doc.layers[0].surface().unwrap();
|
||||
for x in 0..8 {
|
||||
let px = surf.pixel(x, 0);
|
||||
assert!(px[..3] == [1.0, 0.0, 0.0] || px[..3] == [0.0, 0.0, 1.0], "{resample}: no blended colour at x {x}: {px:?}");
|
||||
}
|
||||
s.execute("history.undo", json!({})).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_size_refuses_a_raster_past_the_budget() {
|
||||
let mut s = Session::new();
|
||||
|
||||
@@ -70,7 +70,7 @@ pub fn reset(f: &mut Map<String, Value>) {
|
||||
|
||||
pub fn open(app: &mut PhotocraftApp, command: &str) -> Option<u64> {
|
||||
let unit = pref_unit(app.session.prefs().units_and_rulers.rulers);
|
||||
let resample = pref_resample(app.session.prefs().general.image_interpolation);
|
||||
let mut resample = pref_resample(app.session.prefs().general.image_interpolation);
|
||||
let pt_per_in = app.session.prefs().units_and_rulers.point_size.per_inch();
|
||||
let d = &app.session.active()?.doc;
|
||||
let (w, h, res) = (d.size.width as f64, d.size.height as f64, d.resolution_dpi as f64);
|
||||
@@ -88,6 +88,12 @@ pub fn open(app: &mut PhotocraftApp, command: &str) -> Option<u64> {
|
||||
f.insert("width".into(), json!(w));
|
||||
f.insert("height".into(), json!(h));
|
||||
if command == "image.imageSize" {
|
||||
// Indexed Color resamples with Nearest Neighbor only, as the command does (#6019).
|
||||
let indexed = d.mode == photocraft_color::ColorMode::Indexed;
|
||||
if indexed && resample != "automatic" {
|
||||
resample = "nearest";
|
||||
}
|
||||
f.insert("__indexed".into(), json!(indexed));
|
||||
f.insert("__constrain".into(), json!(true));
|
||||
f.insert("resolution".into(), json!(res));
|
||||
f.insert("resample".into(), json!(resample));
|
||||
@@ -321,6 +327,10 @@ fn image_size(ui: &mut egui::Ui, f: &mut Map<String, Value>) {
|
||||
("bilinear".to_string(), tl!("Bilinear")),
|
||||
("lanczos".to_string(), tl!("Lanczos (sharp)")),
|
||||
];
|
||||
// Indexed Color: every other method blends colours the colour table doesn't have,
|
||||
// which the command doesn't use and a save quantises away (#6019).
|
||||
let indexed = f.get("__indexed").and_then(Value::as_bool) == Some(true);
|
||||
let opts: Vec<_> = opts.into_iter().filter(|(k, _)| !indexed || k == "automatic" || k == "nearest").collect();
|
||||
if crate::widgets::dropdown(ui, "is-resample", &mut cur, &opts, 200.0) {
|
||||
f.insert("resample".into(), json!(cur));
|
||||
}
|
||||
@@ -517,6 +527,34 @@ mod tests {
|
||||
|
||||
/// The dialog starts on Preferences › General › Image Interpolation: Bicubic Automatic opens
|
||||
/// as Automatic and the others as themselves (Bicubic Sharper used to open as Lanczos).
|
||||
#[test]
|
||||
fn indexed_color_resamples_with_nearest_neighbor_only() {
|
||||
let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), crate::Services::default());
|
||||
app.session.execute("file.new", json!({"width": 200, "height": 100})).unwrap();
|
||||
app.session.execute("image.mode.indexedColor", json!({})).unwrap();
|
||||
for (pref, method) in [("bicubic", "nearest"), ("bicubicAutomatic", "automatic"), ("nearestNeighbor", "nearest")] {
|
||||
app.session.execute("prefs.set", json!({"path": "general.imageInterpolation", "value": pref})).unwrap();
|
||||
let id = open(&mut app, "image.imageSize").unwrap();
|
||||
let f = &app.ui.dialog_mut(id).unwrap().fields;
|
||||
assert_eq!(f.get("resample"), Some(&json!(method)), "{pref}");
|
||||
assert_eq!(f.get("__indexed"), Some(&json!(true)));
|
||||
app.ui.close_dialog(id);
|
||||
}
|
||||
// The open list offers only Automatic and Nearest Neighbor.
|
||||
use egui_kittest::{Harness, kittest::Queryable};
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_ui_state(|ui, app| crate::dialogs::show(app, ui.ctx()), app);
|
||||
PhotocraftApp::setup_context(&h.ctx, crate::theme::ThemeKind::ALL[0]);
|
||||
open(h.state_mut(), "image.imageSize").unwrap();
|
||||
h.run_steps(3);
|
||||
let combo = h.query_all_by_role(egui::accesskit::Role::ComboBox).find(|n| n.value().as_deref() == Some("Nearest Neighbor (hard edges)"));
|
||||
combo.expect("the Resample list shows Nearest Neighbor").click();
|
||||
h.run_steps(3);
|
||||
assert!(h.query_by_label_contains("Automatic").is_some(), "Automatic is offered");
|
||||
for gone in ["Bicubic", "Bilinear", "Lanczos", "Preserve Details"] {
|
||||
assert!(h.query_by_label_contains(gone).is_none(), "{gone} is not offered for Indexed Color");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resample_follows_the_interpolation_preference() {
|
||||
let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), crate::Services::default());
|
||||
|
||||
Reference in new issue
Block a user