first commit
This commit is contained in:
commit
63c0901393
10 changed files with 966 additions and 0 deletions
215
lib/image2json.js
Normal file
215
lib/image2json.js
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Sol Toder
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
Copy available at https://github.com/AjaxGb/mc-text-image/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
import * as fs from "fs";
|
||||
|
||||
import { createCanvas, loadImage } from "@napi-rs/canvas";
|
||||
|
||||
const BLOCK_CHAR = "\u2587";
|
||||
const SPACE_CHAR = "\u2007";
|
||||
const TRAILING_SPACE = new RegExp(SPACE_CHAR + "+$");
|
||||
const FONT_RATIO = 1.8;
|
||||
|
||||
function parseSize(text) {
|
||||
const parsed = parseInt(text);
|
||||
return parsed > 0 ? parsed : 0;
|
||||
}
|
||||
|
||||
function calcSize(origW, origH, width, height, keepRatio, pixelShape) {
|
||||
let w = width || origW;
|
||||
let h = height || origH;
|
||||
|
||||
if (keepRatio) {
|
||||
const originRatio = origW / origH;
|
||||
const currRatio = w / h;
|
||||
|
||||
if (currRatio > originRatio || (origH && !origW)) {
|
||||
w = (h * origW) / origH;
|
||||
} else {
|
||||
h = (w * origH) / origW;
|
||||
}
|
||||
}
|
||||
|
||||
const pixelRatio = pixelShape === "font" ? FONT_RATIO : 1.0;
|
||||
|
||||
return {
|
||||
w: Math.round(w),
|
||||
h: Math.round(h / pixelRatio),
|
||||
unscaledH: Math.round(h),
|
||||
origW,
|
||||
origH,
|
||||
};
|
||||
}
|
||||
|
||||
function hexNibble(value) {
|
||||
return value.toString(16).padStart(2, "0");
|
||||
}
|
||||
|
||||
function makeHexColor(pixels, offset, cutoff) {
|
||||
const a = pixels[offset + 3];
|
||||
if (a < cutoff) {
|
||||
pixels[offset + 3] = 0;
|
||||
return null;
|
||||
} else {
|
||||
pixels[offset + 3] = 255;
|
||||
const r = pixels[offset + 0];
|
||||
const g = pixels[offset + 1];
|
||||
const b = pixels[offset + 2];
|
||||
return "#" + hexNibble(r) + hexNibble(g) + hexNibble(b);
|
||||
}
|
||||
}
|
||||
export async function imageToJson(imageBuffer, options = {}) {
|
||||
const {
|
||||
width = null,
|
||||
height = null,
|
||||
keepRatio = true,
|
||||
pixelShape = "square",
|
||||
smoothing = null,
|
||||
transparencyCutoff = 0,
|
||||
stripSpace = false,
|
||||
} = options;
|
||||
|
||||
if (!imageBuffer || !(imageBuffer instanceof Uint8Array)) {
|
||||
console.error("imageToJson: Invalid imageBuffer provided.");
|
||||
throw new Error(`Input must be a Buffer containing image data`);
|
||||
}
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await loadImage(imageBuffer);
|
||||
} catch (err) {
|
||||
console.error("imageToJson: Failed to load image from buffer.", err);
|
||||
throw err;
|
||||
}
|
||||
if (!image || !image.width || !image.height) {
|
||||
console.error(
|
||||
"imageToJson: Loaded image is invalid or missing dimensions.",
|
||||
);
|
||||
throw new Error("Loaded image is invalid or missing dimensions.");
|
||||
}
|
||||
const origW = image.width;
|
||||
const origH = image.height;
|
||||
|
||||
let sizeObj;
|
||||
try {
|
||||
sizeObj = calcSize(origW, origH, width, height, keepRatio, pixelShape);
|
||||
} catch (err) {
|
||||
console.error("imageToJson: Failed to calculate target size.", err);
|
||||
throw err;
|
||||
}
|
||||
const { w, h } = sizeObj;
|
||||
|
||||
let canvas, ctx;
|
||||
try {
|
||||
canvas = createCanvas(w, h);
|
||||
ctx = canvas.getContext("2d");
|
||||
} catch (err) {
|
||||
console.error("imageToJson: Failed to create canvas or context.", err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
try {
|
||||
if (smoothing) {
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
ctx.imageSmoothingQuality = smoothing;
|
||||
} else {
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("imageToJson: Failed to set image smoothing.", err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
try {
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
ctx.drawImage(image, 0, 0, w, h);
|
||||
} catch (err) {
|
||||
console.error("imageToJson: Failed to draw image on canvas.", err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
let cutoff, imageData, pixels;
|
||||
try {
|
||||
cutoff = parseInt(transparencyCutoff) || 0;
|
||||
imageData = ctx.getImageData(0, 0, w, h);
|
||||
pixels = imageData.data;
|
||||
} catch (err) {
|
||||
console.error("imageToJson: Failed to get image data from canvas.", err);
|
||||
throw err;
|
||||
}
|
||||
const json = [];
|
||||
let currColor = null;
|
||||
let currText = "";
|
||||
|
||||
const totalPixels = pixels.length / 4;
|
||||
|
||||
for (let x = 0, i = 0; i < pixels.length; x++, i += 4) {
|
||||
if (x >= w) {
|
||||
if (stripSpace) {
|
||||
currText = currText.replace(TRAILING_SPACE, "");
|
||||
}
|
||||
currText += "\n";
|
||||
x = 0;
|
||||
}
|
||||
|
||||
let newColor;
|
||||
try {
|
||||
newColor = makeHexColor(pixels, i, cutoff);
|
||||
} catch (err) {
|
||||
console.error(`imageToJson: Error processing pixel at index ${i}.`, err);
|
||||
throw err;
|
||||
}
|
||||
if (currColor && newColor && currColor != newColor) {
|
||||
json.push({ text: currText, color: currColor });
|
||||
currText = "";
|
||||
}
|
||||
|
||||
if (newColor) {
|
||||
currColor = newColor;
|
||||
currText += BLOCK_CHAR;
|
||||
} else {
|
||||
currText += SPACE_CHAR;
|
||||
}
|
||||
}
|
||||
|
||||
if (stripSpace) {
|
||||
currText = currText.replace(TRAILING_SPACE, "");
|
||||
}
|
||||
|
||||
if (currText && currColor) {
|
||||
json.push({ text: currText, color: currColor });
|
||||
}
|
||||
|
||||
return {
|
||||
json: json,
|
||||
dimensions: {
|
||||
width: w,
|
||||
height: h,
|
||||
originalWidth: origW,
|
||||
originalHeight: origH,
|
||||
},
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue