qrframe/presets/Halftone.js

173 wiersze
4.2 KiB
JavaScript
Czysty Zwykły widok Historia

import { Module } from "https://qrframe.kylezhe.ng/utils.js";
2024-09-13 07:52:47 +00:00
2024-07-31 23:56:43 +00:00
export const paramsSchema = {
Image: {
type: "file",
2024-07-31 23:56:43 +00:00
},
2024-09-26 07:51:20 +00:00
"Image scale": {
type: "number",
min: 0,
max: 1,
step: 0.01,
default: 1,
},
2024-07-31 23:56:43 +00:00
Contrast: {
type: "number",
min: 0,
max: 10,
step: 0.1,
default: 1,
},
Brightness: {
type: "number",
min: 0,
max: 5,
step: 0.1,
default: 1.8,
},
"QR background": {
type: "boolean",
},
"Alignment pattern": {
type: "boolean",
default: true,
},
"Timing pattern": {
type: "boolean",
},
Margin: {
type: "number",
min: 0,
max: 10,
default: 2,
},
Foreground: {
type: "color",
2024-07-31 23:56:43 +00:00
default: "#000000",
},
Background: {
type: "color",
2024-07-31 23:56:43 +00:00
default: "#ffffff",
},
2024-08-17 03:44:53 +00:00
};
2024-07-31 23:56:43 +00:00
2024-08-22 03:50:21 +00:00
export async function renderCanvas(qr, params, canvas) {
2024-08-13 06:44:46 +00:00
const unit = 3;
const pixel = 1;
2024-07-31 23:56:43 +00:00
const matrixWidth = qr.version * 4 + 17;
const margin = params["Margin"];
const fg = params["Foreground"];
const bg = params["Background"];
const alignment = params["Alignment pattern"];
const timing = params["Timing pattern"];
2024-08-22 03:50:21 +00:00
let file = params["Image"];
if (file == null) {
file = await fetch(
"https://upload.wikimedia.org/wikipedia/commons/1/14/The_Widow_%28Boston_Public_Library%29_%28cropped%29.jpg"
).then((res) => res.blob());
}
const image = await createImageBitmap(file);
2024-07-31 23:56:43 +00:00
const pixelWidth = matrixWidth + 2 * margin;
2024-08-13 06:44:46 +00:00
const canvasSize = pixelWidth * unit;
2024-08-22 03:50:21 +00:00
const ctx = canvas.getContext("2d");
2024-07-31 23:56:43 +00:00
ctx.canvas.width = canvasSize;
ctx.canvas.height = canvasSize;
2024-09-26 07:51:20 +00:00
ctx.fillStyle = bg;
ctx.fillRect(0, 0, canvasSize, canvasSize);
2024-07-31 23:56:43 +00:00
if (params["QR background"]) {
ctx.fillStyle = fg;
for (let y = 0; y < matrixWidth; y++) {
for (let x = 0; x < matrixWidth; x++) {
const module = qr.matrix[y * matrixWidth + x];
2024-09-13 07:52:47 +00:00
if (module & Module.ON) {
const px = x + margin;
const py = y + margin;
2024-08-17 03:44:53 +00:00
ctx.fillRect(px * unit, py * unit, unit, unit);
2024-07-31 23:56:43 +00:00
}
}
}
}
ctx.filter = `brightness(${params["Brightness"]}) contrast(${params["Contrast"]})`;
const imgScale = params["Image scale"];
2024-09-26 07:51:20 +00:00
const imgSize = Math.floor(imgScale * canvasSize);
const imgOffset = Math.floor((canvasSize - imgSize) / 2);
ctx.drawImage(image, imgOffset, imgOffset, imgSize, imgSize);
2024-07-31 23:56:43 +00:00
ctx.filter = "none";
const imageData = ctx.getImageData(0, 0, canvasSize, canvasSize);
const data = imageData.data;
2024-09-26 07:51:20 +00:00
for (let y = imgOffset; y < imgOffset + imgSize; y++) {
for (let x = imgOffset; x < imgOffset + imgSize; x++) {
2024-07-31 23:56:43 +00:00
const i = (y * canvasSize + x) * 4;
if (data[i + 3] === 0) continue;
// Convert to grayscale and normalize to 0-255
const oldPixel =
(data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114) | 0;
let newPixel;
if (oldPixel < 128) {
newPixel = 0;
ctx.fillStyle = fg;
} else {
newPixel = 255;
ctx.fillStyle = bg;
}
2024-08-13 06:44:46 +00:00
ctx.fillRect(x * pixel, y * pixel, pixel, pixel);
2024-07-31 23:56:43 +00:00
data[i] = data[i + 1] = data[i + 2] = newPixel;
const error = oldPixel - newPixel;
// Distribute error to neighboring pixels
if (x < canvasSize - 1) {
data[i + 4] += (error * 7) / 16;
}
if (y < canvasSize - 1) {
if (x > 0) {
data[i + canvasSize * 4 - 4] += (error * 3) / 16;
}
data[i + canvasSize * 4] += (error * 5) / 16;
if (x < canvasSize - 1) {
data[i + canvasSize * 4 + 4] += (error * 1) / 16;
}
}
}
}
2024-08-13 06:44:46 +00:00
const dataOffset = (unit - pixel) / 2;
2024-07-31 23:56:43 +00:00
for (let y = 0; y < matrixWidth; y++) {
for (let x = 0; x < matrixWidth; x++) {
const module = qr.matrix[y * matrixWidth + x];
2024-09-13 07:52:47 +00:00
if (module & Module.ON) {
2024-07-31 23:56:43 +00:00
ctx.fillStyle = fg;
} else {
ctx.fillStyle = bg;
}
const px = x + margin;
const py = y + margin;
2024-07-31 23:56:43 +00:00
if (
2024-09-13 07:52:47 +00:00
module & Module.FINDER ||
(alignment && module & Module.ALIGNMENT) ||
(timing && module & Module.TIMING)
2024-07-31 23:56:43 +00:00
) {
2024-08-13 06:44:46 +00:00
ctx.fillRect(px * unit, py * unit, unit, unit);
2024-07-31 23:56:43 +00:00
} else {
ctx.fillRect(
2024-08-13 06:44:46 +00:00
px * unit + dataOffset,
py * unit + dataOffset,
pixel,
pixel
2024-07-31 23:56:43 +00:00
);
}
}
}
}