Files
aza/APP/nexus-remote/node_modules/png-to-ico/index.js
2026-03-25 14:14:07 +01:00

190 lines
5.9 KiB
JavaScript

/* eslint-disable no-mixed-operators */
import { readPNG, resize } from "./lib/png.js";
// http://fileformats.wikia.com/wiki/Icon
// the correct sizes are 256x256, 48x48, 32x32, 16x16
const sizeList = [48, 32, 16];
const err = new Error("Please give me a square PNG image.");
err.code = "ESIZE";
export default async function(filepath, options = {}) {
if (Array.isArray(filepath)) {
const images = await Promise.all(
filepath.map(file => readPNG(file)));
return imagesToIco(images);
}
const png = await readPNG(filepath);
if (png.width !== png.height) {
throw err;
}
const image = png.width !== 256
? resize(png, 256, 256, options.interpolation)
: png
;
const resizedImages = sizeList.map(
targetSize => resize(image, targetSize, targetSize, options.interpolation)
);
const images = resizedImages.concat(image);
return imagesToIco(images);
}
export function imagesToIco(images) {
const header = getHeader(images.length);
const headerAndIconDir = [header];
const imageDataArr = [];
let len = header.length;
let offset = header.length + 16 * images.length;
images.forEach(img => {
const dir = getDir(img, offset);
const bmpInfoHeader = getBmpInfoHeader(img);
const dib = getDib(img);
len += dir.length + bmpInfoHeader.length + dib.length;
const newSize = bmpInfoHeader.length + dib.length;
offset += newSize;
dir.writeUInt32LE(newSize, 8);
headerAndIconDir.push(dir);
imageDataArr.push(bmpInfoHeader, dib);
});
return Buffer.concat(headerAndIconDir.concat(imageDataArr), len);
}
// https://en.wikipedia.org/wiki/ICO_(file_format)
function getHeader(numOfImages) {
const buf = Buffer.alloc(6);
buf.writeUInt16LE(0, 0); // Reserved. Must always be 0.
buf.writeUInt16LE(1, 2); // Specifies image type: 1 for icon (.ICO) image
buf.writeUInt16LE(numOfImages, 4); // Specifies number of images in the file.
return buf;
}
function getDir(img, offset) {
const buf = Buffer.alloc(16);
const bitmap = img //.bitmap;
const width = bitmap.width >= 256 ? 0 : bitmap.width;
const height = width;
const bpp = 32;
buf.writeUInt8(width, 0); // Specifies image width in pixels.
buf.writeUInt8(height, 1); // Specifies image height in pixels.
buf.writeUInt8(0, 2); // Should be 0 if the image does not use a color palette.
buf.writeUInt8(0, 3); // Reserved. Should be 0.
buf.writeUInt16LE(1, 4); // Specifies color planes. Should be 0 or 1.
buf.writeUInt16LE(bpp, 6); // Specifies bits per pixel.
buf.writeUInt32LE(0, 8); // Specifies the size of the image's data in bytes
buf.writeUInt32LE(offset, 12); // Specifies the offset of BMP or PNG data from the beginning of the ICO/CUR file
return buf;
}
// https://en.wikipedia.org/wiki/BMP_file_format
function getBmpInfoHeader(img) {
const buf = Buffer.alloc(40);
const bitmap = img; //.bitmap;
const width = bitmap.width;
// https://en.wikipedia.org/wiki/ICO_(file_format)
// ...Even if the AND mask is not supplied,
// if the image is in Windows BMP format,
// the BMP header must still specify a doubled height.
const height = width * 2;
const bpp = 32;
buf.writeUInt32LE(40, 0); // The size of this header (40 bytes)
buf.writeInt32LE(width, 4); // The bitmap width in pixels (signed integer)
buf.writeInt32LE(height, 8); // The bitmap height in pixels (signed integer)
buf.writeUInt16LE(1, 12); // The number of color planes (must be 1)
buf.writeUInt16LE(bpp, 14); // The number of bits per pixel
buf.writeUInt32LE(0, 16); // The compression method being used.
buf.writeUInt32LE(0, 20); // The image size.
buf.writeInt32LE(0, 24); // The horizontal resolution of the image. (signed integer)
buf.writeInt32LE(0, 28); // The vertical resolution of the image. (signed integer)
buf.writeUInt32LE(0, 32); // The number of colors in the color palette, or 0 to default to 2n
buf.writeUInt32LE(0, 36); // The number of important colors used, or 0 when every color is important; generally ignored.
return buf;
}
// https://en.wikipedia.org/wiki/BMP_file_format
// Note that the bitmap data starts with the lower left hand corner of the image.
// blue green red alpha in order
function getDib(img) {
const bitmap = img; //.bitmap;
const size = bitmap.data.length;
const width = bitmap.width;
const height = width;
const andMapRow = getRowStride(width);
const andMapSize = andMapRow * height;
const buf = Buffer.alloc(size + andMapSize);
// xor map
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const pxColor = getPixelColor(img, x, y);
const r = (pxColor >> 24) & 255;
const g = (pxColor >> 16) & 255;
const b = (pxColor >> 8) & 255;
const a = pxColor & 255;
const newColor = b | (g << 8) | (r << 16) | (a << 24);
const pos = ((height - y - 1) * width + x) * 4;
buf.writeInt32LE(newColor, pos);
}
}
// and map. It's padded out to 32 bits per line
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const pxColor = getPixelColor(img, x, y);
// TODO make threshhold configurable
const alpha = (pxColor & 255) > 0 ? 0 : 1;
const bitNum = (height - y - 1) * width + x;
// width per line in multiples of 32 bits
const width32 =
width % 32 === 0 ? Math.floor(width / 32) : Math.floor(width / 32) + 1;
const line = Math.floor(bitNum / width);
const offset = Math.floor(bitNum % width);
const bitVal = alpha & 0x00000001;
const pos = size + line * width32 * 4 + Math.floor(offset / 8);
const newVal = buf.readUInt8(pos) | (bitVal << (7 - (offset % 8)));
buf.writeUInt8(newVal, pos);
}
}
return buf;
}
function getRowStride(width) {
if (width % 32 === 0) {
return width / 8;
} else {
return 4 * (Math.floor(width / 32) + 1);
}
}
function getPixelColor(png, x, y) {
let xi = x < 0 ? 0: x;
let yi = y < 0 ? 0: y;
if (x >= png.width) xi = png.width - 1;
if (y >= png.height) yi = png.height - 1;
const i = (xi < 0 || xi >= png.width) || (yi < 0 || yi >= png.height)
? -1
: (png.width * yi + xi) << 2
;
return png.data.readUInt32BE(i);
}