Metadata-Version: 2.4
Name: rosette-halftone
Version: 1.0.0
Summary: Convert color images to CMYK-separated halftone SVG files for screen printing and risograph
Author: datatimp
License: AGPL-3.0-or-later
Project-URL: Homepage, https://palettary.com/halftone
Project-URL: Repository, https://github.com/datatimp/rosette
Keywords: halftone,cmyk,svg,screen-printing,risograph,color-separation,print
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Other Audience
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
Classifier: Topic :: Printing
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Pillow>=10.0
Requires-Dist: numpy>=1.24
Requires-Dist: click>=8.1
Dynamic: license-file

# Rosette

Convert a color image into ICC-accurate CMYK-separated halftone SVG files — one per channel, each a pure vector circle halftone at the correct screen angle.

Built for screen printing, risograph, and any additive color print workflow where you need clean separations as scalable vector art.

**[palettary.com/halftone](https://palettary.com/halftone)**

---

## What Rosette Does

Rosette takes a color image and produces four SVG files:

- `cyan.svg`
- `magenta.svg`
- `yellow.svg`
- `black.svg`

Each file contains only that channel's halftone dots as `<circle>` elements on a transparent background. Load them into Inkscape, Illustrator, or send directly to a print workflow.

Unlike raster-output tools, Rosette's SVGs are resolution-independent — scale them to any size without quality loss.

---

## Installation

```bash
pip install rosette
```

Requires Python 3.10+. Dependencies: Pillow, NumPy, Click.

---

## Usage

```
rosette IMAGE [OPTIONS]
```

### Options

| Option | Default | Description |
|--------|---------|-------------|
| `--dots INTEGER` | 100 | Dots per row (controls grid density) |
| `--preset TEXT` | `us` | Screen angle preset (see table below) |
| `--angles C M Y K` | — | Manual per-channel angles in degrees; overrides `--preset` |
| `--min-radius FLOAT` | 0 | Minimum dot radius as fraction of max (0–1) |
| `--channels TEXT` | `cmyk` | Channels to generate: any combo of `c`, `m`, `y`, `k` |
| `--profile TEXT` | `math` | CMYK conversion: `math` (geometric, recommended for screen/riso) or `swop` (ICC SWOP, offset stock) |
| `--gamma FLOAT` | `1.5` | Dot density curve. Higher = lighter/more open halftone. Lower = heavier. Range 0.5–3.0 typical |
| `--resample TEXT` | `linear` | Resampling kernel: `nearest`, `linear`, `lanczos2`, `lanczos3`, `spline36` |
| `--tac FLOAT` | `220` | Total Area Coverage limit (%). SWOP default 300; screen printing 160–200; riso 180–220 |
| `--output DIR` | `./output` | Output directory |
| `--zip` | off | Package output SVGs into a single ZIP |

### Setting dot density (`--dots`) and LPI

Because Rosette outputs SVG, LPI is not fixed at generation time — it's determined by how large you print the file. The relationship is simple:

> **`--dots` = target LPI × print width in inches**

Use this table to find the right `--dots` value for your target LPI and print size:

| Target LPI | 4" wide | 5" wide | 8.5" wide | 11" wide | 17" wide |
|------------|---------|---------|-----------|----------|----------|
| 35 lpi     | 140     | 175     | 298       | 385      | 595      |
| 45 lpi     | 180     | 225     | 383       | 495      | 765      |
| 55 lpi     | 220     | 275     | 468       | 605      | 935      |
| 65 lpi     | 260     | 325     | 553       | 715      | 1105     |
| 85 lpi     | 340     | 425     | 723       | 935      | 1445     |

Typical ranges by process: **screen printing** 35–65 lpi · **risograph** 50–106 lpi · **offset** 85–150 lpi.

**Image resolution note:** for clean dot gradients, your input image should be at least `--dots × 2` pixels wide. Below that, cell size drops under 2px and the blur/sampling pipeline has too little data to work with.

### Examples

```bash
# Basic — US preset, 100 dots per row
rosette photo.jpg

# EU preset, higher density
rosette photo.jpg --preset eu --dots 150

# CMY only, no black plate, custom output directory
rosette photo.jpg --channels cmy --output ./separations

# Manual screen angles
rosette photo.jpg --angles 15 75 0 45

# Package into a ZIP
rosette photo.jpg --zip

# High-density black-only plate
rosette photo.jpg --channels k --dots 200 --output ./black-plate
```

---

## CMYK Screen Angles

Halftone screens are rotated to prevent moiré patterns when the four color layers overlap. The classic approach assigns each channel a different angle:

| Preset | C  | M  | Y  | K  | Notes |
|--------|----|----|----|----|-------|
| `us`   | 15 | 75 | 0  | 45 | US standard — default |
| `eu`   | 15 | 45 | 0  | 75 | European standard |
| `alt-a`| 15 | 45 | 30 | 45 | |
| `alt-b`| 45 | 15 | 0  | 75 | |
| `alt-c`| 45 | 75 | 0  | 15 | |
| `alt-d`| 75 | 15 | 0  | 45 | |
| `alt-e`| 75 | 45 | 0  | 15 | |
| `alt-f`| 75 | 15 | 60 | 45 | |

**Why it matters:** when two halftone screens at similar angles are overlaid, their dot patterns interfere and produce a visible grid artifact (moiré). Traditional offset printing separates the dominant inks (K and M) by 30°, with C 15° from K and Y on the least-visible 0° angle. Different presets trade off moiré characteristics — try `eu` if `us` shows artifacts on your specific press or paper.

For risograph and screen printing, angle choice also affects ink mixing behavior. Experimenting with the alt presets can produce different visual texture in overlapping color areas.

---

## How It Works

1. **Load and convert** — Pillow opens the image and converts to CMYK via ImageCms (lcms2) using the bundled Artifex CMYK SWOP ICC profile. If the image has an embedded ICC profile (AdobeRGB, P3, etc.) it is used as the input profile; otherwise sRGB is assumed. The SWOP profile handles black generation (GCR) internally.
2. **TAC limiting** — any pixel where C+M+Y+K exceeds the `--tac` threshold is scaled down proportionally, preserving hue. SWOP is calibrated for 300% TAC on coated stock; screen printing and risograph workflows typically need 160–220%.
3. **Pre-blur** — each channel is Gaussian-blurred at radius = cell_size/2 before sampling, smoothing tonal transitions into clean dot gradients.
4. **Rotated grid** — a dot grid is generated at the channel's screen angle, sized to cover the full image diagonal so no edge is missed at any rotation.
5. **Sampling** — each dot's ink density is bilinear-sampled from the blurred channel at the dot centre position.
6. **Radius mapping** — ink density maps to dot radius using area-proportional (square-root) scaling, so ink coverage is linear with density. Maximum radius is `cell_size × 0.47`, keeping adjacent dots from merging in SVG solid-fill rendering.
7. **SVG output** — each channel becomes a standalone SVG with `<circle>` elements, a transparent background, and a viewBox clipped to the original image dimensions.

---

## License

AGPL-3.0-or-later. See [LICENSE](LICENSE).

## Acknowledgements

- Bundled ICC profiles (`sRGB.icc`, `SWOP.icc`) are provided by [Artifex Software](https://www.ghostscript.com/) as part of GPL Ghostscript, distributed here under AGPL-3.0.
- Halftone approach and Pillow/ICC pipeline referenced from [curegit/halftone-converter](https://github.com/curegit/halftone-converter).
- Sampling and grid approach informed by [evestera/svg-halftone](https://github.com/evestera/svg-halftone).
