20 Commits
Author SHA1 Message Date
gronod 238cc435bf feat: Stage 1 layout normalization, Stage 2 deterministic target generation, macOS Universal Binary target, and release v0.5.5 (fixes #162, fixes #163, fixes #164)
Build Linux Packages / Build Linux (push) Failing after 5m51s
Build Windows Packages / Build Windows (push) Failing after 11s
2026-09-01 00:34:49 +01:00
gronod c1298415fc Merge branch 'development'
Build Linux Packages / Build Linux (push) Successful in 9m9s
Build Windows Packages / Build Windows (push) Successful in 9m45s
2026-08-31 00:15:40 +01:00
gronod 04772ff49e Merge branch 'development'
Build Linux Packages / Build Linux (push) Successful in 9m28s
Build Windows Packages / Build Windows (push) Successful in 9m48s
2026-08-30 21:47:55 +01:00
gronod 7b3cef6e48 Merge branch 'development' 2026-08-30 19:31:33 +01:00
gronod a67a0fa2fb Merge branch 'development'
Build Windows Packages / Build Windows (push) Successful in 9m49s
Build Linux Packages / Build Linux (push) Successful in 11m54s
2026-08-30 19:10:53 +01:00
gronod c795669b01 Merge branch 'development' 2026-08-30 19:06:01 +01:00
gronod f87eac3227 Merge branch 'development'
Build Linux Packages / Build Linux (push) Successful in 9m42s
Build Windows Packages / Build Windows (push) Successful in 10m33s
2026-08-30 17:40:36 +01:00
gronod 3bd7152834 Merge branch 'development'
Build Windows Packages / Build Windows (push) Successful in 7m18s
Build Linux Packages / Build Linux (push) Successful in 10m59s
2026-08-29 18:06:34 +01:00
gronod c920a8f861 Merge branch 'development'
Build Windows Packages / Build Windows (push) Failing after 2m16s
Build Linux Packages / Build Linux (push) Failing after 4m9s
2026-08-29 17:57:56 +01:00
gronod 36534a1e96 Merge branch 'development'
Build Windows Packages / Build Windows (push) Failing after 2m47s
Build Linux Packages / Build Linux (push) Failing after 4m10s
2026-08-29 17:52:13 +01:00
gronod 2fb8a7f7b9 Merge branch 'development' for release v0.3.6
Build Windows Packages / Build Windows (push) Successful in 8m15s
Build Linux Packages / Build Linux (push) Successful in 9m21s
2026-08-29 17:23:15 +01:00
gronod 217118aa24 Merge pull request 'Release v0.3.5: fix chartread stdin piping & instlist JSON parsing (resolves #134)' (#136) from development into main
Build Windows Packages / Build Windows (push) Successful in 8m56s
Build Linux Packages / Build Linux (push) Successful in 9m23s
Merge pull request #136 from development into main (release v0.3.5)
2026-08-29 13:25:47 +01:00
gronod 303f22998c Merge pull request 'Release v0.3.4' (#133) from development into main
Build Windows Packages / Build Windows (push) Successful in 7m4s
Build Linux Packages / Build Linux (push) Successful in 9m8s
Merge pull request #133 from development into main (release v0.3.4)
2026-08-29 12:27:55 +01:00
gronod 29e45e93ea Merge pull request 'Release v0.4.0: Milestone 9 - macOS Native Support & Enhanced Print Spooling' (#131) from development into main
Build Windows Packages / Build Windows (push) Successful in 6m57s
Build Linux Packages / Build Linux (push) Successful in 9m26s
Merge pull request #131 from development into main (release v0.4.0)
2026-08-29 11:49:29 +01:00
gronod 122def5e63 Merge pull request 'Merge development into main (release v0.3.3 / #127)' (#129) from development into main
Build Windows Packages / Build Windows (push) Successful in 7m16s
Build Linux Packages / Build Linux (push) Successful in 10m14s
Merge development into main (#129)
2026-08-29 11:20:15 +01:00
gronod 06c783fde1 Merge pull request 'Release v0.3.3 Hot fixes' (#126) from development into main
Build Windows Packages / Build Windows (push) Successful in 8m58s
Build Linux Packages / Build Linux (push) Successful in 12m25s
2026-08-28 19:04:31 +01:00
gronod 03c238bd43 Merge branch 'development' into main for release v0.3.2
Build Windows Packages / Build Windows (push) Successful in 9m17s
Build Linux Packages / Build Linux (push) Successful in 9m47s
2026-08-26 10:39:48 +01:00
gronod 4cb1284281 Merge branch 'development' into main for release v0.3.2
Build Linux Packages / Build Linux (push) Failing after 5m13s
Build Windows Packages / Build Windows (push) Canceled after 5m41s
2026-08-26 10:34:32 +01:00
gronod 1f667f9851 Merge pull request 'Standardize release asset naming and trigger only on tags' (#107) from development into main
Reviewed-on: #107
2026-08-26 10:06:23 +01:00
gronod efc7dc8669 Merge pull request 'Update .github/workflows/build-macos.yml' (#106) from development into main
Build Linux Packages / Build Linux (push) Successful in 10m19s
Build Windows Packages / Build Windows (push) Successful in 7m24s
Reviewed-on: #106
2026-08-25 22:43:49 +01:00
15 changed files with 437 additions and 153 deletions
+20 -15
View File
@@ -13,15 +13,20 @@ jobs:
matrix:
platform:
- name: Intel
os: macos-13
os: macos-15-intel
target: x86_64-apple-darwin
binary_dir: macos-x86_64
arch: x86_64
- name: Apple Silicon
os: macos-14
os: macos-15
target: aarch64-apple-darwin
binary_dir: macos-aarch64
arch: aarm64
arch: arm64
- name: Universal (Intel + Apple Silicon)
os: macos-15
target: universal-apple-darwin
binary_dir: macos-universal
arch: universal
steps:
- name: Checkout repository
@@ -37,7 +42,7 @@ jobs:
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
targets: ${{ matrix.platform.target }}
targets: ${{ matrix.platform.target == 'universal-apple-darwin' && 'x86_64-apple-darwin, aarch64-apple-darwin' || matrix.platform.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
@@ -68,7 +73,7 @@ jobs:
echo "PREFIX=ICCery_${TAG}-${SHORT_SHA}-macos-${{ matrix.platform.arch }}" >> $GITHUB_ENV
- name: Build Tauri App
run: npm run tauri build
run: npm run tauri build -- --target ${{ matrix.platform.target }}
- name: Prepare Release Assets
shell: bash
@@ -76,18 +81,18 @@ jobs:
mkdir -p release-assets
# Stage DMG package
for dmg in src-tauri/target/release/bundle/dmg/*.dmg; do
if [ -f "$dmg" ]; then
cp "$dmg" "release-assets/${PREFIX}.dmg"
fi
done
DMG_FILE=$(find src-tauri/target -type f -name "*.dmg" | head -n 1)
if [ -n "$DMG_FILE" ] && [ -f "$DMG_FILE" ]; then
cp "$DMG_FILE" "release-assets/${PREFIX}.dmg"
fi
# Stage ZIP archive of .app bundle
for app in src-tauri/target/release/bundle/macos/*.app; do
if [ -d "$app" ]; then
(cd src-tauri/target/release/bundle/macos && zip -r "../../../../../release-assets/${PREFIX}.zip" "$(basename "$app")")
fi
done
APP_DIR=$(find src-tauri/target -type d -name "*.app" | head -n 1)
if [ -n "$APP_DIR" ] && [ -d "$APP_DIR" ]; then
APP_PARENT=$(dirname "$APP_DIR")
APP_NAME=$(basename "$APP_DIR")
(cd "$APP_PARENT" && zip -r "${PWD}/release-assets/${PREFIX}.zip" "$APP_NAME")
fi
- name: Upload Artifacts
uses: actions/upload-artifact@v3
+18 -13
View File
@@ -24,7 +24,12 @@ jobs:
os: macos-15
target: aarch64-apple-darwin
binary_dir: macos-aarch64
arch: aarm64
arch: arm64
- name: Universal (Intel + Apple Silicon)
os: macos-15
target: universal-apple-darwin
binary_dir: macos-universal
arch: universal
steps:
- name: Checkout repository
@@ -40,7 +45,7 @@ jobs:
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
targets: ${{ matrix.platform.target }}
targets: ${{ matrix.platform.target == 'universal-apple-darwin' && 'x86_64-apple-darwin, aarch64-apple-darwin' || matrix.platform.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
@@ -70,7 +75,7 @@ jobs:
echo "PREFIX=ICCery_${TAG}-${SHORT_SHA}-macos-${{ matrix.platform.arch }}" >> $GITHUB_ENV
- name: Build Tauri App
run: npm run tauri build
run: npm run tauri build -- --target ${{ matrix.platform.target }}
- name: Prepare Release Assets
shell: bash
@@ -78,18 +83,18 @@ jobs:
mkdir -p release-assets
# Stage DMG package
for dmg in src-tauri/target/release/bundle/dmg/*.dmg; do
if [ -f "$dmg" ]; then
cp "$dmg" "release-assets/${PREFIX}.dmg"
fi
done
DMG_FILE=$(find src-tauri/target -type f -name "*.dmg" | head -n 1)
if [ -n "$DMG_FILE" ] && [ -f "$DMG_FILE" ]; then
cp "$DMG_FILE" "release-assets/${PREFIX}.dmg"
fi
# Stage ZIP archive of .app bundle
for app in src-tauri/target/release/bundle/macos/*.app; do
if [ -d "$app" ]; then
(cd src-tauri/target/release/bundle/macos && zip -r "../../../../../release-assets/${PREFIX}.zip" "$(basename "$app")")
fi
done
APP_DIR=$(find src-tauri/target -type d -name "*.app" | head -n 1)
if [ -n "$APP_DIR" ] && [ -d "$APP_DIR" ]; then
APP_PARENT=$(dirname "$APP_DIR")
APP_NAME=$(basename "$APP_DIR")
(cd "$APP_PARENT" && zip -r "${PWD}/release-assets/${PREFIX}.zip" "$APP_NAME")
fi
- name: Upload Artifacts
uses: actions/upload-artifact@v4
+2
View File
@@ -27,4 +27,6 @@ src-tauri/argyll/linux-x86_64/
src-tauri/argyll/windows-x86_64/
src-tauri/argyll/macos-x86_64/
src-tauri/argyll/macos-aarch64/
src-tauri/argyll/macos-universal/
src-tauri/argyll/usb/
+3 -1
View File
@@ -71,11 +71,13 @@ ICCery is a native, cross-platform desktop application built with:
- [x] **v0.3.4 & v0.3.5 (#119, #134)**: TIFF preview metadata extraction, printtarg custom patch labels, and robust `instlist` JSON parsing.
- [x] **v0.3.6 (#137)**: Interactive `chartread` support for multi-key prompts (accept/override key combos).
### Milestone 10 — Production Ready Features (`v0.5.0` & `v0.5.1`)
### Milestone 10 — Production Ready Features (`v0.5.0` – `v0.5.5`)
- [x] **Structured Logging (#139)**: Integrated `tauri-plugin-log` with rotating logs in native OS app log directory and ArgyllCMS stdout/stderr stream capture.
- [x] **Stage 3 Direct Resume (#140)**: Open existing `.ti2` target file in Stage 1 to parse header metadata and jump directly to Stage 3 (Measurement).
- [x] **Stage 1 Additional Customisation & Tooltips (#141)**: Exposure of 11 advanced `targen` tuning parameters (`-g`, `-s`, `-n`, `-c`, `-N`, `-G`, `-A`, algorithm selection, `-l`, `-V`, `-p`) in a collapsible section with contextual guidance tooltips and preset support.
- [x] **Subprocess Lifecycle & Clean Exit (#147, #149)**: Tauri application and window close hooks invoking `ProcessManager::kill_all` to prevent orphaned hardware-locking processes.
- [x] **Stage 1 Layout Normalization & Stage 2 Deterministic Target Generation (#162, #163)** (`v0.5.5`): Reorganized Stage 1 Advanced Options into structured 2-column grids with normalized heights; enforced deterministic `-R 1` target generation with custom seed and raster order (`-r`) support in Stage 2.
- [x] **macOS Universal Binary Target (#164)** (`v0.5.5`): Added macOS Universal Binary (`universal-apple-darwin`) build target combining Intel (`x86_64`) and Apple Silicon (`arm64`), ArgyllCMS universal sidecar packaging, runtime fallback resolution, and CI release asset automation.
---
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "iccery",
"version": "0.3.3",
"version": "0.5.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "iccery",
"version": "0.3.3",
"version": "0.5.5",
"devDependencies": {
"@tauri-apps/cli": "^2"
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "iccery",
"private": true,
"version": "0.5.4",
"version": "0.5.5",
"type": "module",
"scripts": {
"fetch-argyll": "node scripts/fetch-argyll.mjs",
+6
View File
@@ -43,6 +43,12 @@ const PLATFORMS = {
destDir: path.join(TAURI_ARGYLL_DIR, 'macos-aarch64'),
isWindows: false,
},
'macos-universal': {
assetSuffix: '_macOS_universal_bin.tgz',
markerBinary: 'instlist',
destDir: path.join(TAURI_ARGYLL_DIR, 'macos-universal'),
isWindows: false,
},
};
function parseArgs() {
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "iccery"
version = "0.5.4"
version = "0.5.5"
description = "Modern Printer Profiling UI frontend for ArgyllCMS"
authors = ["Gordon"]
edition = "2021"
+76 -5
View File
@@ -65,8 +65,20 @@ pub async fn resolve_binary(app: AppHandle, binary_name: String) -> Result<Strin
let platform = match (std::env::consts::OS, std::env::consts::ARCH) {
("linux", "x86_64") => "linux-x86_64",
("windows", "x86_64") => "windows-x86_64",
("macos", "aarch64") => "macos-aarch64",
("macos", "x86_64") => "macos-x86_64",
("macos", "aarch64") => {
if app.path().resolve("argyll/macos-universal/instlist", tauri::path::BaseDirectory::Resource).map(|p| p.exists()).unwrap_or(false) {
"macos-universal"
} else {
"macos-aarch64"
}
},
("macos", "x86_64") => {
if app.path().resolve("argyll/macos-universal/instlist", tauri::path::BaseDirectory::Resource).map(|p| p.exists()).unwrap_or(false) {
"macos-universal"
} else {
"macos-x86_64"
}
},
_ => "linux-x86_64",
};
@@ -557,6 +569,10 @@ fn default_dpi() -> u32 {
300
}
fn default_random_seed() -> Option<u32> {
Some(1)
}
#[derive(Debug, Deserialize, Serialize)]
pub struct PrinttargConfig {
pub instrument: String, // One of: "i1", "p3", "CM", "SS", "20", "22", "41", "51"
@@ -565,6 +581,10 @@ pub struct PrinttargConfig {
#[serde(default = "default_dpi")]
pub dpi: u32, // TIFF resolution, defaults to 300 DPI
pub custom_label: Option<String>, // Custom chart label / description metadata string (-d)
#[serde(default = "default_random_seed")]
pub random_seed: Option<u32>, // Random seed (-R <seed>), defaults to Some(1) for deterministic target generation
#[serde(default)]
pub no_randomize: bool, // If true, pass -r (raster layout / no randomization)
pub basename: String, // Must match the .ti1 basename from Stage 1
pub cwd: String, // Working directory where the .ti1 file resides
}
@@ -698,6 +718,13 @@ pub fn build_printtarg_args(config: &PrinttargConfig) -> Vec<String> {
config.page_size.clone(),
];
if config.no_randomize {
args.push("-r".to_string());
} else if let Some(seed) = config.random_seed {
args.push("-R".to_string());
args.push(seed.to_string());
}
if let Some(ref label) = config.custom_label {
args.push("-d".to_string());
args.push(label.clone());
@@ -1289,11 +1316,13 @@ mod tests {
bit_depth: 8,
dpi: 100,
custom_label: None,
random_seed: Some(1),
no_randomize: false,
basename: "my_profile".to_string(),
cwd: "/tmp".to_string(),
};
let args = build_printtarg_args(&config);
assert_eq!(args, vec!["-v", "-u", "-i", "i1", "-p", "A4", "-t", "100", "my_profile"]);
assert_eq!(args, vec!["-v", "-u", "-i", "i1", "-p", "A4", "-R", "1", "-t", "100", "my_profile"]);
}
#[test]
@@ -1304,11 +1333,13 @@ mod tests {
bit_depth: 16,
dpi: 300,
custom_label: None,
random_seed: Some(1),
no_randomize: false,
basename: "cmyk_profile".to_string(),
cwd: "/home/user".to_string(),
};
let args = build_printtarg_args(&config);
assert_eq!(args, vec!["-v", "-u", "-i", "CM", "-p", "Letter", "-T", "300", "cmyk_profile"]);
assert_eq!(args, vec!["-v", "-u", "-i", "CM", "-p", "Letter", "-R", "1", "-T", "300", "cmyk_profile"]);
}
#[test]
@@ -1319,11 +1350,13 @@ mod tests {
bit_depth: 8,
dpi: 150,
custom_label: None,
random_seed: Some(1),
no_randomize: false,
basename: "custom_target".to_string(),
cwd: "/tmp".to_string(),
};
let args = build_printtarg_args(&config);
assert_eq!(args, vec!["-v", "-u", "-i", "SS", "-p", "200x400", "-t", "150", "custom_target"]);
assert_eq!(args, vec!["-v", "-u", "-i", "SS", "-p", "200x400", "-R", "1", "-t", "150", "custom_target"]);
}
#[test]
@@ -1334,6 +1367,8 @@ mod tests {
bit_depth: 8,
dpi: 300,
custom_label: Some("ICCery - Pro900 - Luster - 29/08/2026 12:00".to_string()),
random_seed: Some(1),
no_randomize: false,
basename: "my_profile".to_string(),
cwd: "/tmp".to_string(),
};
@@ -1347,6 +1382,8 @@ mod tests {
"i1",
"-p",
"A4",
"-R",
"1",
"-d",
"ICCery - Pro900 - Luster - 29/08/2026 12:00",
"-t",
@@ -1356,6 +1393,40 @@ mod tests {
);
}
#[test]
fn test_build_printtarg_args_with_custom_seed() {
let config = PrinttargConfig {
instrument: "i1".to_string(),
page_size: "A4".to_string(),
bit_depth: 8,
dpi: 300,
custom_label: None,
random_seed: Some(42),
no_randomize: false,
basename: "my_profile".to_string(),
cwd: "/tmp".to_string(),
};
let args = build_printtarg_args(&config);
assert_eq!(args, vec!["-v", "-u", "-i", "i1", "-p", "A4", "-R", "42", "-t", "300", "my_profile"]);
}
#[test]
fn test_build_printtarg_args_with_raster_order() {
let config = PrinttargConfig {
instrument: "i1".to_string(),
page_size: "A4".to_string(),
bit_depth: 8,
dpi: 300,
custom_label: None,
random_seed: Some(1), // Should be superseded by no_randomize
no_randomize: true,
basename: "my_profile".to_string(),
cwd: "/tmp".to_string(),
};
let args = build_printtarg_args(&config);
assert_eq!(args, vec!["-v", "-u", "-i", "i1", "-p", "A4", "-r", "-t", "300", "my_profile"]);
}
#[test]
fn test_build_chartread_args_auto() {
let config = ChartreadConfig {
+8
View File
@@ -42,6 +42,12 @@ pub struct ProfilingPreset {
pub dark_emphasis: Option<f64>,
#[serde(default)]
pub device_power: Option<f64>,
// Stage 2 fields
#[serde(default)]
pub random_seed: Option<u32>,
#[serde(default)]
pub no_randomize: Option<bool>,
}
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
@@ -322,6 +328,8 @@ mod tests {
total_ink_limit: Some(320),
dark_emphasis: Some(1.5),
device_power: Some(1.2),
random_seed: Some(42),
no_randomize: Some(false),
};
let json = export_preset_json(preset.clone()).expect("Export failed");
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "ICCery",
"version": "0.5.4",
"version": "0.5.5",
"identifier": "com.gronod.iccery",
"build": {
"frontendDist": "../src"
+149 -114
View File
@@ -144,142 +144,156 @@
</div>
<!-- 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;">
<details id="targenAdvancedDetails" class="advanced-section-details">
<summary>
<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="advanced-content">
<!-- Group 1: Neutral Axis & Preconditioning -->
<div class="advanced-group">
<div class="advanced-group-title">Neutral Axis &amp; Preconditioning</div>
<div class="form-group has-tooltip" style="margin-bottom: 0;">
<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">
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.
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="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 class="advanced-grid-2col">
<div class="form-group has-tooltip">
<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">
<label for="targenNeutralConcentration" class="sub-label">Neutral Axis Concentration (-N)</label>
<div class="slider-input-container">
<input type="range" id="targenNeutralConcentration" min="0.0" max="1.0" step="0.05" value="0.5">
<span id="targenNeutralConcVal" class="slider-val-badge">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>
</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>
<!-- Group 2: Patch Distribution & Sampling -->
<div class="advanced-group">
<div class="advanced-group-title">Patch Distribution &amp; Sampling</div>
<div class="advanced-grid-2col">
<div class="form-group has-tooltip">
<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">
<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>
<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 class="advanced-grid-2col">
<div class="form-group has-tooltip">
<label for="targenAdaptation" class="sub-label">OFPS Adaptation (-A)</label>
<div class="slider-input-container">
<input type="range" id="targenAdaptation" min="0.0" max="1.0" step="0.05" value="0.1">
<span id="targenAdaptationVal" class="slider-val-badge">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">
<label for="targenDarkEmphasis" class="sub-label">Dark Region Emphasis (-V)</label>
<div class="slider-input-container">
<input type="range" id="targenDarkEmphasis" min="1.0" max="4.0" step="0.1" value="1.0">
<span id="targenDarkEmphasisVal" class="slider-val-badge">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>
<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 class="advanced-grid-2col">
<div class="form-group has-tooltip">
<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 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 class="form-group has-tooltip" id="targenInkLimitGroup">
<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>
<!-- 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>
<!-- Group 3: Algorithm & Quality -->
<div class="advanced-group">
<div class="advanced-group-title">Algorithm &amp; Quality</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 class="advanced-grid-2col">
<div class="form-group has-tooltip">
<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 &gt;4 channels.<br>
• Device/Perceptual random or BCC – alternative space-filling methods.
</div>
</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 class="form-group has-tooltip">
<label class="sub-label">Quality Optimization</label>
<div class="checkbox-control-container">
<label>
<input type="checkbox" id="targenHighQuality">
<span style="font-size: 0.85rem;">High Quality OFPS (-G)</span>
</label>
</div>
<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>
</div>
@@ -363,6 +377,27 @@
<input type="number" id="tiffDpi" min="72" max="600" value="300" placeholder="300">
</div>
<div class="form-group has-tooltip">
<label for="printtargLayoutOrder">Patch Layout Order</label>
<div class="input-row" style="align-items: center; gap: 10px;">
<select id="printtargLayoutOrder" style="flex: 2;">
<option value="deterministic" selected>Deterministic Random (Default, Seed = 1)</option>
<option value="custom_seed">Custom Random Seed (-R)</option>
<option value="raster">Raster Order (Sequential, -r)</option>
</select>
<div id="printtargCustomSeedGroup" class="hidden" style="flex: 1; display: flex; align-items: center; gap: 6px;">
<label for="printtargCustomSeed" class="sub-label" style="margin: 0; white-space: nowrap;">Seed:</label>
<input type="number" id="printtargCustomSeed" min="1" max="2147483647" value="1" placeholder="1" style="flex: 1;">
</div>
</div>
<div class="tooltip-text">
Controls patch distribution across target pages.<br>
• <strong>Deterministic (Seed = 1)</strong>: Guarantees identical .ti2 and TIFF target generation each run for the same .ti1.<br>
• <strong>Custom Seed</strong>: Reproducible pseudo-random distribution using an explicit integer seed (-R &lt;seed&gt;).<br>
• <strong>Raster Order</strong>: Unrandomized sequential order (-r) across rows and columns.
</div>
</div>
<!-- Target Label & Metadata (printtarg -d) -->
<div class="form-group" style="margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border-color, #333);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
+35
View File
@@ -221,6 +221,23 @@ export async function initPresets() {
tiffDpi.value = preset.dpi;
}
const printtargLayoutOrder = document.getElementById("printtargLayoutOrder");
const printtargCustomSeedGroup = document.getElementById("printtargCustomSeedGroup");
const printtargCustomSeed = document.getElementById("printtargCustomSeed");
if (printtargLayoutOrder) {
if (preset.no_randomize) {
printtargLayoutOrder.value = "raster";
if (printtargCustomSeedGroup) printtargCustomSeedGroup.classList.add("hidden");
} else if (preset.random_seed && preset.random_seed !== 1) {
printtargLayoutOrder.value = "custom_seed";
if (printtargCustomSeed) printtargCustomSeed.value = preset.random_seed;
if (printtargCustomSeedGroup) printtargCustomSeedGroup.classList.remove("hidden");
} else {
printtargLayoutOrder.value = "deterministic";
if (printtargCustomSeedGroup) printtargCustomSeedGroup.classList.add("hidden");
}
}
// Stage 4 controls
const colprofQuality = document.getElementById("colprofQuality");
if (colprofQuality && preset.colprof_quality) colprofQuality.value = preset.colprof_quality;
@@ -296,6 +313,22 @@ export async function initPresets() {
const tiffDpi = document.getElementById("tiffDpi");
const dpi = tiffDpi && tiffDpi.value ? parseInt(tiffDpi.value, 10) || 300 : 300;
const printtargLayoutOrder = document.getElementById("printtargLayoutOrder");
const printtargCustomSeed = document.getElementById("printtargCustomSeed");
let random_seed = 1;
let no_randomize = false;
if (printtargLayoutOrder) {
if (printtargLayoutOrder.value === "raster") {
no_randomize = true;
random_seed = null;
} else if (printtargLayoutOrder.value === "custom_seed") {
const s = printtargCustomSeed ? parseInt(printtargCustomSeed.value, 10) : 1;
random_seed = isNaN(s) || s < 1 ? 1 : s;
} else {
random_seed = 1;
}
}
const colprofQuality = document.getElementById("colprofQuality");
const colprof_quality = colprofQuality ? colprofQuality.value : "m";
@@ -325,6 +358,8 @@ export async function initPresets() {
page_size,
bit_depth,
dpi,
random_seed,
no_randomize,
colprof_algorithm,
colprof_quality,
colprof_intent: null,
+31
View File
@@ -33,6 +33,9 @@ export function initPrinttarg() {
const customPageH = document.getElementById("customPageH");
const bitDepthRadios = document.querySelectorAll('input[name="bitDepth"]');
const tiffDpi = document.getElementById("tiffDpi");
const printtargLayoutOrder = document.getElementById("printtargLayoutOrder");
const printtargCustomSeedGroup = document.getElementById("printtargCustomSeedGroup");
const printtargCustomSeed = document.getElementById("printtargCustomSeed");
const btnCreateLayout = document.getElementById("btnCreateLayout");
const logContainer = document.getElementById("printtargLogContainer");
const logPre = document.getElementById("printtargLog");
@@ -40,6 +43,16 @@ export function initPrinttarg() {
const galleryInfo = document.getElementById("galleryInfo");
const galleryGrid = document.getElementById("galleryGrid");
if (printtargLayoutOrder && printtargCustomSeedGroup) {
printtargLayoutOrder.addEventListener("change", () => {
if (printtargLayoutOrder.value === "custom_seed") {
printtargCustomSeedGroup.classList.remove("hidden");
} else {
printtargCustomSeedGroup.classList.add("hidden");
}
});
}
// Target Label & Metadata elements (printtarg -d)
const targetMetadataPrinter = document.getElementById("targetMetadataPrinter");
const targetMetadataInkSet = document.getElementById("targetMetadataInkSet");
@@ -402,12 +415,30 @@ export function initPrinttarg() {
customLabel = targetLabelPreview.value.trim();
}
// Determine layout order & random seed
let randomSeed = 1;
let noRandomize = false;
const layoutOrder = printtargLayoutOrder ? printtargLayoutOrder.value : "deterministic";
if (layoutOrder === "raster") {
noRandomize = true;
randomSeed = null;
} else if (layoutOrder === "custom_seed") {
const parsedSeed = printtargCustomSeed ? parseInt(printtargCustomSeed.value, 10) : 1;
randomSeed = isNaN(parsedSeed) || parsedSeed < 1 ? 1 : parsedSeed;
noRandomize = false;
} else {
randomSeed = 1;
noRandomize = false;
}
const config = {
instrument: instrumentSelect.value,
page_size: pageSize,
bit_depth: bitDepth,
dpi: dpi,
custom_label: customLabel,
random_seed: randomSeed,
no_randomize: noRandomize,
basename: stage1Basename,
cwd: stage1Cwd,
};
+84
View File
@@ -1206,8 +1206,92 @@ button.danger:hover {
display: block !important;
}
.advanced-section-details {
margin-top: 14px;
border: 1px solid var(--border-color, #333);
border-radius: 8px;
padding: 12px 16px;
background: rgba(0, 0, 0, 0.18);
}
.advanced-section-details summary {
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
user-select: none;
}
.advanced-section-details summary::-webkit-details-marker {
display: none;
}
.advanced-content {
margin-top: 14px;
display: flex;
flex-direction: column;
gap: 14px;
}
.advanced-group {
background: rgba(255, 255, 255, 0.025);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 6px;
padding: 12px 14px;
display: flex;
flex-direction: column;
gap: 12px;
}
.advanced-group-title {
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 700;
color: var(--accent-color, #4facfe);
margin-bottom: 2px;
}
.advanced-grid-2col {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 14px;
align-items: start;
}
.slider-input-container {
display: flex;
align-items: center;
gap: 10px;
height: 36px;
}
.slider-input-container input[type="range"] {
flex: 1;
margin: 0;
}
.slider-val-badge {
font-family: monospace;
font-size: 0.8rem;
min-width: 38px;
text-align: right;
color: #ccc;
}
.checkbox-control-container {
display: flex;
align-items: center;
height: 36px;
}
.checkbox-control-container label {
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 0;
}