ico-utils
Version:
A javascript library to create ICO file from PNG images and extract PNG images from ICO file.
53 lines (52 loc) • 2.46 kB
JavaScript
import { parsePng } from './png-utils.js';
import { getBlob, getArrayBuffer } from './helpers/binaryConverter.js';
import { splitNumberToBytes } from './helpers/bytesConverter.js';
const MAX_FILES = 65536;
const MAX_IMAGE_DIMENSION = 256;
const FILE_HEADER_SIZE = 6;
const IMAGE_HEADER_SIZE = 16;
export async function encodeIco(inputs, limitImageDimension = true, mime = 'image/x-icon') {
if (inputs.length > MAX_FILES) {
throw new Error('TOO_MANY_FILES');
}
const icoFileHeader = [0, 0, 1, 0, ...splitNumberToBytes(inputs.length)];
const initialImagePosition = FILE_HEADER_SIZE + IMAGE_HEADER_SIZE * inputs.length;
const { imagesHeader, imagesData } = await inputs.reduce(async (dataPromise, input) => {
const data = await dataPromise;
const imagePosition = data.imagesData.length + initialImagePosition;
const { imageHeader, imageData } = await processInput(input, imagePosition, limitImageDimension);
return {
imagesHeader: [...data.imagesHeader, ...imageHeader],
imagesData: [...data.imagesData, ...imageData],
};
}, Promise.resolve({ imagesHeader: [], imagesData: [] }));
const ico = new Uint8Array([...icoFileHeader, ...imagesHeader, ...imagesData]);
return getBlob(ico, mime);
}
async function processInput(image, imagePosition, limitImageDimension) {
const buffer = await getArrayBuffer(image);
const { width, height, bpp } = parsePng(buffer);
if (limitImageDimension && (width > MAX_IMAGE_DIMENSION || height > MAX_IMAGE_DIMENSION)) {
throw new Error('INVALID_SIZE');
}
const imageHeader = getImageHeader(width, height, buffer.byteLength, imagePosition, bpp);
const imageData = new Uint8Array(buffer);
return { imageHeader, imageData };
}
function getImageHeader(width, height, size, imagePosition, bpp) {
const widthBytes = width >= MAX_IMAGE_DIMENSION ? 0 : width; // 0 for 256px
const heightBytes = height >= MAX_IMAGE_DIMENSION ? 0 : height; // 0 for 256px
const numOfColoursInPalette = 0; // 0 means 2^n colors
const reservedByte = 0; // must set to 0
const colourPlaneBytes = [1, 0]; // must set to 1
return [
widthBytes,
heightBytes,
numOfColoursInPalette,
reservedByte,
...colourPlaneBytes,
...(bpp ? splitNumberToBytes(bpp) : [0, 0]),
...splitNumberToBytes(size, 4),
...splitNumberToBytes(imagePosition, 4),
];
}