UNPKG

image-info-extractor

Version:

A lib to read image info and to extract/parse image metadata

153 lines (145 loc) 5.26 kB
/** * ICC 4.4 parser * based on https://github.com/lovell/icc * derived from https://github.com/lovell/icc/blob/main/index.js */ // Copyright 2015 Lovell Fuller and others. // SPDX-License-Identifier: Apache-2.0 // http://www.color.org/profileheader.xalter import {binaryDecoder, utf16beDecoder, toHexString} from './commons.js'; import {tagParsers, tagSignatureMap} from './icc-tags.js'; export function parse(buffer) { let array = new Uint8Array(buffer); let view = new TagDataView(buffer); let parsed = {}; let len = view.getUint32(0); if (len !== array.length) { throw new Error(`Expected file length to be ${len} but got ${array.length}`); } let str = view.getBinaryString4(36); const fileSignature = 'acsp'; if (str !== fileSignature) { throw new Error(`Expected file signature "${fileSignature}" but got "${str}"`); } let headers = parsed.headers = {}; // Header headers['cmm'] = view.getBinaryString4(4, true); str = view.getUint32(8).toString(16).padStart(8, '0').slice(1, 3); headers['profileVersion'] = str[0] + '.' + str[1]; headers['deviceClass'] = view.getBinaryString4(12); headers['colorSpace'] = view.getBinaryString4(16).trimEnd(); headers['connectionSpace'] = view.getBinaryString4(20).trimEnd(); headers['createionDate'] = new Date(view.getDateTimeNumber(24)); headers['platform'] = view.getBinaryString4(40, true); headers['flags'] = view.getUint32(44); headers['manufacturer'] = view.getBinaryString4(48, true).trimEnd(); headers['model'] = view.getBinaryString4(52, true).trimEnd(); headers['media'] = (view.getUint32(56) << 32) + view.getUint32(60); headers['intent'] = view.getUint32(64); headers['illuminant'] = view.getXYZNumber(68); headers['creator'] = view.getBinaryString4(80, true).trimEnd(); headers['id'] = toHexString(array.subarray(84, 100)); // 100~128 reverved let tags = parsed.tags = {}; let tagCount = view.getUint32(128); let offset = 132; let tagTypeWarnings = {}; for (let i = 0; i < tagCount; i++) { let tagSignature = view.getBinaryString4(offset); let tagOffset = view.getUint32(offset + 4); let tagSize = view.getUint32(offset + 8); if (tagOffset + tagSize > array.length) { throw new Error('Tag offset out of bounds'); } let tagType = view.getBinaryString4(tagOffset); let parser = tagParsers[tagType]; if (parser) { let field = tagSignatureMap[tagSignature]; if (!field) { field = tagSignature; } tags[field] = parser(view, tagOffset, tagSize); } else if (parser === null) { if (!tagTypeWarnings[tagType]) { tagTypeWarnings[tagType] = true; console.warn('Not implemented ICC tag type: ' + tagType); } } else { if (!tagTypeWarnings[tagType]) { tagTypeWarnings[tagType] = true; console.warn('Unrecognized ICC tag type: ' + tagType); } } offset = offset + 12; } return parsed; } // eslint-disable-next-line no-control-regex const nullEnding = /\x00\x00*$/; class TagDataView extends DataView { getBinaryString4(offset, strip = false) { let a = new Uint8Array(super.buffer, super.byteOffset + offset, 4); if (strip) { let s = ''; for (let i = 0; i < a.length; i++) { if (a[i] === 0) { break; } else { s += String.fromCharCode(a[i]); } } return s; } return String.fromCharCode(a[0], a[1], a[2], a[3]); } getBinaryString(offset, length, strip = false) { let a = new Uint8Array(super.buffer, super.byteOffset + offset, length); if (strip) { let lastIndex = a.length - 1; while (lastIndex > -1) { if (a[lastIndex] === 0) { lastIndex--; } else { break; } } return binaryDecoder.decode(a.subarray(0, lastIndex + 1)); } return binaryDecoder.decode(a); } getUTF16BEString(offset, length, strip = false) { let a = new Uint8Array(super.buffer, super.byteOffset + offset, length); let s = utf16beDecoder.decode(a); return strip ? s.replace(nullEnding, '') : s; } getDateTimeNumber(offset) { return Date.UTC(super.getUint16(offset), super.getUint16(offset + 2) - 1, super.getUint16(offset + 4), super.getUint16(offset + 6), super.getUint16(offset + 8), super.getUint16(offset + 10)); } getPositionNumber(offset) { return {offset: super.getUint32(offset), length: super.getUint32(offset + 4)}; } getResponse16Number(offset) { return {code: super.getUint16(offset), measurement: this.getS15Fixed16Number(offset)}; } getS15Fixed16Number(offset) { return super.getInt16(offset) + super.getUint16(offset + 2) / 65536; } getU16Fixed16Number(offset) { return super.getUint16(offset) + super.getUint16(offset + 2) / 65536; } getU1Fixed15Number(offset) { let n = super.getUint16(offset); return (n >> 15) + (n & 0x8000) / 0x8000; } getU8Fixed8Number(offset) { return super.getUint8(offset) + super.getUint8(offset + 1) / 256; } getXYZNumber(offset) { let a = new Float64Array(3); a[0] = this.getS15Fixed16Number(offset); a[1] = this.getS15Fixed16Number(offset + 4); a[2] = this.getS15Fixed16Number(offset + 8); return a; } }