Skip to content
jpol34Public

About

Scriptable image CLI (crop/resize/convert/annotate/diff) + local side-by-side comparison viewer

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

11 Commits

Folders and files

Repository files navigation

comparo

A scriptable image CLI (crop, resize, convert, annotate, diff) paired with a local web viewer for side-by-side comparison of image sets. Built to be a general-purpose tool for any project on this machine, not tied to any one project's data.

Install

pnpm install
pnpm build

Consumed by another local project as a file: or git dependency, e.g.:

pnpm add file:../comparo

Commands

Every command accepts --json for machine-readable output (a single JSON object on stdout), useful when calling this CLI from a script or an agent. Without --json, output is a human-readable one-line summary.

crop <input> <output> --box x,y,w,h

Crops to the given rectangular region (x,y = top-left corner, non-negative; w,h = size, positive).

resize <input> <output> [--width px] [--height px]

Resizes, preserving aspect ratio if only one of --width/--height is given. At least one is required.

convert <input> <output> [--format <fmt>]

Converts image format. --format is one of jpeg, png, webp, gif, avif, tiff; if omitted, it's inferred from <output>'s extension.

annotate <input> <output> --shapes '<json>' or --shapes-file <path>

Draws boxes, arrows, and text onto an image. --shapes (or --shapes-file, for a longer spec) is a JSON array of shapes:

[
  { "type": "box", "x": 10, "y": 10, "w": 100, "h": 80, "color": "#ff0000", "strokeWidth": 3 },
  { "type": "arrow", "x1": 0, "y1": 0, "x2": 50, "y2": 50, "color": "#00ff00" },
  { "type": "text", "x": 5, "y": 20, "text": "note", "color": "#000000", "fontSize": 16 }
]

color must be a hex color (#rgb, #rrggbb, or #rrggbbaa); strokeWidth/fontSize default to 3/16 if omitted. Output format is inferred from <output>'s extension, same as convert.

diff <a> <b> [--out-dir <dir>]

Pixel-compares two same-dimension images via pixelmatch. Always reports mismatchedPixels, totalPixels, and score (0 = identical, 1 = fully different). With --out-dir, also writes a visual diff image (<a-name>-vs-<b-name>.diff.png) and reports its path as diffPath — this is the field the viewer picks up to render a diff column (see below).

serve <dir> [--port 4173]

Starts a local viewer server (binds to 127.0.0.1 only) at http://127.0.0.1:<port>, rendering a grid of the images in <dir>.

The manifest / folder-scan contract

serve looks for a manifest.json in <dir>. If one exists, it's used as-is. Otherwise <dir> is auto-scanned: only immediate (one level deep) subfolders become groups, files inside a group become items and are labeled by filename (minus extension); loose files sitting directly in <dir> are skipped and reported as a startup warning, not silently dropped.

manifest.json shape

{
  "rows": [
    {
      "id": "lanco-plumbing/photo-1",
      "group": "lanco-plumbing",
      "label": "Storefront",
      "images": [
        { "label": "Original", "path": "/abs/path/original.jpg" },
        { "label": "FLUX", "path": "/abs/path/flux.jpg" },
        { "label": "Gemini", "path": "/abs/path/gemini.jpg" }
      ],
      "diffPath": "/abs/path/original-vs-flux.diff.png"
    }
  ]
}
  • group becomes a section heading in the viewer (e.g. one per business/vertical).
  • images is an ordered array — as many columns as you like, each with its own label.
  • diffPath is optional; when present, the viewer renders it as an extra "diff" column.
  • path/diffPath values may be absolute, or relative — relative paths are resolved against the directory containing manifest.json itself (not the directory serve is invoked from), so a manifest authored alongside its images stays portable. The viewer only serves files that actually appear in the current manifest (see Security below) — it is not a general file server.

Worked example: diff → manifest → serve

comparo diff original.jpg flux.jpg --out-dir diffs --json
# {"ok":true,"command":"diff","a":"original.jpg","b":"flux.jpg","mismatchedPixels":1234,
#  "totalPixels":60000,"score":0.02,"diffPath":"diffs/original-vs-flux.diff.png"}

Reference that diffPath in a manifest row's diffPath field (relative to wherever you put manifest.json, or copy the absolute path diff --json reported), then:

comparo serve ./my-comparison-dir

Open the printed URL to see the original, FLUX output, and diff image side by side.

Security note

serve binds to 127.0.0.1 only and has no authentication — it's meant for personal, local use. The /file route only serves paths that appear in the manifest currently being served (recomputed per request), not an arbitrary filesystem read, to limit exposure if a browser tab visits a malicious page while the server happens to be running.

Non-goals

No EXIF/metadata management, no photo-library organization (albums/tags/search), no ML/embedding features, no batch/watch modes, no cloud storage integration. This stays a thin, scriptable local tool.

About

Scriptable image CLI (crop/resize/convert/annotate/diff) + local side-by-side comparison viewer

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages