KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > wings > util > ImageInfo


1 /*
2  * ImageInfo.java
3  *
4  * Version 1.5
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 2004-02-29
15  */

16
17 /*
18  * $Id: ImageInfo.java,v 1.3 2004/12/01 07:54:30 hengels Exp $
19  * Copyright 2000,2005 wingS development team.
20  *
21  * This file is part of wingS (http://www.j-wings.org).
22  *
23  * wingS is free software; you can redistribute it and/or modify
24  * it under the terms of the GNU Lesser General Public License
25  * as published by the Free Software Foundation; either version 2.1
26  * of the License, or (at your option) any later version.
27  *
28  * Please see COPYING for the complete licence.
29  */

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

136 public class ImageInfo {
137     /**
138      * Return value of {@link #getFormat()} for JPEG streams.
139      * ImageInfo can extract physical resolution and comments
140      * from JPEGs (only from APP0 headers).
141      * Only one image can be stored in a file.
142      * It is determined whether the JPEG stream is progressive
143      * (see {@link #isProgressive()}).
144      */

145     public static final int FORMAT_JPEG = 0;
146
147     /**
148      * Return value of {@link #getFormat()} for GIF streams.
149      * ImageInfo can extract comments from GIFs and count the number
150      * of images (GIFs with more than one image are animations).
151      * If you know of a place where GIFs store the physical resolution
152      * of an image, please
153      * <a HREF="http://www.geocities.com/marcoschmidt.geo/contact.html">send me a mail</a>!
154      * It is determined whether the GIF stream is interlaced (see {@link #isProgressive()}).
155      */

156     public static final int FORMAT_GIF = 1;
157
158     /**
159      * Return value of {@link #getFormat()} for PNG streams.
160      * PNG only supports one image per file.
161      * Both physical resolution and comments can be stored with PNG,
162      * but ImageInfo is currently not able to extract those.
163      * It is determined whether the PNG stream is interlaced (see {@link #isProgressive()}).
164      */

165     public static final int FORMAT_PNG = 2;
166
167     /**
168      * Return value of {@link #getFormat()} for BMP streams.
169      * BMP only supports one image per file.
170      * BMP does not allow for comments.
171      * The physical resolution can be stored.
172      */

173     public static final int FORMAT_BMP = 3;
174
175     /**
176      * Return value of {@link #getFormat()} for PCX streams.
177      * PCX does not allow for comments or more than one image per file.
178      * However, the physical resolution can be stored.
179      */

180     public static final int FORMAT_PCX = 4;
181
182     /**
183      * Return value of {@link #getFormat()} for IFF streams.
184      */

185     public static final int FORMAT_IFF = 5;
186
187     /**
188      * Return value of {@link #getFormat()} for RAS streams.
189      * Sun Raster allows for one image per file only and is not able to
190      * store physical resolution or comments.
191      */

192     public static final int FORMAT_RAS = 6;
193
194     /**
195      * Return value of {@link #getFormat()} for PBM streams.
196      */

197     public static final int FORMAT_PBM = 7;
198
199     /**
200      * Return value of {@link #getFormat()} for PGM streams.
201      */

202     public static final int FORMAT_PGM = 8;
203
204     /**
205      * Return value of {@link #getFormat()} for PPM streams.
206      */

207     public static final int FORMAT_PPM = 9;
208
209     /**
210      * Return value of {@link #getFormat()} for PSD streams.
211      */

212     public static final int FORMAT_PSD = 10;
213
214     /**
215      * Return value of {@link #getFormat()} for SWF (Shockwave) streams.
216      */

217     public static final int FORMAT_SWF = 11;
218
219     public static final int COLOR_TYPE_UNKNOWN = -1;
220     public static final int COLOR_TYPE_TRUECOLOR_RGB = 0;
221     public static final int COLOR_TYPE_PALETTED = 1;
222     public static final int COLOR_TYPE_GRAYSCALE = 2;
223     public static final int COLOR_TYPE_BLACK_AND_WHITE = 3;
224
225     /**
226      * The names of all supported file formats.
227      * The FORMAT_xyz int constants can be used as index values for
228      * this array.
229      */

230     public static final String JavaDoc[] FORMAT_NAMES =
231             {"JPEG", "GIF", "PNG", "BMP", "PCX",
232              "IFF", "RAS", "PBM", "PGM", "PPM",
233              "PSD", "SWF"};
234
235     /**
236      * The names of the MIME types for all supported file formats.
237      * The FORMAT_xyz int constants can be used as index values for
238      * this array.
239      */

240     public static final String JavaDoc[] MIME_TYPE_STRINGS =
241             {"image/jpeg", "image/gif", "image/png", "image/bmp", "image/pcx",
242              "image/iff", "image/ras", "image/x-portable-bitmap", "image/x-portable-graymap", "image/x-portable-pixmap",
243              "image/psd", "application/x-shockwave-flash"};
244
245     private int width;
246     private int height;
247     private int bitsPerPixel;
248     private int colorType = COLOR_TYPE_UNKNOWN;
249     private boolean progressive;
250     private int format;
251     private InputStream JavaDoc in;
252     private DataInput JavaDoc din;
253     private boolean collectComments = true;
254     private List JavaDoc comments;
255     private boolean determineNumberOfImages;
256     private int numberOfImages;
257     private int physicalHeightDpi;
258     private int physicalWidthDpi;
259     private int bitBuf;
260     private int bitPos;
261
262     private void addComment(String JavaDoc s) {
263         if (comments == null) {
264             comments = new ArrayList JavaDoc();
265         }
266         comments.add(s);
267     }
268
269     /**
270      * Call this method after you have provided an input stream or file
271      * using {@link #setInput(java.io.InputStream)} or {@link #setInput(java.io.DataInput)}.
272      * If true is returned, the file format was known and information
273      * on the file's content can be retrieved using the various getXyz methods.
274      *
275      * @return if information could be retrieved from input
276      */

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

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

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

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

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

796     public String JavaDoc getFormatName() {
797         if (format >= 0 && format < FORMAT_NAMES.length) {
798             return FORMAT_NAMES[format];
799         } else {
800             return "?";
801         }
802     }
803
804     /**
805      * If {@link #check()} was successful, returns one the image's vertical
806      * resolution in pixels.
807      *
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      *
834      * @return MIME type, e.g. <code>image/jpeg</code>
835      */

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

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

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

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

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

917     public int getPhysicalWidthDpi() {
918         return physicalWidthDpi;
919     }
920
921     /**
922      * Returns the physical width of an image in inches, or
923      * <code>-1.0f</code> if width information is not available.
924      * Assumes that {@link #check} has been called successfully.
925      *
926      * @return physical width in inches or <code>-1.0f</code> on failure
927      * @see #getPhysicalWidthDpi
928      * @see #getPhysicalHeightInch
929      */

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

956     public int getWidth() {
957         return width;
958     }
959
960     /**
961      * Returns whether the image is stored in a progressive (also called: interlaced) way.
962      *
963      * @return true for progressive/interlaced, false otherwise
964      */

965     public boolean isProgressive() {
966         return progressive;
967     }
968
969     /**
970      * To use this class as a command line application, give it either
971      * some file names as parameters (information on them will be
972      * printed to standard output, one line per file) or call
973      * it with no parameters. It will then check data given to it
974      * via standard input.
975      *
976      * @param args the program arguments which must be file names
977      */

978     public static void main(String JavaDoc[] args) {
979         ImageInfo imageInfo = new ImageInfo();
980         imageInfo.setDetermineImageNumber(true);
981         boolean verbose = determineVerbosity(args);
982         if (args.length == 0) {
983             run(null, System.in, imageInfo, verbose);
984         } else {
985             int index = 0;
986             while (index < args.length) {
987                 InputStream JavaDoc in = null;
988                 try {
989                     String JavaDoc name = args[index++];
990                     System.out.print(name + ";");
991                     if (name.startsWith("http://")) {
992                         in = new URL JavaDoc(name).openConnection().getInputStream();
993                     } else {
994                         in = new FileInputStream JavaDoc(name);
995                     }
996                     run(name, in, imageInfo, verbose);
997                     in.close();
998                 } catch (Exception JavaDoc e) {
999                     System.out.println(e);
1000                    try {
1001                        in.close();
1002                    } catch (Exception JavaDoc ee) {
1003                    }
1004                }
1005            }
1006        }
1007    }
1008
1009    private static void print(String JavaDoc sourceName, ImageInfo ii, boolean verbose) {
1010        if (verbose) {
1011            printVerbose(sourceName, ii);
1012        } else {
1013            printCompact(sourceName, ii);
1014        }
1015    }
1016
1017    private static void printCompact(String JavaDoc sourceName, ImageInfo imageInfo) {
1018        System.out.println(imageInfo.getFormatName() + ";" +
1019                imageInfo.getMimeType() + ";" +
1020                imageInfo.getWidth() + ";" +
1021                imageInfo.getHeight() + ";" +
1022                imageInfo.getBitsPerPixel() + ";" +
1023                imageInfo.getNumberOfImages() + ";" +
1024                imageInfo.getPhysicalWidthDpi() + ";" +
1025                imageInfo.getPhysicalHeightDpi() + ";" +
1026                imageInfo.getPhysicalWidthInch() + ";" +
1027                imageInfo.getPhysicalHeightInch() + ";" +
1028                imageInfo.isProgressive());
1029    }
1030
1031    private static void printLine(int indentLevels, String JavaDoc text, float value, float minValidValue) {
1032        if (value < minValidValue) {
1033            return;
1034        }
1035        printLine(indentLevels, text, Float.toString(value));
1036    }
1037
1038    private static void printLine(int indentLevels, String JavaDoc text, int value, int minValidValue) {
1039        if (value >= minValidValue) {
1040            printLine(indentLevels, text, Integer.toString(value));
1041        }
1042    }
1043
1044    private static void printLine(int indentLevels, String JavaDoc text, String JavaDoc value) {
1045        if (value == null || value.length() == 0) {
1046            return;
1047        }
1048        while (indentLevels-- > 0) {
1049            System.out.print("\t");
1050        }
1051        if (text != null && text.length() > 0) {
1052            System.out.print(text);
1053            System.out.print(" ");
1054        }
1055        System.out.println(value);
1056    }
1057
1058    private static void printVerbose(String JavaDoc sourceName, ImageInfo ii) {
1059        printLine(0, null, sourceName);
1060        printLine(1, "File format: ", ii.getFormatName());
1061        printLine(1, "MIME type: ", ii.getMimeType());
1062        printLine(1, "Width (pixels): ", ii.getWidth(), 1);
1063        printLine(1, "Height (pixels): ", ii.getHeight(), 1);
1064        printLine(1, "Bits per pixel: ", ii.getBitsPerPixel(), 1);
1065        printLine(1, "Progressive: ", Boolean.toString(ii.isProgressive()));
1066        printLine(1, "Number of images: ", ii.getNumberOfImages(), 1);
1067        printLine(1, "Physical width (dpi): ", ii.getPhysicalWidthDpi(), 1);
1068        printLine(1, "Physical height (dpi): ", ii.getPhysicalHeightDpi(), 1);
1069        printLine(1, "Physical width (inches): ", ii.getPhysicalWidthInch(), 1.0f);
1070        printLine(1, "Physical height (inches): ", ii.getPhysicalHeightInch(), 1.0f);
1071        int numComments = ii.getNumberOfComments();
1072        printLine(1, "Number of textual comments: ", numComments, 1);
1073        if (numComments > 0) {
1074            for (int i = 0; i < numComments; i++) {
1075                printLine(2, null, ii.getComment(i));
1076            }
1077        }
1078    }
1079
1080    private int read() throws IOException JavaDoc {
1081        if (in != null) {
1082            return in.read();
1083        } else {
1084            return din.readByte();
1085        }
1086    }
1087
1088    private int read(byte[] a) throws IOException JavaDoc {
1089        if (in != null) {
1090            return in.read(a);
1091        } else {
1092            din.readFully(a);
1093            return a.length;
1094        }
1095    }
1096
1097    private int read(byte[] a, int offset, int num) throws IOException JavaDoc {
1098        if (in != null) {
1099            return in.read(a, offset, num);
1100        } else {
1101            din.readFully(a, offset, num);
1102            return num;
1103        }
1104    }
1105
1106    private String JavaDoc readLine() throws IOException JavaDoc {
1107        return readLine(new StringBuffer JavaDoc());
1108    }
1109
1110    private String JavaDoc readLine(StringBuffer JavaDoc sb) throws IOException JavaDoc {
1111        boolean finished;
1112        do {
1113            int value = read();
1114            finished = (value == -1 || value == 10);
1115            if (!finished) {
1116                sb.append((char) value);
1117            }
1118        } while (!finished);
1119        return sb.toString();
1120    }
1121
1122    private long readUBits(int numBits) throws IOException JavaDoc {
1123        if (numBits == 0) {
1124            return 0;
1125        }
1126        int bitsLeft = numBits;
1127        long result = 0;
1128        if (bitPos == 0) { //no value in the buffer - read a byte
1129
if (in != null) {
1130                bitBuf = in.read();
1131            } else {
1132                bitBuf = din.readByte();
1133            }
1134            bitPos = 8;
1135        }
1136
1137        while (true) {
1138            int shift = bitsLeft - bitPos;
1139            if (shift > 0) {
1140                // Consume the entire buffer
1141
result |= bitBuf << shift;
1142                bitsLeft -= bitPos;
1143
1144                // Get the next byte from the input stream
1145
if (in != null) {
1146                    bitBuf = in.read();
1147                } else {
1148                    bitBuf = din.readByte();
1149                }
1150                bitPos = 8;
1151            } else {
1152                // Consume a portion of the buffer
1153
result |= bitBuf >> -shift;
1154                bitPos -= bitsLeft;
1155                bitBuf &= 0xff >> (8 - bitPos); // mask off the consumed bits
1156

1157                return result;
1158            }
1159        }
1160    }
1161
1162    /**
1163     * Read a signed value from the given number of bits
1164     */

1165    private int readSBits(int numBits) throws IOException JavaDoc {
1166        // Get the number as an unsigned value.
1167
long uBits = readUBits(numBits);
1168
1169        // Is the number negative?
1170
if ((uBits & (1L << (numBits - 1))) != 0) {
1171            // Yes. Extend the sign.
1172
uBits |= -1L << numBits;
1173        }
1174
1175        return (int) uBits;
1176    }
1177
1178    private void synchBits() {
1179        bitBuf = 0;
1180        bitPos = 0;
1181    }
1182
1183    private String JavaDoc readLine(int firstChar) throws IOException JavaDoc {
1184        StringBuffer JavaDoc result = new StringBuffer JavaDoc();
1185        result.append((char) firstChar);
1186        return readLine(result);
1187    }
1188
1189    private static void run(String JavaDoc sourceName, InputStream JavaDoc in, ImageInfo imageInfo, boolean verbose) {
1190        imageInfo.setInput(in);
1191        imageInfo.setDetermineImageNumber(false);
1192        imageInfo.setCollectComments(verbose);
1193        if (imageInfo.check()) {
1194            print(sourceName, imageInfo, verbose);
1195        }
1196    }
1197
1198    /**
1199     * Specify whether textual comments are supposed to be extracted from input.
1200     * Default is <code>false</code>.
1201     * If enabled, comments will be added to an internal list.
1202     *
1203     * @param newValue if <code>true</code>, this class will read comments
1204     * @see #getNumberOfComments
1205     * @see #getComment
1206     */

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

1226    public void setDetermineImageNumber(boolean newValue) {
1227        determineNumberOfImages = newValue;
1228    }
1229
1230    /**
1231     * Set the input stream to the argument stream (or file).
1232     * Note that {@link java.io.RandomAccessFile} implements
1233     * {@link java.io.DataInput}.
1234     *
1235     * @param dataInput the input stream to read from
1236     */

1237    public void setInput(DataInput JavaDoc dataInput) {
1238        din = dataInput;
1239        in = null;
1240    }
1241
1242    /**
1243     * Set the input stream to the argument stream (or file).
1244     *
1245     * @param inputStream the input stream to read from
1246     */

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