image-in-browser
Version:
Package for encoding / decoding images, transforming images, applying filters, drawing primitives on images on the client side (no need for server Node.js)
357 lines • 13.1 kB
JavaScript
import { ExifEntry } from './exif-entry.js';
import { ExifImageTags, ExifTagNameToID } from './exif-tag.js';
import { IfdContainer } from './ifd-container.js';
import { IfdDirectory } from './ifd-directory.js';
import { IfdValueType, IfdValueTypeSize } from './ifd-value-type.js';
import { IfdAsciiValue } from './ifd-value/ifd-ascii-value.js';
import { IfdByteValue } from './ifd-value/ifd-byte-value.js';
import { IfdDoubleValue } from './ifd-value/ifd-double-value.js';
import { IfdLongValue } from './ifd-value/ifd-long-value.js';
import { IfdRationalValue } from './ifd-value/ifd-rational-value.js';
import { IfdSByteValue } from './ifd-value/ifd-sbyte-value.js';
import { IfdShortValue } from './ifd-value/ifd-short-value.js';
import { IfdSingleValue } from './ifd-value/ifd-single-value.js';
import { IfdSLongValue } from './ifd-value/ifd-slong-value.js';
import { IfdSRationalValue } from './ifd-value/ifd-srational-value.js';
import { IfdSShortValue } from './ifd-value/ifd-sshort-value.js';
import { IfdUndefinedValue } from './ifd-value/ifd-undefined-value.js';
export class ExifData extends IfdContainer {
get imageIfd() {
return this.get('ifd0');
}
get thumbnailIfd() {
return this.get('ifd1');
}
get exifIfd() {
return this.get('ifd0').sub.get('exif');
}
get gpsIfd() {
return this.get('ifd0').sub.get('gps');
}
get interopIfd() {
return this.get('ifd0').sub.get('interop');
}
get dataSize() {
var _a, _b;
return 8 + ((_b = (_a = this.directories.get('ifd0')) === null || _a === void 0 ? void 0 : _a.dataSize) !== null && _b !== void 0 ? _b : 0);
}
writeDirectory(out, ifd, dataOffset) {
let offset = dataOffset;
const stripOffsetTag = ExifTagNameToID.get('StripOffsets');
out.writeUint16(ifd.size);
for (const [tag, value] of ifd.entries) {
const tagType = tag === stripOffsetTag && value.type === IfdValueType.undefined
? IfdValueType.long
: value.type;
const tagLength = tag === stripOffsetTag && value.type === IfdValueType.undefined
? 1
: value.length;
out.writeUint16(tag);
out.writeUint16(tagType);
out.writeUint32(tagLength);
let size = value.dataSize;
if (size <= 4) {
value.write(out);
while (size < 4) {
out.writeByte(0);
size++;
}
}
else {
out.writeUint32(offset);
offset += size;
}
}
return offset;
}
writeDirectoryLargeValues(out, ifd) {
for (const value of ifd.values) {
const size = value.dataSize;
if (size > 4) {
value.write(out);
}
}
}
readEntry(block, blockOffset) {
const tag = block.readUint16();
const format = block.readUint16();
const count = block.readUint32();
const entry = new ExifEntry(tag, undefined);
if (format > Object.keys(IfdValueType).length)
return entry;
const f = format;
const fsize = IfdValueTypeSize[format];
const size = count * fsize;
const endOffset = block.offset + 4;
if (size > 4) {
const fieldOffset = block.readUint32();
block.offset = fieldOffset + blockOffset;
}
if (block.offset + size > block.end) {
return entry;
}
const data = block.readRange(size);
switch (f) {
case IfdValueType.none:
break;
case IfdValueType.sByte:
entry.value = IfdSByteValue.data(data, count);
break;
case IfdValueType.byte:
entry.value = IfdByteValue.data(data, count);
break;
case IfdValueType.undefined:
entry.value = IfdUndefinedValue.data(data, count);
break;
case IfdValueType.ascii:
entry.value = IfdAsciiValue.data(data, count);
break;
case IfdValueType.short:
entry.value = IfdShortValue.data(data, count);
break;
case IfdValueType.long:
entry.value = IfdLongValue.data(data, count);
break;
case IfdValueType.rational:
entry.value = IfdRationalValue.data(data, count);
break;
case IfdValueType.sRational:
entry.value = IfdSRationalValue.data(data, count);
break;
case IfdValueType.sShort:
entry.value = IfdSShortValue.data(data, count);
break;
case IfdValueType.sLong:
entry.value = IfdSLongValue.data(data, count);
break;
case IfdValueType.single:
entry.value = IfdSingleValue.data(data, count);
break;
case IfdValueType.double:
entry.value = IfdDoubleValue.data(data, count);
break;
}
block.offset = endOffset;
return entry;
}
static from(other) {
const dirs = new Map(other.directories);
return new ExifData(dirs);
}
static fromInputBuffer(input) {
const data = new ExifData();
data.read(input);
return data;
}
hasTag(tag) {
for (const directory of this.directories.values()) {
if (directory.has(tag)) {
return true;
}
}
return false;
}
getTag(tag) {
for (const directory of this.directories.values()) {
if (directory.has(tag)) {
return directory.getValue(tag);
}
}
return undefined;
}
getTagName(tag) {
var _a, _b;
return (_b = (_a = ExifImageTags.get(tag)) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : '<unknown>';
}
write(out) {
const saveEndian = out.bigEndian;
out.bigEndian = true;
out.writeUint16(0x4d4d);
out.writeUint16(0x002a);
out.writeUint32(8);
if (this.directories.get('ifd0') === undefined)
this.directories.set('ifd0', new IfdDirectory());
let dataOffset = 8;
const offsets = new Map();
for (const [name, ifd] of this.directories) {
offsets.set(name, dataOffset);
if (ifd.sub.has('exif')) {
ifd.setValue(0x8769, new IfdLongValue(0));
}
else {
ifd.setValue(0x8769, undefined);
}
if (ifd.sub.has('interop')) {
ifd.setValue(0xa005, new IfdLongValue(0));
}
else {
ifd.setValue(0xa005, undefined);
}
if (ifd.sub.has('gps')) {
ifd.setValue(0x8825, new IfdLongValue(0));
}
else {
ifd.setValue(0x8825, undefined);
}
dataOffset += 2 + 12 * ifd.size + 4;
for (const value of ifd.values) {
const dataSize = value.dataSize;
if (dataSize > 4) {
dataOffset += dataSize;
}
}
for (const [subName, subDir] of ifd.sub.entries) {
offsets.set(subName, dataOffset);
let subSize = 2 + 12 * subDir.size;
for (const value of subDir.values) {
const dataSize = value.dataSize;
if (dataSize > 4) {
subSize += dataSize;
}
}
dataOffset += subSize;
}
}
const dirArray = Array.from(this.directories);
for (let i = 0; i < dirArray.length; i++) {
const [name, ifd] = dirArray[i];
if (ifd.sub.has('exif')) {
ifd.getValue(0x8769).setInt(offsets.get('exif'));
}
if (ifd.sub.has('interop')) {
ifd.getValue(0xa005).setInt(offsets.get('interop'));
}
if (ifd.sub.has('gps')) {
ifd.getValue(0x8825).setInt(offsets.get('gps'));
}
const ifdOffset = offsets.get(name);
const dataOffset = ifdOffset + 2 + 12 * ifd.size + 4;
this.writeDirectory(out, ifd, dataOffset);
if (i === dirArray.length - 1) {
out.writeUint32(0);
}
else {
const nextName = dirArray[i + 1][0];
out.writeUint32(offsets.get(nextName));
}
this.writeDirectoryLargeValues(out, ifd);
for (const [subName, subDir] of ifd.sub.entries) {
const subOffset = offsets.get(subName);
const dataOffset = subOffset + 2 + 12 * subDir.size;
this.writeDirectory(out, subDir, dataOffset);
this.writeDirectoryLargeValues(out, subDir);
}
}
out.bigEndian = saveEndian;
}
read(block) {
const saveEndian = block.bigEndian;
block.bigEndian = true;
const blockOffset = block.offset;
const endian = block.readUint16();
if (endian === 0x4949) {
block.bigEndian = false;
if (block.readUint16() !== 0x002a) {
block.bigEndian = saveEndian;
return false;
}
}
else if (endian === 0x4d4d) {
block.bigEndian = true;
if (block.readUint16() !== 0x002a) {
block.bigEndian = saveEndian;
return false;
}
}
else {
return false;
}
let ifdOffset = block.readUint32();
let index = 0;
while (ifdOffset > 0) {
block.offset = blockOffset + ifdOffset;
if (block.length < 2) {
break;
}
const directory = new IfdDirectory();
const numEntries = block.readUint16();
const dir = new Array();
for (let i = 0; i < numEntries; i++) {
const entry = this.readEntry(block, blockOffset);
dir.push(entry);
}
for (const entry of dir) {
if (entry.value !== undefined) {
directory.setValue(entry.tag, entry.value);
}
}
this.directories.set(`ifd${index}`, directory);
index++;
const nextIfdOffset = block.readUint32();
if (nextIfdOffset === ifdOffset) {
break;
}
else {
ifdOffset = nextIfdOffset;
}
}
const subTags = new Map([
[0x8769, 'exif'],
[0xa005, 'interop'],
[0x8825, 'gps'],
]);
for (const d of this.directories.values()) {
for (const dt of subTags.keys()) {
if (d.has(dt)) {
const ifdOffset = d.getValue(dt).toInt();
block.offset = blockOffset + ifdOffset;
const directory = new IfdDirectory();
const numEntries = block.readUint16();
const dir = new Array();
for (let i = 0; i < numEntries; i++) {
const entry = this.readEntry(block, blockOffset);
dir.push(entry);
}
for (const entry of dir) {
if (entry.value !== undefined) {
directory.setValue(entry.tag, entry.value);
}
}
d.sub.set(subTags.get(dt), directory);
}
}
}
block.bigEndian = saveEndian;
return false;
}
clone() {
return ExifData.from(this);
}
toString() {
let s = '';
for (const [name, directory] of this.directories) {
s += `${name}\n`;
for (const [tag, value] of directory.entries) {
if (value === undefined) {
s += `\t${this.getTagName(tag)}\n`;
}
else {
s += `\t${this.getTagName(tag)}: ${value.toString()}\n`;
}
}
for (const [subName, subDir] of directory.sub.entries) {
s += `${subName}\n`;
for (const [tag, value] of subDir.entries) {
if (value === undefined) {
s += `\t${this.getTagName(tag)}\n`;
}
else {
s += `\t${this.getTagName(tag)}: ${value}\n`;
}
}
}
}
return `${this.constructor.name} (${s})`;
}
}
//# sourceMappingURL=exif-data.js.map