View Javadoc

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