1 /****************************************************************************
  2  Copyright (c) 2011 Gordon P. Hemsley
  3  http://gphemsley.org/
  4 
  5  Copyright (c) 2010-2013 cocos2d-x.org
  6  http://www.cocos2d-x.org
  7 
  8  Permission is hereby granted, free of charge, to any person obtaining a copy
  9  of this software and associated documentation files (the "Software"), to deal
 10  in the Software without restriction, including without limitation the rights
 11  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 12  copies of the Software, and to permit persons to whom the Software is
 13  furnished to do so, subject to the following conditions:
 14 
 15  The above copyright notice and this permission notice shall be included in
 16  all copies or substantial portions of the Software.
 17 
 18  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 19  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 23  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 24  THE SOFTWARE.
 25  ****************************************************************************/
 26 
 27 cc.TIFFReader = cc.Class.extend({
 28     _littleEndian: false,
 29     _tiffData: null,
 30     _fileDirectories: null,
 31 
 32     ctor: function () {
 33         this._fileDirectories = [];
 34     },
 35 
 36     getUint8: function (offset) {
 37         return this._tiffData[offset];
 38     },
 39 
 40     getUint16: function (offset) {
 41         if (this._littleEndian)
 42             return (this._tiffData[offset + 1] << 8) | (this._tiffData[offset]);
 43         else
 44             return (this._tiffData[offset] << 8) | (this._tiffData[offset + 1]);
 45     },
 46 
 47     getUint32: function (offset) {
 48         var a = this._tiffData;
 49         if (this._littleEndian)
 50             return (a[offset + 3] << 24) | (a[offset + 2] << 16) | (a[offset + 1] << 8) | (a[offset]);
 51         else
 52             return (a[offset] << 24) | (a[offset + 1] << 16) | (a[offset + 2] << 8) | (a[offset + 3]);
 53     },
 54 
 55     checkLittleEndian: function () {
 56         var BOM = this.getUint16(0);
 57 
 58         if (BOM === 0x4949) {
 59             this.littleEndian = true;
 60         } else if (BOM === 0x4D4D) {
 61             this.littleEndian = false;
 62         } else {
 63             console.log(BOM);
 64             throw TypeError("Invalid byte order value.");
 65         }
 66 
 67         return this.littleEndian;
 68     },
 69 
 70     hasTowel: function () {
 71         // Check for towel.
 72         if (this.getUint16(2) !== 42) {
 73             throw RangeError("You forgot your towel!");
 74             return false;
 75         }
 76 
 77         return true;
 78     },
 79 
 80     getFieldTypeName: function (fieldType) {
 81         var typeNames = this.fieldTypeNames;
 82         if (fieldType in typeNames) {
 83             return typeNames[fieldType];
 84         }
 85         return null;
 86     },
 87 
 88     getFieldTagName: function (fieldTag) {
 89         var tagNames = this.fieldTagNames;
 90 
 91         if (fieldTag in tagNames) {
 92             return tagNames[fieldTag];
 93         } else {
 94             console.log("Unknown Field Tag:", fieldTag);
 95             return "Tag" + fieldTag;
 96         }
 97     },
 98 
 99     getFieldTypeLength: function (fieldTypeName) {
100         if (['BYTE', 'ASCII', 'SBYTE', 'UNDEFINED'].indexOf(fieldTypeName) !== -1) {
101             return 1;
102         } else if (['SHORT', 'SSHORT'].indexOf(fieldTypeName) !== -1) {
103             return 2;
104         } else if (['LONG', 'SLONG', 'FLOAT'].indexOf(fieldTypeName) !== -1) {
105             return 4;
106         } else if (['RATIONAL', 'SRATIONAL', 'DOUBLE'].indexOf(fieldTypeName) !== -1) {
107             return 8;
108         }
109         return null;
110     },
111 
112     getFieldValues: function (fieldTagName, fieldTypeName, typeCount, valueOffset) {
113         var fieldValues = [];
114         var fieldTypeLength = this.getFieldTypeLength(fieldTypeName);
115         var fieldValueSize = fieldTypeLength * typeCount;
116 
117         if (fieldValueSize <= 4) {
118             // The value is stored at the big end of the valueOffset.
119             if (this.littleEndian === false)
120                 fieldValues.push(valueOffset >>> ((4 - fieldTypeLength) * 8));
121             else
122                 fieldValues.push(valueOffset);
123         } else {
124             for (var i = 0; i < typeCount; i++) {
125                 var indexOffset = fieldTypeLength * i;
126                 if (fieldTypeLength >= 8) {
127                     if (['RATIONAL', 'SRATIONAL'].indexOf(fieldTypeName) !== -1) {
128                         // Numerator
129                         fieldValues.push(this.getUint32(valueOffset + indexOffset));
130                         // Denominator
131                         fieldValues.push(this.getUint32(valueOffset + indexOffset + 4));
132                     } else {
133                         cc.log("Can't handle this field type or size");
134                     }
135                 } else {
136                     fieldValues.push(this.getBytes(fieldTypeLength, valueOffset + indexOffset));
137                 }
138             }
139         }
140 
141         if (fieldTypeName === 'ASCII') {
142             fieldValues.forEach(function (e, i, a) {
143                 a[i] = String.fromCharCode(e);
144             });
145         }
146         return fieldValues;
147     },
148 
149     getBytes: function (numBytes, offset) {
150         if (numBytes <= 0) {
151             cc.log("No bytes requested");
152         } else if (numBytes <= 1) {
153             return this.getUint8(offset);
154         } else if (numBytes <= 2) {
155             return this.getUint16(offset);
156         } else if (numBytes <= 3) {
157             return this.getUint32(offset) >>> 8;
158         } else if (numBytes <= 4) {
159             return this.getUint32(offset);
160         } else {
161             cc.log("Too many bytes requested");
162         }
163     },
164 
165     getBits: function (numBits, byteOffset, bitOffset) {
166         bitOffset = bitOffset || 0;
167         var extraBytes = Math.floor(bitOffset / 8);
168         var newByteOffset = byteOffset + extraBytes;
169         var totalBits = bitOffset + numBits;
170         var shiftRight = 32 - numBits;
171         var shiftLeft,rawBits;
172 
173         if (totalBits <= 0) {
174             console.log("No bits requested");
175         } else if (totalBits <= 8) {
176             shiftLeft = 24 + bitOffset;
177             rawBits = this.getUint8(newByteOffset);
178         } else if (totalBits <= 16) {
179             shiftLeft = 16 + bitOffset;
180             rawBits = this.getUint16(newByteOffset);
181         } else if (totalBits <= 32) {
182             shiftLeft = bitOffset;
183             rawBits = this.getUint32(newByteOffset);
184         } else {
185             console.log( "Too many bits requested" );
186         }
187 
188         return {
189             'bits': ((rawBits << shiftLeft) >>> shiftRight),
190             'byteOffset': newByteOffset + Math.floor(totalBits / 8),
191             'bitOffset': totalBits % 8
192         };
193     },
194 
195     parseFileDirectory: function (byteOffset) {
196         var numDirEntries = this.getUint16(byteOffset);
197         var tiffFields = [];
198 
199         for (var i = byteOffset + 2, entryCount = 0; entryCount < numDirEntries; i += 12, entryCount++) {
200             var fieldTag = this.getUint16(i);
201             var fieldType = this.getUint16(i + 2);
202             var typeCount = this.getUint32(i + 4);
203             var valueOffset = this.getUint32(i + 8);
204 
205             var fieldTagName = this.getFieldTagName(fieldTag);
206             var fieldTypeName = this.getFieldTypeName(fieldType);
207             var fieldValues = this.getFieldValues(fieldTagName, fieldTypeName, typeCount, valueOffset);
208 
209             tiffFields[fieldTagName] = { type: fieldTypeName, values: fieldValues };
210         }
211 
212         this.fileDirectories.push(tiffFields);
213 
214         var nextIFDByteOffset = this.getUint32(i);
215         if (nextIFDByteOffset !== 0x00000000) {
216             this.parseFileDirectory(nextIFDByteOffset);
217         }
218     },
219 
220     clampColorSample: function(colorSample, bitsPerSample) {
221         var multiplier = Math.pow(2, 8 - bitsPerSample);
222 
223         return Math.floor((colorSample * multiplier) + (multiplier - 1));
224     },
225 
226     parseTIFF: function (tiffData, canvas) {
227         canvas = canvas || document.createElement('canvas');
228 
229         this._tiffData = tiffData;
230         this.canvas = canvas;
231 
232         this.checkLittleEndian();
233 
234         if (!this.hasTowel()) {
235             return;
236         }
237 
238         var firstIFDByteOffset = this.getUint32(4);
239 
240         this.fileDirectories = [];
241         this.parseFileDirectory(firstIFDByteOffset);
242 
243         var fileDirectory = this.fileDirectories[0];
244 
245         var imageWidth = fileDirectory['ImageWidth'].values[0];
246         var imageLength = fileDirectory['ImageLength'].values[0];
247 
248         this.canvas.width = imageWidth;
249         this.canvas.height = imageLength;
250 
251         var strips = [];
252 
253         var compression = (fileDirectory['Compression']) ? fileDirectory['Compression'].values[0] : 1;
254 
255         var samplesPerPixel = fileDirectory['SamplesPerPixel'].values[0];
256 
257         var sampleProperties = [];
258 
259         var bitsPerPixel = 0;
260         var hasBytesPerPixel = false;
261 
262         fileDirectory['BitsPerSample'].values.forEach(function (bitsPerSample, i, bitsPerSampleValues) {
263             sampleProperties[i] = {
264                 'bitsPerSample': bitsPerSample,
265                 'hasBytesPerSample': false,
266                 'bytesPerSample': undefined
267             };
268 
269             if ((bitsPerSample % 8) === 0) {
270                 sampleProperties[i].hasBytesPerSample = true;
271                 sampleProperties[i].bytesPerSample = bitsPerSample / 8;
272             }
273 
274             bitsPerPixel += bitsPerSample;
275         }, this);
276 
277         if ((bitsPerPixel % 8) === 0) {
278             hasBytesPerPixel = true;
279             var bytesPerPixel = bitsPerPixel / 8;
280         }
281 
282         var stripOffsetValues = fileDirectory['StripOffsets'].values;
283         var numStripOffsetValues = stripOffsetValues.length;
284 
285         // StripByteCounts is supposed to be required, but see if we can recover anyway.
286         if (fileDirectory['StripByteCounts']) {
287             var stripByteCountValues = fileDirectory['StripByteCounts'].values;
288         } else {
289             cc.log("Missing StripByteCounts!");
290 
291             // Infer StripByteCounts, if possible.
292             if (numStripOffsetValues === 1) {
293                 var stripByteCountValues = [Math.ceil((imageWidth * imageLength * bitsPerPixel) / 8)];
294             } else {
295                 throw Error("Cannot recover from missing StripByteCounts");
296             }
297         }
298 
299         // Loop through strips and decompress as necessary.
300         for (var i = 0; i < numStripOffsetValues; i++) {
301             var stripOffset = stripOffsetValues[i];
302             strips[i] = [];
303 
304             var stripByteCount = stripByteCountValues[i];
305 
306             // Loop through pixels.
307             for (var byteOffset = 0, bitOffset = 0, jIncrement = 1, getHeader = true, pixel = [], numBytes = 0, sample = 0, currentSample = 0;
308                  byteOffset < stripByteCount; byteOffset += jIncrement) {
309                 // Decompress strip.
310                 switch (compression) {
311                     // Uncompressed
312                     case 1:
313                         // Loop through samples (sub-pixels).
314                         for (var m = 0, pixel = []; m < samplesPerPixel; m++) {
315                             if (sampleProperties[m].hasBytesPerSample) {
316                                 // XXX: This is wrong!
317                                 var sampleOffset = sampleProperties[m].bytesPerSample * m;
318                                 pixel.push(this.getBytes(sampleProperties[m].bytesPerSample, stripOffset + byteOffset + sampleOffset));
319                             } else {
320                                 var sampleInfo = this.getBits(sampleProperties[m].bitsPerSample, stripOffset + byteOffset, bitOffset);
321                                 pixel.push(sampleInfo.bits);
322                                 byteOffset = sampleInfo.byteOffset - stripOffset;
323                                 bitOffset = sampleInfo.bitOffset;
324 
325                                 throw RangeError("Cannot handle sub-byte bits per sample");
326                             }
327                         }
328 
329                         strips[i].push(pixel);
330 
331                         if (hasBytesPerPixel) {
332                             jIncrement = bytesPerPixel;
333                         } else {
334                             jIncrement = 0;
335                             throw RangeError("Cannot handle sub-byte bits per pixel");
336                         }
337                         break;
338 
339                     // CITT Group 3 1-Dimensional Modified Huffman run-length encoding
340                     case 2:
341                         // XXX: Use PDF.js code?
342                         break;
343 
344                     // Group 3 Fax
345                     case 3:
346                         // XXX: Use PDF.js code?
347                         break;
348 
349                     // Group 4 Fax
350                     case 4:
351                         // XXX: Use PDF.js code?
352                         break;
353 
354                     // LZW
355                     case 5:
356                         // XXX: Use PDF.js code?
357                         break;
358 
359                     // Old-style JPEG (TIFF 6.0)
360                     case 6:
361                         // XXX: Use PDF.js code?
362                         break;
363 
364                     // New-style JPEG (TIFF Specification Supplement 2)
365                     case 7:
366                         // XXX: Use PDF.js code?
367                         break;
368 
369                     // PackBits
370                     case 32773:
371                         // Are we ready for a new block?
372                         if (getHeader) {
373                             getHeader = false;
374 
375                             var blockLength = 1;
376                             var iterations = 1;
377 
378                             // The header byte is signed.
379                             var header = this.getInt8(stripOffset + byteOffset);
380 
381                             if ((header >= 0) && (header <= 127)) { // Normal pixels.
382                                 blockLength = header + 1;
383                             } else if ((header >= -127) && (header <= -1)) { // Collapsed pixels.
384                                 iterations = -header + 1;
385                             } else /*if (header === -128)*/ { // Placeholder byte?
386                                 getHeader = true;
387                             }
388                         } else {
389                             var currentByte = this.getUint8(stripOffset + byteOffset);
390 
391                             // Duplicate bytes, if necessary.
392                             for (var m = 0; m < iterations; m++) {
393                                 if (sampleProperties[sample].hasBytesPerSample) {
394                                     // We're reading one byte at a time, so we need to handle multi-byte samples.
395                                     currentSample = (currentSample << (8 * numBytes)) | currentByte;
396                                     numBytes++;
397 
398                                     // Is our sample complete?
399                                     if (numBytes === sampleProperties[sample].bytesPerSample) {
400                                         pixel.push(currentSample);
401                                         currentSample = numBytes = 0;
402                                         sample++;
403                                     }
404                                 } else {
405                                     throw RangeError("Cannot handle sub-byte bits per sample");
406                                 }
407 
408                                 // Is our pixel complete?
409                                 if (sample === samplesPerPixel) {
410                                     strips[i].push(pixel);
411                                     pixel = [];
412                                     sample = 0;
413                                 }
414                             }
415 
416                             blockLength--;
417 
418                             // Is our block complete?
419                             if (blockLength === 0) {
420                                 getHeader = true;
421                             }
422                         }
423 
424                         jIncrement = 1;
425                         break;
426 
427                     // Unknown compression algorithm
428                     default:
429                         // Do not attempt to parse the image data.
430                         break;
431                 }
432             }
433         }
434 
435         if (canvas.getContext) {
436             var ctx = this.canvas.getContext("2d");
437 
438             // Set a default fill style.
439             ctx.fillStyle = "rgba(255, 255, 255, 0)";
440 
441             // If RowsPerStrip is missing, the whole image is in one strip.
442             var rowsPerStrip = fileDirectory['RowsPerStrip'] ? fileDirectory['RowsPerStrip'].values[0] : imageLength;
443 
444             var numStrips = strips.length;
445 
446             var imageLengthModRowsPerStrip = imageLength % rowsPerStrip;
447             var rowsInLastStrip = (imageLengthModRowsPerStrip === 0) ? rowsPerStrip : imageLengthModRowsPerStrip;
448 
449             var numRowsInStrip = rowsPerStrip;
450             var numRowsInPreviousStrip = 0;
451 
452             var photometricInterpretation = fileDirectory['PhotometricInterpretation'].values[0];
453 
454             var extraSamplesValues = [];
455             var numExtraSamples = 0;
456 
457             if (fileDirectory['ExtraSamples']) {
458                 extraSamplesValues = fileDirectory['ExtraSamples'].values;
459                 numExtraSamples = extraSamplesValues.length;
460             }
461 
462             if (fileDirectory['ColorMap']) {
463                 var colorMapValues = fileDirectory['ColorMap'].values;
464                 var colorMapSampleSize = Math.pow(2, sampleProperties[0].bitsPerSample);
465             }
466 
467             // Loop through the strips in the image.
468             for (var i = 0; i < numStrips; i++) {
469                 // The last strip may be short.
470                 if ((i + 1) === numStrips) {
471                     numRowsInStrip = rowsInLastStrip;
472                 }
473 
474                 var numPixels = strips[i].length;
475                 var yPadding = numRowsInPreviousStrip * i;
476 
477                 // Loop through the rows in the strip.
478                 for (var y = 0, j = 0; y < numRowsInStrip, j < numPixels; y++) {
479                     // Loop through the pixels in the row.
480                     for (var x = 0; x < imageWidth; x++, j++) {
481                         var pixelSamples = strips[i][j];
482 
483                         var red = 0;
484                         var green = 0;
485                         var blue = 0;
486                         var opacity = 1.0;
487 
488                         if (numExtraSamples > 0) {
489                             for (var k = 0; k < numExtraSamples; k++) {
490                                 if (extraSamplesValues[k] === 1 || extraSamplesValues[k] === 2) {
491                                     // Clamp opacity to the range [0,1].
492                                     opacity = pixelSamples[3 + k] / 256;
493 
494                                     break;
495                                 }
496                             }
497                         }
498 
499                         switch (photometricInterpretation) {
500                             // Bilevel or Grayscale
501                             // WhiteIsZero
502                             case 0:
503                                 if (sampleProperties[0].hasBytesPerSample) {
504                                     var invertValue = Math.pow(0x10, sampleProperties[0].bytesPerSample * 2);
505                                 }
506 
507                                 // Invert samples.
508                                 pixelSamples.forEach(function (sample, index, samples) {
509                                     samples[index] = invertValue - sample;
510                                 });
511 
512                             // Bilevel or Grayscale
513                             // BlackIsZero
514                             case 1:
515                                 red = green = blue = this.clampColorSample(pixelSamples[0], sampleProperties[0].bitsPerSample);
516                                 break;
517 
518                             // RGB Full Color
519                             case 2:
520                                 red = this.clampColorSample(pixelSamples[0], sampleProperties[0].bitsPerSample);
521                                 green = this.clampColorSample(pixelSamples[1], sampleProperties[1].bitsPerSample);
522                                 blue = this.clampColorSample(pixelSamples[2], sampleProperties[2].bitsPerSample);
523                                 break;
524 
525                             // RGB Color Palette
526                             case 3:
527                                 if (colorMapValues === undefined) {
528                                     throw Error("Palette image missing color map");
529                                 }
530 
531                                 var colorMapIndex = pixelSamples[0];
532 
533                                 red = this.clampColorSample(colorMapValues[colorMapIndex], 16);
534                                 green = this.clampColorSample(colorMapValues[colorMapSampleSize + colorMapIndex], 16);
535                                 blue = this.clampColorSample(colorMapValues[(2 * colorMapSampleSize) + colorMapIndex], 16);
536                                 break;
537 
538                             // Unknown Photometric Interpretation
539                             default:
540                                 throw RangeError('Unknown Photometric Interpretation:', photometricInterpretation);
541                                 break;
542                         }
543 
544                         ctx.fillStyle = "rgba(" + red + ", " + green + ", " + blue + ", " + opacity + ")";
545                         ctx.fillRect(x, yPadding + y, 1, 1);
546                     }
547                 }
548 
549                 numRowsInPreviousStrip = numRowsInStrip;
550             }
551         }
552 
553         return this.canvas;
554     },
555 
556     // See: http://www.digitizationguidelines.gov/guidelines/TIFF_Metadata_Final.pdf
557     // See: http://www.digitalpreservation.gov/formats/content/tiff_tags.shtml
558     fieldTagNames: {
559         // TIFF Baseline
560         0x013B: 'Artist',
561         0x0102: 'BitsPerSample',
562         0x0109: 'CellLength',
563         0x0108: 'CellWidth',
564         0x0140: 'ColorMap',
565         0x0103: 'Compression',
566         0x8298: 'Copyright',
567         0x0132: 'DateTime',
568         0x0152: 'ExtraSamples',
569         0x010A: 'FillOrder',
570         0x0121: 'FreeByteCounts',
571         0x0120: 'FreeOffsets',
572         0x0123: 'GrayResponseCurve',
573         0x0122: 'GrayResponseUnit',
574         0x013C: 'HostComputer',
575         0x010E: 'ImageDescription',
576         0x0101: 'ImageLength',
577         0x0100: 'ImageWidth',
578         0x010F: 'Make',
579         0x0119: 'MaxSampleValue',
580         0x0118: 'MinSampleValue',
581         0x0110: 'Model',
582         0x00FE: 'NewSubfileType',
583         0x0112: 'Orientation',
584         0x0106: 'PhotometricInterpretation',
585         0x011C: 'PlanarConfiguration',
586         0x0128: 'ResolutionUnit',
587         0x0116: 'RowsPerStrip',
588         0x0115: 'SamplesPerPixel',
589         0x0131: 'Software',
590         0x0117: 'StripByteCounts',
591         0x0111: 'StripOffsets',
592         0x00FF: 'SubfileType',
593         0x0107: 'Threshholding',
594         0x011A: 'XResolution',
595         0x011B: 'YResolution',
596 
597         // TIFF Extended
598         0x0146: 'BadFaxLines',
599         0x0147: 'CleanFaxData',
600         0x0157: 'ClipPath',
601         0x0148: 'ConsecutiveBadFaxLines',
602         0x01B1: 'Decode',
603         0x01B2: 'DefaultImageColor',
604         0x010D: 'DocumentName',
605         0x0150: 'DotRange',
606         0x0141: 'HalftoneHints',
607         0x015A: 'Indexed',
608         0x015B: 'JPEGTables',
609         0x011D: 'PageName',
610         0x0129: 'PageNumber',
611         0x013D: 'Predictor',
612         0x013F: 'PrimaryChromaticities',
613         0x0214: 'ReferenceBlackWhite',
614         0x0153: 'SampleFormat',
615         0x022F: 'StripRowCounts',
616         0x014A: 'SubIFDs',
617         0x0124: 'T4Options',
618         0x0125: 'T6Options',
619         0x0145: 'TileByteCounts',
620         0x0143: 'TileLength',
621         0x0144: 'TileOffsets',
622         0x0142: 'TileWidth',
623         0x012D: 'TransferFunction',
624         0x013E: 'WhitePoint',
625         0x0158: 'XClipPathUnits',
626         0x011E: 'XPosition',
627         0x0211: 'YCbCrCoefficients',
628         0x0213: 'YCbCrPositioning',
629         0x0212: 'YCbCrSubSampling',
630         0x0159: 'YClipPathUnits',
631         0x011F: 'YPosition',
632 
633         // EXIF
634         0x9202: 'ApertureValue',
635         0xA001: 'ColorSpace',
636         0x9004: 'DateTimeDigitized',
637         0x9003: 'DateTimeOriginal',
638         0x8769: 'Exif IFD',
639         0x9000: 'ExifVersion',
640         0x829A: 'ExposureTime',
641         0xA300: 'FileSource',
642         0x9209: 'Flash',
643         0xA000: 'FlashpixVersion',
644         0x829D: 'FNumber',
645         0xA420: 'ImageUniqueID',
646         0x9208: 'LightSource',
647         0x927C: 'MakerNote',
648         0x9201: 'ShutterSpeedValue',
649         0x9286: 'UserComment',
650 
651         // IPTC
652         0x83BB: 'IPTC',
653 
654         // ICC
655         0x8773: 'ICC Profile',
656 
657         // XMP
658         0x02BC: 'XMP',
659 
660         // GDAL
661         0xA480: 'GDAL_METADATA',
662         0xA481: 'GDAL_NODATA',
663 
664         // Photoshop
665         0x8649: 'Photoshop'
666     },
667 
668     fieldTypeNames: {
669         0x0001: 'BYTE',
670         0x0002: 'ASCII',
671         0x0003: 'SHORT',
672         0x0004: 'LONG',
673         0x0005: 'RATIONAL',
674         0x0006: 'SBYTE',
675         0x0007: 'UNDEFINED',
676         0x0008: 'SSHORT',
677         0x0009: 'SLONG',
678         0x000A: 'SRATIONAL',
679         0x000B: 'FLOAT',
680         0x000C: 'DOUBLE'
681     }
682 });
683 
684 cc.TIFFReader.__instance = null;
685 cc.TIFFReader.getInstance = function () {
686     if (!cc.TIFFReader.__instance)
687         cc.TIFFReader.__instance = new cc.TIFFReader();
688     return cc.TIFFReader.__instance;
689 };
690