{
  "schema": "leumas.docs.page/1",
  "id": "pkg:@leumas/adapter-image",
  "slug": "adapters/adapter-image",
  "kind": "capabilities",
  "bucket": "package",
  "title": "image — raster image processing adapter",
  "name": "image",
  "eyebrow": "raster image processing adapter",
  "chip": null,
  "summary": "Image processing domain adapter — real raster pixel work via sharp (libvips): resize/crop/fit, format convert, compress, thumbnail, rotate/flip, grayscale/blur/sharpen/tint/negate/modulate, composite...",
  "keywords": [
    "adapter-image",
    "libvips",
    "grayscale",
    "blur",
    "sharpen",
    "adapter image api",
    "leumas adapter image",
    "negate"
  ],
  "audience": "both",
  "funnel": {
    "product": null,
    "cta": null
  },
  "body": "# image — raster image processing adapter\n\nReal image processing for the Leumas ecosystem, powered by [`sharp`](https://sharp.pixelplumbing.com/)\n(libvips). Every tool takes a single `args` object (so an HTTP POST body maps 1:1) and returns a plain\nJSON-serializable result. Inputs accept a **base64 string** (raw or a `data:image/...;base64,` URI) **or\na local file path**; outputs return a **base64-encoded image** plus `format`/`width`/`height`.\n\nContract: `export default { metadata, adapters: { <tool>: async (args) => result } }`.\n\n## Lazy-guarded native dependency\n\n`sharp` is a native (libvips) module. It is imported **lazily** and **guarded**: the pack always loads,\neven on a machine where sharp failed to install/compile. In that case every tool returns:\n\n```json\n{ \"ok\": false, \"unavailable\": true, \"error\": \"sharp not installed: ...\" }\n```\n\nso callers can degrade gracefully instead of the whole registry failing to load. On success each\nprocessing tool returns `{ ok:true, base64, format, width, height, channels, size, mime }`. Pass\n`dataUri:true` in args to get a ready-to-embed `data:image/...;base64,...` string instead of raw base64.\n\n## Tools\n\n| Tool | What it does |\n|---|---|\n| `resize` | Resize to exact `width`/`height` (omit one to keep aspect ratio). Optional `fit`. |\n| `crop` | Extract a `width`×`height` region at `left`/`top`. |\n| `resizeToFit` | Fit into a box with `fit` = `contain` (pad) / `cover` (crop) / `fill` / `inside` / `outside`. |\n| `convert` | Re-encode to `format` = jpeg/png/webp/avif/tiff/gif, optional `quality`. |\n| `compress` | Re-encode at a lower `quality` (default 60) to shrink size; keeps input format by default. |\n| `thumbnail` | Cover-cropped thumbnail at `size` (default 128), webp by default. |\n| `rotate` | Rotate by `angle` degrees (default 90); non-90° fills corners with `background`. |\n| `flip` | Mirror vertically. |\n| `flop` | Mirror horizontally. |\n| `grayscale` | Desaturate to grayscale. |\n| `blur` | Gaussian blur (`sigma`, default 3). |\n| `sharpen` | Sharpen (`sigma`, default 1). |\n| `tint` | Tint toward a `color` (hex or CSS name). |\n| `negate` | Photographic negative (`alpha:true` also inverts alpha). |\n| `modulate` | Adjust `brightness`/`saturation`/`hue`/`lightness`. |\n| `gamma` | Gamma correction (`gammaValue` 1.0–3.0). |\n| `normalize` | Auto-stretch contrast. |\n| `extend` | Pad a border (`all` or `top`/`bottom`/`left`/`right`) with `background`. |\n| `trim` | Trim uniform border pixels. |\n| `extractChannel` | Extract `channel` red/green/blue/alpha (or 0-3) as grayscale. |\n| `composite` | Overlay a watermark/logo (`overlay`) with `position`/`blend`/`opacity`. |\n| `flatten` | Flatten transparency onto a solid `background`. |\n| `metadata` | Read width/height/format/channels/space/hasAlpha/orientation without re-encoding. |\n| `stats` | Per-channel min/max/mean/stdev + isOpaque/entropy/dominant. |\n| `dominantColor` | Dominant colour as `{r,g,b}` + `#hex`. |\n| `stripExif` | Re-encode without EXIF/metadata (bakes in orientation first). |\n| `toBase64` | Normalize any accepted input to base64 (optionally a data URI / target `format`). |\n\n## Usage\n\n```js\nimport image from './index.js';\n\n// Make a 128px webp thumbnail from a file path:\nconst thumb = await image.adapters.thumbnail({ image: 'C:/photos/cat.jpg', size: 128 });\n// → { ok:true, base64:'...', format:'webp', width:128, height:128, ... }\n\n// Convert an inline base64 PNG to a compressed JPEG data URI:\nconst jpg = await image.adapters.convert({ image: pngB64, format: 'jpeg', quality: 70, dataUri: true });\n\n// Watermark: overlay a logo bottom-right at 50% opacity:\nawait image.adapters.composite({ image: photoB64, overlay: logoB64, position: 'southeast', opacity: 0.5 });\n\n// Inspect without decoding a full re-encode:\nawait image.adapters.metadata({ image: 'C:/photos/cat.jpg' });\n```\n\n## DRY boundary\n\n- **vs `a-transformation`** — a-transformation only reads an image's **basename / statInfo** (the\n  filename string + `fs` stat metadata: size, mtime, path parts). That is filesystem/string work, not\n  pixels. **This** `image` pack does the actual pixel decoding & processing via sharp. No overlap — a\n  filename helper stays in a-transformation; anything touching pixels lives here.\n- **vs `numbers`** — `numbers.baseConvert` is integer radix conversion; the base64 in this pack is\n  binary image encoding. Different concerns.\n- Self-contained: no cross-pack imports. `sharp` is the only external dependency (declared in the local\n  `package.json` for provenance; installed at the workspace root).\n",
  "source": {
    "path": "shared/engines/adapters/domain/image/README.md",
    "blobSha": "",
    "commit": "",
    "committedAt": "",
    "provenance": "no-git",
    "bytes": 4812,
    "hash": "1a5e31a9d5674dd595c560502bf74491587e6d90"
  },
  "urls": {
    "html": "/p/adapters/adapter-image",
    "json": "/docs/adapters/adapter-image.json",
    "md": "/docs/adapters/adapter-image.md"
  },
  "links": {
    "composes": [],
    "usedBy": [],
    "product": [],
    "howTo": [
      "how-to:editors-and-captures"
    ],
    "skills": []
  },
  "exports": null
}
