*Epic CLI Manual — [index](../MANUAL.md)*

# 18. `--separate`, `--quantize` and screening

*(1.18.0 – 1.34.0; `--help=separate`, `--help=quantize`)*

```
java -jar java-epic-cli.jar --separate <source.pdf> [--plates=A;B;process] [--page=N] [--dpi=N] [--output=DIR] [screening] [--proof]
java -jar java-epic-cli.jar --separate <picture.png|tif|jpg> --colors=N | process[:GCR] | "TERM;TERM" | --sim-process[=N] | --duotone=.. [--index] [--underbase[=TERM][:MODE]] [--paper=TERM] [screening] [--proof]
```

One engine for spot, CMYK process and simulated-process separation. **Separation** writes one
coverage map per ink, `<base>[_pN]_<Ink>.tif`, 8-bit grayscale, black = 100% ink, resolution
tagged. The numbers are hard and **uncompensated: on process work the RIP owns the curve**.
**Screening** (`--halftone`) burns each map to a 1-bit CCITT G4 TIF of dots on the device grid by a
pure geometric threshold, so 50% in is 50% dot area out at every ruling, angle and shape. One JSON
result on stdout; exit `0` ok / `1` usage / `2` fault.

## 18.1 PDF plates

Every Separation/DeviceN ink the page uses plus Cyan, Magenta, Yellow, Black (process plates
written only when they carry ink; `--keep-empty` writes blanks). Each plate is a full render of the
page in plate mode: clips, forms, transparency groups, soft masks, images (swatched, DeviceN,
Indexed, process, SMask'd), tints and shadings resolve as they print. Paint in the plate's ink lays
coverage down; foreign paint knocks the plate out unless it overprints (OP/op; DeviceCMYK OPM 1
leaves a process plate alone where its component is 0). Rendered at `--render-dpi` (300) and
resampled to `--dpi`. `--plates` names the plates (`process` = the four). `--ink-coverage`
measures every plate and writes nothing.

## 18.2 The colour grammar (house TERMs)

Used by `--colors`, `--paper`, `--underbase`, `--palette`, `--duotone`, `--carrier`, and shared
with `--compile` and `--backers`:

| TERM | Meaning |
|---|---|
| `spot('Name',c,m,y,k)` | Named separation, CMYK **0–100 percent**; the plate carries the name (e.g. a PANTONE name). |
| `cmyk(c,m,y,k)` | A process mix, 0–100 percent. |
| `rgba(r,g,b,a)` | r,g,b 0–255, a 0–1 (a translucent ink); `rgb()` accepted. |
| bare name | A `--library` entry, or the built-ins Black, White, Cyan, Magenta, Yellow (1.30.0). |

**`--quantize=N <picture|pdf>`** reports the N most common colours of a picture (or a rendered
PDF page, `--dpi=150`, `--page`) most populous first, each spelled three ways (`rgba`, `cmyk`,
`spot('ColorN',..)`), plus `colors_spec` — the spot terms joined with `;`, exactly what
`--separate --colors=".."` takes. `--paper=TERM` leaves paper-coloured pixels out;
`--merge=dE` (12), `--min-share=PCT` (0.5). The CMYK is the plain conversion `--separate`
reverses, so handing a term back is exact.

**`--match-colors <pdf|picture> --palette=BOOK`** *(1.42.0; `--help=match-colors`)* reports, for
every colour in the file, the nearest entry of the customer's own book with its CIE76 dE. The
jar ships no Pantone data. The book is `--palette=` an Illustrator Swatches export (`.ase`; RGB,
CMYK, Lab, Gray), a file of TERMs, or TERMs inline, and/or `--library=inks.lib`; both may
repeat. The colours are the PDF's spot inks (`mode: spots`; `--page=N` for one page) or, for a
picture or with `--quantize=N`, its dominant colours as `--quantize` measures them
(`mode: quantized`; `--paper`, `--merge`, `--min-share`, `--dpi`). A CMYK book entry with a
spot's recipe lands at dE 0; an RGB/Lab swatch keeps its own appearance. `--max-de=N` lists
colours farther than N from every entry in `unmatched[]` (still matched, warned);
`--alternates=K` (3) gives the next nearest. Result `{status, source, mode, page?, dpi?,
quantize?, book{sources[], entries}, max_de?, colors[{name, source, population_pct?, cmyk_pct,
rgb, hex, lab, spec, match{name, spec, cmyk_pct, hex, de}, alternates[]}], unmatched[],
colors_spec, warnings[]}`; `colors_spec` is the matched `spot()` terms in colour order, which
`--separate --colors` takes and `--swap-spot-colors` pairs with.

## 18.3 Raster separation modes

A full-colour picture is reduced to inks whose overprint reproduces it. Model: linear light; the
ground is the paper lifted toward the underbase by its coverage, every ink a filter over it;
coverages solved per colour by bounded, perceptually weighted least squares. Pixels are solved as
their alpha-composited colour at full coverage. The result adds `inks[{name, spec, cmyk_pct, rgb,
source, role?}]`, `preview` (PNG recomposed through the model), `underbase{}` and
`quality{de_mean, de_p95, de_max}` (CIE76 dE vs the input) — judge the fit there; a warning fires
above dE 10.

| Mode | What it does |
|---|---|
| `--colors=process[:GCR]` | Cyan, Magenta, Yellow, Black through `--profile=naive\|icc\|FILE.icc` (naive = exactly invertible arithmetic; icc = built-in ISO Coated v2; or a press profile). `--gcr=N` (100) share of common grey moved into Black; `--black-max=N`; `--tac=N`. |
| `--colors=N` | N inks found in the picture (Lab clustering; tints and overprints of chosen inks are not inks), named `Ink1..N` (`--ink-prefix`), each reported with a spec you can feed back. `--max-inks` (24), `--mix-de` (6). |
| `--colors=N --palette=..` | N inks **chosen from your palette** (the inks on the shelf) by greedy fit. `--palette` is `"TERM;TERM"` or a file of TERMs; alone it uses them all. |
| `--colors="TERM;.."` | Fixed inks, one TERM per plate. An ink the colour of the paper becomes the knockout underbase. |
| `--duotone="TERM;TERM[;TERM[;TERM]]"` *(1.28.0)* | Mono/duo/tri/quadtone: the picture's **tone** (1 − L\*, or 1 − luma with `--duotone-tone=luma`) through 1–4 inks, first printed first, each on its own curve (`--duotone-curves="in:out,..\|in:out,.."`, positional; defaults full tone / midtone-highlight lift / two shadow curves). Colour is discarded. Aliases `--monotone`, `--tritone`, `--quadtone`. Result `duotone{kind, tone, inks[{name, curve, curve_default}]}`. |
| `--index` *(1.32.0)* | Index separation: every painted pixel is exactly **one solid ink** (or the ground), nearest in Lab with Floyd–Steinberg error diffusion so the dots average back to the picture. No halftone, no moiré, no tonal ink. With `--colors=N \| "TERM;TERM" \| --palette` (not process, not duotone). `--index-dpi=N` (36..600; default the source grid capped at 200, box-averaged down, never upsampled), `--index-dither=diffusion\|none`. `--halftone` is ignored with a warning; `--underbase` solved becomes full. Result `index{dpi, dither, width_px, height_px, inks[{name, pixels, share_pct}]}`. |
| `--sim-process[=N]` *(1.34.0)* | Simulated process: the picture through N pantones (default 3, 1..8) beside **Black and White**. White is one screen that is the underbase **and** the highlight (`--underbase` solved unless given). Black anchors shadow and outline (`--sim-black=off` drops it). The pantones only have to buy colour, so they are picked greedily by population-weighted RMS dE from the picture's chroma clusters and their more saturated versions, or from `--palette`; a palette without a true black gets Black added. `--colors="TERM;TERM"` inks are kept first. Picks are named by hue (Red, Dark Blue…) with a spec to swap for the matched PANTONE. A pick that buys < 0.5 dE warns — fewest screens is the money. Rejects `--colors=N`, `--colors=process`, `--duotone`. Result `sim_process{pantones, black, ground, candidates, inks[{name, rgb, source given\|black\|picked, fit_de, gain_de}]}`. Measured on four photos: p95 dE 7.6–24.7 with `--colors=4` → 4.2–5.6 with 3 pantones. |

**Underbase.** `--underbase[=TERM][:MODE]` — an opaque ground ink printed first (default
`spot('White',0,0,0,0)`). Modes: `knockout` = under every painted pixel no other ink covers (the
default when the ink matches the paper); `full` = under every painted pixel; `solved` = its
coverage is solved with the inks against the picture (the default on a dark paper, where a white
base lifts the colours); `none`. `--choke=N` erodes by N source pixels, `--spread=N` grows,
`--choke-shape=square|round|diamond`. Result `underbase{name, mode, choke_px, spread_px}`.

**Trapping** *(1.41.0)*. `--trap=N[:MODE]` — wherever two plates abut, one grows under the other
by N source pixels (0..64) so a registration miss shows overlapping ink, not paper. Works on
PDF plates and raster plates alike, before screening, so the proof, the films and the plates PDF
all carry the trap. `MODE`: `spread` (default) = the lighter ink grows under the darker;
`choke` = the darker grows over the lighter; `centre` = half each (the under side gets the odd
pixel). Darker = lower luminance of the solid ink on white, so the join hides under the ink
that wins visually; `--trap-pairs="A>B;.."` names the pairs instead (A goes under B, whatever
their luminance). The underbase (`--choke`/`--spread` own it), blank plates and plates with the
underbase role sit out. A plate grows with the dilation of its own tone (a tint traps as a
tint), only where the partner's original ink is 50% or more; `--choke-shape` sets the window.
Holds every plate of a page at once — on a big page at a high `--render-dpi` that is N plates
of W×H bytes. Result `trap{px, mode, pairs[{page, under, over, pixels}]}`, pairs listed only
where the two plates actually met.

**Paper and input.** `--paper=TERM` (default white): paper-coloured and transparent pixels carry
no ink (`--paper-de` 3, `--paper-tolerance` 5.2, `--alpha-threshold` 8). `--input-dpi=N|auto`
(300 regardless of tag unless `auto`). `--no-preview`.

**Image prep** *(1.33.0)*, applied before separation in this order: `--prep-levels=B,W[,G]`
(Photoshop Levels on sRGB), `--prep-saturation=F` (0 grey, 1 unchanged, 2 doubled; about luma in
linear light), `--prep-sharpen=A[,R]` (unsharp mask). Alpha untouched. Result `prep{}`.

## 18.4 Screening

`--halftone[=LPI[:ANGLE[:DOT[:PHASE]]]]` screens every plate; `--lpi`, `--angle`, `--dot` one at a
time; `--angles="a;b;c" | "Name=a;.."` per plate; `--screens="Name=LPI[:ANGLE[:DOT[:PHASE]]];.."`
per-plate screens by name or order. **Note the `;` separators.** `--dpi` is the device grid
(default 1200 when screening). A plate with no tints is an area fill thresholded at 50%
(`--solid-threshold`, `--solid-tolerance`), never screened unless `--screen-solids`.

**Shop defaults** (every one a flag): process plates 85 LPI at C 15, M 75, Y 60, K 45
(`--process-lpi`, `--process-angles`); every other screened plate 35 LPI (`--spot-lpi`) at an
angle by **dominance** (coverage × darkness; `--angle-rank=dominance|coverage|order`) — with
process plates on the job it fills 30, 0, 7.5, 52.5, 22.5, 67.5, 37.5, 82.5 (`--fill-angles`);
without (sim-process) the dominant ink takes 45, then 15, 75, 60, 30, 0… (`--sim-angles`). Fewer
than 8 device pixels per cell warns.

**Dot shapes** *(1.22.0)*, all area-linear: `euclidean` (default), `round`, `ellipse[/R]`,
`line`, `cross`, `square`, `diamond`, `inverse-SHAPE` (the shape as a hole), `fm[/SIZE]`
(Floyd–Steinberg stochastic), `noise[/SIZE]` (blue-noise ordered mask, reproducible),
`hybrid[/SHAPE]` (AM dot SHAPE, default `--hybrid-dot=euclidean`, between the
`--hybrid-range` coverages 10,90, blue-noise outside). Stochastic plates report
no lpi/angle. Manipulations: `--phase=U,V`, `--jitter=N` + `--seed`, `--fm-dot=N`,
`--invert=A;B`, per plate `--plate-min-dot`, `--plate-max-dot`, `--plate-phase`, `--plate-gain`,
`--plate-range`, `--plate-curve`. `--linearity[=STEPS]` screens a flat wedge through every screen
the job uses and reports `linearity[]{dot, lpi, angle, dpi, plates, steps, max_error_pct}`.

**Other shop parameters:** `--min-dot=N[:drop|hold]`, `--max-dot=N[:fill|hold]`,
`--screen-origin`, `--resample=bilinear|nearest`, `--measure-dpi`, `--antialias`,
`--compression=auto|g4|lzw|none`, `--name-template` (tokens `{base} {_page} {page} {ink} {pass}
{lpi} {angle}`), `--proof-width`. *(2.11.0)* When two plates on a page expand to the same
file name, for example a spot named `Black` beside process K under the default template, the
later plate gets `_2` (then `_3`, …) before `.tif`, and a warning names it. Through 2.10.0 the
second plate silently overwrote the first.

**Print order.** `--plate-order=A,B,..` (= `--print-order`), first printed first: plates are
emitted, numbered (`plates[].pass`) and proofed in this order; unlisted plates follow and warn,
`--strict-order` fails. The art is never re-stacked: the order is the press's, not the file's.

**Tone shaping** — non-process plates only: `--gain=N`, `--floor=N`, `--ceiling=N`,
`--curve=in:out,..`. Cyan/Magenta/Yellow/Black are never bent (`--tone-process` forces it).
Result `tone{}`.

## 18.5 Proofs and press views *(1.27.0)*

`--proof[=SCALE]` composites the plates **as emitted** (dots or tone) over the paper into
`<base>_proof.png`, every ink a filter, order-free — look there for moiré and rosette before
burning anything.

`--proof-view=garment,sheet` adds opaque inks laid **over** one another in pass order:
`garment` = the print as worn; `sheet` = the transfer off the dryer, last pass on top,
**mirrored**, on the carrier. `--press=transfer|direct` (transfer is the default when a view is
asked): on a transfer pass 1 goes down first and ends on top of the garment, and the underbase is
the last pass before the adhesive, so its pass number goes last unless `--plate-order` says
otherwise. `--carrier=TERM`, `--proof-mirror`, `--proof-hide=A,B` (default: plates named
clear/adhesive/glue/varnish), `--opacity=Name=A`. Result `proof_views{garment, sheet}`. A transfer
with the underbase first washes the garment view white — that is the proof doing its job.

## 18.6 Screen check and mesh *(1.31.0)*

`--screen-check` (alias `--moire`) measures every pair of conventional screens as two lattices:
smallest angle, beat frequency, and beat period in cells of the finer screen. ~1.9 cells is the
normal 30° rosette (`none`); up to 4.5 a coarse rosette (`low`); longer a visible moiré (`high`);
two plates on the same screen are `coincident` (high). `--mesh=N | Name=N` (threads per inch) and
`--mesh-angle=N` add the shop's mesh rules: mesh/LPI ratio against 4.5:1, printable dot range (one
mesh cell = smallest dot), and the angle's distance from the thread directions and the diagonal
(3° high, 7.5° low). Result `screen_check{risk, mesh_angle?, plates[], pairs[]}`; anything above
none is also a warning. Stochastic plates are skipped.

