@leumas/adapter-meme
Meme / image-caption adapter — impact-text captions, meme templates (Drake, Distracted Boyfriend, Galaxy Brain, …), demotivational posters, free-form text, and a deep-fry filter. Text is drawn as SVG...
@leumas/adapter-meme
Meme / image-caption generator. Give it a source image (base64 or data: URL) and it hands back a PNG (base64 + data-URL) with meme text baked on.
Text is drawn with no canvas dependency: every tool builds an SVG string (font, stroke, word-wrap and auto-fit computed in pure JS) and hands it to sharp.composite(), which rasterizes the SVG over the decoded image and re-encodes to PNG.
Tools
| Tool | Input | Output |
|---|---|---|
caption | { image, top?, bottom?, options? } | Classic white impact top/bottom text with black stroke, auto-fit + wrapped. |
fromTemplate | { template, texts, image?, options? } | Fill a named template's zones. texts is an object keyed by zone key/alias, or an array in zone order. Without image, a flat template background is generated. |
demotivational | { image, title?, subtitle?, options? } | Black poster: photo inset with a thin white keyline, serif title + subtitle below. |
addText | { image, text, x?, y?, size?, color?, options? } | Free-form caption. x/y accept absolute px or a 0..1 fraction (default centered). |
addBottomBar | { image, text, options? } | Reddit/Tumblr-style solid caption bar added above (position:'top', default) or below the image. |
deepFry | { image, options: { level: 1..5 } } | Crunchy over-saturated "deep-fried" filter (crush → resharpen → saturate → JPEG-crush). |
blankTemplate | { template } | Render a template's empty background canvas. |
imageInfo | { image } | Decode and report { width, height, format, bytes, hasAlpha }. |
listTemplates | (none) | Pure. Catalog of templates with their zones. |
describeTemplate | { template } | Pure. Full zone geometry for one template. |
layoutMeme | { template, texts } or { text, width, height } | Pure. Preview where captions land (fontSize + wrapped lines per zone) without rasterizing. |
Templates: drake, distracted-boyfriend, two-buttons, expanding-brain, change-my-mind, one-does-not-simply (see data/templates.json).
Usage
import meme from '@leumas/adapter-meme'; // or the registry: registry.run('meme', 'caption', args)
const src = fs.readFileSync('cat.png').toString('base64');
const out = await meme.adapters.caption({ image: src, top: 'one does not simply', bottom: 'make a meme' });
// -> { ok:true, format:'png', base64, dataUrl, bytes, width, height }
await meme.adapters.fromTemplate({ template: 'drake', texts: { top: 'writing my own text renderer', bottom: 'building an SVG and letting sharp do it' } });
meme.adapters.listTemplates(); // pure — no image, no sharp needed
Each POST body maps 1:1 onto a call (one args object per tool).
Dependency & lazy guard
The only heavy dependency is sharp (installed at the workspace root, shared with the image pack — not re-installed here). It is imported lazily and guarded: this module always loads even if the native sharp binary is unavailable. The image tools then return { ok:false, unavailable:true, error:'sharp not installed' }, while the pure tools (listTemplates, describeTemplate, layoutMeme) keep working.
DRY boundary
- This pack captions/decorates raster IMAGES. It does not do ASCII art or emoji art — that
is text and lives in the ascii / emoji text packs.
- It does not do general image resize / format-convert / color filters — that is the
image
pack. meme is the meme-specific layer (impact text, meme templates, demotivational frames, deep-fry) built on top of the same shared sharp.
- No
canvas/jimpdependency is added: text rendering is SVG →sharp.composite.