Skip to content

Commit

Permalink
Adds JPEG APP2 ICC Profile parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
bh213 authored and mattiasw committed Nov 4, 2019
1 parent 8cb8ace commit 86bab29
Show file tree
Hide file tree
Showing 17 changed files with 563 additions and 18 deletions.
8 changes: 7 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@
],
"indent": [
"error",
4
4,
{
"SwitchCase": 1
}
],
"key-spacing": [
"error",
Expand Down Expand Up @@ -164,6 +167,9 @@
"error",
"single"
],
"radix": [
"error"
],
"rest-spread-spacing": [
"error",
"never"
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules
*.log
.tags
.tags1
.vscode
2 changes: 1 addition & 1 deletion dist/exif-reader.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/exif-reader.js.map

Large diffs are not rendered by default.

20 changes: 15 additions & 5 deletions exif-reader.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ interface NumberArrayTag {
value: Array<number>
}

interface ValueTag {
description: string,
value: String
}

interface StringArrayTag {
id: number,
description: string,
Expand All @@ -36,15 +41,16 @@ interface XmpTags {
interface ExpandedTags {
exif?: Tags,
iptc?: Tags,
xmp?: XmpTags
xmp?: XmpTags,
icc?: IccTags
}

export function load(data: ArrayBuffer | SharedArrayBuffer | Buffer): Tags & XmpTags;
export function load(data: ArrayBuffer | SharedArrayBuffer | Buffer): Tags & XmpTags & IccTags;
export function load(data: ArrayBuffer | SharedArrayBuffer | Buffer, options: {expanded: true}): ExpandedTags;
export function load(data: ArrayBuffer | SharedArrayBuffer | Buffer, options: {expanded?: false}): Tags & XmpTags;
export function loadView(data: DataView): Tags & XmpTags;
export function load(data: ArrayBuffer | SharedArrayBuffer | Buffer, options: {expanded?: false}): Tags & XmpTags & IccTags;
export function loadView(data: DataView): Tags & XmpTags & IccTags;
export function loadView(data: DataView, options: {expanded: true}): ExpandedTags;
export function loadView(data: DataView, options: {expanded?: false}): Tags & XmpTags;
export function loadView(data: DataView, options: {expanded?: false}): Tags & XmpTags & IccTags;

export namespace errors {
export class MetadataMissingError extends Error {}
Expand Down Expand Up @@ -264,3 +270,7 @@ interface Tags {
'ObjectData Size Announced': NumberArrayTag, // NumberTag
'Maximum ObjectData Size': NumberArrayTag // NumberTag
}

interface IccTags {
[name: string]: ValueTag;
}
16 changes: 15 additions & 1 deletion src/exif-reader.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Tags from './tags';
import FileTags from './file-tags';
import IptcTags from './iptc-tags';
import XmpTags from './xmp-tags';
import IccTags from './icc-tags';
import exifErrors from './errors';

export default {
Expand All @@ -39,7 +40,7 @@ export function loadView(dataView, options = {expanded: false}) {
let foundMetaData = false;
let tags = {};

const {fileDataOffset, tiffHeaderOffset, iptcDataOffset, xmpDataOffset, xmpFieldLength} = ImageHeader.parseAppMarkers(dataView);
const {fileDataOffset, tiffHeaderOffset, iptcDataOffset, xmpDataOffset, xmpFieldLength, iccChunks} = ImageHeader.parseAppMarkers(dataView);

if (hasFileData(fileDataOffset)) {
foundMetaData = true;
Expand Down Expand Up @@ -77,6 +78,15 @@ export function loadView(dataView, options = {expanded: false}) {
tags = Object.assign({}, tags, readTags);
}
}
if (hasIccData(iccChunks)) {
foundMetaData = true;
const readTags = IccTags.read(dataView, iccChunks);
if (options.expanded) {
tags.icc = readTags;
} else {
tags = Object.assign({}, tags, readTags);
}
}
if (!foundMetaData) {
throw new exifErrors.MetadataMissingError();
}
Expand All @@ -99,3 +109,7 @@ function hasIptcData(iptcDataOffset) {
function hasXmpData(xmpDataOffset) {
return xmpDataOffset !== undefined;
}

function hasIccData(iccDataOffsets) {
return Array.isArray(iccDataOffsets) && iccDataOffsets.length > 0;
}
136 changes: 136 additions & 0 deletions src/icc-tag-names.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */

import {getStringFromDataView} from './utils';

export const iccTags = {
'desc': {
'name': 'ICC Description',
},
'cprt': {
'name': 'ICC Copyright',
},
'dmdd': {
'name': 'ICC Device Model Description',
},
'vued': {
'name': 'ICC Viewing Conditions Description',
},
'dmnd': {
'name': 'ICC Device Manufacturer for Display',
},
'tech': {
'name': 'Technology',
},
};

export const iccProfile = {
4: {
'name': 'Preferred CMM type',
'value': (dataView, offset) => getStringFromDataView(dataView, offset, 4),
'description': (value) => value !== null ? toCompany(value) : '',
},
8: {
'name': 'Profile Version',
'value': (dataView, offset) => {
return (dataView.getUint8(offset)).toString(10) + '.'
+ (dataView.getUint8(offset + 1) >> 4).toString(10) + '.'
+ (dataView.getUint8(offset + 1) % 16).toString(10);
}
},
12: {
'name': 'Profile/Device class',
'value': (dataView, offset) => getStringFromDataView(dataView, offset, 4),
'description': (value) => {
switch (value.toLowerCase()) {
case 'scnr': return 'Input Device profile';
case 'mntr': return 'Display Device profile';
case 'prtr': return 'Output Device profile';
case 'link': return 'DeviceLink profile';
case 'abst': return 'Abstract profile';
case 'spac': return 'ColorSpace profile';
case 'nmcl': return 'NamedColor profile';
case 'cenc': return 'ColorEncodingSpace profile';
case 'mid ': return 'MultiplexIdentification profile';
case 'mlnk': return 'MultiplexLink profile';
case 'mvis': return 'MultiplexVisualization profile';
default: return value;
}
}
},
16: {
'name': 'Color Space',
'value': (dataView, offset) => getStringFromDataView(dataView, offset, 4)
},
20: {
'name': 'Connection Space',
'value': (dataView, offset) => getStringFromDataView(dataView, offset, 4)
},
24: {
'name': 'ICC Profile Date',
'value': (dataView, offset) => parseDate(dataView, offset).toISOString()
},
36: {
'name': 'ICC Signature',
'value': (dataView, offset) => sliceToString(dataView.buffer.slice(offset, offset + 4))
},
40: {
'name': 'Primary Platform',
'value': (dataView, offset) => getStringFromDataView(dataView, offset, 4),
'description': (value) => toCompany(value)
},
48: {
'name': 'Device Manufacturer',
'value': (dataView, offset) => getStringFromDataView(dataView, offset, 4),
'description': (value) => toCompany(value)
},
52: {
'name': 'Device Model Number',
'value': (dataView, offset) => getStringFromDataView(dataView, offset, 4)
},
64: {
'name': 'Rendering Intent',
'value': (dataView, offset) => dataView.getUint32(offset),
'description': (value) => {
switch (value) {
case 0: return 'Perceptual';
case 1: return 'Relative Colorimetric';
case 2: return 'Saturation';
case 3: return 'Absolute Colorimetric';
default: return value;
}
}
},

80: {
'name': 'Profile Creator',
'value': (dataView, offset) => getStringFromDataView(dataView, offset, 4)
},
};

function parseDate(dataView, offset) {
const year = dataView.getUint16(offset);
const month = dataView.getUint16(offset + 2) - 1;
const day = dataView.getUint16(offset + 4);
const hours = dataView.getUint16(offset + 6);
const minutes = dataView.getUint16(offset + 8);
const seconds = dataView.getUint16(offset + 10);
return new Date(Date.UTC(year, month, day, hours, minutes, seconds));
}

function sliceToString(slice) {
return String.fromCharCode.apply(null, new Uint8Array(slice));
}

function toCompany(value) {
switch (value.toLowerCase()) {
case 'appl': return 'Apple';
case 'adbe': return 'Adobe';
case 'msft': return 'Microsoft';
case 'sunw': return 'Sun Microsystems';
case 'sgi': return 'Silicon Graphics';
case 'tgnt': return 'Taligent';
default: return value;
}
}
Loading

0 comments on commit 86bab29

Please sign in to comment.