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