feat(stage1): add advanced targen options, collapsible section, tooltips and preset integration (resolves #141) #144

Merged
gronod merged 1 commits from feat/stage1-customisation-141 into development 2026-08-29 17:51:41 +01:00
6 changed files with 693 additions and 14 deletions
+202 -2
View File
@@ -436,7 +436,7 @@ pub async fn extract_gamut(
state.spawn(app, id, binary, args, cwd).await
}
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct TargenConfig {
pub colour_space: String,
#[serde(default)]
@@ -446,6 +446,19 @@ pub struct TargenConfig {
pub total_patches: Option<u32>,
pub basename: 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 {
@@ -470,7 +483,8 @@ pub fn build_targen_args(config: &TargenConfig) -> Vec<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());
} else {
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());
}
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
}
@@ -954,6 +1049,17 @@ mod tests {
total_patches: None,
basename: "my_profile".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);
assert_eq!(args, vec!["-v", "-d", "2", "-f", "800", "-e", "4", "my_profile"]);
@@ -969,6 +1075,17 @@ mod tests {
total_patches: None,
basename: "cmyk_profile".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);
assert_eq!(args, vec!["-v", "-d", "4", "-f", "1500", "-B", "8", "cmyk_profile"]);
@@ -984,11 +1101,94 @@ mod tests {
total_patches: Some(400),
basename: "draft_profile".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);
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]
fn test_build_printtarg_args_i1_a4_8bit() {
let config = PrinttargConfig {
+79
View File
@@ -18,6 +18,30 @@ pub struct ProfilingPreset {
pub colprof_algorithm: String,
pub colprof_quality: 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)]
@@ -46,6 +70,17 @@ pub fn get_default_presets() -> Vec<ProfilingPreset> {
colprof_algorithm: "l".to_string(),
colprof_quality: "m".to_string(),
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 {
id: "preset-hq-cmyk".to_string(),
@@ -62,6 +97,17 @@ pub fn get_default_presets() -> Vec<ProfilingPreset> {
colprof_algorithm: "l".to_string(),
colprof_quality: "h".to_string(),
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 {
id: "preset-draft-rgb".to_string(),
@@ -78,6 +124,17 @@ pub fn get_default_presets() -> Vec<ProfilingPreset> {
colprof_algorithm: "l".to_string(),
colprof_quality: "l".to_string(),
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 {
id: "preset-ultra-rgb".to_string(),
@@ -94,6 +151,17 @@ pub fn get_default_presets() -> Vec<ProfilingPreset> {
colprof_algorithm: "l".to_string(),
colprof_quality: "u".to_string(),
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_quality: "h".to_string(),
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");
+181 -10
View File
@@ -58,19 +58,31 @@
<main class="content">
<!-- Stage 1: targen -->
<section id="stage-1" class="stage active">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<h2>Generate Target</h2>
<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-group">
<div class="form-container" id="stage1FormContainer">
<!-- Basic Settings -->
<div class="form-group has-tooltip">
<label>Colour Space</label>
<div class="radio-group">
<label><input type="radio" name="colourSpace" value="rgb" checked> RGB (Printer Driver)</label>
<label><input type="radio" name="colourSpace" value="cmyk"> CMYK (RIP)</label>
</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 class="form-group">
<div class="form-group has-tooltip">
<label>Patch Count</label>
<select id="patchCountPreset">
<option value="400">Draft (400 patches)</option>
@@ -80,23 +92,36 @@
<option value="custom">Custom...</option>
</select>
<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 class="form-group">
<label>Neutral Axis Boost (Optional)</label>
<div class="input-row">
<div>
<label for="whitePatches" class="sub-label">White Patches</label>
<div class="has-tooltip" style="flex: 1;">
<label for="whitePatches" class="sub-label">White Patches (-e)</label>
<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>
<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 class="form-group">
<div class="form-group has-tooltip">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
<label style="margin: 0;">Project Directory &amp; 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>
@@ -106,10 +131,156 @@
<button type="button" id="btnBrowse" class="secondary">Browse...</button>
</div>
<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>
<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 &gt; 1 tighten spacing near 0; values &lt; 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">
<summary>Process Output</summary>
+97
View File
@@ -152,6 +152,58 @@ export async function initPresets() {
const blackPatches = document.getElementById("blackPatches");
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
const instrumentSelect = document.getElementById("instrumentSelect");
if (instrumentSelect && preset.instrument) instrumentSelect.value = preset.instrument;
@@ -198,6 +250,40 @@ export async function initPresets() {
const blackInput = document.getElementById("blackPatches");
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 instrument = instrumentSelect ? instrumentSelect.value : "i1";
@@ -224,6 +310,17 @@ export async function initPresets() {
patch_count,
white_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,
page_size,
bit_depth,
+99
View File
@@ -18,8 +18,96 @@ export function initTargen() {
const logContainer = document.getElementById("targenLogContainer");
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 = "";
// 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() {
const hasBasename = targetBasename && targetBasename.value.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,
basename: basename,
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 {
+33
View File
@@ -1155,3 +1155,36 @@ button.danger:hover {
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;
}