Stamps

A seal pressed into the paper. Vermilion paste catches the crests of the grain first and fills the valleys as the hand presses harder; one side prints darker where the hand leaned; old rubber leaves nicks; a stamp that rocked leaves a faint second strike beside the first.

import { PAPERS, stampShape, WetGpu } from "shodo/paint";

const sheet = new WetGpu(canvas, 600, 400, { paper: PAPERS.coldPress });

// Draw the seal in dark ink on any canvas, then take its pixels.
const art = new OffscreenCanvas(160, 160);
const g = art.getContext("2d");
g.lineWidth = 10;
g.strokeRect(10, 10, 140, 140);
g.font = "900 60px serif";
g.fillText("書道", 20, 105);
const seal = stampShape(160, 160, g.getImageData(0, 0, 160, 160).data);

sheet.stamp(seal, 220, 120, { pigment: { color: [0.92, 0.25, 0.16], granulation: 0.1 }, feel: { ink: 0.8, wear: 0.3 } });
sheet.show();

The ink settles into the paper where it lands, as seal paste does: it does not run, and washes painted over it later glaze on top. A clean wet brush lifts some of it, as it lifts any dried paint, how much set by the sheet’s staining.

Three prints of one seal. Click to stamp; let go of a dial below to print again with that feel.open in the playground

Feel

Five dials, each from 0 to 1. A dial you don’t set stays where it rests: ink 0.7, pressure 0.6, wear 0, slip 0, squeeze 0.3, which is a fresh seal pressed firmly.

  • ink
    starvedloaded

    How much the pad gave: a starved print breaks up on the paper’s grain, a loaded one fills it.

  • pressure
    kissheavy

    How hard the hand pressed, leaning so one side prints darker.

  • wear
    freshworn

    How old the rubber is: nicks and soft patches that print pale.

2 more dials
  • slip
    stillrocked

    How much the stamp rocked as it lifted, leaving a faint second strike.

  • squeeze
    cleanrimmed

    How much ink was pushed to the edges of the relief, drawing a darker line round it.

The same words live in STAMP_FEEL. seed picks the stamp’s wear and which way the hand leans; the same seed prints the same way, so pass a new one for each press to make a sheet of seals that are not copies of each other.

Things worth knowing

From a picture

Any picture can be a stamp. A rubber stamp is one raised layer, inked or not, so a photo is carved first, in one of three styles:

StyleWhat you see
carveA bold silhouette: the dark parts raised, cut where the picture’s own light and dark split.
dotsA halftone screen: dots that grow with the dark, so the picture’s tones survive.
linesThe picture’s strongest edges as lines, like a woodcut drawn from it.
import { stampFromImage } from "shodo/paint";

const bitmap = await createImageBitmap(file);
const g = new OffscreenCanvas(200, 150).getContext("2d");
g.drawImage(bitmap, 0, 0, 200, 150);
const shape = stampFromImage(200, 150, g.getImageData(0, 0, 200, 150).data, { style: "dots", detail: 0.7 });
sheet.stamp(shape, 100, 80, { pigment: vermilion });

detail runs from bold (0: soft, wide marks, big dots) to fine (1), resting at 0.5. negative: true swaps raised and cut, leaving the picture as paper in an inked block. Transparent pixels are paper. It all runs where the page runs: the picture never leaves the device. In the wet lab, choose a picture (or drop one anywhere), pick a style and press.

Type as material

Text can be a stamp too, so type sits in the paper and paint goes over it: a wash runs across the letters, clear water lifts them pale, a second coat covers them. Once pressed it is pigment in the sheet, not a layer on top.

import { stampFromText } from "shodo/paint";

await document.fonts.load("900 40px 'Noto Serif JP'");
const shape = stampFromText("the 8:14 to
Kyoto, platform 3", { family: "'Noto Serif JP', serif", size: 40, weight: 900, width: 360 });
sheet.stamp(shape, 140, 200, { pigment: ink });
OptionWhat it doesRests at
familyCSS font family list. Load the font first.serif
sizeType size in cells (sheet pixels).48
weightCSS font weight. Heavy type prints better.700
leadingLine height as a multiple of size.1.15
widthWrap width in cells. Without it, lines break only at newlines.none
alignleft, center or right.left
mirrorFlip the face, as on a real stamp.false

The stamp’s feel applies as with any stamp: starved ink breaks the letters up on the grain, wear nicks them. wrapText(text, measure, width) is the line breaker on its own. In the wet lab, set the seal to type, write in the type field (a / starts a new line) and press.

Underneath: the ink model

stampPrint(shape, x, y, width, height, tooth, feel, seed) returns the amount of pigment each cell gets, an amount of 1 showing the pigment’s own colour. WetSurface.stamp and WetGpu.stamp both settle exactly that amount, so the CPU and GPU sheets print the same seal. Use it directly to draw stamps somewhere other than a wet sheet.