Skip to content

Commit

Permalink
Add ICC support for HEIC/HEIF files
Browse files Browse the repository at this point in the history
  • Loading branch information
mattiasw committed May 2, 2020
1 parent 33153dc commit bc6f1a9
Show file tree
Hide file tree
Showing 9 changed files with 282 additions and 155 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ You can try it out on the
| JPEG | **yes** | **yes** | **yes** | **yes** | **yes** |
| TIFF | **yes** | **yes** | **yes** | **yes** | no |
| PNG | no | no | **yes** | no | no |
| HEIC/HEIF | **yes** | no | no | no | no |
| HEIC/HEIF | **yes** | no | no | **yes** | no |
| WebP | **yes** | no | **yes** | **yes** | **yes** |

If you're missing something that you think should be supported, file an issue
Expand Down
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.

55 changes: 40 additions & 15 deletions src/image-header-heic.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,21 @@ function isHeicFile(dataView) {
}

function findHeicOffsets(dataView) {
if (Constants.USE_EXIF) {
if (Constants.USE_EXIF || Constants.USE_ICC) {
const {offset: metaOffset, length: metaLength} = findMetaAtom(dataView);
if (metaOffset === undefined) {
return {hasAppMarkers: false};
}

const metaEndOffset = Math.min(metaOffset + metaLength, dataView.byteLength);
const {exifItemOffset, ilocOffset} = findExifItemAndIloc(dataView, metaOffset, metaEndOffset);
if ((exifItemOffset === undefined) || (ilocOffset === undefined)) {
return {hasAppMarkers: false};
}
const {exifItemOffset, ilocOffset, colrOffset} = findMetaItems(dataView, metaOffset, metaEndOffset);

const exifOffset = findExifOffset(dataView, exifItemOffset, ilocOffset, metaEndOffset);
const iccChunks = findIccChunks(dataView, colrOffset, metaEndOffset);
return {
hasAppMarkers: exifOffset !== undefined,
tiffHeaderOffset: exifOffset
hasAppMarkers: (exifOffset !== undefined) || (iccChunks !== undefined),
tiffHeaderOffset: exifOffset,
iccChunks
};
}

Expand Down Expand Up @@ -105,20 +104,24 @@ function hasEmptyHighBits(dataView, offset) {
return dataView.getUint32(offset + ATOM_EXTENDED_SIZE_OFFSET) === 0;
}

function findExifItemAndIloc(dataView, offset, metaEndOffset) {
function findMetaItems(dataView, offset, metaEndOffset) {
const STRING_SIZE = 4;
const EXIF_ITEM_INDEX_REL_OFFSET = -4;
const ITEM_INDEX_REL_OFFSET = -4;
const offsets = {
ilocOffset: undefined,
exifItemOffset: undefined,
ilocOffset: undefined
colrOffset: undefined
};

while ((offset + STRING_SIZE <= metaEndOffset)
&& (!offsets.exifItemOffset || !offsets.ilocOffset)) {
if (getStringFromDataView(dataView, offset, STRING_SIZE) === 'Exif') {
offsets.exifItemOffset = offset + EXIF_ITEM_INDEX_REL_OFFSET;
} else if (getStringFromDataView(dataView, offset, STRING_SIZE) === 'iloc') {
&& (!offsets.ilocOffset || !offsets.exifItemOffset || !offsets.colrOffset)) {
const itemName = getStringFromDataView(dataView, offset, STRING_SIZE);
if (Constants.USE_EXIF && (itemName === 'iloc')) {
offsets.ilocOffset = offset;
} else if (Constants.USE_EXIF && (itemName === 'Exif')) {
offsets.exifItemOffset = offset + ITEM_INDEX_REL_OFFSET;
} else if (Constants.USE_ICC && (itemName === 'colr')) {
offsets.colrOffset = offset + ITEM_INDEX_REL_OFFSET;
}

offset++;
Expand All @@ -135,7 +138,7 @@ function findExifOffset(dataView, exifItemOffset, offset, metaEndOffset) {
const EXIF_PREFIX_LENGTH_OFFSET = 4;
const ILOC_ITEM_SIZE = 16;

if (exifItemOffset + EXIF_ITEM_OFFSET_SIZE > metaEndOffset) {
if (!offset || !exifItemOffset || (exifItemOffset + EXIF_ITEM_OFFSET_SIZE > metaEndOffset)) {
return undefined;
}

Expand All @@ -157,3 +160,25 @@ function findExifOffset(dataView, exifItemOffset, offset, metaEndOffset) {

return undefined;
}

function findIccChunks(dataView, offset, metaEndOffset) {
const ITEM_TYPE_OFFSET = 8;
const ITEM_TYPE_SIZE = 4;
const ITEM_CONTENT_OFFSET = 12;

if (!offset || (offset + ITEM_CONTENT_OFFSET > metaEndOffset)) {
return undefined;
}

const colorType = getStringFromDataView(dataView, offset + ITEM_TYPE_OFFSET, ITEM_TYPE_SIZE);
if ((colorType !== 'prof') && (colorType !== 'rICC')) {
return undefined;
}

return [{
offset: offset + ITEM_CONTENT_OFFSET,
length: getAtomLength(dataView, offset) - ITEM_CONTENT_OFFSET,
chunkNumber: 1,
chunksTotal: 1
}];
}
3 changes: 2 additions & 1 deletion test/build/custom-builds.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@
"config": {
"include": {
"heic": true,
"exif": true
"exif": true,
"icc": true
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/outputs/test.heic.out
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"combined":{"XResolution":{"id":282,"value":"72, 1","description":"72"},"YResolution":{"id":283,"value":"72, 1","description":"72"},"ResolutionUnit":{"id":296,"value":2,"description":"inches"},"YCbCrPositioning":{"id":531,"value":1,"description":"centered"},"Exif IFD Pointer":{"id":34665,"value":90,"description":90},"ExifVersion":{"id":36864,"value":"48, 50, 51, 50","description":"0232"},"ComponentsConfiguration":{"id":37121,"value":"1, 2, 3, 0","description":""},"FlashpixVersion":{"id":40960,"value":"48, 49, 48, 48","description":"0100"},"ColorSpace":{"id":40961,"value":65535,"description":"Uncalibrated"},"LensModel":{"id":42036,"value":"iPhone 7 back camera 3.99mm f/1.8","description":"iPhone 7 back camera 3.99mm f/1.8"}},"expanded":{"exif":{"XResolution":{"id":282,"value":"72, 1","description":"72"},"YResolution":{"id":283,"value":"72, 1","description":"72"},"ResolutionUnit":{"id":296,"value":2,"description":"inches"},"YCbCrPositioning":{"id":531,"value":1,"description":"centered"},"Exif IFD Pointer":{"id":34665,"value":90,"description":90},"ExifVersion":{"id":36864,"value":"48, 50, 51, 50","description":"0232"},"ComponentsConfiguration":{"id":37121,"value":"1, 2, 3, 0","description":""},"FlashpixVersion":{"id":40960,"value":"48, 49, 48, 48","description":"0100"},"ColorSpace":{"id":40961,"value":65535,"description":"Uncalibrated"},"LensModel":{"id":42036,"value":"iPhone 7 back camera 3.99mm f/1.8","description":"iPhone 7 back camera 3.99mm f/1.8"}}}}
{"combined":{"XResolution":{"id":282,"value":"72, 1","description":"72"},"YResolution":{"id":283,"value":"72, 1","description":"72"},"ResolutionUnit":{"id":296,"value":2,"description":"inches"},"YCbCrPositioning":{"id":531,"value":1,"description":"centered"},"Exif IFD Pointer":{"id":34665,"value":90,"description":90},"ExifVersion":{"id":36864,"value":"48, 50, 51, 50","description":"0232"},"ComponentsConfiguration":{"id":37121,"value":"1, 2, 3, 0","description":""},"FlashpixVersion":{"id":40960,"value":"48, 49, 48, 48","description":"0100"},"ColorSpace":{"id":40961,"value":65535,"description":"Uncalibrated"},"LensModel":{"id":42036,"value":"iPhone 7 back camera 3.99mm f/1.8","description":"iPhone 7 back camera 3.99mm f/1.8"},"Preferred CMM type":{"value":"appl","description":"Apple"},"Profile Version":{"value":"4.0.0","description":"4.0.0"},"Profile/Device class":{"value":"mntr","description":"Display Device profile"},"Color Space":{"value":"RGB ","description":"RGB "},"Connection Space":{"value":"XYZ ","description":"XYZ "},"ICC Profile Date":{"value":"2017-07-07T13:22:32.000Z","description":"2017-07-07T13:22:32.000Z"},"ICC Signature":{"value":"acsp","description":"acsp"},"Primary Platform":{"value":"APPL","description":"Apple"},"Device Manufacturer":{"value":"APPL","description":"Apple"},"Device Model Number":{"value":"\u0000\u0000\u0000\u0000","description":"\u0000\u0000\u0000\u0000"},"Rendering Intent":{"value":0,"description":"Perceptual"},"Profile Creator":{"value":"appl","description":"appl"},"ICC Description":{"value":"Display P3","description":"Display P3"},"ICC Copyright":{"value":"Copyright Apple Inc.","description":"Copyright Apple Inc."}},"expanded":{"exif":{"XResolution":{"id":282,"value":"72, 1","description":"72"},"YResolution":{"id":283,"value":"72, 1","description":"72"},"ResolutionUnit":{"id":296,"value":2,"description":"inches"},"YCbCrPositioning":{"id":531,"value":1,"description":"centered"},"Exif IFD Pointer":{"id":34665,"value":90,"description":90},"ExifVersion":{"id":36864,"value":"48, 50, 51, 50","description":"0232"},"ComponentsConfiguration":{"id":37121,"value":"1, 2, 3, 0","description":""},"FlashpixVersion":{"id":40960,"value":"48, 49, 48, 48","description":"0100"},"ColorSpace":{"id":40961,"value":65535,"description":"Uncalibrated"},"LensModel":{"id":42036,"value":"iPhone 7 back camera 3.99mm f/1.8","description":"iPhone 7 back camera 3.99mm f/1.8"}},"icc":{"Preferred CMM type":{"value":"appl","description":"Apple"},"Profile Version":{"value":"4.0.0","description":"4.0.0"},"Profile/Device class":{"value":"mntr","description":"Display Device profile"},"Color Space":{"value":"RGB ","description":"RGB "},"Connection Space":{"value":"XYZ ","description":"XYZ "},"ICC Profile Date":{"value":"2017-07-07T13:22:32.000Z","description":"2017-07-07T13:22:32.000Z"},"ICC Signature":{"value":"acsp","description":"acsp"},"Primary Platform":{"value":"APPL","description":"Apple"},"Device Manufacturer":{"value":"APPL","description":"Apple"},"Device Model Number":{"value":"\u0000\u0000\u0000\u0000","description":"\u0000\u0000\u0000\u0000"},"Rendering Intent":{"value":0,"description":"Perceptual"},"Profile Creator":{"value":"appl","description":"appl"},"ICC Description":{"value":"Display P3","description":"Display P3"},"ICC Copyright":{"value":"Copyright Apple Inc.","description":"Copyright Apple Inc."}}}}
2 changes: 1 addition & 1 deletion test/fixtures/outputs/test.heic_everything.out
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"combined":{"XResolution":{"id":282,"value":"72, 1","description":"72"},"YResolution":{"id":283,"value":"72, 1","description":"72"},"ResolutionUnit":{"id":296,"value":2,"description":"inches"},"YCbCrPositioning":{"id":531,"value":1,"description":"centered"},"Exif IFD Pointer":{"id":34665,"value":90,"description":90},"ExifVersion":{"id":36864,"value":"48, 50, 51, 50","description":"0232"},"ComponentsConfiguration":{"id":37121,"value":"1, 2, 3, 0","description":""},"FlashpixVersion":{"id":40960,"value":"48, 49, 48, 48","description":"0100"},"ColorSpace":{"id":40961,"value":65535,"description":"Uncalibrated"},"LensModel":{"id":42036,"value":"iPhone 7 back camera 3.99mm f/1.8","description":"iPhone 7 back camera 3.99mm f/1.8"}},"expanded":{"exif":{"XResolution":{"id":282,"value":"72, 1","description":"72"},"YResolution":{"id":283,"value":"72, 1","description":"72"},"ResolutionUnit":{"id":296,"value":2,"description":"inches"},"YCbCrPositioning":{"id":531,"value":1,"description":"centered"},"Exif IFD Pointer":{"id":34665,"value":90,"description":90},"ExifVersion":{"id":36864,"value":"48, 50, 51, 50","description":"0232"},"ComponentsConfiguration":{"id":37121,"value":"1, 2, 3, 0","description":""},"FlashpixVersion":{"id":40960,"value":"48, 49, 48, 48","description":"0100"},"ColorSpace":{"id":40961,"value":65535,"description":"Uncalibrated"},"LensModel":{"id":42036,"value":"iPhone 7 back camera 3.99mm f/1.8","description":"iPhone 7 back camera 3.99mm f/1.8"}}}}
{"combined":{"XResolution":{"id":282,"value":"72, 1","description":"72"},"YResolution":{"id":283,"value":"72, 1","description":"72"},"ResolutionUnit":{"id":296,"value":2,"description":"inches"},"YCbCrPositioning":{"id":531,"value":1,"description":"centered"},"Exif IFD Pointer":{"id":34665,"value":90,"description":90},"ExifVersion":{"id":36864,"value":"48, 50, 51, 50","description":"0232"},"ComponentsConfiguration":{"id":37121,"value":"1, 2, 3, 0","description":""},"FlashpixVersion":{"id":40960,"value":"48, 49, 48, 48","description":"0100"},"ColorSpace":{"id":40961,"value":65535,"description":"Uncalibrated"},"LensModel":{"id":42036,"value":"iPhone 7 back camera 3.99mm f/1.8","description":"iPhone 7 back camera 3.99mm f/1.8"},"Preferred CMM type":{"value":"appl","description":"Apple"},"Profile Version":{"value":"4.0.0","description":"4.0.0"},"Profile/Device class":{"value":"mntr","description":"Display Device profile"},"Color Space":{"value":"RGB ","description":"RGB "},"Connection Space":{"value":"XYZ ","description":"XYZ "},"ICC Profile Date":{"value":"2017-07-07T13:22:32.000Z","description":"2017-07-07T13:22:32.000Z"},"ICC Signature":{"value":"acsp","description":"acsp"},"Primary Platform":{"value":"APPL","description":"Apple"},"Device Manufacturer":{"value":"APPL","description":"Apple"},"Device Model Number":{"value":"\u0000\u0000\u0000\u0000","description":"\u0000\u0000\u0000\u0000"},"Rendering Intent":{"value":0,"description":"Perceptual"},"Profile Creator":{"value":"appl","description":"appl"},"ICC Description":{"value":"Display P3","description":"Display P3"},"ICC Copyright":{"value":"Copyright Apple Inc.","description":"Copyright Apple Inc."}},"expanded":{"exif":{"XResolution":{"id":282,"value":"72, 1","description":"72"},"YResolution":{"id":283,"value":"72, 1","description":"72"},"ResolutionUnit":{"id":296,"value":2,"description":"inches"},"YCbCrPositioning":{"id":531,"value":1,"description":"centered"},"Exif IFD Pointer":{"id":34665,"value":90,"description":90},"ExifVersion":{"id":36864,"value":"48, 50, 51, 50","description":"0232"},"ComponentsConfiguration":{"id":37121,"value":"1, 2, 3, 0","description":""},"FlashpixVersion":{"id":40960,"value":"48, 49, 48, 48","description":"0100"},"ColorSpace":{"id":40961,"value":65535,"description":"Uncalibrated"},"LensModel":{"id":42036,"value":"iPhone 7 back camera 3.99mm f/1.8","description":"iPhone 7 back camera 3.99mm f/1.8"}},"icc":{"Preferred CMM type":{"value":"appl","description":"Apple"},"Profile Version":{"value":"4.0.0","description":"4.0.0"},"Profile/Device class":{"value":"mntr","description":"Display Device profile"},"Color Space":{"value":"RGB ","description":"RGB "},"Connection Space":{"value":"XYZ ","description":"XYZ "},"ICC Profile Date":{"value":"2017-07-07T13:22:32.000Z","description":"2017-07-07T13:22:32.000Z"},"ICC Signature":{"value":"acsp","description":"acsp"},"Primary Platform":{"value":"APPL","description":"Apple"},"Device Manufacturer":{"value":"APPL","description":"Apple"},"Device Model Number":{"value":"\u0000\u0000\u0000\u0000","description":"\u0000\u0000\u0000\u0000"},"Rendering Intent":{"value":0,"description":"Perceptual"},"Profile Creator":{"value":"appl","description":"appl"},"ICC Description":{"value":"Display P3","description":"Display P3"},"ICC Copyright":{"value":"Copyright Apple Inc.","description":"Copyright Apple Inc."}}}}
Loading

0 comments on commit bc6f1a9

Please sign in to comment.