## 18.7 Film *(1.29.0)*

`--film` frames every plate film-ready: a label strip and a margin carrying register targets
(same place on every plate), mirrored with `--film-mirror=on`. The plate file **is** the film;
`plates[]` gains `film`, `film_width_px/height_px`, `art_x_px/art_y_px`. A negative plate gets a
negative frame. `--film-margin` (0.5 in), `--film-target` (0.25), `--film-marks`, `--film-text`,
`--film-label=TEMPLATE` (tokens `{base} {ink} {pass} {passes} {screen} {dpi} {size} {date}
{mirror}`; `""` = none). `--gang-films=WxH` packs framed plates onto film sheets of that size,
`<base>_film_N.tif`; result `film.sheets[{file, width_in, height_in, plates[]}]`.

## 18.8 Shop profile and library *(1.30.0)*

`--shop[=FILE]` — a flat INI of flags without dashes (`lpi = 55`, `paper = Black Tee`,
`film = true`, `[separate]` sections per mode, `#`/`;` comments) expanded **in front of** the
command line, so what you type wins. `--shop` alone reads `$EPIC_SHOP`, else `./shop.ini`. A
relative library/palette file named by a profile is looked for beside it. Result `shop`.

`--library=FILE[;FILE]` (= `--inks`, `--garments`) — `Name = TERM` lines under `[inks]` /
`[garments]`; a bare name then works wherever a TERM does. A cmyk/rgb term under a name becomes
that **named** ink (the plate carries the name); a `spot()` keeps its own. Names match case-blind,
spaces/dashes/underscores alike. Result `libraries[]`.

