Import: Handle uploads with sha1 starting with 0 properly
[mediawiki.git] / resources / src / mediawiki.libs / mediawiki.libs.jpegmeta.js
blobb3ed88c8fd887020d65ba41fdfe0538825e8085b
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  */
13 ( function () {
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         */
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         };
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         };
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         };
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         };
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         };
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         };
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         };
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         };
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         };
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         };
732         // MediaWiki: Add mw.libs wrapper
733         mw.libs.jpegmeta = function( fileReaderResult, fileName ) {
734                 return new JpegMeta.JpegFile( fileReaderResult, fileName );
735         };
737 }() );