View Javadoc

1   /*
2    * ImageInfo.java
3    *
4    * Version 1.6
5    *
6    * A Java class to determine image width, height and color depth for
7    * a number of image file formats.
8    *
9    * Written by Marco Schmidt 
10   * <http://www.geocities.com/marcoschmidt.geo/contact.html>.
11   *
12   * Contributed to the Public Domain.
13   */
14  package com.atlassian.core.util;
15  
16  import java.io.DataInput;
17  import java.io.EOFException;
18  import java.io.FileInputStream;
19  import java.io.InputStream;
20  import java.io.IOException;
21  import java.net.URL;
22  import java.util.Vector;
23  
24  /**
25   * Get file format, image resolution, number of bits per pixel and optionally number of images, comments and physical
26   * resolution from JPEG, GIF, BMP, PCX, PNG, IFF, RAS, PBM, PGM, PPM, PSD and SWF files (or input streams).
27   * <p>
28   * Use the class like this:
29   * 
30   * <pre>
31   * ImageInfo ii = new ImageInfo();
32   * ii.setInput(in); // in can be InputStream or RandomAccessFile
33   * ii.setDetermineImageNumber(true); // default is false
34   * ii.setCollectComments(true); // default is false
35   * if (!ii.check())
36   * {
37   *     System.err.println(&quot;Not a supported image file format.&quot;);
38   *     return;
39   * }
40   * System.out.println(ii.getFormatName() + &quot;, &quot; + ii.getMimeType() + &quot;, &quot; + ii.getWidth() + &quot; x &quot; + ii.getHeight()
41   *         + &quot; pixels, &quot; + ii.getBitsPerPixel() + &quot; bits per pixel, &quot; + ii.getNumberOfImages() + &quot; image(s), &quot;
42   *         + ii.getNumberOfComments() + &quot; comment(s).&quot;);
43   * // there are other properties, check out the API documentation
44   * </pre>
45   * 
46   * You can also use this class as a command line program. Call it with a number of image file names and URLs as
47   * parameters:
48   * 
49   * <pre>
50   *   java ImageInfo *.jpg *.png *.gif http://somesite.tld/image.jpg
51   * </pre>
52   * 
53   * or call it without parameters and pipe data to it:
54   * 
55   * <pre>
56   *   java ImageInfo &lt; image.jpg
57   * </pre>
58   * <p>
59   * Known limitations:
60   * <ul>
61   * <li>When the determination of the number of images is turned off, GIF bits per pixel are only read from the global
62   * header. For some GIFs, local palettes change this to a typically larger value. To be certain to get the correct color
63   * depth, call setDetermineImageNumber(true) before calling check(). The complete scan over the GIF file will take
64   * additional time.</li>
65   * <li>Transparency information is not included in the bits per pixel count. Actually, it was my decision not to include
66   * those bits, so it's a feature! ;-)</li>
67   * </ul>
68   * <p>
69   * Requirements:
70   * <ul>
71   * <li>Java 1.1 or higher</li>
72   * </ul>
73   * <p>
74   * The latest version can be found at <a
75   * href="http://www.geocities.com/marcoschmidt.geo/image-info.html">http://www.geocities
76   * .com/marcoschmidt.geo/image-info.html</a>.
77   * <p>
78   * Written by <a href="http://www.geocities.com/marcoschmidt.geo/contact.html">Marco Schmidt</a>.
79   * <p>
80   * This class is contributed to the Public Domain. Use it at your own risk.
81   * <p>
82   * Last modification 2005-01-02.
83   * <p>
84   * <a name="history">History</a>:
85   * <ul>
86   * <li><strong>2001-08-24</strong> Initial version.</li>
87   * <li><strong>2001-10-13</strong> Added support for the file formats BMP and PCX.</li>
88   * <li><strong>2001-10-16</strong> Fixed bug in read(int[], int, int) that returned
89   * <li><strong>2002-01-22</strong> Added support for file formats Amiga IFF and Sun Raster (RAS).</li>
90   * <li><strong>2002-01-24</strong> Added support for file formats Portable Bitmap / Graymap / Pixmap (PBM, PGM, PPM) and
91   * Adobe Photoshop (PSD). Added new method getMimeType() to return the MIME type associated with a particular file
92   * format.</li>
93   * <li><strong>2002-03-15</strong> Added support to recognize number of images in file. Only works with GIF. Use
94   * {@link #setDetermineImageNumber} with <code>true</code> as argument to identify animated GIFs (
95   * {@link #getNumberOfImages()} will return a value larger than <code>1</code>).</li>
96   * <li><strong>2002-04-10</strong> Fixed a bug in the feature 'determine number of images in animated GIF' introduced
97   * with version 1.1. Thanks to Marcelo P. Lima for sending in the bug report. Released as 1.1.1.</li>
98   * <li><strong>2002-04-18</strong> Added {@link #setCollectComments(boolean)}. That new method lets the user specify
99   * whether textual comments are to be stored in an internal list when encountered in an input image file / stream. Added
100  * two methods to return the physical width and height of the image in dpi: {@link #getPhysicalWidthDpi()} and
101  * {@link #getPhysicalHeightDpi()}. If the physical resolution could not be retrieved, these methods return
102  * <code>-1</code>.</li>
103  * <li><strong>2002-04-23</strong> Added support for the new properties physical resolution and comments for some
104  * formats. Released as 1.2.</li>
105  * <li><strong>2002-06-17</strong> Added support for SWF, sent in by Michael Aird. Changed checkJpeg() so that other APP
106  * markers than APP0 will not lead to a failure anymore. Released as 1.3.</li>
107  * <li><strong>2003-07-28</strong> Bug fix - skip method now takes return values into consideration. Less bytes than
108  * necessary may have been skipped, leading to flaws in the retrieved information in some cases. Thanks to Bernard
109  * Bernstein for pointing that out. Released as 1.4.</li>
110  * <li><strong>2004-02-29</strong> Added support for recognizing progressive JPEG and interlaced PNG and GIF. A new
111  * method {@link #isProgressive()} returns whether ImageInfo has found that the storage type is progressive (or
112  * interlaced). Thanks to Joe Germuska for suggesting the feature. Bug fix: BMP physical resolution is now correctly
113  * determined. Released as 1.5.</li>
114  * <li><strong>2004-11-30</strong> Bug fix: recognizing progressive GIFs (interlaced in GIF terminology) did not work
115  * (thanks to Franz Jeitler for pointing this out). Now it should work, but only if the number of images is determined.
116  * This is because information on interlacing is stored in a local image header. In theory, different images could be
117  * stored interlaced and non-interlaced in one file. However, I think that's unlikely. Right now, the last image in the
118  * GIF file that is examined by ImageInfo is used for the "progressive" status.</li>
119  * <li><strong>2005-01-02</strong> Some code clean up (unused methods and variables commented out, missing javadoc
120  * comments, etc.). Thanks to George Sexton for a long list. Removed usage of Boolean.toString because it's a Java 1.4+
121  * feature (thanks to Gregor Dupont). Changed delimiter character in compact output from semicolon to tabulator (for
122  * better integration with cut(1) and other Unix tools). Added some points to the <a
123  * href="http://www.geocities.com/marcoschmidt.geo/image-info.html#knownissues">'Known issues' section of the
124  * website</a>. Released as 1.6.</li>
125  * </ul>
126  * 
127  * @author Marco Schmidt
128  */
129 public class ImageInfo
130 {
131     /**
132      * Return value of {@link #getFormat()} for JPEG streams. ImageInfo can extract physical resolution and comments
133      * from JPEGs (only from APP0 headers). Only one image can be stored in a file. It is determined whether the JPEG
134      * stream is progressive (see {@link #isProgressive()}).
135      */
136     public static final int FORMAT_JPEG = 0;
137 
138     /**
139      * Return value of {@link #getFormat()} for GIF streams. ImageInfo can extract comments from GIFs and count the
140      * number of images (GIFs with more than one image are animations). If you know of a place where GIFs store the
141      * physical resolution of an image, please <a href="http://www.geocities.com/marcoschmidt.geo/contact.html">send me
142      * a mail</a>! It is determined whether the GIF stream is interlaced (see {@link #isProgressive()}).
143      */
144     public static final int FORMAT_GIF = 1;
145 
146     /**
147      * Return value of {@link #getFormat()} for PNG streams. PNG only supports one image per file. Both physical
148      * resolution and comments can be stored with PNG, but ImageInfo is currently not able to extract those. It is
149      * determined whether the PNG stream is interlaced (see {@link #isProgressive()}).
150      */
151     public static final int FORMAT_PNG = 2;
152 
153     /**
154      * Return value of {@link #getFormat()} for BMP streams. BMP only supports one image per file. BMP does not allow
155      * for comments. The physical resolution can be stored.
156      */
157     public static final int FORMAT_BMP = 3;
158 
159     /**
160      * Return value of {@link #getFormat()} for PCX streams. PCX does not allow for comments or more than one image per
161      * file. However, the physical resolution can be stored.
162      */
163     public static final int FORMAT_PCX = 4;
164 
165     /**
166      * Return value of {@link #getFormat()} for IFF streams.
167      */
168     public static final int FORMAT_IFF = 5;
169 
170     /**
171      * Return value of {@link #getFormat()} for RAS streams. Sun Raster allows for one image per file only and is not
172      * able to store physical resolution or comments.
173      */
174     public static final int FORMAT_RAS = 6;
175 
176     /** Return value of {@link #getFormat()} for PBM streams. */
177     public static final int FORMAT_PBM = 7;
178 
179     /** Return value of {@link #getFormat()} for PGM streams. */
180     public static final int FORMAT_PGM = 8;
181 
182     /** Return value of {@link #getFormat()} for PPM streams. */
183     public static final int FORMAT_PPM = 9;
184 
185     /** Return value of {@link #getFormat()} for PSD streams. */
186     public static final int FORMAT_PSD = 10;
187 
188     /** Return value of {@link #getFormat()} for SWF (Shockwave) streams. */
189     public static final int FORMAT_SWF = 11;
190 
191     /**
192      * The names of all supported file formats. The FORMAT_xyz int constants can be used as index values for this array.
193      */
194     private static final String[] FORMAT_NAMES = { "JPEG", "GIF", "PNG", "BMP", "PCX", "IFF", "RAS", "PBM", "PGM",
195             "PPM", "PSD", "SWF" };
196 
197     /**
198      * The names of the MIME types for all supported file formats. The FORMAT_xyz int constants can be used as index
199      * values for this array.
200      */
201     private static final String[] MIME_TYPE_STRINGS = { "image/jpeg", "image/gif", "image/png", "image/bmp",
202             "image/pcx", "image/iff", "image/ras", "image/x-portable-bitmap", "image/x-portable-graymap",
203             "image/x-portable-pixmap", "image/psd", "application/x-shockwave-flash" };
204 
205     private int width;
206     private int height;
207     private int bitsPerPixel;
208     private boolean progressive;
209     private int format;
210     private InputStream in;
211     private DataInput din;
212     private boolean collectComments = true;
213     private Vector comments;
214     private boolean determineNumberOfImages;
215     private int numberOfImages;
216     private int physicalHeightDpi;
217     private int physicalWidthDpi;
218     private int bitBuf;
219     private int bitPos;
220 
221     private void addComment(String s)
222     {
223         if (comments == null)
224         {
225             comments = new Vector();
226         }
227         comments.addElement(s);
228     }
229 
230     /**
231      * Call this method after you have provided an input stream or file using {@link #setInput(InputStream)} or
232      * {@link #setInput(DataInput)}. If true is returned, the file format was known and information on the file's
233      * content can be retrieved using the various getXyz methods.
234      * 
235      * @return if information could be retrieved from input
236      */
237     public boolean check()
238     {
239         format = -1;
240         width = -1;
241         height = -1;
242         bitsPerPixel = -1;
243         numberOfImages = 1;
244         physicalHeightDpi = -1;
245         physicalWidthDpi = -1;
246         comments = null;
247         try
248         {
249             int b1 = read() & 0xff;
250             int b2 = read() & 0xff;
251             if (b1 == 0x47 && b2 == 0x49)
252             {
253                 return checkGif();
254             }
255             else if (b1 == 0x89 && b2 == 0x50)
256             {
257                 return checkPng();
258             }
259             else if (b1 == 0xff && b2 == 0xd8)
260             {
261                 return checkJpeg();
262             }
263             else if (b1 == 0x42 && b2 == 0x4d)
264             {
265                 return checkBmp();
266             }
267             else if (b1 == 0x0a && b2 < 0x06)
268             {
269                 return checkPcx();
270             }
271             else if (b1 == 0x46 && b2 == 0x4f)
272             {
273                 return checkIff();
274             }
275             else if (b1 == 0x59 && b2 == 0xa6)
276             {
277                 return checkRas();
278             }
279             else if (b1 == 0x50 && b2 >= 0x31 && b2 <= 0x36)
280             {
281                 return checkPnm(b2 - '0');
282             }
283             else if (b1 == 0x38 && b2 == 0x42)
284             {
285                 return checkPsd();
286             }
287             else if (b1 == 0x46 && b2 == 0x57)
288             {
289                 return checkSwf();
290             }
291             else
292             {
293                 return false;
294             }
295         }
296         catch (IOException ioe)
297         {
298             return false;
299         }
300     }
301 
302     private boolean checkBmp() throws IOException
303     {
304         byte[] a = new byte[44];
305         if (read(a) != a.length)
306         {
307             return false;
308         }
309         width = getIntLittleEndian(a, 16);
310         height = getIntLittleEndian(a, 20);
311         if (width < 1 || height < 1)
312         {
313             return false;
314         }
315         bitsPerPixel = getShortLittleEndian(a, 26);
316         if (bitsPerPixel != 1 && bitsPerPixel != 4 && bitsPerPixel != 8 && bitsPerPixel != 16 && bitsPerPixel != 24
317                 && bitsPerPixel != 32)
318         {
319             return false;
320         }
321         int x = (int) (getIntLittleEndian(a, 36) * 0.0254);
322         if (x > 0)
323         {
324             setPhysicalWidthDpi(x);
325         }
326         int y = (int) (getIntLittleEndian(a, 40) * 0.0254);
327         if (y > 0)
328         {
329             setPhysicalHeightDpi(y);
330         }
331         format = FORMAT_BMP;
332         return true;
333     }
334 
335     private boolean checkGif() throws IOException
336     {
337         final byte[] GIF_MAGIC_87A = { 0x46, 0x38, 0x37, 0x61 };
338         final byte[] GIF_MAGIC_89A = { 0x46, 0x38, 0x39, 0x61 };
339         byte[] a = new byte[11]; // 4 from the GIF signature + 7 from the global header
340         if (read(a) != 11)
341         {
342             return false;
343         }
344         if ((!equals(a, 0, GIF_MAGIC_89A, 0, 4)) && (!equals(a, 0, GIF_MAGIC_87A, 0, 4)))
345         {
346             return false;
347         }
348         format = FORMAT_GIF;
349         width = getShortLittleEndian(a, 4);
350         height = getShortLittleEndian(a, 6);
351         int flags = a[8] & 0xff;
352         bitsPerPixel = ((flags >> 4) & 0x07) + 1;
353         // progressive = (flags & 0x02) != 0;
354         if (!determineNumberOfImages)
355         {
356             return true;
357         }
358         // skip global color palette
359         if ((flags & 0x80) != 0)
360         {
361             int tableSize = (1 << ((flags & 7) + 1)) * 3;
362             skip(tableSize);
363         }
364         numberOfImages = 0;
365         int blockType;
366         do
367         {
368             blockType = read();
369             switch (blockType)
370             {
371             case (0x2c): // image separator
372             {
373                 if (read(a, 0, 9) != 9)
374                 {
375                     return false;
376                 }
377                 flags = a[8] & 0xff;
378                 progressive = (flags & 0x40) != 0;
379                 int localBitsPerPixel = (flags & 0x07) + 1;
380                 if (localBitsPerPixel > bitsPerPixel)
381                 {
382                     bitsPerPixel = localBitsPerPixel;
383                 }
384                 if ((flags & 0x80) != 0)
385                 {
386                     skip((1 << localBitsPerPixel) * 3);
387                 }
388                 skip(1); // initial code length
389                 int n;
390                 do
391                 {
392                     n = read();
393                     if (n > 0)
394                     {
395                         skip(n);
396                     }
397                     else if (n == -1)
398                     {
399                         return false;
400                     }
401                 }
402                 while (n > 0);
403                 numberOfImages++;
404                 break;
405             }
406             case (0x21): // extension
407             {
408                 int extensionType = read();
409                 if (collectComments && extensionType == 0xfe)
410                 {
411                     StringBuffer sb = new StringBuffer();
412                     int n;
413                     do
414                     {
415                         n = read();
416                         if (n == -1)
417                         {
418                             return false;
419                         }
420                         if (n > 0)
421                         {
422                             for (int i = 0; i < n; i++)
423                             {
424                                 int ch = read();
425                                 if (ch == -1)
426                                 {
427                                     return false;
428                                 }
429                                 sb.append((char) ch);
430                             }
431                         }
432                     }
433                     while (n > 0);
434                 }
435                 else
436                 {
437                     int n;
438                     do
439                     {
440                         n = read();
441                         if (n > 0)
442                         {
443                             skip(n);
444                         }
445                         else if (n == -1)
446                         {
447                             return false;
448                         }
449                     }
450                     while (n > 0);
451                 }
452                 break;
453             }
454             case (0x3b): // end of file
455             {
456                 break;
457             }
458             default:
459             {
460                 return false;
461             }
462             }
463         }
464         while (blockType != 0x3b);
465         return true;
466     }
467 
468     private boolean checkIff() throws IOException
469     {
470         byte[] a = new byte[10];
471         // read remaining 2 bytes of file id, 4 bytes file size
472         // and 4 bytes IFF subformat
473         if (read(a, 0, 10) != 10)
474         {
475             return false;
476         }
477         final byte[] IFF_RM = { 0x52, 0x4d };
478         if (!equals(a, 0, IFF_RM, 0, 2))
479         {
480             return false;
481         }
482         int type = getIntBigEndian(a, 6);
483         if (type != 0x494c424d && // type must be ILBM...
484                 type != 0x50424d20)
485         { // ...or PBM
486             return false;
487         }
488         // loop chunks to find BMHD chunk
489         do
490         {
491             if (read(a, 0, 8) != 8)
492             {
493                 return false;
494             }
495             int chunkId = getIntBigEndian(a, 0);
496             int size = getIntBigEndian(a, 4);
497             if ((size & 1) == 1)
498             {
499                 size++;
500             }
501             if (chunkId == 0x424d4844)
502             { // BMHD chunk
503                 if (read(a, 0, 9) != 9)
504                 {
505                     return false;
506                 }
507                 format = FORMAT_IFF;
508                 width = getShortBigEndian(a, 0);
509                 height = getShortBigEndian(a, 2);
510                 bitsPerPixel = a[8] & 0xff;
511                 return (width > 0 && height > 0 && bitsPerPixel > 0 && bitsPerPixel < 33);
512             }
513             else
514             {
515                 skip(size);
516             }
517         }
518         while (true);
519     }
520 
521     private boolean checkJpeg() throws IOException
522     {
523         byte[] data = new byte[12];
524         while (true)
525         {
526             if (read(data, 0, 4) != 4)
527             {
528                 return false;
529             }
530             int marker = getShortBigEndian(data, 0);
531             int size = getShortBigEndian(data, 2);
532             if ((marker & 0xff00) != 0xff00)
533             {
534                 return false; // not a valid marker
535             }
536             if (marker == 0xffe0)
537             { // APPx
538                 if (size < 14)
539                 {
540                     return false; // APPx header must be >= 14 bytes
541                 }
542                 if (read(data, 0, 12) != 12)
543                 {
544                     return false;
545                 }
546                 final byte[] APP0_ID = { 0x4a, 0x46, 0x49, 0x46, 0x00 };
547                 if (equals(APP0_ID, 0, data, 0, 5))
548                 {
549                     if (data[7] == 1)
550                     {
551                         setPhysicalWidthDpi(getShortBigEndian(data, 8));
552                         setPhysicalHeightDpi(getShortBigEndian(data, 10));
553                     }
554                     else if (data[7] == 2)
555                     {
556                         int x = getShortBigEndian(data, 8);
557                         int y = getShortBigEndian(data, 10);
558                         setPhysicalWidthDpi((int) (x * 2.54f));
559                         setPhysicalHeightDpi((int) (y * 2.54f));
560                     }
561                 }
562                 skip(size - 14);
563             }
564             else if (collectComments && size > 2 && marker == 0xfffe)
565             { // comment
566                 size -= 2;
567                 byte[] chars = new byte[size];
568                 if (read(chars, 0, size) != size)
569                 {
570                     return false;
571                 }
572                 String comment = new String(chars, "iso-8859-1");
573                 comment = comment.trim();
574                 addComment(comment);
575             }
576             else if (marker >= 0xffc0 && marker <= 0xffcf && marker != 0xffc4 && marker != 0xffc8)
577             {
578                 if (read(data, 0, 6) != 6)
579                 {
580                     return false;
581                 }
582                 format = FORMAT_JPEG;
583                 bitsPerPixel = (data[0] & 0xff) * (data[5] & 0xff);
584                 progressive = marker == 0xffc2 || marker == 0xffc6 || marker == 0xffca || marker == 0xffce;
585                 width = getShortBigEndian(data, 3);
586                 height = getShortBigEndian(data, 1);
587                 return true;
588             }
589             else
590             {
591                 skip(size - 2);
592             }
593         }
594     }
595 
596     private boolean checkPcx() throws IOException
597     {
598         byte[] a = new byte[64];
599         if (read(a) != a.length)
600         {
601             return false;
602         }
603         if (a[0] != 1)
604         { // encoding, 1=RLE is only valid value
605             return false;
606         }
607         // width / height
608         int x1 = getShortLittleEndian(a, 2);
609         int y1 = getShortLittleEndian(a, 4);
610         int x2 = getShortLittleEndian(a, 6);
611         int y2 = getShortLittleEndian(a, 8);
612         if (x1 < 0 || x2 < x1 || y1 < 0 || y2 < y1)
613         {
614             return false;
615         }
616         width = x2 - x1 + 1;
617         height = y2 - y1 + 1;
618         // color depth
619         int bits = a[1];
620         int planes = a[63];
621         if (planes == 1 && (bits == 1 || bits == 2 || bits == 4 || bits == 8))
622         {
623             // paletted
624             bitsPerPixel = bits;
625         }
626         else if (planes == 3 && bits == 8)
627         {
628             // RGB truecolor
629             bitsPerPixel = 24;
630         }
631         else
632         {
633             return false;
634         }
635         setPhysicalWidthDpi(getShortLittleEndian(a, 10));
636         setPhysicalHeightDpi(getShortLittleEndian(a, 10));
637         format = FORMAT_PCX;
638         return true;
639     }
640 
641     private boolean checkPng() throws IOException
642     {
643         final byte[] PNG_MAGIC = { 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
644         byte[] a = new byte[27];
645         if (read(a) != 27)
646         {
647             return false;
648         }
649         if (!equals(a, 0, PNG_MAGIC, 0, 6))
650         {
651             return false;
652         }
653         format = FORMAT_PNG;
654         width = getIntBigEndian(a, 14);
655         height = getIntBigEndian(a, 18);
656         bitsPerPixel = a[22] & 0xff;
657         int colorType = a[23] & 0xff;
658         if (colorType == 2 || colorType == 6)
659         {
660             bitsPerPixel *= 3;
661         }
662         progressive = (a[26] & 0xff) != 0;
663         return true;
664     }
665 
666     private boolean checkPnm(int id) throws IOException
667     {
668         if (id < 1 || id > 6)
669         {
670             return false;
671         }
672         final int[] PNM_FORMATS = { FORMAT_PBM, FORMAT_PGM, FORMAT_PPM };
673         format = PNM_FORMATS[(id - 1) % 3];
674         boolean hasPixelResolution = false;
675         String s;
676         while (true)
677         {
678             s = readLine();
679             if (s != null)
680             {
681                 s = s.trim();
682             }
683             if (s == null || s.length() < 1)
684             {
685                 continue;
686             }
687             if (s.charAt(0) == '#')
688             { // comment
689                 if (collectComments && s.length() > 1)
690                 {
691                     addComment(s.substring(1));
692                 }
693                 continue;
694             }
695             if (!hasPixelResolution)
696             { // split "343 966" into width=343, height=966
697                 int spaceIndex = s.indexOf(' ');
698                 if (spaceIndex == -1)
699                 {
700                     return false;
701                 }
702                 String widthString = s.substring(0, spaceIndex);
703                 spaceIndex = s.lastIndexOf(' ');
704                 if (spaceIndex == -1)
705                 {
706                     return false;
707                 }
708                 String heightString = s.substring(spaceIndex + 1);
709                 try
710                 {
711                     width = Integer.parseInt(widthString);
712                     height = Integer.parseInt(heightString);
713                 }
714                 catch (NumberFormatException nfe)
715                 {
716                     return false;
717                 }
718                 if (width < 1 || height < 1)
719                 {
720                     return false;
721                 }
722                 if (format == FORMAT_PBM)
723                 {
724                     bitsPerPixel = 1;
725                     return true;
726                 }
727                 hasPixelResolution = true;
728             }
729             else
730             {
731                 int maxSample;
732                 try
733                 {
734                     maxSample = Integer.parseInt(s);
735                 }
736                 catch (NumberFormatException nfe)
737                 {
738                     return false;
739                 }
740                 if (maxSample < 0)
741                 {
742                     return false;
743                 }
744                 for (int i = 0; i < 25; i++)
745                 {
746                     if (maxSample < (1 << (i + 1)))
747                     {
748                         bitsPerPixel = i + 1;
749                         if (format == FORMAT_PPM)
750                         {
751                             bitsPerPixel *= 3;
752                         }
753                         return true;
754                     }
755                 }
756                 return false;
757             }
758         }
759     }
760 
761     private boolean checkPsd() throws IOException
762     {
763         byte[] a = new byte[24];
764         if (read(a) != a.length)
765         {
766             return false;
767         }
768         final byte[] PSD_MAGIC = { 0x50, 0x53 };
769         if (!equals(a, 0, PSD_MAGIC, 0, 2))
770         {
771             return false;
772         }
773         format = FORMAT_PSD;
774         width = getIntBigEndian(a, 16);
775         height = getIntBigEndian(a, 12);
776         int channels = getShortBigEndian(a, 10);
777         int depth = getShortBigEndian(a, 20);
778         bitsPerPixel = channels * depth;
779         return (width > 0 && height > 0 && bitsPerPixel > 0 && bitsPerPixel <= 64);
780     }
781 
782     private boolean checkRas() throws IOException
783     {
784         byte[] a = new byte[14];
785         if (read(a) != a.length)
786         {
787             return false;
788         }
789         final byte[] RAS_MAGIC = { 0x6a, (byte) 0x95 };
790         if (!equals(a, 0, RAS_MAGIC, 0, 2))
791         {
792             return false;
793         }
794         format = FORMAT_RAS;
795         width = getIntBigEndian(a, 2);
796         height = getIntBigEndian(a, 6);
797         bitsPerPixel = getIntBigEndian(a, 10);
798         return (width > 0 && height > 0 && bitsPerPixel > 0 && bitsPerPixel <= 24);
799     }
800 
801     // Written by Michael Aird.
802     private boolean checkSwf() throws IOException
803     {
804         // get rid of the last byte of the signature, the byte of the version and 4 bytes of the size
805         byte[] a = new byte[6];
806         if (read(a) != a.length)
807         {
808             return false;
809         }
810         format = FORMAT_SWF;
811         int bitSize = (int) readUBits(5);
812         int maxX = readSBits(bitSize);
813         int maxY = readSBits(bitSize);
814         width = maxX / 20; // cause we're in twips
815         height = maxY / 20; // cause we're in twips
816         setPhysicalWidthDpi(72);
817         setPhysicalHeightDpi(72);
818         return (width > 0 && height > 0);
819     }
820 
821     /**
822      * Run over String list, return false iff at least one of the arguments equals <code>-c</code>.
823      * 
824      * @param args
825      *            string list to check
826      */
827     private static boolean determineVerbosity(String[] args)
828     {
829         if (args != null && args.length > 0)
830         {
831             for (int i = 0; i < args.length; i++)
832             {
833                 if ("-c".equals(args[i]))
834                 {
835                     return false;
836                 }
837             }
838         }
839         return true;
840     }
841 
842     private static boolean equals(byte[] a1, int offs1, byte[] a2, int offs2, int num)
843     {
844         while (num-- > 0)
845         {
846             if (a1[offs1++] != a2[offs2++])
847             {
848                 return false;
849             }
850         }
851         return true;
852     }
853 
854     /**
855      * If {@link #check()} was successful, returns the image's number of bits per pixel. Does not include transparency
856      * information like the alpha channel.
857      * 
858      * @return number of bits per image pixel
859      */
860     public int getBitsPerPixel()
861     {
862         return bitsPerPixel;
863     }
864 
865     /**
866      * Returns the index'th comment retrieved from the file.
867      * 
868      * @param index
869      *            int index of comment to return
870      * @throws IllegalArgumentException
871      *             if index is smaller than 0 or larger than or equal to the number of comments retrieved
872      * @see #getNumberOfComments
873      */
874     public String getComment(int index)
875     {
876         if (comments == null || index < 0 || index >= comments.size())
877         {
878             throw new IllegalArgumentException("Not a valid comment index: " + index);
879         }
880         return (String) comments.elementAt(index);
881     }
882 
883     /**
884      * If {@link #check()} was successful, returns the image format as one of the FORMAT_xyz constants from this class.
885      * Use {@link #getFormatName()} to get a textual description of the file format.
886      * 
887      * @return file format as a FORMAT_xyz constant
888      */
889     public int getFormat()
890     {
891         return format;
892     }
893 
894     /**
895      * If {@link #check()} was successful, returns the image format's name. Use {@link #getFormat()} to get a unique
896      * number.
897      * 
898      * @return file format name
899      */
900     public String getFormatName()
901     {
902         if (format >= 0 && format < FORMAT_NAMES.length)
903         {
904             return FORMAT_NAMES[format];
905         }
906         else
907         {
908             return "?";
909         }
910     }
911 
912     /**
913      * If {@link #check()} was successful, returns one the image's vertical resolution in pixels.
914      * 
915      * @return image height in pixels
916      */
917     public int getHeight()
918     {
919         return height;
920     }
921 
922     private static int getIntBigEndian(byte[] a, int offs)
923     {
924         return (a[offs] & 0xff) << 24 | (a[offs + 1] & 0xff) << 16 | (a[offs + 2] & 0xff) << 8 | a[offs + 3] & 0xff;
925     }
926 
927     private static int getIntLittleEndian(byte[] a, int offs)
928     {
929         return (a[offs + 3] & 0xff) << 24 | (a[offs + 2] & 0xff) << 16 | (a[offs + 1] & 0xff) << 8 | a[offs] & 0xff;
930     }
931 
932     /**
933      * If {@link #check()} was successful, returns a String with the MIME type of the format.
934      * 
935      * @return MIME type, e.g. <code>image/jpeg</code>
936      */
937     public String getMimeType()
938     {
939         if (format >= 0 && format < MIME_TYPE_STRINGS.length)
940         {
941             if (format == FORMAT_JPEG && progressive)
942             {
943                 return "image/pjpeg";
944             }
945             return MIME_TYPE_STRINGS[format];
946         }
947         else
948         {
949             return null;
950         }
951     }
952 
953     /**
954      * If {@link #check()} was successful and {@link #setCollectComments(boolean)} was called with <code>true</code> as
955      * argument, returns the number of comments retrieved from the input image stream / file. Any number &gt;= 0 and
956      * smaller than this number of comments is then a valid argument for the {@link #getComment(int)} method.
957      * 
958      * @return number of comments retrieved from input image
959      */
960     public int getNumberOfComments()
961     {
962         if (comments == null)
963         {
964             return 0;
965         }
966         else
967         {
968             return comments.size();
969         }
970     }
971 
972     /**
973      * Returns the number of images in the examined file. Assumes that <code>setDetermineImageNumber(true);</code> was
974      * called before a successful call to {@link #check()}. This value can currently be only different from
975      * <code>1</code> for GIF images.
976      * 
977      * @return number of images in file
978      */
979     public int getNumberOfImages()
980     {
981         return numberOfImages;
982     }
983 
984     /**
985      * Returns the physical height of this image in dots per inch (dpi). Assumes that {@link #check()} was successful.
986      * Returns <code>-1</code> on failure.
987      * 
988      * @return physical height (in dpi)
989      * @see #getPhysicalWidthDpi()
990      * @see #getPhysicalHeightInch()
991      */
992     public int getPhysicalHeightDpi()
993     {
994         return physicalHeightDpi;
995     }
996 
997     /**
998      * If {@link #check()} was successful, returns the physical width of this image in dpi (dots per inch) or -1 if no
999      * value could be found.
1000      * 
1001      * @return physical height (in dpi)
1002      * @see #getPhysicalHeightDpi()
1003      * @see #getPhysicalWidthDpi()
1004      * @see #getPhysicalWidthInch()
1005      */
1006     public float getPhysicalHeightInch()
1007     {
1008         int h = getHeight();
1009         int ph = getPhysicalHeightDpi();
1010         if (h > 0 && ph > 0)
1011         {
1012             return ((float) h) / ((float) ph);
1013         }
1014         else
1015         {
1016             return -1.0f;
1017         }
1018     }
1019 
1020     /**
1021      * If {@link #check()} was successful, returns the physical width of this image in dpi (dots per inch) or -1 if no
1022      * value could be found.
1023      * 
1024      * @return physical width (in dpi)
1025      * @see #getPhysicalHeightDpi()
1026      * @see #getPhysicalWidthInch()
1027      * @see #getPhysicalHeightInch()
1028      */
1029     public int getPhysicalWidthDpi()
1030     {
1031         return physicalWidthDpi;
1032     }
1033 
1034     /**
1035      * Returns the physical width of an image in inches, or <code>-1.0f</code> if width information is not available.
1036      * Assumes that {@link #check} has been called successfully.
1037      * 
1038      * @return physical width in inches or <code>-1.0f</code> on failure
1039      * @see #getPhysicalWidthDpi
1040      * @see #getPhysicalHeightInch
1041      */
1042     public float getPhysicalWidthInch()
1043     {
1044         int w = getWidth();
1045         int pw = getPhysicalWidthDpi();
1046         if (w > 0 && pw > 0)
1047         {
1048             return ((float) w) / ((float) pw);
1049         }
1050         else
1051         {
1052             return -1.0f;
1053         }
1054     }
1055 
1056     private static int getShortBigEndian(byte[] a, int offs)
1057     {
1058         return (a[offs] & 0xff) << 8 | (a[offs + 1] & 0xff);
1059     }
1060 
1061     private static int getShortLittleEndian(byte[] a, int offs)
1062     {
1063         return (a[offs] & 0xff) | (a[offs + 1] & 0xff) << 8;
1064     }
1065 
1066     /**
1067      * If {@link #check()} was successful, returns one the image's horizontal resolution in pixels.
1068      * 
1069      * @return image width in pixels
1070      */
1071     public int getWidth()
1072     {
1073         return width;
1074     }
1075 
1076     /**
1077      * Returns whether the image is stored in a progressive (also called: interlaced) way.
1078      * 
1079      * @return true for progressive/interlaced, false otherwise
1080      */
1081     public boolean isProgressive()
1082     {
1083         return progressive;
1084     }
1085 
1086     /**
1087      * To use this class as a command line application, give it either some file names as parameters (information on
1088      * them will be printed to standard output, one line per file) or call it with no parameters. It will then check
1089      * data given to it via standard input.
1090      * 
1091      * @param args
1092      *            the program arguments which must be file names
1093      */
1094     public static void main(String[] args)
1095     {
1096         ImageInfo imageInfo = new ImageInfo();
1097         imageInfo.setDetermineImageNumber(true);
1098         boolean verbose = determineVerbosity(args);
1099         if (args.length == 0)
1100         {
1101             run(null, System.in, imageInfo, verbose);
1102         }
1103         else
1104         {
1105             int index = 0;
1106             while (index < args.length)
1107             {
1108                 InputStream in = null;
1109                 try
1110                 {
1111                     String name = args[index++];
1112                     System.out.print(name + ";");
1113                     if (name.startsWith("http://"))
1114                     {
1115                         in = new URL(name).openConnection().getInputStream();
1116                     }
1117                     else
1118                     {
1119                         in = new FileInputStream(name);
1120                     }
1121                     run(name, in, imageInfo, verbose);
1122                     in.close();
1123                 }
1124                 catch (IOException e)
1125                 {
1126                     try
1127                     {
1128                         in.close();
1129                     }
1130                     catch (IOException ee)
1131                     {
1132                     }
1133                 }
1134             }
1135         }
1136     }
1137 
1138     private static void print(String sourceName, ImageInfo ii, boolean verbose)
1139     {
1140         if (verbose)
1141         {
1142             printVerbose(sourceName, ii);
1143         }
1144         else
1145         {
1146             printCompact(sourceName, ii);
1147         }
1148     }
1149 
1150     private static void printCompact(String sourceName, ImageInfo imageInfo)
1151     {
1152         final String SEP = "\t";
1153         System.out.println(sourceName + SEP + imageInfo.getFormatName() + SEP + imageInfo.getMimeType() + SEP
1154                 + imageInfo.getWidth() + SEP + imageInfo.getHeight() + SEP + imageInfo.getBitsPerPixel() + SEP
1155                 + imageInfo.getNumberOfImages() + SEP + imageInfo.getPhysicalWidthDpi() + SEP
1156                 + imageInfo.getPhysicalHeightDpi() + SEP + imageInfo.getPhysicalWidthInch() + SEP
1157                 + imageInfo.getPhysicalHeightInch() + SEP + imageInfo.isProgressive());
1158     }
1159 
1160     private static void printLine(int indentLevels, String text, float value, float minValidValue)
1161     {
1162         if (value < minValidValue)
1163         {
1164             return;
1165         }
1166         printLine(indentLevels, text, Float.toString(value));
1167     }
1168 
1169     private static void printLine(int indentLevels, String text, int value, int minValidValue)
1170     {
1171         if (value >= minValidValue)
1172         {
1173             printLine(indentLevels, text, Integer.toString(value));
1174         }
1175     }
1176 
1177     private static void printLine(int indentLevels, String text, String value)
1178     {
1179         if (value == null || value.length() == 0)
1180         {
1181             return;
1182         }
1183         while (indentLevels-- > 0)
1184         {
1185             System.out.print("\t");
1186         }
1187         if (text != null && text.length() > 0)
1188         {
1189             System.out.print(text);
1190             System.out.print(" ");
1191         }
1192         System.out.println(value);
1193     }
1194 
1195     private static void printVerbose(String sourceName, ImageInfo ii)
1196     {
1197         printLine(0, null, sourceName);
1198         printLine(1, "File format: ", ii.getFormatName());
1199         printLine(1, "MIME type: ", ii.getMimeType());
1200         printLine(1, "Width (pixels): ", ii.getWidth(), 1);
1201         printLine(1, "Height (pixels): ", ii.getHeight(), 1);
1202         printLine(1, "Bits per pixel: ", ii.getBitsPerPixel(), 1);
1203         printLine(1, "Progressive: ", ii.isProgressive() ? "yes" : "no");
1204         printLine(1, "Number of images: ", ii.getNumberOfImages(), 1);
1205         printLine(1, "Physical width (dpi): ", ii.getPhysicalWidthDpi(), 1);
1206         printLine(1, "Physical height (dpi): ", ii.getPhysicalHeightDpi(), 1);
1207         printLine(1, "Physical width (inches): ", ii.getPhysicalWidthInch(), 1.0f);
1208         printLine(1, "Physical height (inches): ", ii.getPhysicalHeightInch(), 1.0f);
1209         int numComments = ii.getNumberOfComments();
1210         printLine(1, "Number of textual comments: ", numComments, 1);
1211         if (numComments > 0)
1212         {
1213             for (int i = 0; i < numComments; i++)
1214             {
1215                 printLine(2, null, ii.getComment(i));
1216             }
1217         }
1218     }
1219 
1220     private int read() throws IOException
1221     {
1222         if (in != null)
1223         {
1224             return in.read();
1225         }
1226         else
1227         {
1228             return din.readByte();
1229         }
1230     }
1231 
1232     private int read(byte[] a) throws IOException
1233     {
1234         if (in != null)
1235         {
1236             return in.read(a);
1237         }
1238         else
1239         {
1240             din.readFully(a);
1241             return a.length;
1242         }
1243     }
1244 
1245     private int read(byte[] a, int offset, int num) throws IOException
1246     {
1247         if (in != null)
1248         {
1249             return in.read(a, offset, num);
1250         }
1251         else
1252         {
1253             din.readFully(a, offset, num);
1254             return num;
1255         }
1256     }
1257 
1258     private String readLine() throws IOException
1259     {
1260         return readLine(new StringBuffer());
1261     }
1262 
1263     private String readLine(StringBuffer sb) throws IOException
1264     {
1265         boolean finished;
1266         do
1267         {
1268             int value = read();
1269             finished = (value == -1 || value == 10);
1270             if (!finished)
1271             {
1272                 sb.append((char) value);
1273             }
1274         }
1275         while (!finished);
1276         return sb.toString();
1277     }
1278 
1279     private long readUBits(int numBits) throws IOException
1280     {
1281         if (numBits == 0)
1282         {
1283             return 0;
1284         }
1285         int bitsLeft = numBits;
1286         long result = 0;
1287         if (bitPos == 0)
1288         { // no value in the buffer - read a byte
1289             if (in != null)
1290             {
1291                 bitBuf = in.read();
1292             }
1293             else
1294             {
1295                 bitBuf = din.readByte();
1296             }
1297             bitPos = 8;
1298         }
1299 
1300         while (true)
1301         {
1302             int shift = bitsLeft - bitPos;
1303             if (shift > 0)
1304             {
1305                 // Consume the entire buffer
1306                 result |= bitBuf << shift;
1307                 bitsLeft -= bitPos;
1308 
1309                 // Get the next byte from the input stream
1310                 if (in != null)
1311                 {
1312                     bitBuf = in.read();
1313                 }
1314                 else
1315                 {
1316                     bitBuf = din.readByte();
1317                 }
1318                 bitPos = 8;
1319             }
1320             else
1321             {
1322                 // Consume a portion of the buffer
1323                 result |= bitBuf >> -shift;
1324                 bitPos -= bitsLeft;
1325                 bitBuf &= 0xff >> (8 - bitPos); // mask off the consumed bits
1326 
1327                 return result;
1328             }
1329         }
1330     }
1331 
1332     /**
1333      * Read a signed integer value from input.
1334      * 
1335      * @param numBits number of bits to read
1336      */
1337     private int readSBits(int numBits) throws IOException
1338     {
1339         // Get the number as an unsigned value.
1340         long uBits = readUBits(numBits);
1341 
1342         // Is the number negative?
1343         if ((uBits & (1L << (numBits - 1))) != 0)
1344         {
1345             // Yes. Extend the sign.
1346             uBits |= -1L << numBits;
1347         }
1348 
1349         return (int) uBits;
1350     }
1351 
1352     private static void run(String sourceName, InputStream in, ImageInfo imageInfo, boolean verbose)
1353     {
1354         imageInfo.setInput(in);
1355         imageInfo.setDetermineImageNumber(true);
1356         imageInfo.setCollectComments(verbose);
1357         if (imageInfo.check())
1358         {
1359             print(sourceName, imageInfo, verbose);
1360         }
1361     }
1362 
1363     /**
1364      * Specify whether textual comments are supposed to be extracted from input. Default is <code>false</code>. If
1365      * enabled, comments will be added to an internal list.
1366      * 
1367      * @param newValue
1368      *            if <code>true</code>, this class will read comments
1369      * @see #getNumberOfComments
1370      * @see #getComment
1371      */
1372     public void setCollectComments(boolean newValue)
1373     {
1374         collectComments = newValue;
1375     }
1376 
1377     /**
1378      * Specify whether the number of images in a file is to be determined - default is <code>false</code>. This is a
1379      * special option because some file formats require running over the entire file to find out the number of images, a
1380      * rather time-consuming task. Not all file formats support more than one image. If this method is called with
1381      * <code>true</code> as argument, the actual number of images can be queried via {@link #getNumberOfImages()} after
1382      * a successful call to {@link #check()}.
1383      * 
1384      * @param newValue
1385      *            will the number of images be determined?
1386      * @see #getNumberOfImages
1387      */
1388     public void setDetermineImageNumber(boolean newValue)
1389     {
1390         determineNumberOfImages = newValue;
1391     }
1392 
1393     /**
1394      * Set the input stream to the argument stream (or file). Note that {@link java.io.RandomAccessFile} implements
1395      * {@link java.io.DataInput}.
1396      * 
1397      * @param dataInput
1398      *            the input stream to read from
1399      */
1400     public void setInput(DataInput dataInput)
1401     {
1402         din = dataInput;
1403         in = null;
1404     }
1405 
1406     /**
1407      * Set the input stream to the argument stream (or file).
1408      * 
1409      * @param inputStream
1410      *            the input stream to read from
1411      */
1412     public void setInput(InputStream inputStream)
1413     {
1414         in = inputStream;
1415         din = null;
1416     }
1417 
1418     private void setPhysicalHeightDpi(int newValue)
1419     {
1420         physicalWidthDpi = newValue;
1421     }
1422 
1423     private void setPhysicalWidthDpi(int newValue)
1424     {
1425         physicalHeightDpi = newValue;
1426     }
1427 
1428     /**
1429      * This method guarantees to skip the next num bytes by read them from the underlying input. If the end of the file
1430      * is reached before num bytes are read an EOFException is thrown. This method doesn't use the skip() method of the
1431      * underlying input because skip() is not blocking and is not guaranteed to skip the given number of bytes for
1432      * different reasons. (CORE-107)
1433      * 
1434      * @param numBytesToSkip number of bytes to skip.
1435      * @throws IOException if an I/O error occurs.
1436      * @throws EOFException if the end of file was reached before the given number of bytes could be skipped.
1437      */
1438     private void skip(int numBytesToSkip) throws IOException
1439     {
1440         for (int bytesSkipped = 0; bytesSkipped < numBytesToSkip; bytesSkipped++)
1441         {
1442             if (read() == -1)
1443                 throw new EOFException("Reached end of stream before "
1444                         + numBytesToSkip + " bytes could be skipped");
1445         } 
1446     }
1447 }