## 18.9 Dry run *(1.33.0)*

`--dry-run` (= `--plan`) computes the whole job — inks, passes, screens, film frames, gang
sheets, checks — and reports it with the file paths it **would** write, but writes nothing and
creates no directory. Result `dry_run: true`; exit codes as for a real run.

## 18.10 Plates PDF *(1.35.0)*

`--pdf` writes `<base>_plates.pdf` beside the TIFs with every plate as a real `/Separation` ink,
so Acrobat Output Preview and Illustrator list the plates by name and a swatch swap is a click.
Page 1 is the plates **stacked in press order** over the paper (underbase opaque, every other
ink Multiply + overprint: the same model the design proof uses); page 2 the **original** picture
or source page; page 3 the **swatches** (name, CMYK, pass, screen); then one page per plate
alone. A screened plate is its dots (a stencil mask), a continuous one a solid soft-masked by its
coverage; the film frame is left out. A pale ink (White on a white source) gets a grey page so it
is not lost. Result `pdf` (first page's file) and `pages[].pdf`; under `--dry-run` the path is
reported and nothing is written.

Swapping a swatch **after** the solve is not a re-separation: the coverages were solved for the
inks that were given, so a different ink under the same plate prints a different colour. To
separate for real stock, give the inks (`--colors="spot('PANTONE 7566 C',24,66,91,11);..."` with
`--sim-process=3`) or pass the shelf as a palette (`--sim-process=3 --palette=book.txt`): the
picks then carry the book's names and the plates are solved for those inks.

## 18.11 Result

`status`, `reason`, `source`, `output_dir`, `dpi`, `render_dpi`, `halftone{lpi, angle, dot,
device_dpi}|null`, `profile`, `underbase?`, `trap?`, `tone?`, `pages[{page, width_px, height_px,
plates[{name, kind spot|process|raster, role?, pass, file|null, coverage_pct, blank, solid,
screened, lpi|null, angle|null, dot|null, inverted?, film?}], pdf?}]`, `inks[]?`, `preview?`,
`proof?`, `proof_views?`, `quality?`, `duotone?`, `index?`, `sim_process?`, `prep?`,
`screen_check?`, `film?`, `linearity?`, `shop?`, `libraries?`, `dry_run?`, `pdf?`, `warnings[]`.
Every optional field is omitted when its flag is absent.

## 18.12 Examples

```
java -jar java-epic-cli.jar --separate art.pdf --dpi=600 --output=plates/
java -jar java-epic-cli.jar --separate photo.png --sim-process=3 --palette=book.txt --paper=black --underbase=solved --pdf
java -jar java-epic-cli.jar --separate art.pdf --halftone --proof --output=film/
java -jar java-epic-cli.jar --separate art.pdf --screens="Black=85:45;HA Red=35:15:round"
java -jar java-epic-cli.jar --separate photo.png --colors=process --profile=icc --gcr=70 --halftone
java -jar java-epic-cli.jar --separate photo.png --sim-process=3 --paper=black --halftone --proof-view=garment --film
java -jar java-epic-cli.jar --separate shirt.png --colors=5 --paper="Black Tee" --library=inks.lib --underbase --palette=shop.pal
java -jar java-epic-cli.jar --separate logo.png --index --colors="spot('PANTONE 485 C',0,95,100,0);Black"
java -jar java-epic-cli.jar --separate art.pdf --shop --screen-check --mesh=230 --dry-run
java -jar java-epic-cli.jar --quantize=5 art.png --paper=cmyk(0,0,0,0)
```
