KickJava   Java API By Example, From Geeks To Geeks.

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

124 public class ImageInfo {
125     /**
126      * Return value of {@link #getFormat()} for JPEG streams.
127      * ImageInfo can extract physical resolution and comments
128      * from JPEGs (only from APP0 headers).
129      * Only one image can be stored in a file.
130      * It is determined whether the JPEG stream is progressive
131      * (see {@link #isProgressive()}).
132      */

133     public static final int FORMAT_JPEG = 0;
134
135     /**
136      * Return value of {@link #getFormat()} for GIF streams.
137      * ImageInfo can extract comments from GIFs and count the number
138      * of images (GIFs with more than one image are animations).
139      * If you know of a place where GIFs store the physical resolution
140      * of an image, please
141      * <a HREF="http://www.geocities.com/marcoschmidt.geo/contact.html">send me a mail</a>!
142      * It is determined whether the GIF stream is interlaced (see {@link #isProgressive()}).
143      */

144     public static final int FORMAT_GIF = 1;
145
146     /**
147      * Return value of {@link #getFormat()} for PNG streams.
148      * PNG only supports one image per file.
149      * Both physical resolution and comments can be stored with PNG,
150      * but ImageInfo is currently not able to extract those.
151      * It is determined whether the PNG stream is interlaced (see {@link #isProgressive()}).
152      */

153     public static final int FORMAT_PNG = 2;
154
155     /**
156      * Return value of {@link #getFormat()} for BMP streams.
157      * BMP only supports one image per file.
158      * BMP does not allow for comments.
159      * The physical resolution can be stored.
160      */

161     public static final int FORMAT_BMP = 3;
162
163     /**
164      * Return value of {@link #getFormat()} for PCX streams.
165      * PCX does not allow for comments or more than one image per file.
166      * However, the physical resolution can be stored.
167      */

168     public static final int FORMAT_PCX = 4;
169
170     /**
171      * Return value of {@link #getFormat()} for IFF streams.
172      */

173     public static final int FORMAT_IFF = 5;
174
175     /**
176      * Return value of {@link #getFormat()} for RAS streams.
177      * Sun Raster allows for one image per file only and is not able to
178      * store physical resolution or comments.
179      */

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

208     private static final String JavaDoc[] FORMAT_NAMES =
209         {"JPEG", "GIF", "PNG", "BMP", "PCX",
210          "IFF", "RAS", "PBM", "PGM", "PPM",
211          "PSD", "SWF"};
212
213     /**
214      * The names of the MIME types for all supported file formats.
215      * The FORMAT_xyz int constants can be used as index values for
216      * this array.
217      */

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

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

752     private static boolean determineVerbosity(String JavaDoc[] args) {
753         if (args != null && args.length > 0) {
754             for (int i = 0; i < args.length; i++) {
755                 if ("-c".equals(args[i])) {
756                     return false;
757                 }
758             }
759         }
760         return true;
761     }
762
763     private boolean equals(byte[] a1, int offs1, byte[] a2, int offs2, int num) {
764         while (num-- > 0) {
765             if (a1[offs1++] != a2[offs2++]) {
766                 return false;
767             }
768         }
769         return true;
770     }
771
772     /**
773      * If {@link #check()} was successful, returns the image's number of bits per pixel.
774      * Does not include transparency information like the alpha channel.
775      * @return number of bits per image pixel
776      */

777     public int getBitsPerPixel() {
778         return bitsPerPixel;
779     }
780
781     /**
782      * Returns the index'th comment retrieved from the image.
783      * @throws IllegalArgumentException if index is smaller than 0 or larger than or equal
784      * to the number of comments retrieved
785      * @see #getNumberOfComments
786      */

787     public String JavaDoc getComment(int index) {
788         if (comments == null || index < 0 || index >= comments.size()) {
789             throw new IllegalArgumentException JavaDoc("Not a valid comment index: " + index);
790         }
791         return (String JavaDoc)comments.elementAt(index);
792     }
793
794     /**
795      * If {@link #check()} was successful, returns the image format as one
796      * of the FORMAT_xyz constants from this class.
797      * Use {@link #getFormatName()} to get a textual description of the file format.
798      * @return file format as a FORMAT_xyz constant
799      */

800     public int getFormat() {
801         return format;
802     }
803
804     /**
805      * If {@link #check()} was successful, returns the image format's name.
806      * Use {@link #getFormat()} to get a unique number.
807      * @return file format name
808      */

809     public String JavaDoc getFormatName() {
810         if (format >= 0 && format < FORMAT_NAMES.length) {
811             return FORMAT_NAMES[format];
812         } else {
813             return "?";
814         }
815     }
816
817     /**
818      * If {@link #check()} was successful, returns one the image's vertical
819      * resolution in pixels.
820      * @return image height in pixels
821      */

822     public int getHeight() {
823         return height;
824     }
825
826     private int getIntBigEndian(byte[] a, int offs) {
827         return
828             (a[offs] & 0xff) << 24 |
829             (a[offs + 1] & 0xff) << 16 |
830             (a[offs + 2] & 0xff) << 8 |
831             a[offs + 3] & 0xff;
832     }
833
834     private int getIntLittleEndian(byte[] a, int offs) {
835         return
836             (a[offs + 3] & 0xff) << 24 |
837             (a[offs + 2] & 0xff) << 16 |
838             (a[offs + 1] & 0xff) << 8 |
839             a[offs] & 0xff;
840     }
841
842     /**
843      * If {@link #check()} was successful, returns a String with the
844      * MIME type of the format.
845      * @return MIME type, e.g. <code>image/jpeg</code>
846      */

847     public String JavaDoc getMimeType() {
848         if (format >= 0 && format < MIME_TYPE_STRINGS.length) {
849             if (format == FORMAT_JPEG && progressive)
850             {
851                 return "image/pjpeg";
852             }
853             return MIME_TYPE_STRINGS[format];
854         } else {
855             return null;
856         }
857     }
858
859     /**
860      * If {@link #check()} was successful and {@link #setCollectComments(boolean)} was called with
861      * <code>true</code> as argument, returns the number of comments retrieved
862      * from the input image stream / file.
863      * Any number &gt;= 0 and smaller than this number of comments is then a
864      * valid argument for the {@link #getComment(int)} method.
865      * @return number of comments retrieved from input image
866      */

867     public int getNumberOfComments()
868     {
869         if (comments == null) {
870             return 0;
871         } else {
872             return comments.size();
873         }
874     }
875
876     /**
877      * Returns the number of images in the examined file.
878      * Assumes that <code>setDetermineImageNumber(true);</code> was called before
879      * a successful call to {@link #check()}.
880      * This value can currently be only different from <code>1</code> for GIF images.
881      * @return number of images in file
882      */

883     public int getNumberOfImages()
884     {
885         return numberOfImages;
886     }
887
888     /**
889      * Returns the physical height of this image in dots per inch (dpi).
890      * Assumes that {@link #check()} was successful.
891      * Returns <code>-1</code> on failure.
892      * @return physical height (in dpi)
893      * @see #getPhysicalWidthDpi()
894      * @see #getPhysicalHeightInch()
895      */

896     public int getPhysicalHeightDpi() {
897         return physicalHeightDpi;
898     }
899
900     /**
901      * If {@link #check()} was successful, returns the physical width of this image in dpi (dots per inch)
902      * or -1 if no value could be found.
903      * @return physical height (in dpi)
904      * @see #getPhysicalHeightDpi()
905      * @see #getPhysicalWidthDpi()
906      * @see #getPhysicalWidthInch()
907      */

908     public float getPhysicalHeightInch() {
909         int h = getHeight();
910         int ph = getPhysicalHeightDpi();
911         if (h > 0 && ph > 0) {
912             return ((float)h) / ((float)ph);
913         } else {
914             return -1.0f;
915         }
916     }
917
918     /**
919      * If {@link #check()} was successful, returns the physical width of this image in dpi (dots per inch)
920      * or -1 if no value could be found.
921      * @return physical width (in dpi)
922      * @see #getPhysicalHeightDpi()
923      * @see #getPhysicalWidthInch()
924      * @see #getPhysicalHeightInch()
925      */

926     public int getPhysicalWidthDpi() {
927         return physicalWidthDpi;
928     }
929
930     /**
931      * Returns the physical width of an image in inches, or
932      * <code>-1.0f</code> if width information is not available.
933      * Assumes that {@link #check} has been called successfully.
934      * @return physical width in inches or <code>-1.0f</code> on failure
935      * @see #getPhysicalWidthDpi
936      * @see #getPhysicalHeightInch
937      */

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

963     public int getWidth() {
964         return width;
965     }
966
967     /**
968      * Returns whether the image is stored in a progressive (also called: interlaced) way.
969      * @return true for progressive/interlaced, false otherwise
970      */

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

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

1170                return result;
1171            }
1172        }
1173    }
1174
1175        /**
1176     * Read a signed value from the given number of bits
1177     */

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

