View Javadoc

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