Bump composer/spdx-licenses to 1.3.0
[lhc/web/wiklou.git] / resources / src / mediawiki.libs / mediawiki.libs.jpegmeta.js
1 /**
2 * This is JsJpegMeta v1.0
3 * From: https://code.google.com/p/jsjpegmeta/downloads/list
4 * From: https://github.com/bennoleslie/jsjpegmeta/blob/v1.0.0/jpegmeta.js
5 *
6 * Ported to MediaWiki ResourceLoader by Bryan Tong Minh
7 * Changes:
8 * - Add closure.
9 * - Add this.JpegMeta assignment to expose it as global.
10 * - Add mw.libs.jpegmeta wrapper.
11 */
12
13 ( function ( mw ) {
14 /*
15 Copyright (c) 2009 Ben Leslie
16
17 Permission is hereby granted, free of charge, to any person obtaining a copy
18 of this software and associated documentation files (the "Software"), to deal
19 in the Software without restriction, including without limitation the rights
20 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21 copies of the Software, and to permit persons to whom the Software is
22 furnished to do so, subject to the following conditions:
23
24 The above copyright notice and this permission notice shall be included in
25 all copies or substantial portions of the Software.
26
27 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
33 THE SOFTWARE.
34 */
35
36 /*
37 This JavaScript library is used to parse meta-data from files
38 with mime-type image/jpeg.
39
40 Include it with something like:
41
42 <script type="text/javascript" src="jpegmeta.js"></script>
43
44 This adds a single 'module' object called 'JpegMeta' to the global
45 namespace.
46
47 Public Functions
48 ----------------
49 JpegMeta.parseNum - parse unsigned integers from binary data
50 JpegMeta.parseSnum - parse signed integers from binary data
51
52 Public Classes
53 --------------
54 JpegMeta.Rational - A rational number class
55 JpegMeta.JfifSegment
56 JpegMeta.ExifSegment
57 JpegMeta.JpegFile - Primary class for Javascript parsing
58 */
59
60 var JpegMeta = {};
61 // MediaWiki: Expose as global
62 this.JpegMeta = JpegMeta;
63
64 /*
65 parse an unsigned number of size bytes at offset in some binary string data.
66 If endian
67 is "<" parse the data as little endian, if endian
68 is ">" parse as big-endian.
69 */
70 JpegMeta.parseNum = function parseNum(endian, data, offset, size) {
71 var i;
72 var ret;
73 var big_endian = (endian === ">");
74 if (offset === undefined) offset = 0;
75 if (size === undefined) size = data.length - offset;
76 for (big_endian ? i = offset : i = offset + size - 1;
77 big_endian ? i < offset + size : i >= offset;
78 big_endian ? i++ : i--) {
79 ret <<= 8;
80 ret += data.charCodeAt(i);
81 }
82 return ret;
83 };
84
85 /*
86 parse an signed number of size bytes at offset in some binary string data.
87 If endian
88 is "<" parse the data as little endian, if endian
89 is ">" parse as big-endian.
90 */
91 JpegMeta.parseSnum = function parseSnum(endian, data, offset, size) {
92 var i;
93 var ret;
94 var neg;
95 var big_endian = (endian === ">");
96 if (offset === undefined) offset = 0;
97 if (size === undefined) size = data.length - offset;
98 for (big_endian ? i = offset : i = offset + size - 1;
99 big_endian ? i < offset + size : i >= offset;
100 big_endian ? i++ : i--) {
101 if (neg === undefined) {
102 /* Negative if top bit is set */
103 neg = (data.charCodeAt(i) & 0x80) === 0x80;
104 }
105 ret <<= 8;
106 /* If it is negative we invert the bits */
107 ret += neg ? ~data.charCodeAt(i) & 0xff: data.charCodeAt(i);
108 }
109 if (neg) {
110 /* If it is negative we do two's complement */
111 ret += 1;
112 ret *= -1;
113 }
114 return ret;
115 };
116
117 /* Rational number class */
118 JpegMeta.Rational = function Rational(num, den)
119 {
120 this.num = num;
121 this.den = den || 1;
122 return this;
123 };
124
125 /* Rational number methods */
126 JpegMeta.Rational.prototype.toString = function toString() {
127 if (this.num === 0) {
128 return "" + this.num;
129 }
130 if (this.den === 1) {
131 return "" + this.num;
132 }
133 if (this.num === 1) {
134 return this.num + " / " + this.den;
135 }
136 return this.num / this.den; // + "/" + this.den;
137 };
138
139 JpegMeta.Rational.prototype.asFloat = function asFloat() {
140 return this.num / this.den;
141 };
142
143 /* MetaGroup class */
144 JpegMeta.MetaGroup = function MetaGroup(fieldName, description) {
145 this.fieldName = fieldName;
146 this.description = description;
147 this.metaProps = {};
148 return this;
149 };
150
151 JpegMeta.MetaGroup.prototype._addProperty = function _addProperty(fieldName, description, value) {
152 var property = new JpegMeta.MetaProp(fieldName, description, value);
153 this[property.fieldName] = property;
154 this.metaProps[property.fieldName] = property;
155 };
156
157 JpegMeta.MetaGroup.prototype.toString = function toString() {
158 return "[MetaGroup " + this.description + "]";
159 };
160
161 /* MetaProp class */
162 JpegMeta.MetaProp = function MetaProp(fieldName, description, value) {
163 this.fieldName = fieldName;
164 this.description = description;
165 this.value = value;
166 return this;
167 };
168
169 JpegMeta.MetaProp.prototype.toString = function toString() {
170 return "" + this.value;
171 };
172
173 /* JpegFile class */
174 JpegMeta.JpegFile = function JpegFile(binary_data, filename) {
175 /* Change this to EOI if we want to parse. */
176 var break_segment = this._SOS;
177
178 this.metaGroups = {};
179 this._binary_data = binary_data;
180 this.filename = filename;
181
182 /* Go through and parse. */
183 var pos = 0;
184 var pos_start_of_segment = 0;
185 var delim;
186 var mark;
187 var _mark;
188 var segsize;
189 var headersize;
190 var mark_code;
191 var mark_fn;
192
193 /* Check to see if this looks like a JPEG file */
194 if (this._binary_data.slice(0, 2) !== this._SOI_MARKER) {
195 throw new Error("Doesn't look like a JPEG file. First two bytes are " +
196 this._binary_data.charCodeAt(0) + "," +
197 this._binary_data.charCodeAt(1) + ".");
198 }
199
200 pos += 2;
201
202 while (pos < this._binary_data.length) {
203 delim = this._binary_data.charCodeAt(pos++);
204 mark = this._binary_data.charCodeAt(pos++);
205
206 pos_start_of_segment = pos;
207
208 if (delim != this._DELIM) {
209 break;
210 }
211
212 if (mark === break_segment) {
213 break;
214 }
215
216 headersize = JpegMeta.parseNum(">", this._binary_data, pos, 2);
217
218 /* Find the end */
219 pos += headersize;
220 while (pos < this._binary_data.length) {
221 delim = this._binary_data.charCodeAt(pos++);
222 if (delim == this._DELIM) {
223 _mark = this._binary_data.charCodeAt(pos++);
224 if (_mark != 0x0) {
225 pos -= 2;
226 break;
227 }
228 }
229 }
230
231 segsize = pos - pos_start_of_segment;
232
233 if (this._markers[mark]) {
234 mark_code = this._markers[mark][0];
235 mark_fn = this._markers[mark][1];
236 } else {
237 mark_code = "UNKN";
238 mark_fn = undefined;
239 }
240
241 if (mark_fn) {
242 this[mark_fn](mark, pos_start_of_segment + 2);
243 }
244
245 }
246
247 if (this.general === undefined) {
248 throw Error("Invalid JPEG file.");
249 }
250
251 return this;
252 };
253
254 this.JpegMeta.JpegFile.prototype.toString = function () {
255 return "[JpegFile " + this.filename + " " +
256 this.general.type + " " +
257 this.general.pixelWidth + "x" +
258 this.general.pixelHeight +
259 " Depth: " + this.general.depth + "]";
260 };
261
262 /* Some useful constants */
263 this.JpegMeta.JpegFile.prototype._SOI_MARKER = '\xff\xd8';
264 this.JpegMeta.JpegFile.prototype._DELIM = 0xff;
265 this.JpegMeta.JpegFile.prototype._EOI = 0xd9;
266 this.JpegMeta.JpegFile.prototype._SOS = 0xda;
267
268 this.JpegMeta.JpegFile.prototype._sofHandler = function _sofHandler (mark, pos) {
269 if (this.general !== undefined) {
270 throw Error("Unexpected multiple-frame image");
271 }
272
273 this._addMetaGroup("general", "General");
274 this.general._addProperty("depth", "Depth", JpegMeta.parseNum(">", this._binary_data, pos, 1));
275 this.general._addProperty("pixelHeight", "Pixel Height", JpegMeta.parseNum(">", this._binary_data, pos + 1, 2));
276 this.general._addProperty("pixelWidth", "Pixel Width",JpegMeta.parseNum(">", this._binary_data, pos + 3, 2));
277 this.general._addProperty("type", "Type", this._markers[mark][2]);
278 };
279
280 /* JFIF idents */
281 this.JpegMeta.JpegFile.prototype._JFIF_IDENT = "JFIF\x00";
282 this.JpegMeta.JpegFile.prototype._JFXX_IDENT = "JFXX\x00";
283
284 /* Exif idents */
285 this.JpegMeta.JpegFile.prototype._EXIF_IDENT = "Exif\x00";
286
287 /* TIFF types */
288 this.JpegMeta.JpegFile.prototype._types = {
289 /* The format is identifier : ["type name", type_size_in_bytes ] */
290 1 : ["BYTE", 1],
291 2 : ["ASCII", 1],
292 3 : ["SHORT", 2],
293 4 : ["LONG", 4],
294 5 : ["RATIONAL", 8],
295 6 : ["SBYTE", 1],
296 7 : ["UNDEFINED", 1],
297 8 : ["SSHORT", 2],
298 9 : ["SLONG", 4],
299 10 : ["SRATIONAL", 8],
300 11 : ["FLOAT", 4],
301 12 : ["DOUBLE", 8]
302 };
303
304 this.JpegMeta.JpegFile.prototype._tifftags = {
305 /* A. Tags relating to image data structure */
306 256 : ["Image width", "ImageWidth"],
307 257 : ["Image height", "ImageLength"],
308 258 : ["Number of bits per component", "BitsPerSample"],
309 259 : ["Compression scheme", "Compression",
310 {1 : "uncompressed", 6 : "JPEG compression" }],
311 262 : ["Pixel composition", "PhotmetricInerpretation",
312 {2 : "RGB", 6 : "YCbCr"}],
313 274 : ["Orientation of image", "Orientation",
314 /* FIXME: Check the mirror-image / reverse encoding and rotation */
315 {1 : "Normal", 2 : "Reverse?",
316 3 : "Upside-down", 4 : "Upside-down Reverse",
317 5 : "90 degree CW", 6 : "90 degree CW reverse",
318 7 : "90 degree CCW", 8 : "90 degree CCW reverse"}],
319 277 : ["Number of components", "SamplesPerPixel"],
320 284 : ["Image data arrangement", "PlanarConfiguration",
321 {1 : "chunky format", 2 : "planar format"}],
322 530 : ["Subsampling ratio of Y to C", "YCbCrSubSampling"],
323 531 : ["Y and C positioning", "YCbCrPositioning",
324 {1 : "centered", 2 : "co-sited"}],
325 282 : ["X Resolution", "XResolution"],
326 283 : ["Y Resolution", "YResolution"],
327 296 : ["Resolution Unit", "ResolutionUnit",
328 {2 : "inches", 3 : "centimeters"}],
329 /* B. Tags realting to recording offset */
330 273 : ["Image data location", "StripOffsets"],
331 278 : ["Number of rows per strip", "RowsPerStrip"],
332 279 : ["Bytes per compressed strip", "StripByteCounts"],
333 513 : ["Offset to JPEG SOI", "JPEGInterchangeFormat"],
334 514 : ["Bytes of JPEG Data", "JPEGInterchangeFormatLength"],
335 /* C. Tags relating to image data characteristics */
336 301 : ["Transfer function", "TransferFunction"],
337 318 : ["White point chromaticity", "WhitePoint"],
338 319 : ["Chromaticities of primaries", "PrimaryChromaticities"],
339 529 : ["Color space transformation matrix coefficients", "YCbCrCoefficients"],
340 532 : ["Pair of black and white reference values", "ReferenceBlackWhite"],
341 /* D. Other tags */
342 306 : ["Date and time", "DateTime"],
343 270 : ["Image title", "ImageDescription"],
344 271 : ["Make", "Make"],
345 272 : ["Model", "Model"],
346 305 : ["Software", "Software"],
347 315 : ["Person who created the image", "Artist"],
348 316 : ["Host Computer", "HostComputer"],
349 33432 : ["Copyright holder", "Copyright"],
350
351 34665 : ["Exif tag", "ExifIfdPointer"],
352 34853 : ["GPS tag", "GPSInfoIfdPointer"]
353 };
354
355 this.JpegMeta.JpegFile.prototype._exiftags = {
356 /* Tag Support Levels (2) - 0th IFX Exif Private Tags */
357 /* A. Tags Relating to Version */
358 36864 : ["Exif Version", "ExifVersion"],
359 40960 : ["FlashPix Version", "FlashpixVersion"],
360
361 /* B. Tag Relating to Image Data Characteristics */
362 40961 : ["Color Space", "ColorSpace"],
363
364 /* C. Tags Relating to Image Configuration */
365 37121 : ["Meaning of each component", "ComponentsConfiguration"],
366 37122 : ["Compressed Bits Per Pixel", "CompressedBitsPerPixel"],
367 40962 : ["Pixel X Dimension", "PixelXDimension"],
368 40963 : ["Pixel Y Dimension", "PixelYDimension"],
369
370 /* D. Tags Relating to User Information */
371 37500 : ["Manufacturer notes", "MakerNote"],
372 37510 : ["User comments", "UserComment"],
373
374 /* E. Tag Relating to Related File Information */
375 40964 : ["Related audio file", "RelatedSoundFile"],
376
377 /* F. Tags Relating to Date and Time */
378 36867 : ["Date Time Original", "DateTimeOriginal"],
379 36868 : ["Date Time Digitized", "DateTimeDigitized"],
380 37520 : ["DateTime subseconds", "SubSecTime"],
381 37521 : ["DateTimeOriginal subseconds", "SubSecTimeOriginal"],
382 37522 : ["DateTimeDigitized subseconds", "SubSecTimeDigitized"],
383
384 /* G. Tags Relating to Picture-Taking Conditions */
385 33434 : ["Exposure time", "ExposureTime"],
386 33437 : ["FNumber", "FNumber"],
387 34850 : ["Exposure program", "ExposureProgram"],
388 34852 : ["Spectral sensitivity", "SpectralSensitivity"],
389 34855 : ["ISO Speed Ratings", "ISOSpeedRatings"],
390 34856 : ["Optoelectric coefficient", "OECF"],
391 37377 : ["Shutter Speed", "ShutterSpeedValue"],
392 37378 : ["Aperture Value", "ApertureValue"],
393 37379 : ["Brightness", "BrightnessValue"],
394 37380 : ["Exposure Bias Value", "ExposureBiasValue"],
395 37381 : ["Max Aperture Value", "MaxApertureValue"],
396 37382 : ["Subject Distance", "SubjectDistance"],
397 37383 : ["Metering Mode", "MeteringMode"],
398 37384 : ["Light Source", "LightSource"],
399 37385 : ["Flash", "Flash"],
400 37386 : ["Focal Length", "FocalLength"],
401 37396 : ["Subject Area", "SubjectArea"],
402 41483 : ["Flash Energy", "FlashEnergy"],
403 41484 : ["Spatial Frequency Response", "SpatialFrequencyResponse"],
404 41486 : ["Focal Plane X Resolution", "FocalPlaneXResolution"],
405 41487 : ["Focal Plane Y Resolution", "FocalPlaneYResolution"],
406 41488 : ["Focal Plane Resolution Unit", "FocalPlaneResolutionUnit"],
407 41492 : ["Subject Location", "SubjectLocation"],
408 41493 : ["Exposure Index", "ExposureIndex"],
409 41495 : ["Sensing Method", "SensingMethod"],
410 41728 : ["File Source", "FileSource"],
411 41729 : ["Scene Type", "SceneType"],
412 41730 : ["CFA Pattern", "CFAPattern"],
413 41985 : ["Custom Rendered", "CustomRendered"],
414 41986 : ["Exposure Mode", "Exposure Mode"],
415 41987 : ["White Balance", "WhiteBalance"],
416 41988 : ["Digital Zoom Ratio", "DigitalZoomRatio"],
417 41990 : ["Scene Capture Type", "SceneCaptureType"],
418 41991 : ["Gain Control", "GainControl"],
419 41992 : ["Contrast", "Contrast"],
420 41993 : ["Saturation", "Saturation"],
421 41994 : ["Sharpness", "Sharpness"],
422 41995 : ["Device settings description", "DeviceSettingDescription"],
423 41996 : ["Subject distance range", "SubjectDistanceRange"],
424
425 /* H. Other Tags */
426 42016 : ["Unique image ID", "ImageUniqueID"],
427
428 40965 : ["Interoperability tag", "InteroperabilityIFDPointer"]
429 };
430
431 this.JpegMeta.JpegFile.prototype._gpstags = {
432 /* A. Tags Relating to GPS */
433 0 : ["GPS tag version", "GPSVersionID"],
434 1 : ["North or South Latitude", "GPSLatitudeRef"],
435 2 : ["Latitude", "GPSLatitude"],
436 3 : ["East or West Longitude", "GPSLongitudeRef"],
437 4 : ["Longitude", "GPSLongitude"],
438 5 : ["Altitude reference", "GPSAltitudeRef"],
439 6 : ["Altitude", "GPSAltitude"],
440 7 : ["GPS time (atomic clock)", "GPSTimeStamp"],
441 8 : ["GPS satellites usedd for measurement", "GPSSatellites"],
442 9 : ["GPS receiver status", "GPSStatus"],
443 10 : ["GPS mesaurement mode", "GPSMeasureMode"],
444 11 : ["Measurement precision", "GPSDOP"],
445 12 : ["Speed unit", "GPSSpeedRef"],
446 13 : ["Speed of GPS receiver", "GPSSpeed"],
447 14 : ["Reference for direction of movement", "GPSTrackRef"],
448 15 : ["Direction of movement", "GPSTrack"],
449 16 : ["Reference for direction of image", "GPSImgDirectionRef"],
450 17 : ["Direction of image", "GPSImgDirection"],
451 18 : ["Geodetic survey data used", "GPSMapDatum"],
452 19 : ["Reference for latitude of destination", "GPSDestLatitudeRef"],
453 20 : ["Latitude of destination", "GPSDestLatitude"],
454 21 : ["Reference for longitude of destination", "GPSDestLongitudeRef"],
455 22 : ["Longitude of destination", "GPSDestLongitude"],
456 23 : ["Reference for bearing of destination", "GPSDestBearingRef"],
457 24 : ["Bearing of destination", "GPSDestBearing"],
458 25 : ["Reference for distance to destination", "GPSDestDistanceRef"],
459 26 : ["Distance to destination", "GPSDestDistance"],
460 27 : ["Name of GPS processing method", "GPSProcessingMethod"],
461 28 : ["Name of GPS area", "GPSAreaInformation"],
462 29 : ["GPS Date", "GPSDateStamp"],
463 30 : ["GPS differential correction", "GPSDifferential"]
464 };
465
466 this.JpegMeta.JpegFile.prototype._markers = {
467 /* Start Of Frame markers, non-differential, Huffman coding */
468 0xc0: ["SOF0", "_sofHandler", "Baseline DCT"],
469 0xc1: ["SOF1", "_sofHandler", "Extended sequential DCT"],
470 0xc2: ["SOF2", "_sofHandler", "Progressive DCT"],
471 0xc3: ["SOF3", "_sofHandler", "Lossless (sequential)"],
472
473 /* Start Of Frame markers, differential, Huffman coding */
474 0xc5: ["SOF5", "_sofHandler", "Differential sequential DCT"],
475 0xc6: ["SOF6", "_sofHandler", "Differential progressive DCT"],
476 0xc7: ["SOF7", "_sofHandler", "Differential lossless (sequential)"],
477
478 /* Start Of Frame markers, non-differential, arithmetic coding */
479 0xc8: ["JPG", null, "Reserved for JPEG extensions"],
480 0xc9: ["SOF9", "_sofHandler", "Extended sequential DCT"],
481 0xca: ["SOF10", "_sofHandler", "Progressive DCT"],
482 0xcb: ["SOF11", "_sofHandler", "Lossless (sequential)"],
483
484 /* Start Of Frame markers, differential, arithmetic coding */
485 0xcd: ["SOF13", "_sofHandler", "Differential sequential DCT"],
486 0xce: ["SOF14", "_sofHandler", "Differential progressive DCT"],
487 0xcf: ["SOF15", "_sofHandler", "Differential lossless (sequential)"],
488
489 /* Huffman table specification */
490 0xc4: ["DHT", null, "Define Huffman table(s)"],
491 0xcc: ["DAC", null, "Define arithmetic coding conditioning(s)"],
492
493 /* Restart interval termination" */
494 0xd0: ["RST0", null, "Restart with modulo 8 count “0”"],
495 0xd1: ["RST1", null, "Restart with modulo 8 count “1”"],
496 0xd2: ["RST2", null, "Restart with modulo 8 count “2”"],
497 0xd3: ["RST3", null, "Restart with modulo 8 count “3”"],
498 0xd4: ["RST4", null, "Restart with modulo 8 count “4”"],
499 0xd5: ["RST5", null, "Restart with modulo 8 count “5”"],
500 0xd6: ["RST6", null, "Restart with modulo 8 count “6”"],
501 0xd7: ["RST7", null, "Restart with modulo 8 count “7”"],
502
503 /* Other markers */
504 0xd8: ["SOI", null, "Start of image"],
505 0xd9: ["EOI", null, "End of image"],
506 0xda: ["SOS", null, "Start of scan"],
507 0xdb: ["DQT", null, "Define quantization table(s)"],
508 0xdc: ["DNL", null, "Define number of lines"],
509 0xdd: ["DRI", null, "Define restart interval"],
510 0xde: ["DHP", null, "Define hierarchical progression"],
511 0xdf: ["EXP", null, "Expand reference component(s)"],
512 0xe0: ["APP0", "_app0Handler", "Reserved for application segments"],
513 0xe1: ["APP1", "_app1Handler"],
514 0xe2: ["APP2", null],
515 0xe3: ["APP3", null],
516 0xe4: ["APP4", null],
517 0xe5: ["APP5", null],
518 0xe6: ["APP6", null],
519 0xe7: ["APP7", null],
520 0xe8: ["APP8", null],
521 0xe9: ["APP9", null],
522 0xea: ["APP10", null],
523 0xeb: ["APP11", null],
524 0xec: ["APP12", null],
525 0xed: ["APP13", null],
526 0xee: ["APP14", null],
527 0xef: ["APP15", null],
528 0xf0: ["JPG0", null], /* Reserved for JPEG extensions */
529 0xf1: ["JPG1", null],
530 0xf2: ["JPG2", null],
531 0xf3: ["JPG3", null],
532 0xf4: ["JPG4", null],
533 0xf5: ["JPG5", null],
534 0xf6: ["JPG6", null],
535 0xf7: ["JPG7", null],
536 0xf8: ["JPG8", null],
537 0xf9: ["JPG9", null],
538 0xfa: ["JPG10", null],
539 0xfb: ["JPG11", null],
540 0xfc: ["JPG12", null],
541 0xfd: ["JPG13", null],
542 0xfe: ["COM", null], /* Comment */
543
544 /* Reserved markers */
545 0x01: ["JPG13", null] /* For temporary private use in arithmetic coding */
546 /* 02 -> bf are reserverd */
547 };
548
549 /* Private methods */
550 this.JpegMeta.JpegFile.prototype._addMetaGroup = function _addMetaGroup(name, description) {
551 var group = new JpegMeta.MetaGroup(name, description);
552 this[group.fieldName] = group;
553 this.metaGroups[group.fieldName] = group;
554 return group;
555 };
556
557 this.JpegMeta.JpegFile.prototype._parseIfd = function _parseIfd(endian, _binary_data, base, ifd_offset, tags, name, description) {
558 var num_fields = JpegMeta.parseNum(endian, _binary_data, base + ifd_offset, 2);
559 /* Per tag variables */
560 var i, j;
561 var tag_base;
562 var tag_field;
563 var type, type_field, type_size;
564 var num_values;
565 var value_offset;
566 var value;
567 var _val;
568 var num;
569 var den;
570
571 var group;
572
573 group = this._addMetaGroup(name, description);
574
575 for (var i = 0; i < num_fields; i++) {
576 /* parse the field */
577 tag_base = base + ifd_offset + 2 + (i * 12);
578 tag_field = JpegMeta.parseNum(endian, _binary_data, tag_base, 2);
579 type_field = JpegMeta.parseNum(endian, _binary_data, tag_base + 2, 2);
580 num_values = JpegMeta.parseNum(endian, _binary_data, tag_base + 4, 4);
581 value_offset = JpegMeta.parseNum(endian, _binary_data, tag_base + 8, 4);
582 if (this._types[type_field] === undefined) {
583 continue;
584 }
585 type = this._types[type_field][0];
586 type_size = this._types[type_field][1];
587
588 if (type_size * num_values <= 4) {
589 /* Data is in-line */
590 value_offset = tag_base + 8;
591 } else {
592 value_offset = base + value_offset;
593 }
594
595 /* Read the value */
596 if (type == "UNDEFINED") {
597 value = _binary_data.slice(value_offset, value_offset + num_values);
598 } else if (type == "ASCII") {
599 value = _binary_data.slice(value_offset, value_offset + num_values);
600 value = value.split('\x00')[0];
601 /* strip trail nul */
602 } else {
603 value = new Array();
604 for (j = 0; j < num_values; j++, value_offset += type_size) {
605 if (type == "BYTE" || type == "SHORT" || type == "LONG") {
606 value.push(JpegMeta.parseNum(endian, _binary_data, value_offset, type_size));
607 }
608 if (type == "SBYTE" || type == "SSHORT" || type == "SLONG") {
609 value.push(JpegMeta.parseSnum(endian, _binary_data, value_offset, type_size));
610 }
611 if (type == "RATIONAL") {
612 num = JpegMeta.parseNum(endian, _binary_data, value_offset, 4);
613 den = JpegMeta.parseNum(endian, _binary_data, value_offset + 4, 4);
614 value.push(new JpegMeta.Rational(num, den));
615 }
616 if (type == "SRATIONAL") {
617 num = JpegMeta.parseSnum(endian, _binary_data, value_offset, 4);
618 den = JpegMeta.parseSnum(endian, _binary_data, value_offset + 4, 4);
619 value.push(new JpegMeta.Rational(num, den));
620 }
621 value.push();
622 }
623 if (num_values === 1) {
624 value = value[0];
625 }
626 }
627 if (tags[tag_field] !== undefined) {
628 group._addProperty(tags[tag_field][1], tags[tag_field][0], value);
629 }
630 }
631 };
632
633 this.JpegMeta.JpegFile.prototype._jfifHandler = function _jfifHandler(mark, pos) {
634 if (this.jfif !== undefined) {
635 throw Error("Multiple JFIF segments found");
636 }
637 this._addMetaGroup("jfif", "JFIF");
638 this.jfif._addProperty("version_major", "Version Major", this._binary_data.charCodeAt(pos + 5));
639 this.jfif._addProperty("version_minor", "Version Minor", this._binary_data.charCodeAt(pos + 6));
640 this.jfif._addProperty("version", "JFIF Version", this.jfif.version_major.value + "." + this.jfif.version_minor.value);
641 this.jfif._addProperty("units", "Density Unit", this._binary_data.charCodeAt(pos + 7));
642 this.jfif._addProperty("Xdensity", "X density", JpegMeta.parseNum(">", this._binary_data, pos + 8, 2));
643 this.jfif._addProperty("Ydensity", "Y Density", JpegMeta.parseNum(">", this._binary_data, pos + 10, 2));
644 this.jfif._addProperty("Xthumbnail", "X Thumbnail", JpegMeta.parseNum(">", this._binary_data, pos + 12, 1));
645 this.jfif._addProperty("Ythumbnail", "Y Thumbnail", JpegMeta.parseNum(">", this._binary_data, pos + 13, 1));
646 };
647
648 /* Handle app0 segments */
649 this.JpegMeta.JpegFile.prototype._app0Handler = function app0Handler(mark, pos) {
650 var ident = this._binary_data.slice(pos, pos + 5);
651 if (ident == this._JFIF_IDENT) {
652 this._jfifHandler(mark, pos);
653 } else if (ident == this._JFXX_IDENT) {
654 /* Don't handle JFXX Ident yet */
655 } else {
656 /* Don't know about other idents */
657 }
658 };
659
660 /* Handle app1 segments */
661 this.JpegMeta.JpegFile.prototype._app1Handler = function _app1Handler(mark, pos) {
662 var ident = this._binary_data.slice(pos, pos + 5);
663 if (ident == this._EXIF_IDENT) {
664 this._exifHandler(mark, pos + 6);
665 } else {
666 /* Don't know about other idents */
667 }
668 };
669
670 /* Handle exif segments */
671 JpegMeta.JpegFile.prototype._exifHandler = function _exifHandler(mark, pos) {
672 if (this.exif !== undefined) {
673 throw new Error("Multiple JFIF segments found");
674 }
675
676 /* Parse this TIFF header */
677 var endian;
678 var magic_field;
679 var ifd_offset;
680 var primary_ifd, exif_ifd, gps_ifd;
681 var endian_field = this._binary_data.slice(pos, pos + 2);
682
683 /* Trivia: This 'I' is for Intel, the 'M' is for Motorola */
684 if (endian_field === "II") {
685 endian = "<";
686 } else if (endian_field === "MM") {
687 endian = ">";
688 } else {
689 throw new Error("Malformed TIFF meta-data. Unknown endianess: " + endian_field);
690 }
691
692 magic_field = JpegMeta.parseNum(endian, this._binary_data, pos + 2, 2);
693
694 if (magic_field !== 42) {
695 throw new Error("Malformed TIFF meta-data. Bad magic: " + magic_field);
696 }
697
698 ifd_offset = JpegMeta.parseNum(endian, this._binary_data, pos + 4, 4);
699
700 /* Parse 0th IFD */
701 this._parseIfd(endian, this._binary_data, pos, ifd_offset, this._tifftags, "tiff", "TIFF");
702
703 if (this.tiff.ExifIfdPointer) {
704 this._parseIfd(endian, this._binary_data, pos, this.tiff.ExifIfdPointer.value, this._exiftags, "exif", "Exif");
705 }
706
707 if (this.tiff.GPSInfoIfdPointer) {
708 this._parseIfd(endian, this._binary_data, pos, this.tiff.GPSInfoIfdPointer.value, this._gpstags, "gps", "GPS");
709 if (this.gps.GPSLatitude) {
710 var latitude;
711 latitude = this.gps.GPSLatitude.value[0].asFloat() +
712 (1 / 60) * this.gps.GPSLatitude.value[1].asFloat() +
713 (1 / 3600) * this.gps.GPSLatitude.value[2].asFloat();
714 if (this.gps.GPSLatitudeRef.value === "S") {
715 latitude = -latitude;
716 }
717 this.gps._addProperty("latitude", "Dec. Latitude", latitude);
718 }
719 if (this.gps.GPSLongitude) {
720 var longitude;
721 longitude = this.gps.GPSLongitude.value[0].asFloat() +
722 (1 / 60) * this.gps.GPSLongitude.value[1].asFloat() +
723 (1 / 3600) * this.gps.GPSLongitude.value[2].asFloat();
724 if (this.gps.GPSLongitudeRef.value === "W") {
725 longitude = -longitude;
726 }
727 this.gps._addProperty("longitude", "Dec. Longitude", longitude);
728 }
729 }
730 };
731
732 // MediaWiki: Add mw.libs wrapper
733 mw.libs.jpegmeta = function( fileReaderResult, fileName ) {
734 return new JpegMeta.JpegFile( fileReaderResult, fileName );
735 };
736
737 }( mediaWiki ) );