feat(stage1): add advanced targen options, collapsible section, tooltips and preset integration (resolves #141) #144
+202
-2
@@ -436,7 +436,7 @@ pub async fn extract_gamut(
|
|||||||
state.spawn(app, id, binary, args, cwd).await
|
state.spawn(app, id, binary, args, cwd).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct TargenConfig {
|
pub struct TargenConfig {
|
||||||
pub colour_space: String,
|
pub colour_space: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -446,6 +446,19 @@ pub struct TargenConfig {
|
|||||||
pub total_patches: Option<u32>,
|
pub total_patches: Option<u32>,
|
||||||
pub basename: String,
|
pub basename: String,
|
||||||
pub cwd: String,
|
pub cwd: String,
|
||||||
|
|
||||||
|
// Advanced Stage 1 options
|
||||||
|
pub grey_steps: Option<u32>, // -g
|
||||||
|
pub single_channel_steps: Option<u32>, // -s
|
||||||
|
pub neutral_steps: Option<u32>, // -n
|
||||||
|
pub preconditioning_profile: Option<String>, // -c
|
||||||
|
pub neutral_concentration: Option<f64>, // -N
|
||||||
|
pub ofps_high_quality: Option<bool>, // -G
|
||||||
|
pub ofps_adaptation: Option<f64>, // -A
|
||||||
|
pub full_spread_algorithm: Option<String>, // "ofps"|"t"|"r"|"R"|"q"|"Q"|"i"|"I"
|
||||||
|
pub total_ink_limit: Option<u32>, // -l
|
||||||
|
pub dark_emphasis: Option<f64>, // -V
|
||||||
|
pub device_power: Option<f64>, // -p
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_dpi() -> u32 {
|
fn default_dpi() -> u32 {
|
||||||
@@ -470,7 +483,8 @@ pub fn build_targen_args(config: &TargenConfig) -> Vec<String> {
|
|||||||
"-d".to_string(),
|
"-d".to_string(),
|
||||||
];
|
];
|
||||||
|
|
||||||
if config.colour_space.to_lowercase() == "cmyk" {
|
let is_cmyk = config.colour_space.to_lowercase() == "cmyk";
|
||||||
|
if is_cmyk {
|
||||||
args.push("4".to_string());
|
args.push("4".to_string());
|
||||||
} else {
|
} else {
|
||||||
args.push("2".to_string()); // Default to RGB
|
args.push("2".to_string()); // Default to RGB
|
||||||
@@ -497,6 +511,87 @@ pub fn build_targen_args(config: &TargenConfig) -> Vec<String> {
|
|||||||
args.push(black.to_string());
|
args.push(black.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(grey) = config.grey_steps {
|
||||||
|
if grey > 0 {
|
||||||
|
args.push("-g".to_string());
|
||||||
|
args.push(grey.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(single) = config.single_channel_steps {
|
||||||
|
if single > 0 {
|
||||||
|
args.push("-s".to_string());
|
||||||
|
args.push(single.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref precond) = config.preconditioning_profile {
|
||||||
|
let trimmed = precond.trim();
|
||||||
|
if !trimmed.is_empty() {
|
||||||
|
args.push("-c".to_string());
|
||||||
|
args.push(trimmed.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(neutral) = config.neutral_steps {
|
||||||
|
if neutral > 0 {
|
||||||
|
args.push("-n".to_string());
|
||||||
|
args.push(neutral.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(conc) = config.neutral_concentration {
|
||||||
|
if (conc - 0.50).abs() > 0.001 {
|
||||||
|
args.push("-N".to_string());
|
||||||
|
args.push(format!("{:.2}", conc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(true) = config.ofps_high_quality {
|
||||||
|
args.push("-G".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(adapt) = config.ofps_adaptation {
|
||||||
|
args.push("-A".to_string());
|
||||||
|
args.push(format!("{:.2}", adapt));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref algo) = config.full_spread_algorithm {
|
||||||
|
match algo.as_str() {
|
||||||
|
"t" => args.push("-t".to_string()),
|
||||||
|
"r" => args.push("-r".to_string()),
|
||||||
|
"R" => args.push("-R".to_string()),
|
||||||
|
"q" => args.push("-q".to_string()),
|
||||||
|
"Q" => args.push("-Q".to_string()),
|
||||||
|
"i" => args.push("-i".to_string()),
|
||||||
|
"I" => args.push("-I".to_string()),
|
||||||
|
_ => {} // default is OFPS (no flag needed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_cmyk {
|
||||||
|
if let Some(limit) = config.total_ink_limit {
|
||||||
|
if limit > 0 && limit <= 400 {
|
||||||
|
args.push("-l".to_string());
|
||||||
|
args.push(limit.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(dark) = config.dark_emphasis {
|
||||||
|
if (dark - 1.0).abs() > 0.001 {
|
||||||
|
args.push("-V".to_string());
|
||||||
|
args.push(format!("{:.2}", dark));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(power) = config.device_power {
|
||||||
|
if (power - 1.0).abs() > 0.001 && power > 0.0 {
|
||||||
|
args.push("-p".to_string());
|
||||||
|
args.push(format!("{:.2}", power));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
args.push(config.basename.clone());
|
args.push(config.basename.clone());
|
||||||
args
|
args
|
||||||
}
|
}
|
||||||
@@ -954,6 +1049,17 @@ mod tests {
|
|||||||
total_patches: None,
|
total_patches: None,
|
||||||
basename: "my_profile".to_string(),
|
basename: "my_profile".to_string(),
|
||||||
cwd: "/tmp".to_string(),
|
cwd: "/tmp".to_string(),
|
||||||
|
grey_steps: None,
|
||||||
|
single_channel_steps: None,
|
||||||
|
neutral_steps: None,
|
||||||
|
preconditioning_profile: None,
|
||||||
|
neutral_concentration: None,
|
||||||
|
ofps_high_quality: None,
|
||||||
|
ofps_adaptation: None,
|
||||||
|
full_spread_algorithm: None,
|
||||||
|
total_ink_limit: None,
|
||||||
|
dark_emphasis: None,
|
||||||
|
device_power: None,
|
||||||
};
|
};
|
||||||
let args = build_targen_args(&config);
|
let args = build_targen_args(&config);
|
||||||
assert_eq!(args, vec!["-v", "-d", "2", "-f", "800", "-e", "4", "my_profile"]);
|
assert_eq!(args, vec!["-v", "-d", "2", "-f", "800", "-e", "4", "my_profile"]);
|
||||||
@@ -969,6 +1075,17 @@ mod tests {
|
|||||||
total_patches: None,
|
total_patches: None,
|
||||||
basename: "cmyk_profile".to_string(),
|
basename: "cmyk_profile".to_string(),
|
||||||
cwd: "/tmp".to_string(),
|
cwd: "/tmp".to_string(),
|
||||||
|
grey_steps: None,
|
||||||
|
single_channel_steps: None,
|
||||||
|
neutral_steps: None,
|
||||||
|
preconditioning_profile: None,
|
||||||
|
neutral_concentration: None,
|
||||||
|
ofps_high_quality: None,
|
||||||
|
ofps_adaptation: None,
|
||||||
|
full_spread_algorithm: None,
|
||||||
|
total_ink_limit: None,
|
||||||
|
dark_emphasis: None,
|
||||||
|
device_power: None,
|
||||||
};
|
};
|
||||||
let args = build_targen_args(&config);
|
let args = build_targen_args(&config);
|
||||||
assert_eq!(args, vec!["-v", "-d", "4", "-f", "1500", "-B", "8", "cmyk_profile"]);
|
assert_eq!(args, vec!["-v", "-d", "4", "-f", "1500", "-B", "8", "cmyk_profile"]);
|
||||||
@@ -984,11 +1101,94 @@ mod tests {
|
|||||||
total_patches: Some(400),
|
total_patches: Some(400),
|
||||||
basename: "draft_profile".to_string(),
|
basename: "draft_profile".to_string(),
|
||||||
cwd: "/tmp".to_string(),
|
cwd: "/tmp".to_string(),
|
||||||
|
grey_steps: None,
|
||||||
|
single_channel_steps: None,
|
||||||
|
neutral_steps: None,
|
||||||
|
preconditioning_profile: None,
|
||||||
|
neutral_concentration: None,
|
||||||
|
ofps_high_quality: None,
|
||||||
|
ofps_adaptation: None,
|
||||||
|
full_spread_algorithm: None,
|
||||||
|
total_ink_limit: None,
|
||||||
|
dark_emphasis: None,
|
||||||
|
device_power: None,
|
||||||
};
|
};
|
||||||
let args = build_targen_args(&config);
|
let args = build_targen_args(&config);
|
||||||
assert_eq!(args, vec!["-v", "-d", "2", "-f", "400", "draft_profile"]);
|
assert_eq!(args, vec!["-v", "-d", "2", "-f", "400", "draft_profile"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_build_targen_args_all_advanced_flags() {
|
||||||
|
let config = TargenConfig {
|
||||||
|
colour_space: "cmyk".to_string(),
|
||||||
|
patch_count: 1200,
|
||||||
|
white_patches: Some(4),
|
||||||
|
black_patches: Some(4),
|
||||||
|
total_patches: None,
|
||||||
|
basename: "adv_target".to_string(),
|
||||||
|
cwd: "/home/user".to_string(),
|
||||||
|
grey_steps: Some(16),
|
||||||
|
single_channel_steps: Some(8),
|
||||||
|
neutral_steps: Some(10),
|
||||||
|
preconditioning_profile: Some("/profiles/precond.icc".to_string()),
|
||||||
|
neutral_concentration: Some(0.75),
|
||||||
|
ofps_high_quality: Some(true),
|
||||||
|
ofps_adaptation: Some(0.80),
|
||||||
|
full_spread_algorithm: Some("t".to_string()),
|
||||||
|
total_ink_limit: Some(320),
|
||||||
|
dark_emphasis: Some(1.5),
|
||||||
|
device_power: Some(1.2),
|
||||||
|
};
|
||||||
|
let args = build_targen_args(&config);
|
||||||
|
assert_eq!(
|
||||||
|
args,
|
||||||
|
vec![
|
||||||
|
"-v", "-d", "4",
|
||||||
|
"-f", "1200",
|
||||||
|
"-e", "4",
|
||||||
|
"-B", "4",
|
||||||
|
"-g", "16",
|
||||||
|
"-s", "8",
|
||||||
|
"-c", "/profiles/precond.icc",
|
||||||
|
"-n", "10",
|
||||||
|
"-N", "0.75",
|
||||||
|
"-G",
|
||||||
|
"-A", "0.80",
|
||||||
|
"-t",
|
||||||
|
"-l", "320",
|
||||||
|
"-V", "1.50",
|
||||||
|
"-p", "1.20",
|
||||||
|
"adv_target"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_build_targen_args_rgb_ignores_ink_limit() {
|
||||||
|
let config = TargenConfig {
|
||||||
|
colour_space: "rgb".to_string(),
|
||||||
|
patch_count: 800,
|
||||||
|
white_patches: None,
|
||||||
|
black_patches: None,
|
||||||
|
total_patches: None,
|
||||||
|
basename: "rgb_target".to_string(),
|
||||||
|
cwd: "/tmp".to_string(),
|
||||||
|
grey_steps: None,
|
||||||
|
single_channel_steps: None,
|
||||||
|
neutral_steps: None,
|
||||||
|
preconditioning_profile: None,
|
||||||
|
neutral_concentration: None,
|
||||||
|
ofps_high_quality: None,
|
||||||
|
ofps_adaptation: None,
|
||||||
|
full_spread_algorithm: None,
|
||||||
|
total_ink_limit: Some(300), // Should be ignored for RGB
|
||||||
|
dark_emphasis: None,
|
||||||
|
device_power: None,
|
||||||
|
};
|
||||||
|
let args = build_targen_args(&config);
|
||||||
|
assert!(!args.contains(&"-l".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_build_printtarg_args_i1_a4_8bit() {
|
fn test_build_printtarg_args_i1_a4_8bit() {
|
||||||
let config = PrinttargConfig {
|
let config = PrinttargConfig {
|
||||||
|
|||||||
@@ -18,6 +18,30 @@ pub struct ProfilingPreset {
|
|||||||
pub colprof_algorithm: String,
|
pub colprof_algorithm: String,
|
||||||
pub colprof_quality: String,
|
pub colprof_quality: String,
|
||||||
pub colprof_intent: Option<String>,
|
pub colprof_intent: Option<String>,
|
||||||
|
|
||||||
|
// Advanced Stage 1 fields
|
||||||
|
#[serde(default)]
|
||||||
|
pub grey_steps: Option<u32>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub single_channel_steps: Option<u32>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub neutral_steps: Option<u32>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub preconditioning_profile: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub neutral_concentration: Option<f64>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub ofps_high_quality: Option<bool>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub ofps_adaptation: Option<f64>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub full_spread_algorithm: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub total_ink_limit: Option<u32>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub dark_emphasis: Option<f64>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub device_power: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
||||||
@@ -46,6 +70,17 @@ pub fn get_default_presets() -> Vec<ProfilingPreset> {
|
|||||||
colprof_algorithm: "l".to_string(),
|
colprof_algorithm: "l".to_string(),
|
||||||
colprof_quality: "m".to_string(),
|
colprof_quality: "m".to_string(),
|
||||||
colprof_intent: None,
|
colprof_intent: None,
|
||||||
|
grey_steps: None,
|
||||||
|
single_channel_steps: None,
|
||||||
|
neutral_steps: None,
|
||||||
|
preconditioning_profile: None,
|
||||||
|
neutral_concentration: None,
|
||||||
|
ofps_high_quality: None,
|
||||||
|
ofps_adaptation: None,
|
||||||
|
full_spread_algorithm: None,
|
||||||
|
total_ink_limit: None,
|
||||||
|
dark_emphasis: None,
|
||||||
|
device_power: None,
|
||||||
},
|
},
|
||||||
ProfilingPreset {
|
ProfilingPreset {
|
||||||
id: "preset-hq-cmyk".to_string(),
|
id: "preset-hq-cmyk".to_string(),
|
||||||
@@ -62,6 +97,17 @@ pub fn get_default_presets() -> Vec<ProfilingPreset> {
|
|||||||
colprof_algorithm: "l".to_string(),
|
colprof_algorithm: "l".to_string(),
|
||||||
colprof_quality: "h".to_string(),
|
colprof_quality: "h".to_string(),
|
||||||
colprof_intent: None,
|
colprof_intent: None,
|
||||||
|
grey_steps: None,
|
||||||
|
single_channel_steps: None,
|
||||||
|
neutral_steps: None,
|
||||||
|
preconditioning_profile: None,
|
||||||
|
neutral_concentration: None,
|
||||||
|
ofps_high_quality: None,
|
||||||
|
ofps_adaptation: None,
|
||||||
|
full_spread_algorithm: None,
|
||||||
|
total_ink_limit: Some(320),
|
||||||
|
dark_emphasis: None,
|
||||||
|
device_power: None,
|
||||||
},
|
},
|
||||||
ProfilingPreset {
|
ProfilingPreset {
|
||||||
id: "preset-draft-rgb".to_string(),
|
id: "preset-draft-rgb".to_string(),
|
||||||
@@ -78,6 +124,17 @@ pub fn get_default_presets() -> Vec<ProfilingPreset> {
|
|||||||
colprof_algorithm: "l".to_string(),
|
colprof_algorithm: "l".to_string(),
|
||||||
colprof_quality: "l".to_string(),
|
colprof_quality: "l".to_string(),
|
||||||
colprof_intent: None,
|
colprof_intent: None,
|
||||||
|
grey_steps: None,
|
||||||
|
single_channel_steps: None,
|
||||||
|
neutral_steps: None,
|
||||||
|
preconditioning_profile: None,
|
||||||
|
neutral_concentration: None,
|
||||||
|
ofps_high_quality: None,
|
||||||
|
ofps_adaptation: None,
|
||||||
|
full_spread_algorithm: None,
|
||||||
|
total_ink_limit: None,
|
||||||
|
dark_emphasis: None,
|
||||||
|
device_power: None,
|
||||||
},
|
},
|
||||||
ProfilingPreset {
|
ProfilingPreset {
|
||||||
id: "preset-ultra-rgb".to_string(),
|
id: "preset-ultra-rgb".to_string(),
|
||||||
@@ -94,6 +151,17 @@ pub fn get_default_presets() -> Vec<ProfilingPreset> {
|
|||||||
colprof_algorithm: "l".to_string(),
|
colprof_algorithm: "l".to_string(),
|
||||||
colprof_quality: "u".to_string(),
|
colprof_quality: "u".to_string(),
|
||||||
colprof_intent: None,
|
colprof_intent: None,
|
||||||
|
grey_steps: None,
|
||||||
|
single_channel_steps: None,
|
||||||
|
neutral_steps: None,
|
||||||
|
preconditioning_profile: None,
|
||||||
|
neutral_concentration: None,
|
||||||
|
ofps_high_quality: Some(true),
|
||||||
|
ofps_adaptation: None,
|
||||||
|
full_spread_algorithm: None,
|
||||||
|
total_ink_limit: None,
|
||||||
|
dark_emphasis: None,
|
||||||
|
device_power: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -219,6 +287,17 @@ mod tests {
|
|||||||
colprof_algorithm: "l".to_string(),
|
colprof_algorithm: "l".to_string(),
|
||||||
colprof_quality: "h".to_string(),
|
colprof_quality: "h".to_string(),
|
||||||
colprof_intent: None,
|
colprof_intent: None,
|
||||||
|
grey_steps: Some(16),
|
||||||
|
single_channel_steps: Some(8),
|
||||||
|
neutral_steps: Some(10),
|
||||||
|
preconditioning_profile: Some("/profiles/precond.icc".to_string()),
|
||||||
|
neutral_concentration: Some(0.75),
|
||||||
|
ofps_high_quality: Some(true),
|
||||||
|
ofps_adaptation: Some(0.80),
|
||||||
|
full_spread_algorithm: Some("t".to_string()),
|
||||||
|
total_ink_limit: Some(320),
|
||||||
|
dark_emphasis: Some(1.5),
|
||||||
|
device_power: Some(1.2),
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = export_preset_json(preset.clone()).expect("Export failed");
|
let json = export_preset_json(preset.clone()).expect("Export failed");
|
||||||
|
|||||||
+181
-10
@@ -58,19 +58,31 @@
|
|||||||
<main class="content">
|
<main class="content">
|
||||||
<!-- Stage 1: targen -->
|
<!-- Stage 1: targen -->
|
||||||
<section id="stage-1" class="stage active">
|
<section id="stage-1" class="stage active">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
<div>
|
||||||
<h2>Generate Target</h2>
|
<h2>Generate Target</h2>
|
||||||
<p>Configure the colour patches for your profiling target.</p>
|
<p>Configure the colour patches for your profiling target.</p>
|
||||||
|
</div>
|
||||||
|
<button type="button" id="btnToggleAllHelp" class="secondary" style="font-size: 0.75rem; padding: 4px 8px;" title="Toggle tooltip hints for all options">💡 Show Tooltip Hints</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-container">
|
<div class="form-container" id="stage1FormContainer">
|
||||||
<div class="form-group">
|
<!-- Basic Settings -->
|
||||||
|
<div class="form-group has-tooltip">
|
||||||
<label>Colour Space</label>
|
<label>Colour Space</label>
|
||||||
<div class="radio-group">
|
<div class="radio-group">
|
||||||
<label><input type="radio" name="colourSpace" value="rgb" checked> RGB (Printer Driver)</label>
|
<label><input type="radio" name="colourSpace" value="rgb" checked> RGB (Printer Driver)</label>
|
||||||
<label><input type="radio" name="colourSpace" value="cmyk"> CMYK (RIP)</label>
|
<label><input type="radio" name="colourSpace" value="cmyk"> CMYK (RIP)</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tooltip-text">
|
||||||
|
Choose the device colour model for the test chart.<br>
|
||||||
|
• RGB (Printer Driver) – for printers driven by an RGB driver or host colour management.<br>
|
||||||
|
• CMYK (RIP) – for printers controlled by a RIP or when you need explicit CMYK separation.<br>
|
||||||
|
This sets the targen -d colourant combination (2 = Print RGB, 4 = CMYK).
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group has-tooltip">
|
||||||
<label>Patch Count</label>
|
<label>Patch Count</label>
|
||||||
<select id="patchCountPreset">
|
<select id="patchCountPreset">
|
||||||
<option value="400">Draft (400 patches)</option>
|
<option value="400">Draft (400 patches)</option>
|
||||||
@@ -80,23 +92,36 @@
|
|||||||
<option value="custom">Custom...</option>
|
<option value="custom">Custom...</option>
|
||||||
</select>
|
</select>
|
||||||
<input type="number" id="patchCountCustom" value="800" min="50" max="10000" class="hidden" style="margin-top: 8px;">
|
<input type="number" id="patchCountCustom" value="800" min="50" max="10000" class="hidden" style="margin-top: 8px;">
|
||||||
|
<div class="tooltip-text">
|
||||||
|
Total number of colour patches that will appear in the final chart.<br>
|
||||||
|
Higher counts improve profile accuracy (especially in smooth gradients and near the neutrals) but increase print time, paper usage and measurement time.<br>
|
||||||
|
Typical guidance: 400 (draft), 800 (standard), 1500 (photo/proof), 2500+ (maximum quality). Passed to targen -f.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Neutral Axis Boost (Optional)</label>
|
<label>Neutral Axis Boost (Optional)</label>
|
||||||
<div class="input-row">
|
<div class="input-row">
|
||||||
<div>
|
<div class="has-tooltip" style="flex: 1;">
|
||||||
<label for="whitePatches" class="sub-label">White Patches</label>
|
<label for="whitePatches" class="sub-label">White Patches (-e)</label>
|
||||||
<input type="number" id="whitePatches" min="0" max="100" placeholder="e.g. 4">
|
<input type="number" id="whitePatches" min="0" max="100" placeholder="e.g. 4">
|
||||||
|
<div class="tooltip-text">
|
||||||
|
Number of pure-white patches included in the chart.<br>
|
||||||
|
White defines the paper white point that the ICC profile is made relative to. Measuring several white patches improves robustness against noise and paper variation. Default is 4.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="has-tooltip" style="flex: 1;">
|
||||||
|
<label for="blackPatches" class="sub-label">Black Patches (-B)</label>
|
||||||
|
<input type="number" id="blackPatches" min="0" max="100" placeholder="e.g. 4">
|
||||||
|
<div class="tooltip-text">
|
||||||
|
Number of pure-black patches included in the chart.<br>
|
||||||
|
Black is especially important for additive (RGB) devices; measuring multiple black patches improves black-point accuracy. Default is 4 for RGB and 0 for CMYK.
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<label for="blackPatches" class="sub-label">Black Patches</label>
|
|
||||||
<input type="number" id="blackPatches" min="0" max="100" placeholder="e.g. 8">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group has-tooltip">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
|
||||||
<label style="margin: 0;">Project Directory & Filename</label>
|
<label style="margin: 0;">Project Directory & Filename</label>
|
||||||
<button type="button" id="btnOpenExisting" class="secondary" style="font-size: 0.75rem; padding: 2px 8px;" title="Open existing .ti1 or .ti2 target file">📂 Open Target...</button>
|
<button type="button" id="btnOpenExisting" class="secondary" style="font-size: 0.75rem; padding: 2px 8px;" title="Open existing .ti1 or .ti2 target file">📂 Open Target...</button>
|
||||||
@@ -106,10 +131,156 @@
|
|||||||
<button type="button" id="btnBrowse" class="secondary">Browse...</button>
|
<button type="button" id="btnBrowse" class="secondary">Browse...</button>
|
||||||
</div>
|
</div>
|
||||||
<small id="selectedPathDisplay" class="path-display">No directory selected (using default).</small>
|
<small id="selectedPathDisplay" class="path-display">No directory selected (using default).</small>
|
||||||
|
<div class="tooltip-text">
|
||||||
|
Base name used for all generated files (.ti1, later .ti2, .ti3, .icc/.icm) and folder where artefacts will be created.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="primary" id="btnGenerate" disabled>Generate (.ti1)</button>
|
<!-- Collapsible Advanced Section -->
|
||||||
|
<details id="targenAdvancedDetails" class="advanced-section-details" style="margin-top: 10px; border: 1px solid var(--border-color, #333); border-radius: 8px; padding: 10px 14px; background: rgba(0,0,0,0.15);">
|
||||||
|
<summary style="font-weight: 600; cursor: pointer; display: flex; align-items: center; justify-content: space-between; user-select: none;">
|
||||||
|
<span>⚙️ Advanced Target Generation Options</span>
|
||||||
|
<span class="summary-badge" style="font-size: 0.75rem; opacity: 0.6;">Click to expand</span>
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
<div class="advanced-content" style="margin-top: 14px; display: flex; flex-direction: column; gap: 12px;">
|
||||||
|
<!-- Grey & Single Channel Steps -->
|
||||||
|
<div class="input-row">
|
||||||
|
<div class="form-group has-tooltip" style="flex: 1;">
|
||||||
|
<label for="targenGreySteps" class="sub-label">Grey / Combined Axis Steps (-g)</label>
|
||||||
|
<input type="number" id="targenGreySteps" min="0" max="256" placeholder="0">
|
||||||
|
<div class="tooltip-text">
|
||||||
|
Number of evenly-spaced steps along the combined grey axis (equal R=G=B or C=M=Y).<br>
|
||||||
|
These patches improve neutral-axis rendering and are useful when device will later be used as source space. 0 = none.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group has-tooltip" style="flex: 1;">
|
||||||
|
<label for="targenSingleChannelSteps" class="sub-label">Single-Channel Steps (-s)</label>
|
||||||
|
<input type="number" id="targenSingleChannelSteps" min="0" max="256" placeholder="0">
|
||||||
|
<div class="tooltip-text">
|
||||||
|
Number of steps for each individual colorant wedge (R, G, B or C, M, Y, K).<br>
|
||||||
|
Useful for diagnosing per-channel non-linearity and source-space characterisation. 0 = none.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Preconditioning Profile & Neutral Axis Steps -->
|
||||||
|
<div class="form-group has-tooltip">
|
||||||
|
<label for="targenPrecondProfile" class="sub-label">Preconditioning Profile (-c)</label>
|
||||||
|
<div class="input-row has-btn">
|
||||||
|
<input type="text" id="targenPrecondProfile" placeholder="Optional previous profile (.icc / .icm)">
|
||||||
|
<button type="button" id="btnBrowsePrecondProfile" class="secondary">Browse...</button>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip-text">
|
||||||
|
Optional previous ICC/ICM (or MPP) profile of this or a similar device.<br>
|
||||||
|
Used by targen to estimate perceptual distances, colourspace curvature and the true neutral axis. Enables adaptive OFPS and Neutral Axis Steps.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-row">
|
||||||
|
<div class="form-group has-tooltip" style="flex: 1;">
|
||||||
|
<label for="targenNeutralSteps" class="sub-label">Neutral Axis Steps (-n)</label>
|
||||||
|
<input type="number" id="targenNeutralSteps" min="0" max="256" placeholder="0">
|
||||||
|
<div class="tooltip-text">
|
||||||
|
Number of patches placed along the true neutral axis as estimated by preconditioning profile.<br>
|
||||||
|
Requires a Preconditioning Profile. Improves neutral rendering of final profile.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group has-tooltip" style="flex: 1;">
|
||||||
|
<label for="targenNeutralConcentration" class="sub-label">Neutral Axis Concentration (-N)</label>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<input type="range" id="targenNeutralConcentration" min="0.0" max="1.0" step="0.05" value="0.5" style="flex: 1;">
|
||||||
|
<span id="targenNeutralConcVal" style="font-family: monospace; font-size: 0.8rem; min-width: 35px;">0.50</span>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip-text">
|
||||||
|
How strongly the patch distribution should favour the neutral axis (0 = none, 1 = maximum).<br>
|
||||||
|
Default 0.5. Most effective with preconditioning profile and high OFPS Adaptation.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Quality & Algorithm -->
|
||||||
|
<div class="input-row">
|
||||||
|
<div class="form-group has-tooltip" style="flex: 1;">
|
||||||
|
<label for="targenAlgorithm" class="sub-label">Full-spread Algorithm</label>
|
||||||
|
<select id="targenAlgorithm">
|
||||||
|
<option value="ofps" selected>OFPS (Optimised Farthest Point Sampling - Default)</option>
|
||||||
|
<option value="t">Incremental Far Point (-t)</option>
|
||||||
|
<option value="r">Device Random (-r)</option>
|
||||||
|
<option value="R">Perceptual Random (-R)</option>
|
||||||
|
<option value="q">Device Quasi-Random (-q)</option>
|
||||||
|
<option value="Q">Perceptual Quasi-Random (-Q)</option>
|
||||||
|
<option value="i">Device BCC (-i)</option>
|
||||||
|
<option value="I">Perceptual BCC (-I)</option>
|
||||||
|
</select>
|
||||||
|
<div class="tooltip-text">
|
||||||
|
Algorithm used to place remaining “full-spread” patches.<br>
|
||||||
|
• OFPS (default) – best evenness in most cases.<br>
|
||||||
|
• Incremental Far Point – used automatically for >4 channels.<br>
|
||||||
|
• Device/Perceptual random or BCC – alternative space-filling methods.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group has-tooltip" style="flex: 1; display: flex; flex-direction: column; justify-content: flex-end;">
|
||||||
|
<label style="cursor: pointer; display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
|
||||||
|
<input type="checkbox" id="targenHighQuality">
|
||||||
|
<span style="font-size: 0.85rem;">High Quality OFPS (-G)</span>
|
||||||
|
</label>
|
||||||
|
<div class="tooltip-text">
|
||||||
|
When checked, targen runs the OFPS algorithm in “good” mode (more iterations).<br>
|
||||||
|
Produces a more evenly distributed set of patches at cost of longer generation time. Recommended for final production charts.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Adaptation & Ink Limit & Tone Remapping -->
|
||||||
|
<div class="input-row">
|
||||||
|
<div class="form-group has-tooltip" style="flex: 1;">
|
||||||
|
<label for="targenAdaptation" class="sub-label">OFPS Adaptation (-A)</label>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<input type="range" id="targenAdaptation" min="0.0" max="1.0" step="0.05" value="0.1" style="flex: 1;">
|
||||||
|
<span id="targenAdaptationVal" style="font-family: monospace; font-size: 0.8rem; min-width: 35px;">0.10</span>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip-text">
|
||||||
|
Degree to which OFPS adapts to device behaviour from preconditioning profile (0 = ignore, 1 = fully adapt).<br>
|
||||||
|
Default 0.1; automatically rises to 1.0 when preconditioning profile is supplied.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group has-tooltip" id="targenInkLimitGroup" style="flex: 1;">
|
||||||
|
<label for="targenInkLimit" class="sub-label">Total Ink Limit / TAC (-l %)</label>
|
||||||
|
<input type="number" id="targenInkLimit" min="0" max="400" placeholder="e.g. 320" disabled>
|
||||||
|
<div class="tooltip-text">
|
||||||
|
Maximum total ink coverage (Total Area Coverage) in percent.<br>
|
||||||
|
Essential for CMYK to avoid over-inking. Set at least 10% higher than final profile TAC. Disabled in RGB mode.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-row">
|
||||||
|
<div class="form-group has-tooltip" style="flex: 1;">
|
||||||
|
<label for="targenDarkEmphasis" class="sub-label">Dark Region Emphasis (-V)</label>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<input type="range" id="targenDarkEmphasis" min="1.0" max="4.0" step="0.1" value="1.0" style="flex: 1;">
|
||||||
|
<span id="targenDarkEmphasisVal" style="font-family: monospace; font-size: 0.8rem; min-width: 35px;">1.00</span>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip-text">
|
||||||
|
Concentrates more patches toward dark tones (1.0 = none, up to 4.0 = strong).<br>
|
||||||
|
Useful for devices with non-linearity or important shadow detail. Default 1.0.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group has-tooltip" style="flex: 1;">
|
||||||
|
<label for="targenDevicePower" class="sub-label">Device Value Power (-p)</label>
|
||||||
|
<input type="number" id="targenDevicePower" min="0.1" max="4.0" step="0.1" placeholder="Linear (1.0)">
|
||||||
|
<div class="tooltip-text">
|
||||||
|
Optional power-like remapping applied to device values after generation.<br>
|
||||||
|
Values > 1 tighten spacing near 0; values < 1 tighten spacing near 1. Leave empty for linear spacing.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="primary" id="btnGenerate" disabled style="margin-top: 14px;">Generate (.ti1)</button>
|
||||||
|
|
||||||
<details class="log-container hidden" id="targenLogContainer">
|
<details class="log-container hidden" id="targenLogContainer">
|
||||||
<summary>Process Output</summary>
|
<summary>Process Output</summary>
|
||||||
|
|||||||
@@ -152,6 +152,58 @@ export async function initPresets() {
|
|||||||
const blackPatches = document.getElementById("blackPatches");
|
const blackPatches = document.getElementById("blackPatches");
|
||||||
if (blackPatches) blackPatches.value = preset.black_patches !== null && preset.black_patches !== undefined ? preset.black_patches : "";
|
if (blackPatches) blackPatches.value = preset.black_patches !== null && preset.black_patches !== undefined ? preset.black_patches : "";
|
||||||
|
|
||||||
|
// Advanced Stage 1 controls
|
||||||
|
const targenGreySteps = document.getElementById("targenGreySteps");
|
||||||
|
if (targenGreySteps) targenGreySteps.value = (preset.grey_steps !== null && preset.grey_steps !== undefined) ? preset.grey_steps : "";
|
||||||
|
|
||||||
|
const targenSingleChannelSteps = document.getElementById("targenSingleChannelSteps");
|
||||||
|
if (targenSingleChannelSteps) targenSingleChannelSteps.value = (preset.single_channel_steps !== null && preset.single_channel_steps !== undefined) ? preset.single_channel_steps : "";
|
||||||
|
|
||||||
|
const targenPrecondProfile = document.getElementById("targenPrecondProfile");
|
||||||
|
if (targenPrecondProfile) targenPrecondProfile.value = preset.preconditioning_profile || "";
|
||||||
|
|
||||||
|
const targenNeutralSteps = document.getElementById("targenNeutralSteps");
|
||||||
|
if (targenNeutralSteps) targenNeutralSteps.value = (preset.neutral_steps !== null && preset.neutral_steps !== undefined) ? preset.neutral_steps : "";
|
||||||
|
|
||||||
|
const targenNeutralConcentration = document.getElementById("targenNeutralConcentration");
|
||||||
|
const targenNeutralConcVal = document.getElementById("targenNeutralConcVal");
|
||||||
|
if (targenNeutralConcentration) {
|
||||||
|
const conc = (preset.neutral_concentration !== null && preset.neutral_concentration !== undefined) ? preset.neutral_concentration : 0.50;
|
||||||
|
targenNeutralConcentration.value = conc;
|
||||||
|
if (targenNeutralConcVal) targenNeutralConcVal.textContent = parseFloat(conc).toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targenHighQuality = document.getElementById("targenHighQuality");
|
||||||
|
if (targenHighQuality) targenHighQuality.checked = !!preset.ofps_high_quality;
|
||||||
|
|
||||||
|
const targenAdaptation = document.getElementById("targenAdaptation");
|
||||||
|
const targenAdaptationVal = document.getElementById("targenAdaptationVal");
|
||||||
|
if (targenAdaptation) {
|
||||||
|
const adapt = (preset.ofps_adaptation !== null && preset.ofps_adaptation !== undefined) ? preset.ofps_adaptation : 0.10;
|
||||||
|
targenAdaptation.value = adapt;
|
||||||
|
if (targenAdaptationVal) targenAdaptationVal.textContent = parseFloat(adapt).toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targenAlgorithm = document.getElementById("targenAlgorithm");
|
||||||
|
if (targenAlgorithm) targenAlgorithm.value = preset.full_spread_algorithm || "ofps";
|
||||||
|
|
||||||
|
const targenInkLimit = document.getElementById("targenInkLimit");
|
||||||
|
if (targenInkLimit) {
|
||||||
|
targenInkLimit.disabled = preset.colour_space.toLowerCase() !== "cmyk";
|
||||||
|
targenInkLimit.value = (preset.total_ink_limit !== null && preset.total_ink_limit !== undefined) ? preset.total_ink_limit : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const targenDarkEmphasis = document.getElementById("targenDarkEmphasis");
|
||||||
|
const targenDarkEmphasisVal = document.getElementById("targenDarkEmphasisVal");
|
||||||
|
if (targenDarkEmphasis) {
|
||||||
|
const dark = (preset.dark_emphasis !== null && preset.dark_emphasis !== undefined) ? preset.dark_emphasis : 1.00;
|
||||||
|
targenDarkEmphasis.value = dark;
|
||||||
|
if (targenDarkEmphasisVal) targenDarkEmphasisVal.textContent = parseFloat(dark).toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targenDevicePower = document.getElementById("targenDevicePower");
|
||||||
|
if (targenDevicePower) targenDevicePower.value = (preset.device_power !== null && preset.device_power !== undefined) ? preset.device_power : "";
|
||||||
|
|
||||||
// Stage 2 controls
|
// Stage 2 controls
|
||||||
const instrumentSelect = document.getElementById("instrumentSelect");
|
const instrumentSelect = document.getElementById("instrumentSelect");
|
||||||
if (instrumentSelect && preset.instrument) instrumentSelect.value = preset.instrument;
|
if (instrumentSelect && preset.instrument) instrumentSelect.value = preset.instrument;
|
||||||
@@ -198,6 +250,40 @@ export async function initPresets() {
|
|||||||
const blackInput = document.getElementById("blackPatches");
|
const blackInput = document.getElementById("blackPatches");
|
||||||
const black_patches = blackInput && blackInput.value ? parseInt(blackInput.value, 10) : null;
|
const black_patches = blackInput && blackInput.value ? parseInt(blackInput.value, 10) : null;
|
||||||
|
|
||||||
|
// Advanced Stage 1 inputs
|
||||||
|
const targenGreySteps = document.getElementById("targenGreySteps");
|
||||||
|
const grey_steps = targenGreySteps && targenGreySteps.value ? parseInt(targenGreySteps.value, 10) : null;
|
||||||
|
|
||||||
|
const targenSingleChannelSteps = document.getElementById("targenSingleChannelSteps");
|
||||||
|
const single_channel_steps = targenSingleChannelSteps && targenSingleChannelSteps.value ? parseInt(targenSingleChannelSteps.value, 10) : null;
|
||||||
|
|
||||||
|
const targenPrecondProfile = document.getElementById("targenPrecondProfile");
|
||||||
|
const preconditioning_profile = targenPrecondProfile && targenPrecondProfile.value.trim() ? targenPrecondProfile.value.trim() : null;
|
||||||
|
|
||||||
|
const targenNeutralSteps = document.getElementById("targenNeutralSteps");
|
||||||
|
const neutral_steps = targenNeutralSteps && targenNeutralSteps.value ? parseInt(targenNeutralSteps.value, 10) : null;
|
||||||
|
|
||||||
|
const targenNeutralConcentration = document.getElementById("targenNeutralConcentration");
|
||||||
|
const neutral_concentration = targenNeutralConcentration ? parseFloat(targenNeutralConcentration.value) : null;
|
||||||
|
|
||||||
|
const targenHighQuality = document.getElementById("targenHighQuality");
|
||||||
|
const ofps_high_quality = targenHighQuality ? targenHighQuality.checked : false;
|
||||||
|
|
||||||
|
const targenAdaptation = document.getElementById("targenAdaptation");
|
||||||
|
const ofps_adaptation = targenAdaptation ? parseFloat(targenAdaptation.value) : null;
|
||||||
|
|
||||||
|
const targenAlgorithm = document.getElementById("targenAlgorithm");
|
||||||
|
const full_spread_algorithm = targenAlgorithm && targenAlgorithm.value !== "ofps" ? targenAlgorithm.value : null;
|
||||||
|
|
||||||
|
const targenInkLimit = document.getElementById("targenInkLimit");
|
||||||
|
const total_ink_limit = (colour_space === "cmyk" && targenInkLimit && targenInkLimit.value) ? parseInt(targenInkLimit.value, 10) : null;
|
||||||
|
|
||||||
|
const targenDarkEmphasis = document.getElementById("targenDarkEmphasis");
|
||||||
|
const dark_emphasis = targenDarkEmphasis ? parseFloat(targenDarkEmphasis.value) : null;
|
||||||
|
|
||||||
|
const targenDevicePower = document.getElementById("targenDevicePower");
|
||||||
|
const device_power = targenDevicePower && targenDevicePower.value ? parseFloat(targenDevicePower.value) : null;
|
||||||
|
|
||||||
const instrumentSelect = document.getElementById("instrumentSelect");
|
const instrumentSelect = document.getElementById("instrumentSelect");
|
||||||
const instrument = instrumentSelect ? instrumentSelect.value : "i1";
|
const instrument = instrumentSelect ? instrumentSelect.value : "i1";
|
||||||
|
|
||||||
@@ -224,6 +310,17 @@ export async function initPresets() {
|
|||||||
patch_count,
|
patch_count,
|
||||||
white_patches,
|
white_patches,
|
||||||
black_patches,
|
black_patches,
|
||||||
|
grey_steps,
|
||||||
|
single_channel_steps,
|
||||||
|
preconditioning_profile,
|
||||||
|
neutral_steps,
|
||||||
|
neutral_concentration,
|
||||||
|
ofps_high_quality,
|
||||||
|
ofps_adaptation,
|
||||||
|
full_spread_algorithm,
|
||||||
|
total_ink_limit,
|
||||||
|
dark_emphasis,
|
||||||
|
device_power,
|
||||||
instrument,
|
instrument,
|
||||||
page_size,
|
page_size,
|
||||||
bit_depth,
|
bit_depth,
|
||||||
|
|||||||
@@ -18,8 +18,96 @@ export function initTargen() {
|
|||||||
const logContainer = document.getElementById("targenLogContainer");
|
const logContainer = document.getElementById("targenLogContainer");
|
||||||
const logPre = document.getElementById("targenLog");
|
const logPre = document.getElementById("targenLog");
|
||||||
|
|
||||||
|
// Advanced controls
|
||||||
|
const targenGreySteps = document.getElementById("targenGreySteps");
|
||||||
|
const targenSingleChannelSteps = document.getElementById("targenSingleChannelSteps");
|
||||||
|
const targenPrecondProfile = document.getElementById("targenPrecondProfile");
|
||||||
|
const btnBrowsePrecondProfile = document.getElementById("btnBrowsePrecondProfile");
|
||||||
|
const targenNeutralSteps = document.getElementById("targenNeutralSteps");
|
||||||
|
const targenNeutralConcentration = document.getElementById("targenNeutralConcentration");
|
||||||
|
const targenNeutralConcVal = document.getElementById("targenNeutralConcVal");
|
||||||
|
const targenAlgorithm = document.getElementById("targenAlgorithm");
|
||||||
|
const targenHighQuality = document.getElementById("targenHighQuality");
|
||||||
|
const targenAdaptation = document.getElementById("targenAdaptation");
|
||||||
|
const targenAdaptationVal = document.getElementById("targenAdaptationVal");
|
||||||
|
const targenInkLimit = document.getElementById("targenInkLimit");
|
||||||
|
const targenDarkEmphasis = document.getElementById("targenDarkEmphasis");
|
||||||
|
const targenDarkEmphasisVal = document.getElementById("targenDarkEmphasisVal");
|
||||||
|
const targenDevicePower = document.getElementById("targenDevicePower");
|
||||||
|
const btnToggleAllHelp = document.getElementById("btnToggleAllHelp");
|
||||||
|
const stage1FormContainer = document.getElementById("stage1FormContainer");
|
||||||
|
|
||||||
let currentWorkingDir = "";
|
let currentWorkingDir = "";
|
||||||
|
|
||||||
|
// Help hints toggle
|
||||||
|
if (btnToggleAllHelp && stage1FormContainer) {
|
||||||
|
btnToggleAllHelp.addEventListener("click", () => {
|
||||||
|
stage1FormContainer.classList.toggle("show-all-tooltips");
|
||||||
|
if (stage1FormContainer.classList.contains("show-all-tooltips")) {
|
||||||
|
btnToggleAllHelp.textContent = "💡 Hide Tooltip Hints";
|
||||||
|
} else {
|
||||||
|
btnToggleAllHelp.textContent = "💡 Show Tooltip Hints";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sliders dynamic display
|
||||||
|
if (targenNeutralConcentration && targenNeutralConcVal) {
|
||||||
|
targenNeutralConcentration.addEventListener("input", (e) => {
|
||||||
|
targenNeutralConcVal.textContent = parseFloat(e.target.value).toFixed(2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (targenAdaptation && targenAdaptationVal) {
|
||||||
|
targenAdaptation.addEventListener("input", (e) => {
|
||||||
|
targenAdaptationVal.textContent = parseFloat(e.target.value).toFixed(2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (targenDarkEmphasis && targenDarkEmphasisVal) {
|
||||||
|
targenDarkEmphasis.addEventListener("input", (e) => {
|
||||||
|
targenDarkEmphasisVal.textContent = parseFloat(e.target.value).toFixed(2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Colour space change listener to enable/disable ink limit
|
||||||
|
colourSpaceRadios.forEach((radio) => {
|
||||||
|
radio.addEventListener("change", (e) => {
|
||||||
|
const isCmyk = e.target.value === "cmyk";
|
||||||
|
if (targenInkLimit) {
|
||||||
|
targenInkLimit.disabled = !isCmyk;
|
||||||
|
if (!isCmyk) {
|
||||||
|
targenInkLimit.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (blackPatches) {
|
||||||
|
if (isCmyk && (!blackPatches.value || blackPatches.value === "4")) {
|
||||||
|
blackPatches.value = "0";
|
||||||
|
} else if (!isCmyk && (!blackPatches.value || blackPatches.value === "0")) {
|
||||||
|
blackPatches.value = "4";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Browse preconditioning profile
|
||||||
|
if (btnBrowsePrecondProfile && targenPrecondProfile) {
|
||||||
|
btnBrowsePrecondProfile.addEventListener("click", async () => {
|
||||||
|
try {
|
||||||
|
const filePath = await invoke("select_existing_target", {
|
||||||
|
defaultDir: currentWorkingDir || null,
|
||||||
|
});
|
||||||
|
if (filePath) {
|
||||||
|
targenPrecondProfile.value = filePath;
|
||||||
|
if (targenAdaptation && targenAdaptation.value === "0.1") {
|
||||||
|
targenAdaptation.value = "1.0";
|
||||||
|
if (targenAdaptationVal) targenAdaptationVal.textContent = "1.00";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to select preconditioning profile:", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function updateGenerateButton() {
|
function updateGenerateButton() {
|
||||||
const hasBasename = targetBasename && targetBasename.value.trim().length > 0;
|
const hasBasename = targetBasename && targetBasename.value.trim().length > 0;
|
||||||
const hasCwd = currentWorkingDir && currentWorkingDir.trim().length > 0;
|
const hasCwd = currentWorkingDir && currentWorkingDir.trim().length > 0;
|
||||||
@@ -210,6 +298,17 @@ export function initTargen() {
|
|||||||
black_patches: (blackPatches && blackPatches.value) ? parseInt(blackPatches.value, 10) : null,
|
black_patches: (blackPatches && blackPatches.value) ? parseInt(blackPatches.value, 10) : null,
|
||||||
basename: basename,
|
basename: basename,
|
||||||
cwd: currentWorkingDir,
|
cwd: currentWorkingDir,
|
||||||
|
grey_steps: (targenGreySteps && targenGreySteps.value) ? parseInt(targenGreySteps.value, 10) : null,
|
||||||
|
single_channel_steps: (targenSingleChannelSteps && targenSingleChannelSteps.value) ? parseInt(targenSingleChannelSteps.value, 10) : null,
|
||||||
|
preconditioning_profile: (targenPrecondProfile && targenPrecondProfile.value.trim()) ? targenPrecondProfile.value.trim() : null,
|
||||||
|
neutral_steps: (targenNeutralSteps && targenNeutralSteps.value) ? parseInt(targenNeutralSteps.value, 10) : null,
|
||||||
|
neutral_concentration: (targenNeutralConcentration) ? parseFloat(targenNeutralConcentration.value) : null,
|
||||||
|
ofps_high_quality: (targenHighQuality) ? targenHighQuality.checked : false,
|
||||||
|
ofps_adaptation: (targenAdaptation) ? parseFloat(targenAdaptation.value) : null,
|
||||||
|
full_spread_algorithm: (targenAlgorithm && targenAlgorithm.value !== "ofps") ? targenAlgorithm.value : null,
|
||||||
|
total_ink_limit: (colourSpace === "cmyk" && targenInkLimit && targenInkLimit.value) ? parseInt(targenInkLimit.value, 10) : null,
|
||||||
|
dark_emphasis: (targenDarkEmphasis) ? parseFloat(targenDarkEmphasis.value) : null,
|
||||||
|
device_power: (targenDevicePower && targenDevicePower.value) ? parseFloat(targenDevicePower.value) : null,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1155,3 +1155,36 @@ button.danger:hover {
|
|||||||
to { transform: rotate(360deg); }
|
to { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tooltip & Help Styling */
|
||||||
|
.has-tooltip {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-text {
|
||||||
|
display: none;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
color: #e0e0e0;
|
||||||
|
background: #252530;
|
||||||
|
border: 1px solid #444455;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-tooltip:hover .tooltip-text,
|
||||||
|
.has-tooltip:focus-within .tooltip-text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-all-tooltips .tooltip-text {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advanced-section-details summary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user