q13x-eaglerproxy/server/proxy/skins/ImageEditor.js

222 lines
11 KiB
JavaScript
Raw Normal View History

2024-09-04 05:02:00 -07:00
import { Logger } from "../../logger.js";
import { Constants } from "../Constants.js";
import fs from "fs/promises";
let Jimp = null;
let sharp = null;
export var ImageEditor;
(function (ImageEditor) {
let loadedLibraries = false;
async function loadLibraries(native) {
if (loadedLibraries)
return;
if (native)
sharp = (await import("sharp")).default;
else {
try {
Jimp = (await import("jimp")).default;
}
catch (err) {
const logger = new Logger("ImageEditor.js");
logger.fatal("**** ERROR: UNABLE TO LOAD JIMP!");
logger.fatal("Please ensure that Jimp is installed by running 'npm install jimp' in the terminal.");
logger.fatal("If you'd like to use the faster native image editor, please set the 'useNatives' option in the config to true.");
logger.fatal(`Error: ${err.stack}`);
process.exit(1);
}
Jimp.appendConstructorOption("Custom Bitmap Constructor", (args) => args[0] && args[0].width != null && args[0].height != null && args[0].data != null, (res, rej, args) => {
this.bitmap = args[0];
res();
});
}
loadedLibraries = true;
}
ImageEditor.loadLibraries = loadLibraries;
async function copyRawPixelsJS(imageIn, imageOut, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2) {
console.log(imageOut);
if (dx1 > dx2) {
return _copyRawPixelsJS(imageIn, imageOut, sx1, sy1, dx2, dy1, sx2 - sx1, sy2 - sy1, imageIn.getWidth(), imageOut.getWidth(), true);
}
else {
return _copyRawPixelsJS(imageIn, imageOut, sx1, sy1, dx2, dy1, sx2 - sx1, sy2 - sy1, imageIn.getWidth(), imageOut.getWidth(), false);
}
}
ImageEditor.copyRawPixelsJS = copyRawPixelsJS;
// async function _copyRawPixelsJS(imageIn: Jimp, imageOut: Jimp, srcX: number, srcY: number, dstX: number, dstY: number, width: number, height: number, imgSrcWidth: number, imgDstWidth: number, flip: boolean): Promise<Jimp> {
// const inData = imageIn.bitmap.data,
// outData = imageOut.bitmap.data;
// for (let y = 0; y < height; y++) {
// for (let x = 0; x < width; x++) {
// let srcIndex = (srcY + y) * imgSrcWidth + srcX + x;
// let dstIndex = (dstY + y) * imgDstWidth + dstX + x;
// if (flip) {
// srcIndex = (srcY + y) * imgSrcWidth + srcX + (width - x - 1);
// }
// for (let c = 0; c < 4; c++) {
// // Assuming RGBA channels
// outData[dstIndex * 4 + c] = inData[srcIndex * 4 + c];
// }
// }
// }
// return imageOut;
// // return sharp(outData, {
// // raw: {
// // width: outMeta.width!,
// // height: outMeta.height!,
// // channels: 4,
// // },
// // });
// }
async function _copyRawPixelsJS(imageIn, imageOut, srcX, srcY, dstX, dstY, width, height, imgSrcWidth, imgDstWidth, flip) {
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let srcIndex = (srcY + y) * imgSrcWidth + srcX + x;
if (flip) {
srcIndex = (srcY + y) * imgSrcWidth + srcX + (width - x - 1);
}
const pixelColor = imageIn.getPixelColor(srcX + x, srcY + y);
const rgba = Jimp.intToRGBA(pixelColor);
imageOut.setPixelColor(Jimp.rgbaToInt(rgba.r, rgba.g, rgba.b, rgba.a), dstX + x, dstY + y);
}
}
return imageOut;
}
async function copyRawPixels(imageIn, imageOut, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2) {
const inMeta = await imageIn.metadata(), outMeta = await imageOut.metadata();
if (dx1 > dx2) {
return _copyRawPixels(imageIn, imageOut, sx1, sy1, dx2, dy1, sx2 - sx1, sy2 - sy1, inMeta.width, outMeta.width, true);
}
else {
return _copyRawPixels(imageIn, imageOut, sx1, sy1, dx1, dy1, sx2 - sx1, sy2 - sy1, inMeta.width, outMeta.width, false);
}
}
ImageEditor.copyRawPixels = copyRawPixels;
async function _copyRawPixels(imageIn, imageOut, srcX, srcY, dstX, dstY, width, height, imgSrcWidth, imgDstWidth, flip) {
const inData = await imageIn.raw().toBuffer();
const outData = await imageOut.raw().toBuffer();
const outMeta = await imageOut.metadata();
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let srcIndex = (srcY + y) * imgSrcWidth + srcX + x;
let dstIndex = (dstY + y) * imgDstWidth + dstX + x;
if (flip) {
srcIndex = (srcY + y) * imgSrcWidth + srcX + (width - x - 1);
}
for (let c = 0; c < 4; c++) {
// Assuming RGBA channels
outData[dstIndex * 4 + c] = inData[srcIndex * 4 + c];
}
}
}
return sharp(outData, {
raw: {
width: outMeta.width,
height: outMeta.height,
channels: 4,
},
});
}
async function toEaglerSkinJS(image) {
let jimpImage = await Jimp.read(image), height = jimpImage.getHeight();
if (height != 64) {
// assume 32 height skin
let imageOut = await Jimp.create(64, 64, 0x0);
for (let x = 0; x < jimpImage.getWidth(); x++) {
for (let y = 0; y < jimpImage.getHeight(); y++) {
imageOut.setPixelColor(jimpImage.getPixelColor(x, y), x, y);
}
}
imageOut = await copyRawPixelsJS(jimpImage, imageOut, 24, 48, 20, 52, 4, 16, 8, 20);
imageOut = await copyRawPixelsJS(jimpImage, imageOut, 28, 48, 24, 52, 8, 16, 12, 20);
imageOut = await copyRawPixelsJS(jimpImage, imageOut, 20, 52, 16, 64, 8, 20, 12, 32);
imageOut = await copyRawPixelsJS(jimpImage, imageOut, 24, 52, 20, 64, 4, 20, 8, 32);
imageOut = await copyRawPixelsJS(jimpImage, imageOut, 28, 52, 24, 64, 0, 20, 4, 32);
imageOut = await copyRawPixelsJS(jimpImage, imageOut, 32, 52, 28, 64, 12, 20, 16, 32);
imageOut = await copyRawPixelsJS(jimpImage, imageOut, 40, 48, 36, 52, 44, 16, 48, 20);
imageOut = await copyRawPixelsJS(jimpImage, imageOut, 44, 48, 40, 52, 48, 16, 52, 20);
imageOut = await copyRawPixelsJS(jimpImage, imageOut, 36, 52, 32, 64, 48, 20, 52, 32);
imageOut = await copyRawPixelsJS(jimpImage, imageOut, 40, 52, 36, 64, 44, 20, 48, 32);
imageOut = await copyRawPixelsJS(jimpImage, imageOut, 44, 52, 40, 64, 40, 20, 44, 32);
imageOut = await copyRawPixelsJS(jimpImage, imageOut, 48, 52, 44, 64, 52, 20, 56, 32);
jimpImage = imageOut;
}
const newBuff = Buffer.alloc(Constants.EAGLERCRAFT_SKIN_CUSTOM_LENGTH);
const bitmap = jimpImage.bitmap.data;
for (let i = 1; i < 64 ** 2; i++) {
const bytePos = i * 4;
// red, green, blue, alpha => alpha, blue, green, red
newBuff[bytePos] = bitmap[bytePos + 3];
newBuff[bytePos + 1] = bitmap[bytePos + 2];
newBuff[bytePos + 2] = bitmap[bytePos + 1];
newBuff[bytePos + 3] = bitmap[bytePos];
}
return newBuff;
}
ImageEditor.toEaglerSkinJS = toEaglerSkinJS;
async function toEaglerSkin(image) {
const meta = await sharp(image).metadata();
let sharpImage = sharp(image);
if (meta.height != 64) {
// assume 32 height skin
let imageOut = sharp(await sharpImage.extend({ bottom: 32, background: { r: 0, g: 0, b: 0, alpha: 0 } }).toBuffer());
imageOut = await copyRawPixels(sharpImage, imageOut, 24, 48, 20, 52, 4, 16, 8, 20);
imageOut = await copyRawPixels(sharpImage, imageOut, 28, 48, 24, 52, 8, 16, 12, 20);
imageOut = await copyRawPixels(sharpImage, imageOut, 20, 52, 16, 64, 8, 20, 12, 32);
imageOut = await copyRawPixels(sharpImage, imageOut, 24, 52, 20, 64, 4, 20, 8, 32);
imageOut = await copyRawPixels(sharpImage, imageOut, 28, 52, 24, 64, 0, 20, 4, 32);
imageOut = await copyRawPixels(sharpImage, imageOut, 32, 52, 28, 64, 12, 20, 16, 32);
imageOut = await copyRawPixels(sharpImage, imageOut, 40, 48, 36, 52, 44, 16, 48, 20);
imageOut = await copyRawPixels(sharpImage, imageOut, 44, 48, 40, 52, 48, 16, 52, 20);
imageOut = await copyRawPixels(sharpImage, imageOut, 36, 52, 32, 64, 48, 20, 52, 32);
imageOut = await copyRawPixels(sharpImage, imageOut, 40, 52, 36, 64, 44, 20, 48, 32);
imageOut = await copyRawPixels(sharpImage, imageOut, 44, 52, 40, 64, 40, 20, 44, 32);
imageOut = await copyRawPixels(sharpImage, imageOut, 48, 52, 44, 64, 52, 20, 56, 32);
sharpImage = imageOut;
}
const r = await sharpImage.extractChannel("red").raw({ depth: "uchar" }).toBuffer();
const g = await sharpImage.extractChannel("green").raw({ depth: "uchar" }).toBuffer();
const b = await sharpImage.extractChannel("blue").raw({ depth: "uchar" }).toBuffer();
const a = await sharpImage.ensureAlpha().extractChannel(3).toColorspace("b-w").raw({ depth: "uchar" }).toBuffer();
const newBuff = Buffer.alloc(Constants.EAGLERCRAFT_SKIN_CUSTOM_LENGTH);
for (let i = 1; i < 64 ** 2; i++) {
const bytePos = i * 4;
newBuff[bytePos] = a[i];
newBuff[bytePos + 1] = b[i];
newBuff[bytePos + 2] = g[i];
newBuff[bytePos + 3] = r[i];
}
return newBuff;
}
ImageEditor.toEaglerSkin = toEaglerSkin;
function generateEaglerMOTDImage(file) {
return new Promise((res, rej) => {
sharp(file)
.resize(Constants.ICON_SQRT, Constants.ICON_SQRT, {
kernel: "nearest",
})
.raw({
depth: "uchar",
})
.toBuffer()
.then((buff) => {
for (const pixel of buff) {
if ((pixel & 0xffffff) == 0) {
buff[buff.indexOf(pixel)] = 0;
}
}
res(buff);
})
.catch(rej);
});
}
ImageEditor.generateEaglerMOTDImage = generateEaglerMOTDImage;
function generateEaglerMOTDImageJS(file) {
return new Promise(async (res, rej) => {
Jimp.read(typeof file == "string" ? await fs.readFile(file) : file, async (err, image) => {
image = image.resize(Constants.ICON_SQRT, Constants.ICON_SQRT, Jimp.RESIZE_NEAREST_NEIGHBOR);
res(image.bitmap.data);
});
});
}
ImageEditor.generateEaglerMOTDImageJS = generateEaglerMOTDImageJS;
})(ImageEditor || (ImageEditor = {}));