1222    public void setCollectComments(boolean newValue)
1223    {
1224        collectComments = newValue;
1225    }
1226
1227    /**
1228     * Specify whether the number of images in a file is to be
1229     * determined - default is <code>false</code>.
1230     * This is a special option because some file formats require running over
1231     * the entire file to find out the number of images, a rather time-consuming
1232     * task.
1233     * Not all file formats support more than one image.
1234     * If this method is called with <code>true</code> as argument,
1235     * the actual number of images can be queried via
1236     * {@link #getNumberOfImages()} after a successful call to
1237     * {@link #check()}.
1238     * @param newValue will the number of images be determined?
1239     * @see #getNumberOfImages
1240     */

1241    public void setDetermineImageNumber(boolean newValue)
1242    {
1243        determineNumberOfImages = newValue;
1244    }
1245
1246    /**
1247     * Set the input stream to the argument stream (or file).
1248     * Note that {@link java.io.RandomAccessFile} implements
1249     * {@link java.io.DataInput}.
1250     * @param dataInput the input stream to read from
1251     */

1252    public void setInput(DataInput JavaDoc dataInput) {
1253        din = dataInput;
1254        in = null;
1255    }
1256
1257    /**
1258     * Set the input stream to the argument stream (or file).
1259     * @param inputStream the input stream to read from
1260     */

1261    public void setInput(InputStream JavaDoc inputStream) {
1262        in = inputStream;
1263        din = null;
1264    }
1265
1266    private void setPhysicalHeightDpi(int newValue) {
1267        physicalWidthDpi = newValue;
1268    }
1269
1270    private void setPhysicalWidthDpi(int newValue) {
1271        physicalHeightDpi = newValue;
1272    }
1273
1274    private void skip(int num) throws IOException JavaDoc {
1275        while (num > 0) {
1276            long result;
1277            if (in != null) {
1278                result = in.skip(num);
1279            } else {
1280                result = din.skipBytes(num);
1281            }
1282            if (result > 0) {
1283                num -= result;
1284            }
1285        }
1286    }
1287}
1288
Popular Tags