kisscut
v0.1.0
Published
Sticker outlines (Cloudinary outline:fill equivalent), fit/fill resize, and rotation for raster images. Pure TypeScript, zero dependencies, browser + Node.
Downloads
152
Maintainers
Readme
kisscut
Sticker outlines for raster images, an open-source equivalent of Cloudinary's e_outline:fill. Also does fit/fill resize and rotation. Pure TypeScript, zero runtime dependencies, ~5 KB minified, runs in browsers (ImageData) and Node (skia-canvas or raw RGBA buffers).
A kiss cut is the sticker cut that traces just outside your artwork. Give this library a transparent PNG and it returns the artwork wrapped in a constant-width, anti-aliased outline, with enclosed holes filled so the result prints as one sticker.
One of these was made by Cloudinary, the other by kisscut, locally (kisscut is on the left):

The raw per-pixel difference, unamplified. Black means identical; the faint red hairlines along the edges are the entire difference:

Install
npm install kisscutUsage
import { outline, transform } from 'kisscut'
// Anything shaped like { data: Uint8ClampedArray, width, height } works.
// Browser ImageData is structurally compatible.
const imageData = context.getImageData(0, 0, canvas.width, canvas.height)
const { image, pad } = outline(imageData, {
width: 18, // outline radius in px
color: '#ffffff80', // '#rrggbbaa' and 'rgb:rrggbbaa' accepted, alpha works natively
fillHoles: true // fill enclosed transparent regions (default)
})
// The canvas grows by `pad` on every side so the outline is never clipped
context.putImageData(new ImageData(image.data, image.width, image.height), x - pad, y - pad)transform() chains Cloudinary-style operations (resize, then rotate, then outline):
const { image, pad } = transform(imageData, {
width: 563, height: 563, crop: 'fit', // c_fit / c_fill parity
angle: -90, // multiples of 90, or 'auto_left'
outline: { width: 17.6, color: 'white' }
})The building blocks are exported too: distanceTransform, dilateCoverage, fillHoles, resize, rotate, parseColor, and similarity (overlap-score two images and get a difference heatmap).
How it works
- Read alpha coverage onto a padded grid
- Exact Euclidean distance transform (Felzenszwalb-Huttenlocher, O(n)), seeded sub-pixel from alpha coverage so the outline follows the true edge, not pixel centers
- Smoothstep over the distance field: constant-width outline, round joins, anti-aliased edges
- Holes filled after dilation, matching Cloudinary. Gaps narrower than 2x the width close, and the newly enclosed interior fills
- The colored outline layer is composited under the artwork. Semi-transparent colors just work, no need for Cloudinary's
o_/l_fetch/fl_layer_applydouble-render workaround - Resize is separable Catmull-Rom in premultiplied-alpha space
Fidelity
The test suite compares output against real Cloudinary renders committed as fixtures. Images are overlapped and scored on per-pixel agreement, where 1.0 is pixel-identical. Measured scores are 0.990 to 0.998; every case must pass 0.988. What remains is single-pixel edge anti-aliasing against a proprietary renderer. Run yarn compare <dir> to reproduce the scores and heatmaps.
![]()
![]()
Performance
Single-threaded, Node 24, desktop:
| Image | Outline width | Time | | --- | --- | --- | | 800×800 | 25 px | ~60 ms | | 1600×1600 | 50 px | ~230 ms | | 3200×3200 | 100 px | ~940 ms |
vs. a Cloudinary request
Same transform, measured against the public demo cloud:
| Path | Time | | --- | --- | | Cloudinary, fresh transform (any new artwork) | ~1,000 ms | | Cloudinary, CDN-cached repeat of the same URL | ~36 ms | | kisscut, locally, 1126×1126 (2x retina of a 563 px preview) | ~134 ms |
For previews of user-uploaded artwork every image is new, so Cloudinary always pays the fresh-transform price. Running locally is roughly 7x faster there, works offline, and a local result can be cached for repeat renders just like a CDN would.
Development
yarn # install
yarn test # unit + property + Cloudinary-parity tests
yarn demo # interactive demo: drag in a PNG, tweak width/color/opacity
yarn bench # benchmarks at 800/1600/3200 px
yarn compare out/ # overlap scores + difference heatmaps vs Cloudinary fixtures
yarn build # dist/ (ESM + CJS + d.ts)Roadmap
- Unifying outline for multi-part artwork: MST capsule bridges so disconnected pieces become one printable sticker (beyond what Cloudinary offers)
- Vector cutline: marching squares + simplification + smoothing to an SVG path, for actual die lines
License
MIT
