KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > imageinfo > ImageInfo


1 /*
2  * ImageInfo.java
3  *
4  * Version 1.4
5  *
6  * A Java class to determine image width, height and color depth for
7  * a number of image file formats.
8  *
9  * Written by Marco Schmidt
10  * <http://www.geocities.com/marcoschmidt.geo/contact.html>.
11  *
12  * Contributed to the Public Domain.
13  *
14  * Last modification 2003-07-28
15  */

16
17 package imageinfo;
18 import java.io.DataInput JavaDoc;
19 import java.io.FileInputStream JavaDoc;
20 import java.io.InputStream JavaDoc;
21 import java.io.IOException JavaDoc;
22 import java.net.*;
23 import java.util.Vector JavaDoc;
24
25 /**
26  * Get file format, image resolution, number of bits per pixel and optionally
27  * number of images, comments and physical resolution from
28  * JPEG, GIF, BMP, PCX, PNG, IFF, RAS, PBM, PGM, PPM, PSD and SWF files
29  * (or input streams).
30  * <p>
31  * Use the class like this:
32  * <pre>
33  * ImageInfo ii = new ImageInfo();
34  * ii.setInput(in); // in can be InputStream or RandomAccessFile
35  * ii.setDetermineImageNumber(true); // default is false
36  * ii.setCollectComments(true); // default is false
37  * if (!ii.check()) {
38  * System.err.println("Not a supported image file format.");
39  * return;
40  * }
41  * System.out.println(ii.getFormatName() + ", " + ii.getMimeType() +
42  * ", " + ii.getWidth() + " x " + ii.getHeight() + " pixels, " +
43  * ii.getBitsPerPixel() + " bits per pixel, " + ii.getNumberOfImages() +
44  * " image(s), " + ii.getNumberOfComments() + " comment(s).");
45  * </pre>
46  * You can also use this class as a command line program.
47  * Call it with a number of image file names as parameters:
48  * <pre>
49  * java ImageInfo *.jpg *.png *.gif
50  * </pre>
51  * or call it without parameters and pipe data to it:
52  * <pre>
53  * cat image.jpg | java ImageInfo
54  * </pre>
55  * <p>
56  * Known limitations:
57  * <ul>
58  * <li>When the determination of the number of images is turned off, GIF bits
59  * per pixel are only read from the global header.
60  * For some GIFs, local palettes change this to a typically larger
61  * value. To be certain to get the correct color depth, call
62  * setDetermineImageNumber(true) before calling check().
63  * The complete scan over the GIF file will take additional time.</li>
64  * <li>Transparency information is not included in the bits per pixel count.
65  * Actually, it was my decision not to include those bits, so it's a feature! ;-)</li>
66  * </ul>
67  * <p>
68  * Requirements:
69  * <ul>
70  * <li>Java 1.1 or higher</li>
71  * </ul>
72  * <p>
73  * The latest version can be found at <a HREF="http://www.geocities.com/marcoschmidt.geo/image-info.html">http://www.geocities.com/marcoschmidt.geo/image-info.html</a>.
74  * <p>
75  * Written by <a HREF="mailto:marcoschmidt@users.sourceforge.net">Marco Schmidt</a>.
76  * <p>
77  * This class is contributed to the Public Domain.
78  * Use it at your own risk.
79  * <p>
80  * Last modification 2003-07-28.
81  * <p>
82  * History:
83  * <ul>
84  * <li><strong>2001-08-24</strong> Initial version.</li>
85  * <li><strong>2001-10-13</strong> Added support for the file formats BMP and PCX.</li>
86  * <li><strong>2001-10-16</strong> Fixed bug in read(int[], int, int) that returned
87  * <li><strong>2002-01-22</strong> Added support for file formats Amiga IFF and Sun Raster (RAS).</li>
88  * <li><strong>2002-01-24</strong> Added support for file formats Portable Bitmap / Graymap / Pixmap (PBM, PGM, PPM) and Adobe Photoshop (PSD).
89  * Added new method getMimeType() to return the MIME type associated with a particular file format.</li>
90  * <li><strong>2002-03-15</strong> Added support to recognize number of images in file. Only works with GIF.
91  * Use {@link #setDetermineImageNumber} with <code>true</code> as argument to identify animated GIFs
92  * ({@link #getNumberOfImages()} will return a value larger than <code>1</code>).</li>
93  * <li><strong>2002-04-10</strong> Fixed a bug in the feature 'determine number of images in animated GIF' introduced with version 1.1.
94  * Thanks to Marcelo P. Lima for sending in the bug report.
95  * Released as 1.1.1.</li>
96  * <li><strong>2002-04-18</strong> Added {@link #setCollectComments(boolean)}.
97  * That new method lets the user specify whether textual comments are to be
98  * stored in an internal list when encountered in an input image file / stream.
99  * Added two methods to return the physical width and height of the image in dpi:
100  * {@link #getPhysicalWidthDpi()} and {@link #getPhysicalHeightDpi()}.
101  * If the physical resolution could not be retrieved, these methods return <code>-1</code>.
102  * </li>
103  * <li><strong>2002-04-23</strong> Added support for the new properties physical resolution and
104  * comments for some formats. Released as 1.2.</li>
105  * <li><strong>2002-06-17</strong> Added support for SWF, sent in by Michael Aird.
106  * Changed checkJpeg() so that other APP markers than APP0 will not lead to a failure anymore.
107  * Released as 1.3.</li>
108  * <li><strong>2003-07-28</strong> Bug fix - skip method now takes return values into consideration.
109  * Less bytes than necessary may have been skipped, leading to flaws in the retrieved information in some cases.
110  * Thanks to Bernard Bernstein for pointing that out.
111  * Released as 1.4.</li>
112  * </ul>
113  */

