Metadata-Version: 2.4
Name: rosetta-squint
Version: 1.0.0
Summary: Cross-language byte-exact perceptual image hashing — decode + hash in one call
Author-email: Will Metcalf <william.metcalf@gmail.com>
License: BSD-2-Clause
Project-URL: Homepage, https://github.com/wmetcalf/rosetta-squint
Project-URL: Repository, https://github.com/wmetcalf/rosetta-squint
Project-URL: Issues, https://github.com/wmetcalf/rosetta-squint/issues
Project-URL: Changelog, https://github.com/wmetcalf/rosetta-squint/blob/main/CHANGELOG.md
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: rosetta-squint-hash<2.0,>=1.0.0
Requires-Dist: Pillow==12.2.*
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"

# rosetta_squint — Python convenience API

Point at an image file or pass in raw image bytes; get back the same perceptual hash hex string that every other `rosetta-squint` port produces for the same input.

## Install

```bash
pip install -e ../../hash/python                    # rosetta-squint-hash (wrapper around imagehash)
pip install -e .                                    # this package
```

(Not on PyPI yet — both are local.)

## Usage

```python
import rosetta_squint as rs

# Path on disk
h = rs.phash("photo.jpg", 8)
print(h)                                            # "c3f8a1b27d0e4f96"

# Raw image bytes (from an HTTP response, a database BLOB, a multipart upload)
with open("photo.jpg", "rb") as f:
    h = rs.phash_bytes(f.read(), 8)

# Every algorithm available has both flavors:
rs.average_hash(path, 8)       # rs.average_hash_bytes(bytes, 8)
rs.phash(path, 8)              # rs.phash_bytes(bytes, 8)
rs.phash_simple(path, 8)       # rs.phash_simple_bytes(bytes, 8)
rs.dhash(path, 8)              # rs.dhash_bytes(bytes, 8)
rs.dhash_vertical(path, 8)     # rs.dhash_vertical_bytes(bytes, 8)
rs.whash_haar(path, 8)         # rs.whash_haar_bytes(bytes, 8)
rs.whash_db4(path, 8)          # rs.whash_db4_bytes(bytes, 8)
rs.whash_db4_robust(path, 8)   # rs.whash_db4_robust_bytes(bytes, 8) — cross-port-stable
rs.colorhash(path, 3)          # rs.colorhash_bytes(bytes, 3) — takes binbits
rs.crop_resistant_hash(path)   # rs.crop_resistant_hash_bytes(bytes) — no size, returns ImageMultiHash

# Hex round-trips:
restored = rs.hex_to_hash("c3f8a1b27d0e4f96")
restored = rs.hex_to_flathash("...", hashsize=3)
restored = rs.hex_to_multihash("hex1,hex2,hex3")
```

## Cross-port equivalence

The output of `rs.phash("photo.jpg", 8)` is the same hex string as you'd get from the Rust, Go, Java, JS, or Swift `rosetta-squint` ports given the same byte input. **Verified live for `imagehash.png` at size 8: `ba8c84536bd3c366` across Python, Go, Java, JS, Swift.**

## Decode strategy

| Format | Decoder | Why |
|---|---|---|
| BMP, PNG, GIF, JPEG, WebP, TIFF | PIL/Pillow (system) | The canonical Python decoders; the goldens used to validate the 5 native ports were generated by PIL itself, so output matches by construction. |
| HEIC | ctypes wrapper around system `libheif.so.1` | pillow-heif bundles libheif 1.21.2 in its wheel; the 5 native ports link to system libheif 1.17.6. The wrapper avoids the ±1 px divergence. |

If you already have a `PIL.Image.Image` (from `PIL.Image.open(...)`, a thumbnailer, etc.), use the `rosetta_squint_hash` lower-level API directly — the squint layer's only job is the decode step:

```python
import rosetta_squint_hash as rih
from PIL import Image
img = Image.open("photo.jpg")
h = rih.phash(img, hash_size=8)
```

## Dependencies

- `rosetta_squint_hash` (which re-exports `imagehash==4.3.2` + adds `whash_db4_robust`)
- `Pillow==12.2.*`

Tight pins are intentional. See [`../../hash/python/README.md`](../../hash/python/README.md) under "Version policy" for the upgrade workflow.

## Testing

```bash
pytest
```

Tests verify (1) path/bytes parity for every algorithm, (2) chain consistency between `rs.phash(path)` and `imagehash.phash(rs.decode_file(path))`, (3) cross-port byte-exact equality with Go/Java/JS for `imagehash.png`.
