Find the right gamma
gamma is the per-printer darkness-calibration knob. Different thermal heads, different tape stocks, different ambient temperatures — they all shift how dark a given bitmap actually prints. gamma lets you compensate without re-rendering source artwork.
This recipe is the empirical procedure for picking a value.
What gamma does
gamma is a pow-curve applied to luminance after autoLevels and before threshold/dither:
gamma < 1brightens midtones — use when the printer comes out too dark.gamma > 1darkens midtones — use when the printer comes out too light.gamma === 1is a no-op (the default).
Pure black input pixels stay black at every gamma; pure white stays white. Only the midtones shift.
Procedure
Generate a gamma sweep test card. Print the same source image at five gamma values —
0.7,0.85,1.0,1.15,1.3— using a representative dither method ('floyd-steinberg'is fine).tsimport { padBitmap, renderImage, stackBitmaps } from '@mbtech-nl/bitmap'; const sweep = [0.7, 0.85, 1.0, 1.15, 1.3].map((g) => padBitmap( renderImage(testRgba, { dither: 'floyd-steinberg', gamma: g }), { bottom: 8 }, ), ); const card = stackBitmaps(sweep, 'vertical'); await printer.send(card);Print on the actual stock. Don't calibrate against your screen, against a different printer, or against a different tape — gamma is per-printer-per-tape.
Pick by eye. Look for the panel where:
- Solid blacks are dense but not bleeding.
- Light greys (skies, faces, gradient highlights) are visible but not muddy.
- You can still read fine text and thin lines.
Bake the chosen value into your driver's defaults. Don't make end-users tune this — they shouldn't have to.
tsconst PRINTER_GAMMA = 0.85; renderImage(rgba, { dither: 'floyd-steinberg', gamma: PRINTER_GAMMA });
Per-tape adjustments
Some printers (Brother QL with DK-22251, two-colour DYMO) print red/blue foils with a different effective darkness from black. For those, calibrate gamma per plane via renderMultiPlaneImage's planes[name].gamma override:
renderMultiPlaneImage(image, {
palette: [
{ name: 'black', rgb: [0, 0, 0] },
{ name: 'red', rgb: [204, 0, 0] },
],
defaults: { dither: 'floyd-steinberg' },
planes: {
red: { gamma: 1.15 }, // red foil burns lighter on this printer
},
});When to use autoLevels instead
If you're calibrating to compensate for low-contrast input (faded scans, washed-out photos), you want autoLevels, not gamma. autoLevels rescales the input range; gamma reshapes midtones. They compose — and autoLevels runs first in the pipeline, so a typical "auto-enhance" config sets both:
renderImage(rgba, {
dither: 'floyd-steinberg',
autoLevels: true,
gamma: PRINTER_GAMMA,
});