feat(image): add ImageMagick-equivalent ops (shave/deskew/colors/colorspace/contrast-stretch/density) #16

Merged
David merged 1 commit from feat/magick-ops into main 2026-05-14 03:31:03 +02:00
Owner

Native Rust equivalents of six common magick flags, exposed under monkey image:

Subcommand Magick flag Notes
image shave --width N --height N -shave WxH Centred crop.
image deskew [--threshold DEG] -deskew Projection-profile angle search over [-15°, +15°] in 0.5° steps on a downsampled grayscale copy, then bilinear counter-rotation. --threshold default is 1.0 so sub-degree noise detections are ignored.
image colors --count N -colors N NeuQuant via the color_quant crate. N must be in 2..=256.
image colorspace --space gray|srgb -colorspace gray uses Rec. 709 luminance; srgb is identity.
image contrast-stretch [--black F] [--white F] -contrast-stretch BLACK%xWHITE% Per-channel histogram clip and linear remap to [0, 1].
image density --dpi DPI -density DPI Metadata-only. PNG gets a pHYs chunk (DPI -> pixels-per-metre); JPEG gets its JFIF APP0 density bytes patched after re-encoding. Pixel data is unchanged.

Five of these are pixel transforms on the existing ImageBuf and dispatch through run_filter. colors returns Result (range check) and density operates on paths (no ImageBuf round-trip), so both have explicit dispatch arms.

New dependency: color_quant = "1" (MIT/Apache-2.0).

Unit tests in src/image/magick_ops.rs cover dimensions, parameter validation, gray equalisation, and contrast expansion (10 new tests; full suite 39 passing). End-to-end smoke test against a magick-generated 100x60 line drawing confirmed each subcommand produces correct output dimensions and that monkey image density --dpi 300 round-trips through ImageMagick's identifier as 118.11 ppcm (= 300 DPI).

cargo fmt --check, cargo clippy --all-targets -- -D warnings, cargo build --all-targets, and cargo test all pass on this branch.

Test plan

  • Confirm Forgejo Check workflow passes for this branch.
  • Run monkey image shave, deskew, colors, colorspace, contrast-stretch, and density against a real document and visually compare to the equivalent magick invocation.
  • Confirm a PNG produced by monkey image density --dpi 300 in.png out.png reports 300 DPI in viewers that read the pHYs chunk.
  • Confirm a JPEG produced by monkey image density --dpi 300 in.jpg out.jpg reports 300 DPI in viewers that read the JFIF density bytes.
Native Rust equivalents of six common `magick` flags, exposed under `monkey image`: | Subcommand | Magick flag | Notes | |---|---|---| | `image shave --width N --height N` | `-shave WxH` | Centred crop. | | `image deskew [--threshold DEG]` | `-deskew` | Projection-profile angle search over `[-15°, +15°]` in 0.5° steps on a downsampled grayscale copy, then bilinear counter-rotation. `--threshold` default is `1.0` so sub-degree noise detections are ignored. | | `image colors --count N` | `-colors N` | NeuQuant via the `color_quant` crate. `N` must be in `2..=256`. | | `image colorspace --space gray\|srgb` | `-colorspace` | `gray` uses Rec. 709 luminance; `srgb` is identity. | | `image contrast-stretch [--black F] [--white F]` | `-contrast-stretch BLACK%xWHITE%` | Per-channel histogram clip and linear remap to `[0, 1]`. | | `image density --dpi DPI` | `-density DPI` | Metadata-only. PNG gets a pHYs chunk (DPI -> pixels-per-metre); JPEG gets its JFIF APP0 density bytes patched after re-encoding. Pixel data is unchanged. | Five of these are pixel transforms on the existing `ImageBuf` and dispatch through `run_filter`. `colors` returns `Result` (range check) and `density` operates on paths (no `ImageBuf` round-trip), so both have explicit dispatch arms. New dependency: `color_quant = "1"` (MIT/Apache-2.0). Unit tests in `src/image/magick_ops.rs` cover dimensions, parameter validation, gray equalisation, and contrast expansion (10 new tests; full suite 39 passing). End-to-end smoke test against a `magick`-generated 100x60 line drawing confirmed each subcommand produces correct output dimensions and that `monkey image density --dpi 300` round-trips through ImageMagick's identifier as `118.11 ppcm` (= 300 DPI). `cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`, `cargo build --all-targets`, and `cargo test` all pass on this branch. ## Test plan - [ ] Confirm Forgejo `Check` workflow passes for this branch. - [ ] Run `monkey image shave`, `deskew`, `colors`, `colorspace`, `contrast-stretch`, and `density` against a real document and visually compare to the equivalent `magick` invocation. - [ ] Confirm a PNG produced by `monkey image density --dpi 300 in.png out.png` reports 300 DPI in viewers that read the pHYs chunk. - [ ] Confirm a JPEG produced by `monkey image density --dpi 300 in.jpg out.jpg` reports 300 DPI in viewers that read the JFIF density bytes.
feat(image): add ImageMagick-equivalent ops (shave/deskew/colors/colorspace/contrast-stretch/density)
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 18s
Create release / Create release from merged PR (pull_request) Has been skipped
4f5130f8b8
Native Rust implementations of six common `magick` flags, exposed under `monkey image`. Five are pixel transforms on the existing `ImageBuf`; `density` is a metadata-only op that writes DPI into the PNG pHYs chunk or patches the JFIF APP0 segment of a re-encoded JPEG, leaving pixel data unchanged. `shave` is a centred crop; `deskew` runs a projection-profile angle search over [-15°, +15°] in 0.5° steps on a downsampled grayscale copy and counter-rotates with bilinear sampling, with a `--threshold` default of 1.0° so trivial sub-degree detections are skipped; `colors` runs NeuQuant via the `color_quant` crate (rejects counts outside 2..=256); `colorspace` supports `gray` (Rec. 709 luminance) and `srgb` (identity); `contrast-stretch` clips a per-channel histogram and linearly remaps to [0, 1]. All five are dispatched through the existing `run_filter` helper except `colors` (fallible) and `density` (path-based). Unit tests cover dimensions, parameter validation, gray equalisation, and contrast expansion; end-to-end smoke testing against a magick-generated input confirms each subcommand produces correct output and that `density --dpi 300` round-trips to the expected pHYs value.
David merged commit abba222446 into main 2026-05-14 03:31:03 +02:00
David deleted branch feat/magick-ops 2026-05-14 03:31:03 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pandoras-box/monkey!16
No description provided.