114 public class ImageInfo {
115     /**
116      * Return value of {@link #getFormat()} for JPEG streams.
117      * ImageInfo can extract physical resolution and comments
118      * from JPEGs (only from APP0 headers).
119      * Only one image can be stored in a file.
120      */

121     public static final int FORMAT_JPEG = 0;
122
123     /**
124      * Return value of {@link #getFormat()} for GIF streams.
125      * ImageInfo can extract comments from GIFs and count the number
126      * of images (GIFs with more than one image are animations).
127      * If you know of a place where GIFs store the physical resolution
128      * of an image, please
129      * <a HREF="http://www.geocities.com/marcoschmidt.geo/contact.html">send me a mail</a>!
130      */

131     public static final int FORMAT_GIF = 1;
132
133     /**
134      * Return value of {@link #getFormat()} for PNG streams.
135      * PNG only supports one image per file.
136      * Both physical resolution and comments can be stored with PNG,
137      * but ImageInfo is currently not able to extract those.
138      */

139     public static final int FORMAT_PNG = 2;
140
141     /**
142      * Return value of {@link #getFormat()} for BMP streams.
143      * BMP only supports one image per file.
144      * BMP does not allow for comments.
145      * The physical resolution can be stored.
146      * <em>The specification that I have says that the values must be
147      * interpreted as dots per meter. However, given that I only
148      * encounter typical dpi values like 72 or 300, I currently
149      * consider those values dpi. Maybe someone can shed some light
150      * on this, please send me a mail in that case.</em>
151      */

152     public static final int FORMAT_BMP = 3;
153
154     /**
155      * Return value of {@link #getFormat()} for PCX streams.
156      * PCX does not allow for comments or more than one image per file.
157      * However, the physical resolution can be stored.
158      */

159     public static final int FORMAT_PCX = 4;
160
161     /**
162      * Return value of {@link #getFormat()} for IFF streams.
163      */

164     public static final int FORMAT_IFF = 5;
165
166     /**
167      * Return value of {@link #getFormat()} for RAS streams.
168      * Sun Raster allows for one image per file only and is not able to
169      * store physical resolution or comments.
170      */

171     public static final int FORMAT_RAS = 6;
172
173     /** Return value of {@link #getFormat()} for PBM streams. */
174     public static final int FORMAT_PBM = 7;
175
176     /** Return value of {@link #getFormat()} for PGM streams. */
177     public static final int FORMAT_PGM = 8;
178
179     /** Return value of {@link #getFormat()} for PPM streams. */
180     public static final int FORMAT_PPM = 9;
181
182     /** Return value of {@link #getFormat()} for PSD streams. */
183     public static final int FORMAT_PSD = 10;
184
185     /** Return value of {@link #getFormat()} for SWF (Shockwave) streams. */
186     public static final int FORMAT_SWF = 11;
187
188     public static final int COLOR_TYPE_UNKNOWN = -1;
189     public static final int COLOR_TYPE_TRUECOLOR_RGB = 0;
190     public static final int COLOR_TYPE_PALETTED = 1;
191     public static final int COLOR_TYPE_GRAYSCALE= 2;
192     public static final int COLOR_TYPE_BLACK_AND_WHITE = 3;
193
194     /**
195      * The names of all supported file formats.
196      * The FORMAT_xyz int constants can be used as index values for
197      * this array.
198      */

199     private static final String JavaDoc[] FORMAT_NAMES =
200         {"JPEG", "GIF", "PNG", "BMP", "PCX",
201          "IFF", "RAS", "PBM", "PGM", "PPM",
202          "PSD", "SWF"};
203
204     /**
205      * The names of the MIME types for all supported file formats.
206      * The FORMAT_xyz int constants can be used as index values for
207      * this array.
208      */

209     private static final String JavaDoc[] MIME_TYPE_STRINGS =
210         {"image/jpeg", "image/gif", "image/png", "image/bmp", "image/pcx",
211          "image/iff", "image/ras", "image/x-portable-bitmap", "image/x-portable-graymap", "image/x-portable-pixmap",
212          "image/psd", "application/x-shockwave-flash"};
213
214     private int width;
215     private int height;
216     private int bitsPerPixel;
217     private int colorType = COLOR_TYPE_UNKNOWN;
218     private int format;
219     private InputStream JavaDoc in;
220     private DataInput JavaDoc din;
221     private boolean collectComments = true;
222     private Vector JavaDoc comments;
223     private boolean determineNumberOfImages;
224     private int numberOfImages;
225     private int physicalHeightDpi;
226     private int physicalWidthDpi;
227     private int bitBuf;
228     private int bitPos;
229
230     private void addComment(String JavaDoc s) {
231         if (comments == null) {
232             comments = new Vector JavaDoc();
233         }
234         comments.addElement(s);
235     }
236
237     /**
238      * Call this method after you have provided an input stream or file
239      * using {@link #setInput(InputStream)} or {@link #setInput(DataInput)}.
240      * If true is returned, the file format was known and information
241      * on the file's content can be retrieved using the various getXyz methods.
242      * @return if information could be retrieved from input
243      */

244     public boolean check() {
245         format = -1;
246         width = -1;
247         height = -1;
248         bitsPerPixel = -1;
249         numberOfImages = 1;
250         physicalHeightDpi = -1;
251         physicalWidthDpi = -1;
252         comments = null;
253         try {
254             int b1 = read() & 0xff;
255             int b2 = read() & 0xff;
256             if (b1 == 0x47 && b2 == 0x49) {
257                 return checkGif();
258             }
259             else
260             if (b1 == 0x89 && b2 == 0x50) {
261                 return checkPng();
262             }
263             else
264             if (b1 == 0xff && b2 == 0xd8) {
265                 return checkJpeg();
266             }
267             else
268             if (b1 == 0x42 && b2 == 0x4d) {
269                 return checkBmp();
270             }
271             else
272             if (b1 == 0x0a && b2 < 0x06) {
273                 return checkPcx();
274             }
275             else
276             if (b1 == 0x46 && b2 == 0x4f) {
277                 return checkIff();
278             }
279             else
280             if (b1 == 0x59 && b2 == 0xa6) {
281                 return checkRas();
282             }
283             else
284             if (b1 == 0x50 && b2 >= 0x31 && b2 <= 0x36) {
285                 return checkPnm(b2 - '0');
286             }
287             else
288             if (b1 == 0x38 && b2 == 0x42) {
289                 return checkPsd();
290             }
291             else
292             if (b1 == 0x46 && b2 == 0x57) {
293                 return checkSwf();
294             }
295             else {
296                 return false;
297             }
298         } catch (IOException JavaDoc ioe) {
299             return false;
300         }
301     }
302
303     private boolean checkBmp() throws IOException JavaDoc {
304         byte[] a = new byte[44];
305         if (read(a) != a.length) {
306             return false;
307         }
308         width = getIntLittleEndian(a, 16);
309         height = getIntLittleEndian(a, 20);
310         if (width < 1 || height < 1) {
311             return false;
312         }
313         bitsPerPixel = getShortLittleEndian(a, 26);
314         if (bitsPerPixel != 1 && bitsPerPixel != 4 &&
315             bitsPerPixel != 8 && bitsPerPixel != 16 &&
316             bitsPerPixel != 24 && bitsPerPixel != 32) {
317             return false;
318         }
319         int x = getIntLittleEndian(a, 36);
320         if (x > 0) {
321             setPhysicalWidthDpi(x);
322         }
323         int y = getIntLittleEndian(a, 40);
324         if (y > 0) {
325             setPhysicalHeightDpi(y);
326         }
327         format = FORMAT_BMP;
328         return true;
329     }
330
331     private boolean checkGif() throws IOException JavaDoc {
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 header
335
if (read(a) != 11) {
336             return false;
337         }
338         if ((!equals(a, 0, GIF_MAGIC_89A, 0, 4)) &&
339             (!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         if (!determineNumberOfImages) {
348             return true;
349         }
350         // skip global color palette
351
if ((flags & 0x80) != 0) {
352             int tableSize = (1 << ((flags & 7) + 1)) * 3;
353             skip(tableSize);
354         }
355         numberOfImages = 0;
356         int blockType;
357         do
358         {
359             blockType = read();
360             switch(blockType)
361             {
362                 case(0x2c): // image separator
363
{
364                     if (read(a, 0, 9) != 9) {
365                         return false;
366                     }
367                     flags = a[8] & 0xff;
368                     int localBitsPerPixel = (flags & 0x07) + 1;
369                     if (localBitsPerPixel > bitsPerPixel) {
370                         bitsPerPixel = localBitsPerPixel;
371                     }
372                     if ((flags & 0x80) != 0) {
373                         skip((1 << localBitsPerPixel) * 3);
374                     }
375                     skip(1); // initial code length
376
int n;
377                     do
378                     {
379                         n = read();
380                         if (n > 0) {
381                             skip(n);
382                         }
383                         else
384                         if (n == -1) {
385                             return false;
386                         }
387                     }
388                     while (n > 0);
389                     numberOfImages++;
390                     break;
391                 }
392                 case(0x21): // extension
393
{
394                     int extensionType = read();
395                     if (collectComments && extensionType == 0xfe) {
396                         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
397                         int n;
398                         do
399                         {
400                             n = read();
401                             if (n == -1) {
402                                 return false;
403                             }
404                             if (n > 0) {
405                                 for (int i = 0; i < n; i++) {
406                                     int ch = read();
407                                     if (ch == -1) {
408                                         return false;
409                                     }
410                                     sb.append((char)ch);
411                                 }
412                             }
413                         }
414                         while (n > 0);
415                     } else {
416                         int n;
417                         do
418                         {
419                             n = read();
420                             if (n > 0) {
421                                 skip(n);
422                             }
423                             else
424                             if (n == -1) {
425                                 return false;
426                             }
427                         }
428                         while (n > 0);
429                     }
430                     break;
431                 }
432                 case(0x3b): // end of file
433
{
434                     break;
435                 }
436                 default:
437                 {
438                     return false;
439                 }
440             }
441         }
442         while (blockType != 0x3b);
443         return true;
444     }
445
446     private boolean checkIff() throws IOException JavaDoc {
447         byte[] a = new byte[10];
448         // read remaining 2 bytes of file id, 4 bytes file size
449
// and 4 bytes IFF subformat
450
if (read(a, 0, 10) != 10) {
451             return false;
452         }
453         final byte[] IFF_RM = {0x52, 0x4d};
454         if (!equals(a, 0, IFF_RM, 0, 2)) {
455             return false;
456         }
457         int type = getIntBigEndian(a, 6);
458         if (type != 0x494c424d && // type must be ILBM...
459
type != 0x50424d20) { // ...or PBM
460
return false;
461         }
462         // loop chunks to find BMHD chunk
463
do {
464             if (read(a, 0, 8) != 8) {
465                 return false;
466             }
467             int chunkId = getIntBigEndian(a, 0);
468             int size = getIntBigEndian(a, 4);
469             if ((size & 1) == 1) {
470                 size++;
471             }
472             if (chunkId == 0x424d4844) { // BMHD chunk
473
if (read(a, 0, 9) != 9) {
474                     return false;
475                 }
476                 format = FORMAT_IFF;
477                 width = getShortBigEndian(a, 0);
478                 height = getShortBigEndian(a, 2);
479                 bitsPerPixel = a[8] & 0xff;
480                 return (width > 0 && height > 0 && bitsPerPixel > 0 && bitsPerPixel < 33);
481             } else {
482                 skip(size);
483             }
484         } while (true);
485     }
486
487     private boolean checkJpeg() throws IOException JavaDoc {
488         byte[] data = new byte[12];
489         while (true) {
490             if (read(data, 0, 4) != 4) {
491                 return false;
492             }
493             int marker = getShortBigEndian(data, 0);
494             int size = getShortBigEndian(data, 2);
495             if ((marker & 0xff00) != 0xff00) {
496                 return false; // not a valid marker
497
}
498             if (marker == 0xffe0) { // APPx
499
if (size < 14) {
500                     return false; // APPx header must be >= 14 bytes
501
}
502                 if (read(data, 0, 12) != 12) {
503                     return false;
504                 }
505                 final byte[] APP0_ID = {0x4a, 0x46, 0x49, 0x46, 0x00};
506                 if (equals(APP0_ID, 0, data, 0, 5)) {
507                     //System.out.println("data 7=" + data[7]);
508
if (data[7] == 1) {
509                         setPhysicalWidthDpi(getShortBigEndian(data, 8));
510                         setPhysicalHeightDpi(getShortBigEndian(data, 10));
511                     }
512                     else
513                     if (data[7] == 2) {
514                         int x = getShortBigEndian(data, 8);
515                         int y = getShortBigEndian(data, 10);
516                         setPhysicalWidthDpi((int)(x * 2.54f));
517                         setPhysicalHeightDpi((int)(y * 2.54f));
518                     }
519                 }
520                 skip(size - 14);
521             }
522             else
523             if (collectComments && size > 2 && marker == 0xfffe) { // comment
524
size -= 2;
525                 byte[] chars = new byte[size];
526                 if (read(chars, 0, size) != size) {
527                     return false;
528                 }
529                 String JavaDoc comment = new String JavaDoc(chars, "iso-8859-1");
530                 comment = comment.trim();
531                 //System.out.println(comment);
532
addComment(comment);
533             }
534             else
535             if (marker >= 0xffc0 && marker <= 0xffcf && marker != 0xffc4 && marker != 0xffc8) {
536                 if (read(data, 0, 6) != 6) {
537                     return false;
538                 }
539                 format = FORMAT_JPEG;
540                 bitsPerPixel = (data[0] & 0xff) * (data[5] & 0xff);
541                 width = getShortBigEndian(data, 3);
542                 height = getShortBigEndian(data, 1);
543                 return true;
544             } else {
545                 skip(size - 2);
546             }
547         }
548     }
549
550     private boolean checkPcx() throws IOException JavaDoc {
551         byte[] a = new byte[64];
552         if (read(a) != a.length) {
553             return false;
554         }
555         if (a[0] != 1) { // encoding, 1=RLE is only valid value
556
return false;
557         }
558         // width / height
559
int x1 = getShortLittleEndian(a, 2);
560         int y1 = getShortLittleEndian(a, 4);
561         int x2 = getShortLittleEndian(a, 6);
562         int y2 = getShortLittleEndian(a, 8);
563         if (x1 < 0 || x2 < x1 || y1 < 0 || y2 < y1) {
564             return false;
565         }
566         width = x2 - x1 + 1;
567         height = y2 - y1 + 1;
568         // color depth
569
int bits = a[1];
570         int planes = a[63];
571         if (planes == 1 &&
572             (bits == 1 || bits == 2 || bits == 4 || bits == 8)) {
573             // paletted
574
bitsPerPixel = bits;
575         } else
576         if (planes == 3 && bits == 8) {
577             // RGB truecolor
578
bitsPerPixel = 24;
579         } else {
580             return false;
581         }
582         setPhysicalWidthDpi(getShortLittleEndian(a, 10));
583         setPhysicalHeightDpi(getShortLittleEndian(a, 10));
584         format = FORMAT_PCX;
585         return true;
586     }
587
588     private boolean checkPng() throws IOException JavaDoc {
589         final byte[] PNG_MAGIC = {0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a};
590         byte[] a = new byte[24];
591         if (read(a) != 24) {
592             return false;
593         }
594         if (!equals(a, 0, PNG_MAGIC, 0, 6)) {
595             return false;
596         }
597         format = FORMAT_PNG;
598         width = getIntBigEndian(a, 14);
599         height = getIntBigEndian(a, 18);
600         bitsPerPixel = a[22] & 0xff;
601         int colorType = a[23] & 0xff;
602         if (colorType == 2 || colorType == 6) {
603             bitsPerPixel *= 3;
604         }
605         return true;
606     }
607
608     private boolean checkPnm(int id) throws IOException JavaDoc {
609         if (id < 1 || id > 6) {
610             return false;
611         }
612         final int[] PNM_FORMATS = {FORMAT_PBM, FORMAT_PGM, FORMAT_PPM};
613         format = PNM_FORMATS[(id - 1) % 3];
614         boolean hasPixelResolution = false;
615         String JavaDoc s;
616         while (true)
617         {
618             s = readLine();
619             if (s != null) {
620                 s = s.trim();
621             }
622             if (s == null || s.length() < 1) {
623                 continue;
624             }
625             if (s.charAt(0) == '#') { // comment
626
if (collectComments && s.length() > 1) {
627                     addComment(s.substring(1));
628                 }
629                 continue;
630             }
631             if (!hasPixelResolution) { // split "343 966" into width=343, height=966
632
int spaceIndex = s.indexOf(' ');
633                 if (spaceIndex == -1) {
634                     return false;
635                 }
636                 String JavaDoc widthString = s.substring(0, spaceIndex);
637                 spaceIndex = s.lastIndexOf(' ');
638                 if (spaceIndex == -1) {
639                     return false;
640                 }
641                 String JavaDoc heightString = s.substring(spaceIndex + 1);
642                 try {
643                     width = Integer.parseInt(widthString);
644                     height = Integer.parseInt(heightString);
645                 } catch (NumberFormatException JavaDoc nfe) {
646                     return false;
647                 }
648                 if (width < 1 || height < 1) {
649                     return false;
650                 }
651                 if (format == FORMAT_PBM) {
652                     bitsPerPixel = 1;
653                     return true;
654                 }
655                 hasPixelResolution = true;
656             }
657             else
658             {
659                 int maxSample;
660                 try {
661                     maxSample = Integer.parseInt(s);
662                 } catch (NumberFormatException JavaDoc nfe) {
663                     return false;
664                 }
665                 if (maxSample < 0) {
666                     return false;
667                 }
668                 for (int i = 0; i < 25; i++) {
669                     if (maxSample < (1 << (i + 1))) {
670                         bitsPerPixel = i + 1;
671                         if (format == FORMAT_PPM) {
672                             bitsPerPixel *= 3;
673                         }
674                         return true;
675                     }
676                 }
677                 return false;
678             }
679         }
680     }
681
682     private boolean checkPsd() throws IOException JavaDoc {
683         byte[] a = new byte[24];
684         if (read(a) != a.length) {
685             return false;
686         }
687         final byte[] PSD_MAGIC = {0x50, 0x53};
688         if (!equals(a, 0, PSD_MAGIC, 0, 2)) {
689             return false;
690         }
691         format = FORMAT_PSD;
692         width = getIntBigEndian(a, 16);
693         height = getIntBigEndian(a, 12);
694         int channels = getShortBigEndian(a, 10);
695         int depth = getShortBigEndian(a, 20);
696         bitsPerPixel = channels * depth;
697         return (width > 0 && height > 0 && bitsPerPixel > 0 && bitsPerPixel <= 64);
698     }
699
700     private boolean checkRas() throws IOException JavaDoc {
701         byte[] a = new byte[14];
702         if (read(a) != a.length) {
703             return false;
704         }
705         final byte[] RAS_MAGIC = {0x6a, (byte)0x95};
706         if (!equals(a, 0, RAS_MAGIC, 0, 2)) {
707             return false;
708         }
709         format = FORMAT_RAS;
710         width = getIntBigEndian(a, 2);
711         height = getIntBigEndian(a, 6);
712         bitsPerPixel = getIntBigEndian(a, 10);
713         return (width > 0 && height > 0 && bitsPerPixel > 0 && bitsPerPixel <= 24);
714     }
715
716     // Written by Michael Aird.
717
private boolean checkSwf() throws IOException JavaDoc {
718         //get rid of the last byte of the signature, the byte of the version and 4 bytes of the size
719
byte[] a = new byte[6];
720         if (read(a) != a.length) {
721             return false;
722         }
723         format = FORMAT_SWF;
724         int bitSize = (int)readUBits( 5 );
725         int minX = (int)readSBits( bitSize );
726         int maxX = (int)readSBits( bitSize );
727         int minY = (int)readSBits( bitSize );
728         int maxY = (int)readSBits( bitSize );
729         width = maxX/20; //cause we're in twips
730
height = maxY/20; //cause we're in twips
731
setPhysicalWidthDpi(72);
732         setPhysicalHeightDpi(72);
733         return (width > 0 && height > 0);
734     }
735
736     /**
737      * Run over String list, return false iff at least one of the arguments
738      * equals <code>-c</code>.
739      */

740     private static boolean determineVerbosity(String JavaDoc[] args) {
741         if (args != null && args.length > 0) {
742             for (int i = 0; i < args.length; i++) {
743                 if ("-c".equals(args[i])) {
744                     return false;
745                 }
746             }
747         }
748         return true;
749     }
750
751     private boolean equals(byte[] a1, int offs1, byte[] a2, int offs2, int num) {
752         while (num-- > 0) {
753             if (a1[offs1++] != a2[offs2++]) {
754                 return false;
755             }
756         }
757         return true;
758     }
759
760     /**
761      * If {@link #check()} was successful, returns the image's number of bits per pixel.
762      * Does not include transparency information like the alpha channel.
763      * @return number of bits per image pixel
764      */

765     public int getBitsPerPixel() {
766         return bitsPerPixel;
767     }
768
769     /**
770      * Returns the index'th comment retrieved from the image.
771      * @throws IllegalArgumentException if index is smaller than 0 or larger than or equal
772      * to the number of comments retrieved
773      * @see #getNumberOfComments
774      */

775     public String JavaDoc getComment(int index) {
776         if (comments == null || index < 0 || index >= comments.size()) {
777             throw new IllegalArgumentException JavaDoc("Not a valid comment index: " + index);
778         }
779         return (String JavaDoc)comments.elementAt(index);
780     }
781
782     /**
783      * If {@link #check()} was successful, returns the image format as one
784      * of the FORMAT_xyz constants from this class.
785      * Use {@link #getFormatName()} to get a textual description of the file format.
786      * @return file format as a FORMAT_xyz constant
787      */

788     public int getFormat() {
789         return format;
790     }
791
792     /**
793      * If {@link #check()} was successful, returns the image format's name.
794      * Use {@link #getFormat()} to get a unique number.
795      * @return file format name
796      */

797     public String JavaDoc getFormatName() {
798         if (format >= 0 && format < FORMAT_NAMES.length) {
799             return FORMAT_NAMES[format];
800         } else {
801             return "?";
802         }
803     }
804
805     /**
806      * If {@link #check()} was successful, returns one the image's vertical
807      * resolution in pixels.
808      * @return image height in pixels
809      */

810     public int getHeight() {
811         return height;
812     }
813
814     private int getIntBigEndian(byte[] a, int offs) {
815         return
816             (a[offs] & 0xff) << 24 |
817             (a[offs + 1] & 0xff) << 16 |
818             (a[offs + 2] & 0xff) << 8 |
819             a[offs + 3] & 0xff;
820     }
821
822     private int getIntLittleEndian(byte[] a, int offs) {
823         return
824             (a[offs + 3] & 0xff) << 24 |
825             (a[offs + 2] & 0xff) << 16 |
826             (a[offs + 1] & 0xff) << 8 |
827             a[offs] & 0xff;
828     }
829
830     /**
831      * If {@link #check()} was successful, returns a String with the
832      * MIME type of the format.
833      * @return MIME type, e.g. <code>image/jpeg</code>
834      */

835     public String JavaDoc getMimeType() {
836         if (format >= 0 && format < MIME_TYPE_STRINGS.length) {
837             return MIME_TYPE_STRINGS[format];
838         } else {
839             return null;
840         }
841     }
842
843     /**
844      * If {@link #check()} was successful and {@link #setCollectComments(boolean)} was called with
845      * <code>true</code> as argument, returns the number of comments retrieved
846      * from the input image stream / file.
847      * Any number &gt;= 0 and smaller than this number of comments is then a
848      * valid argument for the {@link #getComment(int)} method.
849      * @return number of comments retrieved from input image
850      */

851     public int getNumberOfComments()
852     {
853         if (comments == null) {
854             return 0;
855         } else {
856             return comments.size();
857         }
858     }
859
860     /**
861      * Returns the number of images in the examined file.
862      * Assumes that <code>setDetermineImageNumber(true);</code> was called before
863      * a successful call to {@link #check()}.
864      * This value can currently be only different from <code>1</code> for GIF images.
865      * @return number of images in file
866      */

867     public int getNumberOfImages()
868     {
869         return numberOfImages;
870     }
871
872     /**
873      * Returns the physical height of this image in dots per inch (dpi).
874      * Assumes that {@link #check()} was successful.
875      * Returns <code>-1</code> on failure.
876      * @return physical height (in dpi)
877      * @see #getPhysicalWidthDpi()
878      * @see #getPhysicalHeightInch()
879      */

880     public int getPhysicalHeightDpi() {
881         return physicalHeightDpi;
882     }
883
884     /**
885      * If {@link #check()} was successful, returns the physical width of this image in dpi (dots per inch)
886      * or -1 if no value could be found.
887      * @return physical height (in dpi)
888      * @see #getPhysicalHeightDpi()
889      * @see #getPhysicalWidthDpi()
890      * @see #getPhysicalWidthInch()
891      */

892     public float getPhysicalHeightInch() {
893         int h = getHeight();
894         int ph = getPhysicalHeightDpi();
895         if (h > 0 && ph > 0) {
896             return ((float)h) / ((float)ph);
897         } else {
898             return -1.0f;
899         }
900     }
901
902     /**
903      * If {@link #check()} was successful, returns the physical width of this image in dpi (dots per inch)
904      * or -1 if no value could be found.
905      * @return physical width (in dpi)
906      * @see #getPhysicalHeightDpi()
907      * @see #getPhysicalWidthInch()
908      * @see #getPhysicalHeightInch()
909      */

910     public int getPhysicalWidthDpi() {
911         return physicalWidthDpi;
912     }
913
914     /**
915      * Returns the physical width of an image in inches, or
916      * <code>-1.0f</code> if width information is not available.
917      * Assumes that {@link #check} has been called successfully.
918      * @return physical width in inches or <code>-1.0f</code> on failure
919      * @see #getPhysicalWidthDpi
920      * @see #getPhysicalHeightInch
921      */

922     public float getPhysicalWidthInch() {
923         int w = getWidth();
924         int pw = getPhysicalWidthDpi();
925         if (w > 0 && pw > 0) {
926             return ((float)w) / ((float)pw);
927         } else {
928             return -1.0f;
929         }
930     }
931
932     private int getShortBigEndian(byte[] a, int offs) {
933         return
934             (a[offs] & 0xff) << 8 |
935             (a[offs + 1] & 0xff);
936     }
937
938     private int getShortLittleEndian(byte[] a, int offs) {
939         return (a[offs] & 0xff) | (a[offs + 1] & 0xff) << 8;
940     }
941
942     /**
943      * If {@link #check()} was successful, returns one the image's horizontal
944      * resolution in pixels.
945      * @return image width in pixels
946      */

947     public int getWidth() {
948         return width;
949     }
950
951     /**
952      * To use this class as a command line application, give it either
953      * some file names as parameters (information on them will be
954      * printed to standard output, one line per file) or call
955      * it with no parameters. It will then check data given to it
956      * via standard input.
957      * @param args the program arguments which must be file names
958      */

959     public static void main(String JavaDoc[] args) {
960         ImageInfo imageInfo = new ImageInfo();
961         imageInfo.setDetermineImageNumber(true);
962         boolean verbose = determineVerbosity(args);
963         if (args.length == 0) {
964             run(null, System.in, imageInfo, verbose);
965         } else {
966             int index = 0;
967             while (index < args.length) {
968                 InputStream JavaDoc in = null;
969                 try {
970                     String JavaDoc name = args[index++];
971                     System.out.print(name + ";");
972                     if (name.startsWith("http://")) {
973                         in = new URL(name).openConnection().getInputStream();
974                     } else {
975                         in = new FileInputStream JavaDoc(name);
976                     }
977                     run(name, in, imageInfo, verbose);
978                     in.close();
979                 } catch (Exception JavaDoc e) {
980                     System.out.println(e);
981                     try {
982                         in.close();
983                     } catch (Exception JavaDoc ee) {
984                     }
985                 }
986             }
987         }
988     }
989
990     private static void print(String JavaDoc sourceName, ImageInfo ii, boolean verbose) {
991         if (verbose) {
992             printVerbose(sourceName, ii);
993         } else {
994             printCompact(sourceName, ii);
995         }
996     }
997
998     private static void printCompact(String JavaDoc sourceName, ImageInfo imageInfo) {
999         System.out.println(
1000            imageInfo.getFormatName() + ";" +
1001            imageInfo.getMimeType() + ";" +
1002            imageInfo.getWidth() + ";" +
1003            imageInfo.getHeight() + ";" +
1004            imageInfo.getBitsPerPixel() + ";" +
1005            imageInfo.getNumberOfImages() + ";" +
1006            imageInfo.getPhysicalWidthDpi() + ";" +
1007            imageInfo.getPhysicalHeightDpi() + ";" +
1008            imageInfo.getPhysicalWidthInch() + ";" +
1009            imageInfo.getPhysicalHeightInch()
1010        );
1011    }
1012
1013    private static void printLine(int indentLevels, String JavaDoc text, float value, float minValidValue) {
1014        if (value < minValidValue) {
1015            return;
1016        }
1017        printLine(indentLevels, text, Float.toString(value));
1018    }
1019
1020    private static void printLine(int indentLevels, String JavaDoc text, int value, int minValidValue) {
1021        if (value >= minValidValue) {
1022            printLine(indentLevels, text, Integer.toString(value));
1023        }
1024    }
1025
1026    private static void printLine(int indentLevels, String JavaDoc text, String JavaDoc value) {
1027        if (value == null || value.length() == 0) {
1028            return;
1029        }
1030        while (indentLevels-- > 0) {
1031            System.out.print("\t");
1032        }
1033        if (text != null && text.length() > 0) {
1034            System.out.print(text);
1035            System.out.print(" ");
1036        }
1037        System.out.println(value);
1038    }
1039
1040    private static void printVerbose(String JavaDoc sourceName, ImageInfo ii) {
1041        printLine(0, null, sourceName);
1042        printLine(1, "File format: ", ii.getFormatName());
1043        printLine(1, "MIME type: ", ii.getMimeType());
1044        printLine(1, "Width (pixels): ", ii.getWidth(), 1);
1045        printLine(1, "Height (pixels): ", ii.getHeight(), 1);
1046        printLine(1, "Bits per pixel: ", ii.getBitsPerPixel(), 1);
1047        printLine(1, "Number of images: ", ii.getNumberOfImages(), 1);
1048        printLine(1, "Physical width (dpi): ", ii.getPhysicalWidthDpi(), 1);
1049        printLine(1, "Physical height (dpi): ", ii.getPhysicalHeightDpi(), 1);
1050        printLine(1, "Physical width (inches): ", ii.getPhysicalWidthInch(), 1.0f);
1051        printLine(1, "Physical height (inches): ", ii.getPhysicalHeightInch(), 1.0f);
1052        int numComments = ii.getNumberOfComments();
1053        printLine(1, "Number of textual comments: ", numComments, 1);
1054        if (numComments > 0) {
1055            for (int i = 0; i < numComments; i++) {
1056                printLine(2, null, ii.getComment(i));
1057            }
1058        }
1059    }
1060
1061    private int read() throws IOException JavaDoc {
1062        if (in != null) {
1063            return in.read();
1064        } else {
1065            return din.readByte();
1066        }
1067    }
1068
1069    private int read(byte[] a) throws IOException JavaDoc {
1070        if (in != null) {
1071            return in.read(a);
1072        } else {
1073            din.readFully(a);
1074            return a.length;
1075        }
1076    }
1077
1078    private int read(byte[] a, int offset, int num) throws IOException JavaDoc {
1079        if (in != null) {
1080            return in.read(a, offset, num);
1081        } else {
1082            din.readFully(a, offset, num);
1083            return num;
1084        }
1085    }
1086
1087    private String JavaDoc readLine() throws IOException JavaDoc {
1088        return readLine(new StringBuffer JavaDoc());
1089    }
1090
1091    private String JavaDoc readLine(StringBuffer JavaDoc sb) throws IOException JavaDoc {
1092        boolean finished;
1093        do {
1094            int value = read();
1095            finished = (value == -1 || value == 10);
1096            if (!finished) {
1097                sb.append((char)value);
1098            }
1099        } while (!finished);
1100        return sb.toString();
1101    }
1102
1103    /**
1104     * Read an unsigned value from the given number of bits
1105     */

1106    public long readUBits( int numBits ) throws IOException JavaDoc
1107    {
1108        if (numBits == 0) {
1109            return 0;
1110        }
1111        int bitsLeft = numBits;
1112        long result = 0;
1113        if (bitPos == 0) { //no value in the buffer - read a byte
1114
if (in != null) {
1115                bitBuf = in.read();
1116            } else {
1117                bitBuf = din.readByte();
1118            }
1119            bitPos = 8;
1120        }
1121        
1122        while( true )
1123        {
1124            int shift = bitsLeft - bitPos;
1125            if( shift > 0 )
1126            {
1127                // Consume the entire buffer
1128
result |= bitBuf << shift;
1129                bitsLeft -= bitPos;
1130
1131                // Get the next byte from the input stream
1132
if (in != null) {
1133                  bitBuf = in.read();
1134                } else {
1135                  bitBuf = din.readByte();
1136                }
1137                bitPos = 8;
1138            }
1139            else
1140            {
1141                // Consume a portion of the buffer
1142
result |= bitBuf >> -shift;
1143                bitPos -= bitsLeft;
1144                bitBuf &= 0xff >> (8 - bitPos); // mask off the consumed bits
1145

1146                return result;
1147            }
1148        }
1149    }
1150    
1151        /**
1152     * Read a signed value from the given number of bits
1153     */

1154    private int readSBits( int numBits ) throws IOException JavaDoc
1155    {
1156        // Get the number as an unsigned value.
1157
long uBits = readUBits( numBits );
1158
1159        // Is the number negative?
1160
if( ( uBits & (1L << (numBits - 1))) != 0 )
1161        {
1162            // Yes. Extend the sign.
1163
uBits |= -1L << numBits;
1164        }
1165
1166        return (int)uBits;
1167    }
1168   
1169    /**
1170     * Reset the bit buffer
1171     */

1172    public void synchBits()
1173    {
1174        bitBuf = 0;
1175        bitPos = 0;
1176    }
1177
1178    private String JavaDoc readLine(int firstChar) throws IOException JavaDoc {
1179        StringBuffer JavaDoc result = new StringBuffer JavaDoc();
1180        result.append((char)firstChar);
1181        return readLine(result);
1182    }
1183
1184    private static void run(String JavaDoc sourceName, InputStream JavaDoc in, ImageInfo imageInfo, boolean verbose) {
1185        imageInfo.setInput(in);
1186        imageInfo.setDetermineImageNumber(false);
1187        imageInfo.setCollectComments(verbose);
1188        if (imageInfo.check()) {
1189            print(sourceName, imageInfo, verbose);
1190        }
1191    }
1192
1193    /**
1194     * Specify whether textual comments are supposed to be extracted from input.
1195     * Default is <code>false</code>.
1196     * If enabled, comments will be added to an internal list.
1197     * @param newValue if <code>true</code>, this class will read comments
1198     * @see #getNumberOfComments
1199     * @see #getComment
1200     */

1201    public void setCollectComments(boolean newValue)
1202    {
1203        collectComments = newValue;
1204    }
1205
1206    /**
1207     * Specify whether the number of images in a file is to be
1208     * determined - default is <code>false</code>.
1209     * This is a special option because some file formats require running over
1210     * the entire file to find out the number of images, a rather time-consuming
1211     * task.
1212     * Not all file formats support more than one image.
1213     * If this method is called with <code>true</code> as argument,
1214     * the actual number of images can be queried via
1215     * {@link #getNumberOfImages()} after a successful call to
1216     * {@link #check()}.
1217     * @param newValue will the number of images be determined?
1218     * @see #getNumberOfImages
1219     */

1220    public void setDetermineImageNumber(boolean newValue)
1221    {
1222        determineNumberOfImages = newValue;
1223    }
1224
1225    /**
1226     * Set the input stream to the argument stream (or file).
1227     * Note that {@link java.io.RandomAccessFile} implements
1228     * {@link java.io.DataInput}.
1229     * @param dataInput the input stream to read from
1230     */

1231    public void setInput(DataInput JavaDoc dataInput) {
1232        din = dataInput;
1233        in = null;
1234    }
1235
1236    /**
1237     * Set the input stream to the argument stream (or file).
1238     * @param inputStream the input stream to read from
1239     */

1240    public void setInput(InputStream JavaDoc inputStream) {
1241        in = inputStream;
1242        din = null;
1243    }
1244
1245    private void setPhysicalHeightDpi(int newValue) {
1246        physicalWidthDpi = newValue;
1247    }
1248
1249    private void setPhysicalWidthDpi(int newValue) {
1250        physicalHeightDpi = newValue;
1251    }
1252
1253    private void skip(int num) throws IOException JavaDoc {
1254        while (num > 0) {
1255            long result;
1256            if (in != null) {
1257                result = in.skip(num);
1258            } else {
1259                result = din.skipBytes(num);
1260            }
1261            if (result > 0) {
1262                num -= result;
1263            }
1264        }
1265    }
1266}
1267
Popular Tags