Files
dashback/src/services/labelThumb.js
2026-09-28 06:50:00 +02:00

78 lines
2.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sharp from 'sharp';
import { renderLabel } from 'react-canvas-label-editor';
const THUMB_W = 150;
const THUMB_H = 80;
/**
* Renders a 150×80 thumbnail for a label template.
*
* Workflow:
* 1. Render the label at its native pixel resolution via renderLabel().
* 2. Resize to fit within 150×80 preserving aspect ratio (fit: 'contain').
* Letterbox areas remain transparent.
* 3. Process raw RGBA pixels:
* - Transparent letterbox padding (alpha < 128) → fully transparent (0,0,0,0).
* - Label pixels (alpha >= 128) → fully opaque (alpha=255) with 4-bit greyscale
* (16 levels: 0, 17, 34, ..., 255) for smooth anti-aliased text and barcodes.
* 4. Output as palette PNG with transparency support.
*
* The result is a "4-bit grey + 1-bit alpha" image:
* letterbox area is transparent, and the label area is anti-aliased 4-bit greyscale.
*
* @param {object} opts
* @param {number} opts.width - Label width in pixels.
* @param {number} opts.height - Label height in pixels.
* @param {Array} opts.objects - Label editor objects array.
* @returns {Promise<Buffer>} PNG buffer.
*/
export async function renderThumb({ width, height, objects }) {
// 1. Render at native resolution → RGBA PNG buffer
const nativePng = await renderLabel({ width, height, objects });
// 2. Resize to fit within 150×80, preserving aspect ratio.
// Transparent background for letterbox padding.
const { data, info } = await sharp(nativePng)
.ensureAlpha()
.resize(THUMB_W, THUMB_H, {
fit: 'contain',
background: { r: 0, g: 0, b: 0, alpha: 0 },
kernel: sharp.kernel.lanczos3,
})
.raw()
.toBuffer({ resolveWithObject: true });
const { width: w, height: h } = info;
// 3. Per-pixel: transparent letterbox remains transparent (alpha=0),
// label area is opaque (alpha=255) with 4-bit greyscale (16 levels).
const rgba = Buffer.alloc(w * h * 4);
for (let i = 0; i < w * h; i++) {
const base = i * 4;
const a = data[base + 3];
if (a < 128) {
// Transparent letterbox area — leave as (0,0,0,0)
} else {
const factor = a === 255 ? 1 : 255 / a;
const r = Math.min(255, Math.round(data[base] * factor));
const g = Math.min(255, Math.round(data[base + 1] * factor));
const b = Math.min(255, Math.round(data[base + 2] * factor));
const grey = 0.299 * r + 0.587 * g + 0.114 * b;
// Quantize to 4-bit grey (16 levels: 0, 17, 34, ..., 255)
const qGrey = Math.round(grey / 17) * 17;
rgba[base] = qGrey;
rgba[base + 1] = qGrey;
rgba[base + 2] = qGrey;
rgba[base + 3] = 255;
}
}
// 4. Encode as palette PNG with transparency support (16 greys + 1 transparent entry).
const thumb = await sharp(rgba, { raw: { width: w, height: h, channels: 4 } })
.png({ palette: true, colours: 17, compressionLevel: 9 })
.toBuffer();
return thumb;
}