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