1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | ;(function (factory) {
|
17 | 'use strict'
|
18 | if (typeof define === 'function' && define.amd) {
|
19 |
|
20 | define(['./load-image', './load-image-meta'], factory)
|
21 | } else if (typeof module === 'object' && module.exports) {
|
22 | factory(require('./load-image'), require('./load-image-meta'))
|
23 | } else {
|
24 |
|
25 | factory(window.loadImage)
|
26 | }
|
27 | })(function (loadImage) {
|
28 | 'use strict'
|
29 |
|
30 | |
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | function ExifMap(tagCode) {
|
38 | if (tagCode) {
|
39 | Object.defineProperty(this, 'map', {
|
40 | value: this.ifds[tagCode].map
|
41 | })
|
42 | Object.defineProperty(this, 'tags', {
|
43 | value: (this.tags && this.tags[tagCode]) || {}
|
44 | })
|
45 | }
|
46 | }
|
47 |
|
48 | ExifMap.prototype.map = {
|
49 | Orientation: 0x0112,
|
50 | Thumbnail: 'ifd1',
|
51 | Blob: 0x0201,
|
52 | Exif: 0x8769,
|
53 | GPSInfo: 0x8825,
|
54 | Interoperability: 0xa005
|
55 | }
|
56 |
|
57 | ExifMap.prototype.ifds = {
|
58 | ifd1: { name: 'Thumbnail', map: ExifMap.prototype.map },
|
59 | 0x8769: { name: 'Exif', map: {} },
|
60 | 0x8825: { name: 'GPSInfo', map: {} },
|
61 | 0xa005: { name: 'Interoperability', map: {} }
|
62 | }
|
63 |
|
64 | |
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 | ExifMap.prototype.get = function (id) {
|
71 | return this[id] || this[this.map[id]]
|
72 | }
|
73 |
|
74 | |
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 | function getExifThumbnail(dataView, offset, length) {
|
83 | if (!length) return
|
84 | if (offset + length > dataView.byteLength) {
|
85 | console.log('Invalid Exif data: Invalid thumbnail data.')
|
86 | return
|
87 | }
|
88 | return new Blob(
|
89 | [loadImage.bufferSlice.call(dataView.buffer, offset, offset + length)],
|
90 | {
|
91 | type: 'image/jpeg'
|
92 | }
|
93 | )
|
94 | }
|
95 |
|
96 | var ExifTagTypes = {
|
97 |
|
98 | 1: {
|
99 | getValue: function (dataView, dataOffset) {
|
100 | return dataView.getUint8(dataOffset)
|
101 | },
|
102 | size: 1
|
103 | },
|
104 |
|
105 | 2: {
|
106 | getValue: function (dataView, dataOffset) {
|
107 | return String.fromCharCode(dataView.getUint8(dataOffset))
|
108 | },
|
109 | size: 1,
|
110 | ascii: true
|
111 | },
|
112 |
|
113 | 3: {
|
114 | getValue: function (dataView, dataOffset, littleEndian) {
|
115 | return dataView.getUint16(dataOffset, littleEndian)
|
116 | },
|
117 | size: 2
|
118 | },
|
119 |
|
120 | 4: {
|
121 | getValue: function (dataView, dataOffset, littleEndian) {
|
122 | return dataView.getUint32(dataOffset, littleEndian)
|
123 | },
|
124 | size: 4
|
125 | },
|
126 |
|
127 | 5: {
|
128 | getValue: function (dataView, dataOffset, littleEndian) {
|
129 | return (
|
130 | dataView.getUint32(dataOffset, littleEndian) /
|
131 | dataView.getUint32(dataOffset + 4, littleEndian)
|
132 | )
|
133 | },
|
134 | size: 8
|
135 | },
|
136 |
|
137 | 9: {
|
138 | getValue: function (dataView, dataOffset, littleEndian) {
|
139 | return dataView.getInt32(dataOffset, littleEndian)
|
140 | },
|
141 | size: 4
|
142 | },
|
143 |
|
144 | 10: {
|
145 | getValue: function (dataView, dataOffset, littleEndian) {
|
146 | return (
|
147 | dataView.getInt32(dataOffset, littleEndian) /
|
148 | dataView.getInt32(dataOffset + 4, littleEndian)
|
149 | )
|
150 | },
|
151 | size: 8
|
152 | }
|
153 | }
|
154 |
|
155 | ExifTagTypes[7] = ExifTagTypes[1]
|
156 |
|
157 | |
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 | function getExifValue(
|
169 | dataView,
|
170 | tiffOffset,
|
171 | offset,
|
172 | type,
|
173 | length,
|
174 | littleEndian
|
175 | ) {
|
176 | var tagType = ExifTagTypes[type]
|
177 | var tagSize
|
178 | var dataOffset
|
179 | var values
|
180 | var i
|
181 | var str
|
182 | var c
|
183 | if (!tagType) {
|
184 | console.log('Invalid Exif data: Invalid tag type.')
|
185 | return
|
186 | }
|
187 | tagSize = tagType.size * length
|
188 |
|
189 |
|
190 | dataOffset =
|
191 | tagSize > 4
|
192 | ? tiffOffset + dataView.getUint32(offset + 8, littleEndian)
|
193 | : offset + 8
|
194 | if (dataOffset + tagSize > dataView.byteLength) {
|
195 | console.log('Invalid Exif data: Invalid data offset.')
|
196 | return
|
197 | }
|
198 | if (length === 1) {
|
199 | return tagType.getValue(dataView, dataOffset, littleEndian)
|
200 | }
|
201 | values = []
|
202 | for (i = 0; i < length; i += 1) {
|
203 | values[i] = tagType.getValue(
|
204 | dataView,
|
205 | dataOffset + i * tagType.size,
|
206 | littleEndian
|
207 | )
|
208 | }
|
209 | if (tagType.ascii) {
|
210 | str = ''
|
211 |
|
212 | for (i = 0; i < values.length; i += 1) {
|
213 | c = values[i]
|
214 |
|
215 | if (c === '\u0000') {
|
216 | break
|
217 | }
|
218 | str += c
|
219 | }
|
220 | return str
|
221 | }
|
222 | return values
|
223 | }
|
224 |
|
225 | |
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 | function shouldIncludeTag(includeTags, excludeTags, tagCode) {
|
234 | return (
|
235 | (!includeTags || includeTags[tagCode]) &&
|
236 | (!excludeTags || excludeTags[tagCode] !== true)
|
237 | )
|
238 | }
|
239 |
|
240 | |
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 | function parseExifTags(
|
254 | dataView,
|
255 | tiffOffset,
|
256 | dirOffset,
|
257 | littleEndian,
|
258 | tags,
|
259 | tagOffsets,
|
260 | includeTags,
|
261 | excludeTags
|
262 | ) {
|
263 | var tagsNumber, dirEndOffset, i, tagOffset, tagNumber, tagValue
|
264 | if (dirOffset + 6 > dataView.byteLength) {
|
265 | console.log('Invalid Exif data: Invalid directory offset.')
|
266 | return
|
267 | }
|
268 | tagsNumber = dataView.getUint16(dirOffset, littleEndian)
|
269 | dirEndOffset = dirOffset + 2 + 12 * tagsNumber
|
270 | if (dirEndOffset + 4 > dataView.byteLength) {
|
271 | console.log('Invalid Exif data: Invalid directory size.')
|
272 | return
|
273 | }
|
274 | for (i = 0; i < tagsNumber; i += 1) {
|
275 | tagOffset = dirOffset + 2 + 12 * i
|
276 | tagNumber = dataView.getUint16(tagOffset, littleEndian)
|
277 | if (!shouldIncludeTag(includeTags, excludeTags, tagNumber)) continue
|
278 | tagValue = getExifValue(
|
279 | dataView,
|
280 | tiffOffset,
|
281 | tagOffset,
|
282 | dataView.getUint16(tagOffset + 2, littleEndian),
|
283 | dataView.getUint32(tagOffset + 4, littleEndian),
|
284 | littleEndian
|
285 | )
|
286 | tags[tagNumber] = tagValue
|
287 | if (tagOffsets) {
|
288 | tagOffsets[tagNumber] = tagOffset
|
289 | }
|
290 | }
|
291 |
|
292 | return dataView.getUint32(dirEndOffset, littleEndian)
|
293 | }
|
294 |
|
295 | |
296 |
|
297 |
|
298 |
|
299 |
|
300 |
|
301 |
|
302 |
|
303 |
|
304 |
|
305 |
|
306 | function parseExifIFD(
|
307 | data,
|
308 | tagCode,
|
309 | dataView,
|
310 | tiffOffset,
|
311 | littleEndian,
|
312 | includeTags,
|
313 | excludeTags
|
314 | ) {
|
315 | var dirOffset = data.exif[tagCode]
|
316 | if (dirOffset) {
|
317 | data.exif[tagCode] = new ExifMap(tagCode)
|
318 | if (data.exifOffsets) {
|
319 | data.exifOffsets[tagCode] = new ExifMap(tagCode)
|
320 | }
|
321 | parseExifTags(
|
322 | dataView,
|
323 | tiffOffset,
|
324 | tiffOffset + dirOffset,
|
325 | littleEndian,
|
326 | data.exif[tagCode],
|
327 | data.exifOffsets && data.exifOffsets[tagCode],
|
328 | includeTags && includeTags[tagCode],
|
329 | excludeTags && excludeTags[tagCode]
|
330 | )
|
331 | }
|
332 | }
|
333 |
|
334 | loadImage.parseExifData = function (dataView, offset, length, data, options) {
|
335 | if (options.disableExif) {
|
336 | return
|
337 | }
|
338 | var includeTags = options.includeExifTags
|
339 | var excludeTags = options.excludeExifTags || {
|
340 | 0x8769: {
|
341 |
|
342 | 0x927c: true
|
343 | }
|
344 | }
|
345 | var tiffOffset = offset + 10
|
346 | var littleEndian
|
347 | var dirOffset
|
348 | var thumbnailIFD
|
349 |
|
350 | if (dataView.getUint32(offset + 4) !== 0x45786966) {
|
351 |
|
352 | return
|
353 | }
|
354 | if (tiffOffset + 8 > dataView.byteLength) {
|
355 | console.log('Invalid Exif data: Invalid segment size.')
|
356 | return
|
357 | }
|
358 |
|
359 | if (dataView.getUint16(offset + 8) !== 0x0000) {
|
360 | console.log('Invalid Exif data: Missing byte alignment offset.')
|
361 | return
|
362 | }
|
363 |
|
364 | switch (dataView.getUint16(tiffOffset)) {
|
365 | case 0x4949:
|
366 | littleEndian = true
|
367 | break
|
368 | case 0x4d4d:
|
369 | littleEndian = false
|
370 | break
|
371 | default:
|
372 | console.log('Invalid Exif data: Invalid byte alignment marker.')
|
373 | return
|
374 | }
|
375 |
|
376 | if (dataView.getUint16(tiffOffset + 2, littleEndian) !== 0x002a) {
|
377 | console.log('Invalid Exif data: Missing TIFF marker.')
|
378 | return
|
379 | }
|
380 |
|
381 | dirOffset = dataView.getUint32(tiffOffset + 4, littleEndian)
|
382 |
|
383 | data.exif = new ExifMap()
|
384 | if (!options.disableExifOffsets) {
|
385 | data.exifOffsets = new ExifMap()
|
386 | data.exifTiffOffset = tiffOffset
|
387 | data.exifLittleEndian = littleEndian
|
388 | }
|
389 |
|
390 |
|
391 | dirOffset = parseExifTags(
|
392 | dataView,
|
393 | tiffOffset,
|
394 | tiffOffset + dirOffset,
|
395 | littleEndian,
|
396 | data.exif,
|
397 | data.exifOffsets,
|
398 | includeTags,
|
399 | excludeTags
|
400 | )
|
401 | if (dirOffset && shouldIncludeTag(includeTags, excludeTags, 'ifd1')) {
|
402 | data.exif.ifd1 = dirOffset
|
403 | if (data.exifOffsets) {
|
404 | data.exifOffsets.ifd1 = tiffOffset + dirOffset
|
405 | }
|
406 | }
|
407 | Object.keys(data.exif.ifds).forEach(function (tagCode) {
|
408 | parseExifIFD(
|
409 | data,
|
410 | tagCode,
|
411 | dataView,
|
412 | tiffOffset,
|
413 | littleEndian,
|
414 | includeTags,
|
415 | excludeTags
|
416 | )
|
417 | })
|
418 | thumbnailIFD = data.exif.ifd1
|
419 |
|
420 | if (thumbnailIFD && thumbnailIFD[0x0201]) {
|
421 | thumbnailIFD[0x0201] = getExifThumbnail(
|
422 | dataView,
|
423 | tiffOffset + thumbnailIFD[0x0201],
|
424 | thumbnailIFD[0x0202]
|
425 | )
|
426 | }
|
427 | }
|
428 |
|
429 |
|
430 | loadImage.metaDataParsers.jpeg[0xffe1].push(loadImage.parseExifData)
|
431 |
|
432 | loadImage.exifWriters = {
|
433 |
|
434 | 0x0112: function (buffer, data, value) {
|
435 | var orientationOffset = data.exifOffsets[0x0112]
|
436 | if (!orientationOffset) return buffer
|
437 | var view = new DataView(buffer, orientationOffset + 8, 2)
|
438 | view.setUint16(0, value, data.exifLittleEndian)
|
439 | return buffer
|
440 | }
|
441 | }
|
442 |
|
443 | loadImage.writeExifData = function (buffer, data, id, value) {
|
444 | loadImage.exifWriters[data.exif.map[id]](buffer, data, value)
|
445 | }
|
446 |
|
447 | loadImage.ExifMap = ExifMap
|
448 |
|
449 |
|
450 |
|
451 |
|
452 |
|
453 |
|
454 |
|
455 |
|
456 |
|
457 |
|
458 |
|
459 |
|
460 | })
|