Skip to content

Commit 4417437

Browse files
committed
Add JFIF tags
Resolves: mattiasw#166
1 parent 35fdf16 commit 4417437

File tree

14 files changed

+347
-9
lines changed

14 files changed

+347
-9
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ Possible modules to include or exclude:
334334
| `heic` | HEIC/HEIF images. |
335335
| `webp` | WebP images. |
336336
| `file` | JPEG file details: image width, height etc. |
337+
| `jfif` | JFIF details in JPEG files: resolution, thumbnail etc. |
337338
| `png_file` | PNG file details: image width, height etc. |
338339
| `exif` | Regular Exif tags. If excluded, will also exclude `mpf` and `thumbnail`. For TIFF files, excluding this will also exclude IPTC, XMP, and ICC. |
339340
| `iptc` | IPTC tags. |

dist/exif-reader.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/exif-reader.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

exif-reader.d.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,26 @@ interface FileTags {
1111
'Subsampling'?: NumberArrayFileTag
1212
}
1313

14+
interface JfifTags {
15+
'JFIF Version'?: NumberFileTag,
16+
'Resolution Unit'?: JfifResolutionUnitTag,
17+
'XResolution'?: NumberFileTag,
18+
'YResolution'?: NumberFileTag,
19+
'JFIF Thumbnail Width'?: NumberFileTag,
20+
'JFIF Thumbnail Height'?: NumberFileTag,
21+
'JFIF Thumbnail'?: JfifThumbnailTag
22+
}
23+
24+
interface JfifResolutionUnitTag {
25+
value: number,
26+
description: 'None' | 'inches' | 'cm' | 'Unknown'
27+
}
28+
29+
interface JfifThumbnailTag {
30+
value: ArrayBuffer | SharedArrayBuffer | Buffer,
31+
description: '<24-bit RGB pixel data>'
32+
}
33+
1434
interface PngFileTags {
1535
'Image Width'?: NumberFileTag,
1636
'Image Height'?: NumberFileTag,
@@ -107,6 +127,7 @@ interface ThumbnailTags {
107127

108128
interface ExpandedTags {
109129
file?: FileTags,
130+
jfif?: JfifTags,
110131
pngFile?: PngFileTags,
111132
exif?: Tags,
112133
iptc?: Tags,
@@ -185,8 +206,8 @@ interface Tags {
185206
'SamplesPerPixel'?: NumberTag,
186207
'RowsPerStrip'?: NumberTag,
187208
'StripByteCounts'?: NumberArrayTag,
188-
'XResolution'?: NumberTag,
189-
'YResolution'?: NumberTag,
209+
'XResolution'?: NumberTag | NumberFileTag, // Also in JFIF tags.
210+
'YResolution'?: NumberTag | NumberFileTag, // Also in JFIF tags.
190211
'PlanarConfiguration'?: NumberTag,
191212
'ResolutionUnit'?: NumberTag,
192213
'TransferFunction'?: NumberArrayTag,
@@ -205,6 +226,13 @@ interface Tags {
205226
'Exif IFD Pointer'?: NumberTag,
206227
'GPS Info IFD Pointer'?: NumberTag,
207228

229+
// JFIF tags
230+
'JFIF Version'?: NumberFileTag,
231+
'Resolution Unit'?: JfifResolutionUnitTag,
232+
'JFIF Thumbnail Width'?: NumberFileTag,
233+
'JFIF Thumbnail Height'?: NumberFileTag,
234+
'JFIF Thumbnail'?: JfifThumbnailTag,
235+
208236
// Exif tags
209237
'ExposureTime'?: NumberTag,
210238
'FNumber'?: NumberTag,

src/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
export default {
66
USE_FILE: true,
7+
USE_JFIF: true,
78
USE_PNG_FILE: true,
89
USE_EXIF: true,
910
USE_IPTC: true,

src/exif-reader.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {getCalculatedGpsValue} from './tag-names-utils.js';
1616
import ImageHeader from './image-header.js';
1717
import Tags from './tags.js';
1818
import FileTags from './file-tags.js';
19+
import JfifTags from './jfif-tags.js';
1920
import IptcTags from './iptc-tags.js';
2021
import XmpTags from './xmp-tags.js';
2122
import IccTags from './icc-tags.js';
@@ -170,6 +171,7 @@ export function loadView(dataView, {expanded = false, includeUnknown = false} =
170171

171172
const {
172173
fileDataOffset,
174+
jfifDataOffset,
173175
tiffHeaderOffset,
174176
iptcDataOffset,
175177
xmpChunks,
@@ -188,6 +190,16 @@ export function loadView(dataView, {expanded = false, includeUnknown = false} =
188190
}
189191
}
190192

193+
if (Constants.USE_JPEG && Constants.USE_JFIF && hasJfifData(jfifDataOffset)) {
194+
foundMetaData = true;
195+
const readTags = JfifTags.read(dataView, jfifDataOffset);
196+
if (expanded) {
197+
tags.jfif = readTags;
198+
} else {
199+
tags = objectAssign({}, tags, readTags);
200+
}
201+
}
202+
191203
if (Constants.USE_EXIF && hasExifData(tiffHeaderOffset)) {
192204
foundMetaData = true;
193205
const readTags = Tags.read(dataView, tiffHeaderOffset, includeUnknown);
@@ -311,6 +323,10 @@ function hasFileData(fileDataOffset) {
311323
return fileDataOffset !== undefined;
312324
}
313325

326+
function hasJfifData(jfifDataOffset) {
327+
return jfifDataOffset !== undefined;
328+
}
329+
314330
function hasExifData(tiffHeaderOffset) {
315331
return tiffHeaderOffset !== undefined;
316332
}

src/image-header-jpeg.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const JPEG_ID = 0xffd8;
1515
const JPEG_ID_SIZE = 2;
1616
const APP_ID_OFFSET = 4;
1717
const APP_MARKER_SIZE = 2;
18+
const JFIF_DATA_OFFSET = 2; // From start of APP0 marker.
1819
const TIFF_HEADER_OFFSET = 10; // From start of APP1 marker.
1920
const IPTC_DATA_OFFSET = 18; // From start of APP13 marker.
2021
const XMP_DATA_OFFSET = 33; // From start of APP1 marker.
@@ -42,6 +43,7 @@ const APP13_MARKER = 0xffed;
4243
const APP15_MARKER = 0xffef;
4344
const COMMENT_MARKER = 0xfffe;
4445

46+
const APP0_JFIF_IDENTIFIER = 'JFIF';
4547
const APP1_EXIF_IDENTIFIER = 'Exif';
4648
const APP1_XMP_IDENTIFIER = 'http://ns.adobe.com/xap/1.0/\x00';
4749
const APP1_XMP_EXTENDED_IDENTIFIER = 'http://ns.adobe.com/xmp/extension/\x00';
@@ -56,6 +58,7 @@ function findJpegOffsets(dataView) {
5658
let fieldLength;
5759
let sof0DataOffset;
5860
let sof2DataOffset;
61+
let jfifDataOffset;
5962
let tiffHeaderOffset;
6063
let iptcDataOffset;
6164
let xmpChunks;
@@ -67,6 +70,9 @@ function findJpegOffsets(dataView) {
6770
sof0DataOffset = appMarkerPosition + APP_MARKER_SIZE;
6871
} else if (Constants.USE_FILE && isSOF2Marker(dataView, appMarkerPosition)) {
6972
sof2DataOffset = appMarkerPosition + APP_MARKER_SIZE;
73+
} else if (Constants.USE_JFIF && isApp0JfifMarker(dataView, appMarkerPosition)) {
74+
fieldLength = dataView.getUint16(appMarkerPosition + APP_MARKER_SIZE);
75+
jfifDataOffset = appMarkerPosition + JFIF_DATA_OFFSET;
7076
} else if (Constants.USE_EXIF && isApp1ExifMarker(dataView, appMarkerPosition)) {
7177
fieldLength = dataView.getUint16(appMarkerPosition + APP_MARKER_SIZE);
7278
tiffHeaderOffset = appMarkerPosition + TIFF_HEADER_OFFSET;
@@ -110,6 +116,7 @@ function findJpegOffsets(dataView) {
110116
return {
111117
hasAppMarkers: appMarkerPosition > JPEG_ID_SIZE,
112118
fileDataOffset: sof0DataOffset || sof2DataOffset,
119+
jfifDataOffset,
113120
tiffHeaderOffset,
114121
iptcDataOffset,
115122
xmpChunks,
@@ -140,6 +147,14 @@ function isApp2MPFMarker(dataView, appMarkerPosition) {
140147
&& (getStringFromDataView(dataView, appMarkerPosition + APP_ID_OFFSET, markerIdLength) === APP2_MPF_IDENTIFIER);
141148
}
142149

150+
function isApp0JfifMarker(dataView, appMarkerPosition) {
151+
const markerIdLength = APP1_EXIF_IDENTIFIER.length;
152+
153+
return (dataView.getUint16(appMarkerPosition) === APP0_MARKER)
154+
&& (getStringFromDataView(dataView, appMarkerPosition + APP_ID_OFFSET, markerIdLength) === APP0_JFIF_IDENTIFIER)
155+
&& (dataView.getUint8(appMarkerPosition + APP_ID_OFFSET + markerIdLength) === 0x00);
156+
}
157+
143158
function isApp1ExifMarker(dataView, appMarkerPosition) {
144159
const markerIdLength = APP1_EXIF_IDENTIFIER.length;
145160

src/jfif-tags.js

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4+
5+
import Types from './types.js';
6+
7+
export default {
8+
read
9+
};
10+
11+
function read(dataView, jfifDataOffset) {
12+
const length = getLength(dataView, jfifDataOffset);
13+
const thumbnailWidth = getThumbnailWidth(dataView, jfifDataOffset, length);
14+
const thumbnailHeight = getThumbnailHeight(dataView, jfifDataOffset, length);
15+
const tags = {
16+
'JFIF Version': getVersion(dataView, jfifDataOffset, length),
17+
'Resolution Unit': getResolutionUnit(dataView, jfifDataOffset, length),
18+
'XResolution': getXResolution(dataView, jfifDataOffset, length),
19+
'YResolution': getYResolution(dataView, jfifDataOffset, length),
20+
'JFIF Thumbnail Width': thumbnailWidth,
21+
'JFIF Thumbnail Height': thumbnailHeight
22+
};
23+
24+
if (thumbnailWidth !== undefined && thumbnailHeight !== undefined) {
25+
const thumbnail = getThumbnail(dataView, jfifDataOffset, 3 * thumbnailWidth.value * thumbnailHeight.value, length);
26+
if (thumbnail) {
27+
tags['JFIF Thumbnail'] = thumbnail;
28+
}
29+
}
30+
31+
for (const tagName in tags) {
32+
if (tags[tagName] === undefined) {
33+
delete tags[tagName];
34+
}
35+
}
36+
37+
return tags;
38+
}
39+
40+
function getLength(dataView, jfifDataOffset) {
41+
return Types.getShortAt(dataView, jfifDataOffset);
42+
}
43+
44+
function getVersion(dataView, jfifDataOffset, length) {
45+
const OFFSET = 7;
46+
const SIZE = 2;
47+
48+
if (OFFSET + SIZE > length) {
49+
return undefined;
50+
}
51+
52+
const majorVersion = Types.getByteAt(dataView, jfifDataOffset + OFFSET);
53+
const minorVersion = Types.getByteAt(dataView, jfifDataOffset + OFFSET + 1);
54+
return {
55+
value: majorVersion * 0x100 + minorVersion,
56+
description: majorVersion + '.' + minorVersion
57+
};
58+
}
59+
60+
function getResolutionUnit(dataView, jfifDataOffset, length) {
61+
const OFFSET = 9;
62+
const SIZE = 1;
63+
64+
if (OFFSET + SIZE > length) {
65+
return undefined;
66+
}
67+
68+
const value = Types.getByteAt(dataView, jfifDataOffset + OFFSET);
69+
return {
70+
value,
71+
description: getResolutionUnitDescription(value)
72+
};
73+
}
74+
75+
function getResolutionUnitDescription(value) {
76+
if (value === 0) {
77+
return 'None';
78+
}
79+
if (value === 1) {
80+
return 'inches';
81+
}
82+
if (value === 2) {
83+
return 'cm';
84+
}
85+
return 'Unknown';
86+
}
87+
88+
function getXResolution(dataView, jfifDataOffset, length) {
89+
const OFFSET = 10;
90+
const SIZE = 2;
91+
92+
if (OFFSET + SIZE > length) {
93+
return undefined;
94+
}
95+
96+
const value = Types.getShortAt(dataView, jfifDataOffset + OFFSET);
97+
return {
98+
value,
99+
description: '' + value
100+
};
101+
}
102+
103+
function getYResolution(dataView, jfifDataOffset, length) {
104+
const OFFSET = 12;
105+
const SIZE = 2;
106+
107+
if (OFFSET + SIZE > length) {
108+
return undefined;
109+
}
110+
111+
const value = Types.getShortAt(dataView, jfifDataOffset + OFFSET);
112+
return {
113+
value,
114+
description: '' + value
115+
};
116+
}
117+
118+
function getThumbnailWidth(dataView, jfifDataOffset, length) {
119+
const OFFSET = 14;
120+
const SIZE = 1;
121+
122+
if (OFFSET + SIZE > length) {
123+
return undefined;
124+
}
125+
126+
const value = Types.getByteAt(dataView, jfifDataOffset + OFFSET);
127+
return {
128+
value,
129+
description: `${value}px`
130+
};
131+
}
132+
133+
function getThumbnailHeight(dataView, jfifDataOffset, length) {
134+
const OFFSET = 15;
135+
const SIZE = 1;
136+
137+
if (OFFSET + SIZE > length) {
138+
return undefined;
139+
}
140+
141+
const value = Types.getByteAt(dataView, jfifDataOffset + OFFSET);
142+
return {
143+
value,
144+
description: `${value}px`
145+
};
146+
}
147+
148+
function getThumbnail(dataView, jfifDataOffset, thumbnailLength, length) {
149+
const OFFSET = 16;
150+
151+
if (thumbnailLength === 0 || OFFSET + thumbnailLength > length) {
152+
return undefined;
153+
}
154+
155+
const value = dataView.buffer.slice(jfifDataOffset + OFFSET, jfifDataOffset + OFFSET + thumbnailLength);
156+
return {
157+
value,
158+
description: '<24-bit RGB pixel data>'
159+
};
160+
}

0 commit comments

Comments
 (0)