u
This commit is contained in:
@@ -1,23 +1,28 @@
|
||||
import sharp from 'sharp';
|
||||
import { renderLabel } from 'react-canvas-label-editor';
|
||||
|
||||
const THUMB_SIZE = 50;
|
||||
const THUMB_W = 150;
|
||||
const THUMB_H = 80;
|
||||
|
||||
/**
|
||||
* Renders a 50×50 1-bit indexed PNG thumbnail for a label template.
|
||||
* Renders a 150×80 thumbnail for a label template.
|
||||
*
|
||||
* Workflow:
|
||||
* 1. Render the label at its native pixel resolution via renderLabel().
|
||||
* 2. Resize to 50×50 using Lanczos3 (covers).
|
||||
* 3. Convert to 1-bit PNG:
|
||||
* - Greyscale → threshold at 128 → 1-bit PNG.
|
||||
* - Pixels outside the label area (transparent in the RGBA source) are
|
||||
* rendered WHITE (255) so they read as 0 in the final 1-bit output,
|
||||
* keeping the file as small as possible.
|
||||
* 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 {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.
|
||||
*/
|
||||
@@ -25,19 +30,47 @@ 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 50×50, convert to greyscale, flatten transparency to white,
|
||||
// then threshold to 1-bit and output as PNG.
|
||||
const thumb = await sharp(nativePng)
|
||||
.resize(THUMB_SIZE, THUMB_SIZE, {
|
||||
// 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: 255, g: 255, b: 255, alpha: 1 },
|
||||
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
||||
kernel: sharp.kernel.lanczos3,
|
||||
})
|
||||
.greyscale()
|
||||
// Threshold: pixels ≥ 128 → white (0 in 1-bit), < 128 → black (1 in 1-bit)
|
||||
.threshold(128)
|
||||
// PNG with 1-bit colour depth; compression level 9 for smallest file
|
||||
.png({ colours: 2, compressionLevel: 9, adaptiveFiltering: false })
|
||||
.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;
|
||||
|
||||
Reference in New Issue
Block a user