image-js
Version:
Image processing and manipulation in JavaScript
119 lines • 3.69 kB
JavaScript
import { decode } from 'fast-png';
import { Image } from '../Image.js';
import { assert } from '../utils/validators/assert.js';
/**
* Decode a PNG. See the fast-png npm module.
* @param buffer - The data to decode.
* @returns The decoded image.
*/
export function decodePng(buffer) {
const png = decode(buffer);
let colorModel;
const bitDepth = png.depth;
if (png.palette) {
return loadPalettePng(png);
}
if (bitDepth === 1) {
return new Image(png.width, png.height, {
data: decodeBinary(png),
colorModel: 'GREY',
});
}
switch (png.channels) {
case 1:
colorModel = 'GREY';
break;
case 2:
colorModel = 'GREYA';
break;
case 3:
colorModel = 'RGB';
break;
case 4:
colorModel = 'RGBA';
break;
default:
throw new RangeError(`invalid number of channels: ${png.channels}`);
}
const resolution = getResolution(png);
return new Image(png.width, png.height, {
colorModel,
bitDepth,
data: png.data,
resolution,
});
}
/**
* Compute PNG data from palette information and return a new image.
* @param png - Decoded PNG.
* @returns The new image.
*/
function loadPalettePng(png) {
assert(png.palette);
const pixels = png.width * png.height;
const data = new Uint8Array(pixels * png.palette[0].length);
const pixelsPerByte = 8 / png.depth;
const factor = png.depth < 8 ? pixelsPerByte : 1;
const mask = Number.parseInt('1'.repeat(png.depth), 2);
let dataIndex = 0;
for (let i = 0; i < pixels; i++) {
const index = Math.floor(i / factor);
let value = png.data[index];
if (png.depth < 8) {
value =
(value >>> (png.depth * (pixelsPerByte - 1 - (i % pixelsPerByte)))) &
mask;
}
const paletteValue = png.palette[value];
for (const paletteChannel of paletteValue) {
data[dataIndex++] = paletteChannel;
}
}
const resolution = getResolution(png);
return new Image(png.width, png.height, {
data,
colorModel: png.palette[0].length === 4 ? 'RGBA' : 'RGB',
resolution,
});
}
function decodeBinary(png) {
const totalPixels = png.width * png.height;
const result = new Uint8Array(totalPixels);
const pngData = png.data;
const padding = png.width % 8;
const bytesPerLine = Math.ceil(png.width / 8);
let pixelIndex = 0;
for (let byteIndex = 0; byteIndex < pngData.length && pixelIndex < totalPixels; byteIndex++) {
const byte = pngData[byteIndex];
const limit = byteIndex % bytesPerLine === 0 ? 8 - padding : 0;
for (let bitIndex = 7; bitIndex >= limit && pixelIndex < totalPixels; bitIndex--) {
const bit = (byte >> bitIndex) & 1;
result[pixelIndex++] = bit * 255;
}
}
return result;
}
/**
* Gets image's resolution from its parsed data.
* @param png - Parsed .png image.
* @returns Object with resolution data if exists.
*/
function getResolution(png) {
if (png.resolution) {
return png.resolution.unit === 1
? /*If the resolution unit is meters*/ {
x: png.resolution.x,
y: png.resolution.y,
unit: 'meter',
}
: /*If resolution unit is unknown */ {
x: png.resolution.x,
y: png.resolution.y,
unit: 'unknown',
};
}
else {
return undefined;
}
}
//# sourceMappingURL=decodePng.js.map