mirror of
https://github.com/codedread/bitjs
synced 2025-10-03 17:49:16 +02:00
321 lines
11 KiB
JavaScript
321 lines
11 KiB
JavaScript
import * as fs from 'node:fs';
|
|
import 'mocha';
|
|
import { expect } from 'chai';
|
|
import { PngColorType, PngInterlaceMethod, PngUnitSpecifier, PngParser } from '../image/parsers/png.js';
|
|
import { ExifDataFormat, ExifTagNumber } from '../image/parsers/exif.js';
|
|
|
|
/** @typedef {import('../image/parsers/exif.js').ExifValue} ExifValue */
|
|
|
|
/** @typedef {import('../image/parsers/png.js').PngBackgroundColor} PngBackgroundColor */
|
|
/** @typedef {import('../image/parsers/png.js').PngChromaticities} PngChromaticies */
|
|
/** @typedef {import('../image/parsers/png.js').PngCompressedTextualData} PngCompressedTextualData */
|
|
/** @typedef {import('../image/parsers/png.js').PngHistogram} PngHistogram */
|
|
/** @typedef {import('../image/parsers/png.js').PngImageData} PngImageData */
|
|
/** @typedef {import('../image/parsers/png.js').PngImageGamma} PngImageGamma */
|
|
/** @typedef {import('../image/parsers/png.js').PngImageHeader} PngImageHeader */
|
|
/** @typedef {import('../image/parsers/png.js').PngIntlTextualData} PngIntlTextualData */
|
|
/** @typedef {import('../image/parsers/png.js').PngLastModTime} PngLastModTime */
|
|
/** @typedef {import('../image/parsers/png.js').PngPalette} PngPalette */
|
|
/** @typedef {import('../image/parsers/png.js').PngPhysicalPixelDimensions} PngPhysicalPixelDimensions */
|
|
/** @typedef {import('../image/parsers/png.js').PngSignificantBits} PngSignificantBits */
|
|
/** @typedef {import('../image/parsers/png.js').PngSuggestedPalette} PngSuggestedPalette */
|
|
/** @typedef {import('../image/parsers/png.js').PngTextualData} PngTextualData */
|
|
/** @typedef {import('../image/parsers/png.js').PngTransparency} PngTransparency */
|
|
|
|
function getPngParser(fileName) {
|
|
const nodeBuf = fs.readFileSync(fileName);
|
|
const ab = nodeBuf.buffer.slice(nodeBuf.byteOffset, nodeBuf.byteOffset + nodeBuf.length);
|
|
return new PngParser(ab);
|
|
}
|
|
|
|
describe('bitjs.image.parsers.PngParser', () => {
|
|
describe('IHDR', () => {
|
|
it('extracts IHDR', async () => {
|
|
/** @type {PngImageHeader} */
|
|
let header;
|
|
|
|
await getPngParser('tests/image-testfiles/PngSuite.png')
|
|
.onImageHeader(evt => { header = evt.detail })
|
|
.start();
|
|
|
|
expect(header.width).equals(256);
|
|
expect(header.height).equals(256);
|
|
expect(header.bitDepth).equals(8);
|
|
expect(header.colorType).equals(PngColorType.TRUE_COLOR);
|
|
expect(header.compressionMethod).equals(0);
|
|
expect(header.filterMethod).equals(0);
|
|
expect(header.interlaceMethod).equals(PngInterlaceMethod.NO_INTERLACE);
|
|
});
|
|
|
|
it('throws on corrupt signature', async () => {
|
|
/** @type {PngImageHeader} */
|
|
let header;
|
|
|
|
try {
|
|
await getPngParser('tests/image-testfiles/xs1n0g01.png')
|
|
.onImageHeader(evt => { header = evt.detail })
|
|
.start();
|
|
throw new Error(`PngParser did not throw an error for corrupt PNG signature`);
|
|
} catch (err) {
|
|
expect(err.startsWith('Bad PNG signature')).equals(true);
|
|
}
|
|
});
|
|
});
|
|
|
|
it('extracts gAMA', async () => {
|
|
/** @type {number} */
|
|
let gamma;
|
|
await getPngParser('tests/image-testfiles/g05n3p04.png')
|
|
.onGamma(evt => gamma = evt.detail)
|
|
.start();
|
|
expect(gamma).equals(55000);
|
|
});
|
|
|
|
it('extracts sBIT', async () => {
|
|
/** @type {PngSignificantBits} */
|
|
let sBits;
|
|
await getPngParser('tests/image-testfiles/cs3n2c16.png')
|
|
.onSignificantBits(evt => sBits = evt.detail)
|
|
.start();
|
|
expect(sBits.significant_red).equals(13);
|
|
expect(sBits.significant_green).equals(13);
|
|
expect(sBits.significant_blue).equals(13);
|
|
expect(sBits.significant_greyscale).equals(undefined);
|
|
expect(sBits.significant_alpha).equals(undefined);
|
|
});
|
|
|
|
it('extracts cHRM', async () => {
|
|
/** @type {PngChromaticies} */
|
|
let chromaticities;
|
|
await getPngParser('tests/image-testfiles/ccwn2c08.png')
|
|
.onChromaticities(evt => chromaticities = evt.detail)
|
|
.start();
|
|
expect(chromaticities.whitePointX).equals(31270);
|
|
expect(chromaticities.whitePointY).equals(32900);
|
|
expect(chromaticities.redX).equals(64000);
|
|
expect(chromaticities.redY).equals(33000);
|
|
expect(chromaticities.greenX).equals(30000);
|
|
expect(chromaticities.greenY).equals(60000);
|
|
expect(chromaticities.blueX).equals(15000);
|
|
expect(chromaticities.blueY).equals(6000);
|
|
});
|
|
|
|
it('extracts PLTE', async () => {
|
|
/** @type {PngPalette} */
|
|
let palette;
|
|
await getPngParser('tests/image-testfiles/tbbn3p08.png')
|
|
.onPalette(evt => palette = evt.detail)
|
|
.start();
|
|
expect(palette.entries.length).equals(246);
|
|
const entry = palette.entries[1];
|
|
expect(entry.red).equals(128);
|
|
expect(entry.green).equals(86);
|
|
expect(entry.blue).equals(86);
|
|
});
|
|
|
|
describe('tRNS', () => {
|
|
it('extracts alpha palette', async () => {
|
|
/** @type {PngTransparency} */
|
|
let transparency;
|
|
await getPngParser('tests/image-testfiles/tbbn3p08.png')
|
|
.onTransparency(evt => transparency = evt.detail)
|
|
.start();
|
|
|
|
expect(transparency.alphaPalette.length).equals(1);
|
|
expect(transparency.alphaPalette[0]).equals(0);
|
|
});
|
|
|
|
it('extracts 8-bit RGB transparency', async () => {
|
|
/** @type {PngTransparency} */
|
|
let transparency;
|
|
await getPngParser('tests/image-testfiles/tbrn2c08.png')
|
|
.onTransparency(evt => transparency = evt.detail)
|
|
.start();
|
|
|
|
expect(transparency.redSampleValue).equals(255);
|
|
expect(transparency.blueSampleValue).equals(255);
|
|
expect(transparency.greenSampleValue).equals(255);
|
|
});
|
|
|
|
it('extracts 16-bit RGB transparency', async () => {
|
|
/** @type {PngTransparency} */
|
|
let transparency;
|
|
await getPngParser('tests/image-testfiles/tbgn2c16.png')
|
|
.onTransparency(evt => transparency = evt.detail)
|
|
.start();
|
|
|
|
expect(transparency.redSampleValue).equals(65535);
|
|
expect(transparency.blueSampleValue).equals(65535);
|
|
expect(transparency.greenSampleValue).equals(65535);
|
|
});
|
|
});
|
|
|
|
it('extracts IDAT', async () => {
|
|
/** @type {PngImageData} */
|
|
let data;
|
|
|
|
await getPngParser('tests/image-testfiles/PngSuite.png')
|
|
.onImageData(evt => { data = evt.detail })
|
|
.start();
|
|
|
|
expect(data.rawImageData.byteLength).equals(2205);
|
|
expect(data.rawImageData[0]).equals(120);
|
|
});
|
|
|
|
it('extracts tEXt', async () => {
|
|
/** @type {PngTextualData[]} */
|
|
let textualDataArr = [];
|
|
|
|
await getPngParser('tests/image-testfiles/ctzn0g04.png')
|
|
.onTextualData(evt => { textualDataArr.push(evt.detail) })
|
|
.start();
|
|
|
|
expect(textualDataArr.length).equals(2);
|
|
expect(textualDataArr[0].keyword).equals('Title');
|
|
expect(textualDataArr[0].textString).equals('PngSuite');
|
|
expect(textualDataArr[1].keyword).equals('Author');
|
|
expect(textualDataArr[1].textString).equals('Willem A.J. van Schaik\n(willem@schaik.com)');
|
|
});
|
|
|
|
it('extracts zTXt', async () => {
|
|
/** @type {PngCompressedTextualData} */
|
|
let data;
|
|
|
|
await getPngParser('tests/image-testfiles/ctzn0g04.png')
|
|
.onCompressedTextualData(evt => { data = evt.detail })
|
|
.start();
|
|
|
|
expect(data.keyword).equals('Disclaimer');
|
|
expect(data.compressionMethod).equals(0);
|
|
expect(data.compressedText.byteLength).equals(17);
|
|
|
|
const blob = new Blob([data.compressedText.buffer]);
|
|
const decompressedStream = blob.stream().pipeThrough(new DecompressionStream('deflate'));
|
|
const decompressedText = await new Response(decompressedStream).text();
|
|
expect(decompressedText).equals('Freeware.');
|
|
});
|
|
|
|
it('extracts iTXt', async () => {
|
|
/** @type {PngIntlTextualData[]} */
|
|
let data = [];
|
|
|
|
await getPngParser('tests/image-testfiles/ctjn0g04.png')
|
|
.onIntlTextualData(evt => { data.push(evt.detail) })
|
|
.start();
|
|
|
|
expect(data.length).equals(6);
|
|
expect(data[1].keyword).equals('Author');
|
|
expect(data[1].compressionFlag).equals(0)
|
|
expect(data[5].keyword).equals('Disclaimer');
|
|
// TODO: Test this better!
|
|
});
|
|
|
|
describe('bKGD', () => {
|
|
it('greyscale', async () => {
|
|
/** @type {PngBackgroundColor} */
|
|
let bc;
|
|
await getPngParser('tests/image-testfiles/bggn4a16.png')
|
|
.onBackgroundColor(evt => { bc = evt.detail })
|
|
.start();
|
|
expect(bc.greyscale).equals(43908);
|
|
});
|
|
|
|
it('rgb', async () => {
|
|
/** @type {PngBackgroundColor} */
|
|
let bc;
|
|
await getPngParser('tests/image-testfiles/tbrn2c08.png')
|
|
.onBackgroundColor(evt => { bc = evt.detail })
|
|
.start();
|
|
expect(bc.red).equals(255);
|
|
expect(bc.green).equals(0);
|
|
expect(bc.blue).equals(0);
|
|
});
|
|
|
|
it('paletteIndex', async () => {
|
|
/** @type {PngBackgroundColor} */
|
|
let bc;
|
|
await getPngParser('tests/image-testfiles/tbbn3p08.png')
|
|
.onBackgroundColor(evt => { bc = evt.detail })
|
|
.start();
|
|
expect(bc.paletteIndex).equals(245);
|
|
});
|
|
});
|
|
|
|
it('extracts tIME', async () => {
|
|
/** @type {PngLastModTime} */
|
|
let lastModTime;
|
|
await getPngParser('tests/image-testfiles/cm9n0g04.png')
|
|
.onLastModTime(evt => { lastModTime = evt.detail })
|
|
.start();
|
|
expect(lastModTime.year).equals(1999);
|
|
expect(lastModTime.month).equals(12);
|
|
expect(lastModTime.day).equals(31);
|
|
expect(lastModTime.hour).equals(23);
|
|
expect(lastModTime.minute).equals(59);
|
|
expect(lastModTime.second).equals(59);
|
|
});
|
|
|
|
it('extracts pHYs', async () => {
|
|
/** @type {PngPhysicalPixelDimensions} */
|
|
let pixelDims;
|
|
await getPngParser('tests/image-testfiles/cdun2c08.png')
|
|
.onPhysicalPixelDimensions(evt => { pixelDims = evt.detail })
|
|
.start();
|
|
expect(pixelDims.pixelPerUnitX).equals(1000);
|
|
expect(pixelDims.pixelPerUnitY).equals(1000);
|
|
expect(pixelDims.unitSpecifier).equals(PngUnitSpecifier.METRE);
|
|
});
|
|
|
|
it('extracts eXIf', async () => {
|
|
/** @type {PngPhysicalPixelDimensions} */
|
|
let exif;
|
|
await getPngParser('tests/image-testfiles/exif2c08.png')
|
|
.onExifProfile(evt => { exif = evt.detail })
|
|
.start();
|
|
|
|
const descVal = exif.get(ExifTagNumber.COPYRIGHT);
|
|
expect(descVal.dataFormat).equals(ExifDataFormat.ASCII_STRING);
|
|
expect(descVal.stringValue).equals('2017 Willem van Schaik');
|
|
});
|
|
|
|
it('extracts hIST', async () => {
|
|
/** @type {PngPalette} */
|
|
let palette;
|
|
/** @type {PngHistogram} */
|
|
let hist;
|
|
await getPngParser('tests/image-testfiles/ch1n3p04.png')
|
|
.onHistogram(evt => { hist = evt.detail })
|
|
.onPalette(evt => { palette = evt.detail })
|
|
.start();
|
|
|
|
expect(hist.frequencies.length).equals(palette.entries.length);
|
|
expect(hist.frequencies[0]).equals(64);
|
|
expect(hist.frequencies[1]).equals(112);
|
|
});
|
|
|
|
it('extracts sPLT', async () => {
|
|
/** @type {PngSuggestedPalette} */
|
|
let sPalette;
|
|
await getPngParser('tests/image-testfiles/ps1n0g08.png')
|
|
.onSuggestedPalette(evt => { sPalette = evt.detail })
|
|
.start();
|
|
|
|
expect(sPalette.entries.length).equals(216);
|
|
expect(sPalette.paletteName).equals('six-cube');
|
|
expect(sPalette.sampleDepth).equals(8);
|
|
|
|
const entry0 = sPalette.entries[0];
|
|
expect(entry0.red).equals(0);
|
|
expect(entry0.green).equals(0);
|
|
expect(entry0.blue).equals(0);
|
|
expect(entry0.alpha).equals(255);
|
|
expect(entry0.frequency).equals(0);
|
|
|
|
const entry1 = sPalette.entries[1];
|
|
expect(entry1.red).equals(0);
|
|
expect(entry1.green).equals(0);
|
|
expect(entry1.blue).equals(51);
|
|
expect(entry1.alpha).equals(255);
|
|
expect(entry1.frequency).equals(0);
|
|
});
|
|
});
|