npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ishk9/stampy

v0.1.0

Published

Turn any photo into a perforated postage-stamp PNG, from the command line or as a library.

Readme

Stampy

Turn any photo into a perforated postage-stamp PNG, from the command line.

Stampy is a TypeScript port of the original Python stampy tool. It takes an arbitrary input image, fits it into a stamp-shaped canvas, applies an optional photo effect, draws a themed border with a caption and denomination, and punches perforation holes around the edge so the result looks like a real postage stamp.

Install

npm install -g @ishk9/stampy
# or, run without installing:
npx @ishk9/stampy

Quick start

stampy                    # interactive wizard
stampy create photo.jpg   # one-shot

If -o is omitted the output is written next to the source as <input>-stamp.png.

Usage

stampy [--verbose]
       create INPUT [-o OUTPUT] [-c "CAPTION"] [-d "$1"]
              [-t THEME] [-e EFFECT]
              [-W INNER_W] [-H INNER_H] [-b BORDER]
              [--no-perforations] [--perf-radius N] [--perf-spacing N]
       themes
       effects
       wizard

Examples

stampy
stampy create cat.jpg
stampy create cat.jpg -o cat-stamp.png -c "POSTES * PARIS" -d "$1"
stampy create cat.jpg -t midnight -e vintage --perf-spacing 38
stampy create cat.jpg --no-perforations -W 1200 -H 1200

Themes

| Key | Label | Border | Background | | ---------- | -------------- | --------------- | --------------- | | classic | Classic Red | rgb(180, 30, 50) | rgb(255, 252, 240) | | midnight | Midnight Blue | rgb(25, 55, 120) | rgb(245, 248, 255) | | forest | Forest Green | rgb(35, 95, 60) | rgb(250, 252, 245) | | ink | Ink Black | rgb(30, 30, 30) | rgb(252, 250, 245) | | burgundy | Burgundy Gold | rgb(110, 25, 50) | rgb(252, 244, 220) |

Run stampy themes to print this table from the live registry.

Effects

| Key | Description | | ------------ | ------------------------------------------------------- | | none | Pass-through; the photo's tones are preserved as-is. | | sepia | Warm, brown-toned colorisation. | | grayscale | Luminance-only conversion. | | vintage | Faded, low-contrast colorisation with a warm cast. |

Run stampy effects to print this table.

Architecture

src/
├── cli.ts                          # CLI entrypoint (commander)
├── cli/commands/                   # `create`, `themes`, `effects`, `wizard`
├── application/
│   ├── makeStamp.ts                # MakeStampUseCase
│   └── pipelineBuilder.ts          # StampPipelineBuilder
├── domain/
│   ├── models.ts                   # Effect, StampSpec, makeStampSpec
│   ├── themes.ts                   # ThemeRegistry, defaultThemeRegistry
│   └── errors.ts                   # StampyError hierarchy
├── effects/
│   ├── base.ts                     # EffectStrategy interface
│   ├── strategies.ts               # None / Sepia / Grayscale / Vintage
│   └── registry.ts                 # EffectRegistry, defaultEffectRegistry
├── pipeline/
│   ├── base.ts                     # StampImage, StampProcessor, Pipeline
│   ├── orientation.ts              # EXIF normalisation hook
│   ├── effect.ts                   # Effect step (delegates to strategy)
│   ├── fit.ts                      # Cover-fit into innerSize
│   ├── frame.ts                    # Coloured border + concentric strokes
│   ├── caption.ts                  # Top caption text
│   ├── denomination.ts             # Bottom-corner price chip
│   └── perforation.ts              # Punch transparent holes
├── rendering/
│   ├── fonts.ts                    # FontProvider, SystemFontProvider
│   └── canvas.ts                   # @napi-rs/canvas helpers
└── infrastructure/
    └── imageRepository.ts          # SharpImageRepository (load / save)

Patterns at a glance

  • StrategyEffectStrategy lets each photo effect (NoneEffect, SepiaEffect, GrayscaleEffect, VintageEffect) implement one tonal transform with a single-method interface.
  • RegistryEffectRegistry and ThemeRegistry are the only places that know about all available strategies and themes; consumers ask by key.
  • Pipes-and-Filters — the rendering pipeline is a sequence of StampProcessor steps that pass StampImage from one to the next.
  • CompositePipeline implements StampProcessor, so a whole pipeline is interchangeable with a single step.
  • BuilderStampPipelineBuilder hides the construction order and dependencies of the default pipeline.
  • RepositoryImageRepository abstracts I/O; only SharpImageRepository knows about sharp.
  • Use CaseMakeStampUseCase is the single application entrypoint and depends only on abstractions.
  • Command — each CLI subcommand is its own module under cli/commands/.
  • Dependency Injection — the CLI wires the registries, font provider, repository, and pipeline at the composition root and passes them down through constructors.

SOLID

| Principle | Where it shows up | | --------- | --------------------------------------------------------------------------------- | | SRP | Each pipeline step does one thing (FitProcessor resizes, FrameProcessor draws the border, etc.). | | OCP | Adding a new effect is a registry entry plus a strategy class — no other code changes. | | LSP | All StampProcessor implementations are interchangeable; Pipeline is one. | | ISP | Narrow interfaces (EffectStrategy.apply, FontProvider.fontSpec, ImageRepository.load/save). | | DIP | MakeStampUseCase depends on ImageRepository and StampProcessor interfaces, not on sharp or @napi-rs/canvas. |

Library use

import {
  defaultEffectRegistry,
  defaultThemeRegistry,
  Effect,
  makeStampSpec,
  MakeStampUseCase,
  SharpImageRepository,
  StampPipelineBuilder,
  SystemFontProvider,
} from "@ishk9/stampy";

const themes = defaultThemeRegistry();
const effects = defaultEffectRegistry();
const fonts = new SystemFontProvider();

const repository = new SharpImageRepository();
const pipeline = new StampPipelineBuilder(effects, fonts).build();
const useCase = new MakeStampUseCase(repository, pipeline);

const spec = makeStampSpec({
  theme: themes.get("classic"),
  caption: "POSTES",
  denomination: "$1",
  effect: Effect.SEPIA,
});

const result = await useCase.execute("cat.jpg", "cat-stamp.png", spec);
console.log(`wrote ${result.outputPath} (${result.width} x ${result.height})`);

A runnable version of this snippet lives at examples/library-use.ts.

Development

npm install
npm run dev -- create cat.jpg     # via tsx
npm run build && npm start -- create cat.jpg
npm test

The test suite is vitest plus tsx-style ESM resolution, so local imports keep their .js extensions even though the sources are .ts.