Index: /FCKeditor.Java/branches/2.4/pom.xml
===================================================================
--- /FCKeditor.Java/branches/2.4/pom.xml	(revision 1642)
+++ /FCKeditor.Java/branches/2.4/pom.xml	(revision 1643)
@@ -20,9 +20,4 @@
 			<artifactId>commons-io</artifactId>
 			<version>1.3.2</version>
-		</dependency>
-		<dependency>
-			<groupId>org.devlib.schmidt</groupId>
-			<artifactId>imageinfo</artifactId>
-			<version>1.7</version>
 		</dependency>
 		<dependency>
Index: /FCKeditor.Java/branches/2.4/src/main/java/org/devlib/schmidt/imageinfo/ImageInfo.java
===================================================================
--- /FCKeditor.Java/branches/2.4/src/main/java/org/devlib/schmidt/imageinfo/ImageInfo.java	(revision 1643)
+++ /FCKeditor.Java/branches/2.4/src/main/java/org/devlib/schmidt/imageinfo/ImageInfo.java	(revision 1643)
@@ -0,0 +1,1231 @@
+/*
+ * ImageInfo.java
+ *
+ * Version 1.9
+ *
+ * A Java class to determine image width, height and color depth for
+ * a number of image file formats.
+ *
+ * Written by Marco Schmidt 
+ *
+ * Contributed to the Public Domain.
+ */
+package org.devlib.schmidt.imageinfo;
+
+import java.io.DataInput;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Vector;
+
+/**
+ * Get file format, image resolution, number of bits per pixel and optionally 
+ * number of images, comments and physical resolution from 
+ * JPEG, GIF, BMP, PCX, PNG, IFF, RAS, PBM, PGM, PPM and PSD files 
+ * (or input streams).
+ * <p>
+ * Use the class like this:
+ * <pre>
+ * ImageInfo ii = new ImageInfo();
+ * ii.setInput(in); // in can be InputStream or RandomAccessFile
+ * ii.setDetermineImageNumber(true); // default is false
+ * ii.setCollectComments(true); // default is false
+ * if (!ii.check()) {
+ *   System.err.println("Not a supported image file format.");
+ *   return;
+ * }
+ * System.out.println(ii.getFormatName() + ", " + ii.getMimeType() + 
+ *   ", " + ii.getWidth() + " x " + ii.getHeight() + " pixels, " + 
+ *   ii.getBitsPerPixel() + " bits per pixel, " + ii.getNumberOfImages() +
+ *   " image(s), " + ii.getNumberOfComments() + " comment(s).");
+ *  // there are other properties, check out the API documentation
+ * </pre>
+ * You can also use this class as a command line program.
+ * Call it with a number of image file names and URLs as parameters:
+ * <pre>
+ *   java ImageInfo *.jpg *.png *.gif http://somesite.tld/image.jpg
+ * </pre>
+ * or call it without parameters and pipe data to it:
+ * <pre>
+ *   java ImageInfo &lt; image.jpg  
+ * </pre>
+ * <p>
+ * Known limitations:
+ * <ul>
+ * <li>When the determination of the number of images is turned off, GIF bits 
+ *  per pixel are only read from the global header.
+ *  For some GIFs, local palettes change this to a typically larger
+ *  value. To be certain to get the correct color depth, call
+ *  setDetermineImageNumber(true) before calling check().
+ *  The complete scan over the GIF file will take additional time.</li>
+ * <li>Transparency information is not included in the bits per pixel count.
+ *  Actually, it was my decision not to include those bits, so it's a feature! ;-)</li>
+ * </ul>
+ * <p>
+ * Requirements:
+ * <ul>
+ * <li>Java 1.1 or higher</li>
+ * </ul>
+ * <p>
+ * The latest version can be found at <a href="http://schmidt.devlib.org/image-info/">http://schmidt.devlib.org/image-info/</a>.
+ * <p>
+ * Written by Marco Schmidt.
+ * <p>
+ * This class is contributed to the Public Domain.
+ * Use it at your own risk.
+ * <p>
+ * <a name="history">History</a>:
+ * <ul>
+ * <li><strong>2001-08-24</strong> Initial version.</li>
+ * <li><strong>2001-10-13</strong> Added support for the file formats BMP and PCX.</li>
+ * <li><strong>2001-10-16</strong> Fixed bug in read(int[], int, int) that returned
+ * <li><strong>2002-01-22</strong> Added support for file formats Amiga IFF and Sun Raster (RAS).</li>
+ * <li><strong>2002-01-24</strong> Added support for file formats Portable Bitmap / Graymap / Pixmap (PBM, PGM, PPM) and Adobe Photoshop (PSD).
+ *   Added new method getMimeType() to return the MIME type associated with a particular file format.</li>
+ * <li><strong>2002-03-15</strong> Added support to recognize number of images in file. Only works with GIF.
+ *   Use {@link #setDetermineImageNumber} with <code>true</code> as argument to identify animated GIFs
+ *   ({@link #getNumberOfImages()} will return a value larger than <code>1</code>).</li>
+ * <li><strong>2002-04-10</strong> Fixed a bug in the feature 'determine number of images in animated GIF' introduced with version 1.1.
+ *   Thanks to Marcelo P. Lima for sending in the bug report. 
+ *   Released as 1.1.1.</li>
+ * <li><strong>2002-04-18</strong> Added {@link #setCollectComments(boolean)}. 
+ *  That new method lets the user specify whether textual comments are to be  
+ *  stored in an internal list when encountered in an input image file / stream.
+ *  Added two methods to return the physical width and height of the image in dpi: 
+ *   {@link #getPhysicalWidthDpi()} and {@link #getPhysicalHeightDpi()}.
+ *  If the physical resolution could not be retrieved, these methods return <code>-1</code>.
+ *  </li>
+ * <li><strong>2002-04-23</strong> Added support for the new properties physical resolution and
+ *   comments for some formats. Released as 1.2.</li>
+ * <li><strong>2002-06-17</strong> Added support for SWF, sent in by Michael Aird.
+ *  Changed checkJpeg() so that other APP markers than APP0 will not lead to a failure anymore.
+ *  Released as 1.3.</li>
+ * <li><strong>2003-07-28</strong> Bug fix - skip method now takes return values into consideration.
+ *  Less bytes than necessary may have been skipped, leading to flaws in the retrieved information in some cases.
+ *  Thanks to Bernard Bernstein for pointing that out.
+ *  Released as 1.4.</li>
+ * <li><strong>2004-02-29</strong> Added support for recognizing progressive JPEG and
+ *  interlaced PNG and GIF. A new method {@link #isProgressive()} returns whether ImageInfo
+ *  has found that the storage type is progressive (or interlaced). 
+ *  Thanks to Joe Germuska for suggesting the feature.
+ *  Bug fix: BMP physical resolution is now correctly determined.
+ *  Released as 1.5.</li>
+ * <li><strong>2004-11-30</strong> Bug fix: recognizing progressive GIFs 
+ * (interlaced in GIF terminology) did not work (thanks to Franz Jeitler for 
+ *   pointing this out). Now it should work, but only if the number of images is determined.
+ *  This is because information on interlacing is stored in a local image header.
+ *  In theory, different images could be stored interlaced and non-interlaced in one 
+ *  file. However, I think  that's unlikely. Right now, the last image in the GIF file 
+ *  that is examined by ImageInfo is used for the "progressive" status.</li>
+ * <li><strong>2005-01-02</strong> Some code clean up (unused methods and variables
+ *  commented out, missing javadoc comments, etc.). Thanks to George Sexton for a long list.
+ *  Removed usage of Boolean.toString because
+ *  it's a Java 1.4+ feature (thanks to Gregor Dupont).
+ *  Changed delimiter character in compact output from semicolon to tabulator
+ * (for better integration with cut(1) and other Unix tools).
+ *  Added some points to the <a href="http://schmidt.devlib.org/image-info/index.html#knownissues">'Known
+ *  issues' section of the website</a>. 
+ *  Released as 1.6.</li>
+ * <li><strong>2005-07-26</strong> Removed code to identify Flash (SWF) files.
+ *  Has repeatedly led to problems and support requests, and I don't know the
+ *  format and don't have the time and interest to fix it myself.
+ *  I repeatedly included fixes by others which didn't work for some people.
+ *  I give up on SWF. Please do not contact me about it anymore.
+ *  Set package of ImageInfo class to org.devlib.schmidt.imageinfo (a package
+ *  was repeatedly requested by some users).
+ *  Released as 1.7.</li>
+ *  <li><strong>2006-02-23</strong> Removed Flash helper methods which weren't used elsewhere.
+ *   Updated skip method which tries "read" whenever "skip(Bytes)" returns a result of 0.
+ *   The old method didn't work with certain input stream types on truncated data streams.
+ *   Thanks to Martin Leidig for reporting this and sending in code.
+ *   Released as 1.8.</li>
+ *  </li>
+ *  <li><strong>2006-11-13</strong> Removed check that made ImageInfo report JPEG APPx
+ *   markers smaller than 14 bytes as files in unknown format. Such JPEGs seem to be
+ *   generated by Google's Picasa application. First reported with fix by 
+ *   Karl von Randow. Released as 1.9.</li>  
+ * </ul>
+ * @author Marco Schmidt
+ */
+public class ImageInfo {
+	/**
+	 * Return value of {@link #getFormat()} for JPEG streams.
+	 * ImageInfo can extract physical resolution and comments
+	 * from JPEGs (only from APP0 headers).
+	 * Only one image can be stored in a file.
+	 * It is determined whether the JPEG stream is progressive 
+	 * (see {@link #isProgressive()}).
+	 */
+	public static final int FORMAT_JPEG = 0;
+
+	/**
+	 * Return value of {@link #getFormat()} for GIF streams.
+	 * ImageInfo can extract comments from GIFs and count the number
+	 * of images (GIFs with more than one image are animations).
+	 * It is determined whether the GIF stream is interlaced (see {@link #isProgressive()}).
+	 */
+	public static final int FORMAT_GIF = 1;
+
+	/**
+	 * Return value of {@link #getFormat()} for PNG streams.
+	 * PNG only supports one image per file.
+	 * Both physical resolution and comments can be stored with PNG,
+	 * but ImageInfo is currently not able to extract those.
+	 * It is determined whether the PNG stream is interlaced (see {@link #isProgressive()}).
+	 */
+	public static final int FORMAT_PNG = 2;
+
+	/**
+	 * Return value of {@link #getFormat()} for BMP streams.
+	 * BMP only supports one image per file.
+	 * BMP does not allow for comments.
+	 * The physical resolution can be stored.
+	 */
+	public static final int FORMAT_BMP = 3;
+
+	/**
+	 * Return value of {@link #getFormat()} for PCX streams.
+	 * PCX does not allow for comments or more than one image per file.
+	 * However, the physical resolution can be stored.
+	 */
+	public static final int FORMAT_PCX = 4;
+
+	/**
+	 * Return value of {@link #getFormat()} for IFF streams.
+	 */
+	public static final int FORMAT_IFF = 5;
+
+	/**
+	 * Return value of {@link #getFormat()} for RAS streams.
+	 * Sun Raster allows for one image per file only and is not able to
+	 * store physical resolution or comments.
+	 */
+	public static final int FORMAT_RAS = 6;
+
+	/** Return value of {@link #getFormat()} for PBM streams. */
+	public static final int FORMAT_PBM = 7;
+
+	/** Return value of {@link #getFormat()} for PGM streams. */
+	public static final int FORMAT_PGM = 8;
+
+	/** Return value of {@link #getFormat()} for PPM streams. */
+	public static final int FORMAT_PPM = 9;
+
+	/** Return value of {@link #getFormat()} for PSD streams. */
+	public static final int FORMAT_PSD = 10;
+
+/*	public static final int COLOR_TYPE_UNKNOWN = -1;
+	public static final int COLOR_TYPE_TRUECOLOR_RGB = 0;
+	public static final int COLOR_TYPE_PALETTED = 1;
+	public static final int COLOR_TYPE_GRAYSCALE= 2;
+	public static final int COLOR_TYPE_BLACK_AND_WHITE = 3;*/
+
+	/**
+	 * The names of all supported file formats.
+	 * The FORMAT_xyz int constants can be used as index values for
+	 * this array.
+	 */
+	private static final String[] FORMAT_NAMES =
+		{"JPEG", "GIF", "PNG", "BMP", "PCX", 
+		 "IFF", "RAS", "PBM", "PGM", "PPM", 
+		 "PSD"};
+
+	/**
+	 * The names of the MIME types for all supported file formats.
+	 * The FORMAT_xyz int constants can be used as index values for
+	 * this array.
+	 */
+	private static final String[] MIME_TYPE_STRINGS =
+		{"image/jpeg", "image/gif", "image/png", "image/bmp", "image/pcx", 
+		 "image/iff", "image/ras", "image/x-portable-bitmap", "image/x-portable-graymap", "image/x-portable-pixmap", 
+		 "image/psd"};
+
+	private int width;
+	private int height;
+	private int bitsPerPixel;
+	//private int colorType = COLOR_TYPE_UNKNOWN;
+	private boolean progressive;
+	private int format;
+	private InputStream in;
+	private DataInput din;
+	private boolean collectComments = true;
+	private Vector comments;
+	private boolean determineNumberOfImages;
+	private int numberOfImages;
+	private int physicalHeightDpi;
+	private int physicalWidthDpi;
+
+	private void addComment(String s) {
+		if (comments == null) {
+			comments = new Vector();
+		}
+		comments.addElement(s);
+	}
+
+	/**
+	 * Call this method after you have provided an input stream or file
+	 * using {@link #setInput(InputStream)} or {@link #setInput(DataInput)}.
+	 * If true is returned, the file format was known and information
+	 * on the file's content can be retrieved using the various getXyz methods.
+	 * @return if information could be retrieved from input
+	 */
+	public boolean check() {
+		format = -1;
+		width = -1;
+		height = -1;
+		bitsPerPixel = -1;
+		numberOfImages = 1;
+		physicalHeightDpi = -1;
+		physicalWidthDpi = -1;
+		comments = null;
+		try {
+			int b1 = read() & 0xff;
+			int b2 = read() & 0xff;
+			if (b1 == 0x47 && b2 == 0x49) {
+				return checkGif();
+			}
+			else
+			if (b1 == 0x89 && b2 == 0x50) {
+				return checkPng();
+			}
+			else
+			if (b1 == 0xff && b2 == 0xd8) {
+				return checkJpeg();
+			}
+			else
+			if (b1 == 0x42 && b2 == 0x4d) {
+				return checkBmp();
+			}
+			else
+			if (b1 == 0x0a && b2 < 0x06) {
+				return checkPcx();
+			}
+			else
+			if (b1 == 0x46 && b2 == 0x4f) {
+				return checkIff();
+			}
+			else
+			if (b1 == 0x59 && b2 == 0xa6) {
+				return checkRas();
+			}
+			else
+			if (b1 == 0x50 && b2 >= 0x31 && b2 <= 0x36) {
+				return checkPnm(b2 - '0');
+			}
+			else
+			if (b1 == 0x38 && b2 == 0x42) {
+				return checkPsd();
+			}
+			else {
+				return false;
+			}
+		} catch (IOException ioe) {
+			return false;
+		}
+	}
+
+	private boolean checkBmp() throws IOException {
+		byte[] a = new byte[44];
+		if (read(a) != a.length) {
+			return false;
+		}
+		width = getIntLittleEndian(a, 16);
+		height = getIntLittleEndian(a, 20);
+		if (width < 1 || height < 1) {
+			return false;
+		}
+		bitsPerPixel = getShortLittleEndian(a, 26);
+		if (bitsPerPixel != 1 && bitsPerPixel != 4 &&
+		    bitsPerPixel != 8 && bitsPerPixel != 16 &&
+		    bitsPerPixel != 24 && bitsPerPixel != 32) {
+		    return false;
+		}
+		int x = (int)(getIntLittleEndian(a, 36) * 0.0254);
+		if (x > 0) {
+			setPhysicalWidthDpi(x);
+		}
+		int y = (int)(getIntLittleEndian(a, 40) * 0.0254);
+		if (y > 0) {
+			setPhysicalHeightDpi(y);
+		}
+		format = FORMAT_BMP;
+		return true;
+	}
+
+	private boolean checkGif() throws IOException {
+		final byte[] GIF_MAGIC_87A = {0x46, 0x38, 0x37, 0x61};
+		final byte[] GIF_MAGIC_89A = {0x46, 0x38, 0x39, 0x61};
+		byte[] a = new byte[11]; // 4 from the GIF signature + 7 from the global header
+		if (read(a) != 11) {
+			return false;
+		}
+		if ((!equals(a, 0, GIF_MAGIC_89A, 0, 4)) &&
+			(!equals(a, 0, GIF_MAGIC_87A, 0, 4))) {
+			return false;
+		}
+		format = FORMAT_GIF;
+		width = getShortLittleEndian(a, 4);
+		height = getShortLittleEndian(a, 6);
+		int flags = a[8] & 0xff;
+		bitsPerPixel = ((flags >> 4) & 0x07) + 1;
+		//progressive = (flags & 0x02) != 0;
+		if (!determineNumberOfImages) {
+			return true;
+		}
+		// skip global color palette
+		if ((flags & 0x80) != 0) {
+			int tableSize = (1 << ((flags & 7) + 1)) * 3;
+			skip(tableSize);
+		}
+		numberOfImages = 0;
+		int blockType;
+		do
+		{
+			blockType = read();
+			switch(blockType)
+			{
+				case(0x2c): // image separator
+				{
+					if (read(a, 0, 9) != 9) {
+						return false;
+					}
+					flags = a[8] & 0xff;
+					progressive = (flags & 0x40) != 0;
+					/*int locWidth = getShortLittleEndian(a, 4);
+					int locHeight = getShortLittleEndian(a, 6);
+					System.out.println("LOCAL: " + locWidth + " x " + locHeight);*/
+					int localBitsPerPixel = (flags & 0x07) + 1;
+					if (localBitsPerPixel > bitsPerPixel) {
+						bitsPerPixel = localBitsPerPixel;
+					}
+					if ((flags & 0x80) != 0) {
+						skip((1 << localBitsPerPixel) * 3);
+					}
+					skip(1); // initial code length
+					int n;
+					do
+					{
+						n = read();
+						if (n > 0) {
+							skip(n);
+						}
+						else
+						if (n == -1) {
+							return false;
+						}
+					}
+					while (n > 0);
+					numberOfImages++;
+					break;
+				}
+				case(0x21): // extension
+				{
+					int extensionType = read();
+					if (collectComments && extensionType == 0xfe) {
+						StringBuffer sb = new StringBuffer();
+						int n;
+						do
+						{
+							n = read();
+							if (n == -1) {
+								return false;
+							}
+							if (n > 0) {
+								for (int i = 0; i < n; i++) {
+									int ch = read();
+									if (ch == -1) {
+										return false;
+									}
+									sb.append((char)ch);
+								}
+							}
+						}
+						while (n > 0);
+					} else {
+						int n;
+						do
+						{
+							n = read();
+							if (n > 0) {
+								skip(n);
+							}
+							else
+							if (n == -1) {
+								return false;
+							}
+						}
+						while (n > 0);
+					}
+					break;
+				}
+				case(0x3b): // end of file
+				{
+					break;
+				}
+				default:
+				{
+					return false;
+				}
+			}
+		}
+		while (blockType != 0x3b);
+		return true;
+	}
+
+	private boolean checkIff() throws IOException {
+		byte[] a = new byte[10];
+		// read remaining 2 bytes of file id, 4 bytes file size 
+		// and 4 bytes IFF subformat
+		if (read(a, 0, 10) != 10) {
+			return false;
+		}
+		final byte[] IFF_RM = {0x52, 0x4d};
+		if (!equals(a, 0, IFF_RM, 0, 2)) {
+			return false;
+		}
+		int type = getIntBigEndian(a, 6);
+		if (type != 0x494c424d && // type must be ILBM...
+		    type != 0x50424d20) { // ...or PBM
+		    return false;
+		}
+		// loop chunks to find BMHD chunk
+		do {
+			if (read(a, 0, 8) != 8) {
+				return false;
+			}
+			int chunkId = getIntBigEndian(a, 0);
+			int size = getIntBigEndian(a, 4);
+			if ((size & 1) == 1) {
+				size++;
+			}
+			if (chunkId == 0x424d4844) { // BMHD chunk
+				if (read(a, 0, 9) != 9) {
+					return false;
+				}
+				format = FORMAT_IFF;
+				width = getShortBigEndian(a, 0);
+				height = getShortBigEndian(a, 2);
+				bitsPerPixel = a[8] & 0xff;
+				return (width > 0 && height > 0 && bitsPerPixel > 0 && bitsPerPixel < 33);
+			} else {
+				skip(size);
+			}
+		} while (true);
+	}
+
+	private boolean checkJpeg() throws IOException {
+		byte[] data = new byte[12];
+		while (true) {
+			if (read(data, 0, 4) != 4) {
+				return false;
+			}
+			int marker = getShortBigEndian(data, 0);
+			int size = getShortBigEndian(data, 2);
+			if ((marker & 0xff00) != 0xff00) {
+				return false; // not a valid marker
+			}
+			if (marker == 0xffe0) { // APPx 
+				if (size < 14) {
+					// not an APPx header as we know it, skip
+					skip(size - 2);
+					continue;
+				}
+				if (read(data, 0, 12) != 12) {
+					return false;
+				}
+				final byte[] APP0_ID = {0x4a, 0x46, 0x49, 0x46, 0x00};
+				if (equals(APP0_ID, 0, data, 0, 5)) {
+					//System.out.println("data 7=" + data[7]);
+					if (data[7] == 1) {
+						setPhysicalWidthDpi(getShortBigEndian(data, 8));
+						setPhysicalHeightDpi(getShortBigEndian(data, 10));
+					}
+					else
+					if (data[7] == 2) {
+						int x = getShortBigEndian(data, 8);
+						int y = getShortBigEndian(data, 10);
+						setPhysicalWidthDpi((int)(x * 2.54f));
+						setPhysicalHeightDpi((int)(y * 2.54f));
+					}
+				}
+				skip(size - 14);
+			}
+			else
+			if (collectComments && size > 2 && marker == 0xfffe) { // comment
+				size -= 2;
+				byte[] chars = new byte[size];
+				if (read(chars, 0, size) != size) {
+					return false;
+				}
+				String comment = new String(chars, "iso-8859-1");
+				comment = comment.trim();
+				addComment(comment);
+			}
+			else
+			if (marker >= 0xffc0 && marker <= 0xffcf && marker != 0xffc4 && marker != 0xffc8) {
+				if (read(data, 0, 6) != 6) {
+					return false;
+				}
+				format = FORMAT_JPEG;
+				bitsPerPixel = (data[0] & 0xff) * (data[5] & 0xff);
+				progressive = marker == 0xffc2 || marker == 0xffc6 ||
+					marker == 0xffca || marker == 0xffce;
+				width = getShortBigEndian(data, 3);
+				height = getShortBigEndian(data, 1);
+				return true;
+			} else {
+				skip(size - 2);
+			}
+		}
+	}
+
+	private boolean checkPcx() throws IOException {
+		byte[] a = new byte[64];
+		if (read(a) != a.length) {
+			return false;
+		}
+		if (a[0] != 1) { // encoding, 1=RLE is only valid value
+			return false;
+		}
+		// width / height
+		int x1 = getShortLittleEndian(a, 2);
+		int y1 = getShortLittleEndian(a, 4);
+		int x2 = getShortLittleEndian(a, 6);
+		int y2 = getShortLittleEndian(a, 8);
+		if (x1 < 0 || x2 < x1 || y1 < 0 || y2 < y1) {
+			return false;
+		}
+		width = x2 - x1 + 1;
+		height = y2 - y1 + 1;
+		// color depth
+		int bits = a[1];
+		int planes = a[63];
+		if (planes == 1 &&
+		    (bits == 1 || bits == 2 || bits == 4 || bits == 8)) {
+			// paletted
+			bitsPerPixel = bits;
+		} else
+		if (planes == 3 && bits == 8) {
+			// RGB truecolor
+			bitsPerPixel = 24;
+		} else {
+			return false;
+		}
+		setPhysicalWidthDpi(getShortLittleEndian(a, 10));
+		setPhysicalHeightDpi(getShortLittleEndian(a, 10));
+		format = FORMAT_PCX;
+		return true;
+	}
+
+	private boolean checkPng() throws IOException {
+		final byte[] PNG_MAGIC = {0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a};
+		byte[] a = new byte[27];
+		if (read(a) != 27) {
+			return false;
+		}
+		if (!equals(a, 0, PNG_MAGIC, 0, 6)) {
+			return false;
+		}
+		format = FORMAT_PNG;
+		width = getIntBigEndian(a, 14);
+		height = getIntBigEndian(a, 18);
+		bitsPerPixel = a[22] & 0xff;
+		int colorType = a[23] & 0xff;
+		if (colorType == 2 || colorType == 6) {
+			bitsPerPixel *= 3;
+		}
+		progressive = (a[26] & 0xff) != 0;
+		return true;
+	}
+
+	private boolean checkPnm(int id) throws IOException {
+		if (id < 1 || id > 6) {
+			return false;
+		}
+		final int[] PNM_FORMATS = {FORMAT_PBM, FORMAT_PGM, FORMAT_PPM};
+		format = PNM_FORMATS[(id - 1) % 3];
+		boolean hasPixelResolution = false;
+		String s;
+		while (true)
+		{
+			s = readLine();
+			if (s != null) {
+				s = s.trim();
+			}
+			if (s == null || s.length() < 1) {
+				continue;
+			}
+			if (s.charAt(0) == '#') { // comment
+				if (collectComments && s.length() > 1) {
+					addComment(s.substring(1));
+				}
+				continue;
+			}
+			if (!hasPixelResolution) { // split "343 966" into width=343, height=966
+				int spaceIndex = s.indexOf(' ');
+				if (spaceIndex == -1) {
+					return false;
+				}
+				String widthString = s.substring(0, spaceIndex);
+				spaceIndex = s.lastIndexOf(' ');
+				if (spaceIndex == -1) {
+					return false;
+				}
+				String heightString = s.substring(spaceIndex + 1);
+				try {
+					width = Integer.parseInt(widthString);
+					height = Integer.parseInt(heightString);
+				} catch (NumberFormatException nfe) {
+					return false;
+				}
+				if (width < 1 || height < 1) {
+					return false;
+				}
+				if (format == FORMAT_PBM) {
+					bitsPerPixel = 1;
+					return true;
+				}
+				hasPixelResolution = true;
+			}
+			else
+			{
+				int maxSample;
+				try {
+					maxSample = Integer.parseInt(s);
+				} catch (NumberFormatException nfe) {
+					return false;
+				}
+				if (maxSample < 0) {
+					return false;
+				}
+				for (int i = 0; i < 25; i++) {
+					if (maxSample < (1 << (i + 1))) {
+						bitsPerPixel = i + 1;
+						if (format == FORMAT_PPM) {
+							bitsPerPixel *= 3;
+						}
+						return true;
+					}
+				}
+				return false;
+			}
+		}
+	}
+
+	private boolean checkPsd() throws IOException {
+		byte[] a = new byte[24];
+		if (read(a) != a.length) {
+			return false;
+		}
+		final byte[] PSD_MAGIC = {0x50, 0x53};
+		if (!equals(a, 0, PSD_MAGIC, 0, 2)) {
+			return false;
+		}
+		format = FORMAT_PSD;
+		width = getIntBigEndian(a, 16);
+		height = getIntBigEndian(a, 12);
+		int channels = getShortBigEndian(a, 10);
+		int depth = getShortBigEndian(a, 20);
+		bitsPerPixel = channels * depth;
+		return (width > 0 && height > 0 && bitsPerPixel > 0 && bitsPerPixel <= 64);
+	}
+
+	private boolean checkRas() throws IOException {
+		byte[] a = new byte[14];
+		if (read(a) != a.length) {
+			return false;
+		}
+		final byte[] RAS_MAGIC = {0x6a, (byte)0x95};
+		if (!equals(a, 0, RAS_MAGIC, 0, 2)) {
+			return false;
+		}
+		format = FORMAT_RAS;
+		width = getIntBigEndian(a, 2);
+		height = getIntBigEndian(a, 6);
+		bitsPerPixel = getIntBigEndian(a, 10);
+		return (width > 0 && height > 0 && bitsPerPixel > 0 && bitsPerPixel <= 24);
+	}
+
+	/**
+	 * Run over String list, return false iff at least one of the arguments
+	 * equals <code>-c</code>.
+	 * @param args string list to check
+	 */
+	private static boolean determineVerbosity(String[] args) {
+		if (args != null && args.length > 0) {
+			for (int i = 0; i < args.length; i++) {
+				if ("-c".equals(args[i])) {
+					return false;
+				}
+			}
+		}
+		return true;
+	}
+
+	private static boolean equals(byte[] a1, int offs1, byte[] a2, int offs2, int num) {
+		while (num-- > 0) {
+			if (a1[offs1++] != a2[offs2++]) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/** 
+	 * If {@link #check()} was successful, returns the image's number of bits per pixel.
+	 * Does not include transparency information like the alpha channel.
+	 * @return number of bits per image pixel
+	 */
+	public int getBitsPerPixel() {
+		return bitsPerPixel;
+	}
+
+	/**
+	 * Returns the index'th comment retrieved from the file.
+	 * @param index int index of comment to return
+	 * @throws IllegalArgumentException if index is smaller than 0 or larger than or equal
+	 * to the number of comments retrieved
+	 * @see #getNumberOfComments
+	 */
+	public String getComment(int index) {
+		if (comments == null || index < 0 || index >= comments.size()) {
+			throw new IllegalArgumentException("Not a valid comment index: " + index);
+		}
+		return (String)comments.elementAt(index);
+	}
+
+	/**
+	 * If {@link #check()} was successful, returns the image format as one
+	 * of the FORMAT_xyz constants from this class.
+	 * Use {@link #getFormatName()} to get a textual description of the file format.
+	 * @return file format as a FORMAT_xyz constant
+	 */
+	public int getFormat() {
+		return format;
+	}
+
+	/**
+	 * If {@link #check()} was successful, returns the image format's name.
+	 * Use {@link #getFormat()} to get a unique number.
+	 * @return file format name
+	 */
+	public String getFormatName() {
+		if (format >= 0 && format < FORMAT_NAMES.length) {
+			return FORMAT_NAMES[format];
+		} else {
+			return "?";
+		}
+	}
+
+	/** 
+	 * If {@link #check()} was successful, returns one the image's vertical
+	 * resolution in pixels.
+	 * @return image height in pixels
+	 */
+	public int getHeight() {
+		return height;
+	}
+
+	private static int getIntBigEndian(byte[] a, int offs) {
+		return
+			(a[offs] & 0xff) << 24 | 
+			(a[offs + 1] & 0xff) << 16 | 
+			(a[offs + 2] & 0xff) << 8 | 
+			a[offs + 3] & 0xff;
+	}
+
+	private static int getIntLittleEndian(byte[] a, int offs) {
+		return
+			(a[offs + 3] & 0xff) << 24 | 
+			(a[offs + 2] & 0xff) << 16 | 
+			(a[offs + 1] & 0xff) << 8 | 
+			a[offs] & 0xff;
+	}
+
+	/** 
+	 * If {@link #check()} was successful, returns a String with the
+	 * MIME type of the format.
+	 * @return MIME type, e.g. <code>image/jpeg</code>
+	 */
+	public String getMimeType() {
+		if (format >= 0 && format < MIME_TYPE_STRINGS.length) {
+			if (format == FORMAT_JPEG && progressive)
+			{
+				return "image/pjpeg";
+			}
+			return MIME_TYPE_STRINGS[format];
+		} else {
+			return null;
+		}
+	}
+
+	/**
+	 * If {@link #check()} was successful and {@link #setCollectComments(boolean)} was called with
+	 * <code>true</code> as argument, returns the number of comments retrieved 
+	 * from the input image stream / file.
+	 * Any number &gt;= 0 and smaller than this number of comments is then a
+	 * valid argument for the {@link #getComment(int)} method.
+	 * @return number of comments retrieved from input image
+	 */
+	public int getNumberOfComments()
+	{
+		if (comments == null) {
+			return 0;
+		} else {
+			return comments.size();
+		}
+	}
+
+	/**
+	 * Returns the number of images in the examined file.
+	 * Assumes that <code>setDetermineImageNumber(true);</code> was called before
+	 * a successful call to {@link #check()}.
+	 * This value can currently be only different from <code>1</code> for GIF images.
+	 * @return number of images in file
+	 */
+	public int getNumberOfImages()
+	{
+		return numberOfImages;
+	}
+
+	/**
+	 * Returns the physical height of this image in dots per inch (dpi).
+	 * Assumes that {@link #check()} was successful.
+	 * Returns <code>-1</code> on failure.
+	 * @return physical height (in dpi)
+	 * @see #getPhysicalWidthDpi()
+	 * @see #getPhysicalHeightInch()
+	 */
+	public int getPhysicalHeightDpi() {
+		return physicalHeightDpi;
+	}
+
+	/**
+	 * If {@link #check()} was successful, returns the physical width of this image in dpi (dots per inch)
+	 * or -1 if no value could be found.
+	 * @return physical height (in dpi)
+	 * @see #getPhysicalHeightDpi()
+	 * @see #getPhysicalWidthDpi()
+	 * @see #getPhysicalWidthInch()
+	 */
+	public float getPhysicalHeightInch() {
+		int h = getHeight();
+		int ph = getPhysicalHeightDpi();
+		if (h > 0 && ph > 0) {
+			return ((float)h) / ((float)ph);
+		} else {
+			return -1.0f;
+		}
+	}
+
+	/**
+	 * If {@link #check()} was successful, returns the physical width of this image in dpi (dots per inch)
+	 * or -1 if no value could be found.
+	 * @return physical width (in dpi)
+	 * @see #getPhysicalHeightDpi()
+	 * @see #getPhysicalWidthInch()
+	 * @see #getPhysicalHeightInch()
+	 */
+	public int getPhysicalWidthDpi() {
+		return physicalWidthDpi;
+	}
+
+	/**
+	 * Returns the physical width of an image in inches, or
+	 * <code>-1.0f</code> if width information is not available.
+	 * Assumes that {@link #check} has been called successfully.
+	 * @return physical width in inches or <code>-1.0f</code> on failure
+	 * @see #getPhysicalWidthDpi
+	 * @see #getPhysicalHeightInch
+	 */
+	public float getPhysicalWidthInch() {
+		int w = getWidth();
+		int pw = getPhysicalWidthDpi();
+		if (w > 0 && pw > 0) {
+			return ((float)w) / ((float)pw);
+		} else {
+			return -1.0f;
+		}
+	}
+
+	private static int getShortBigEndian(byte[] a, int offs) {
+		return
+			(a[offs] & 0xff) << 8 | 
+			(a[offs + 1] & 0xff);
+	}
+
+	private static int getShortLittleEndian(byte[] a, int offs) {
+		return (a[offs] & 0xff) | (a[offs + 1] & 0xff) << 8;
+	}
+
+	/** 
+	 * If {@link #check()} was successful, returns one the image's horizontal
+	 * resolution in pixels.
+	 * @return image width in pixels
+	 */
+	public int getWidth() {
+		return width;
+	}
+
+	/**
+	 * Returns whether the image is stored in a progressive (also called: interlaced) way.
+	 * @return true for progressive/interlaced, false otherwise
+	 */
+	public boolean isProgressive()
+	{
+		return progressive;
+	}
+
+	/**
+	 * To use this class as a command line application, give it either 
+	 * some file names as parameters (information on them will be
+	 * printed to standard output, one line per file) or call
+	 * it with no parameters. It will then check data given to it
+	 * via standard input.
+	 * @param args the program arguments which must be file names
+	 */
+	public static void main(String[] args) {
+		ImageInfo imageInfo = new ImageInfo();
+		imageInfo.setDetermineImageNumber(true);
+		boolean verbose = determineVerbosity(args);
+		if (args.length == 0) {
+			run(null, System.in, imageInfo, verbose);
+		} else {
+			int index = 0;
+			while (index < args.length) {
+				InputStream in = null;
+				try {
+					String name = args[index++];
+					System.out.print(name + ";");
+					if (name.startsWith("http://")) {
+						in = new URL(name).openConnection().getInputStream();
+					} else {
+						in = new FileInputStream(name);
+					}
+					run(name, in, imageInfo, verbose);
+					in.close();
+				} catch (IOException e) {
+					System.out.println(e);
+					try {
+						if (in != null) {
+							in.close();
+						}
+					} catch (IOException ee) {
+					}
+				}
+			}
+		}
+	}
+
+	private static void print(String sourceName, ImageInfo ii, boolean verbose) {
+		if (verbose) {
+			printVerbose(sourceName, ii);
+		} else {
+			printCompact(sourceName, ii);
+		}
+	}
+
+	private static void printCompact(String sourceName, ImageInfo imageInfo) {
+		final String SEP = "\t";
+		System.out.println(
+			sourceName + SEP + 
+			imageInfo.getFormatName() + SEP +
+			imageInfo.getMimeType() + SEP +
+			imageInfo.getWidth() + SEP +
+			imageInfo.getHeight() + SEP +
+			imageInfo.getBitsPerPixel() + SEP +
+			imageInfo.getNumberOfImages() + SEP +
+			imageInfo.getPhysicalWidthDpi() + SEP +
+			imageInfo.getPhysicalHeightDpi() + SEP +
+			imageInfo.getPhysicalWidthInch() + SEP +
+			imageInfo.getPhysicalHeightInch() + SEP +
+			imageInfo.isProgressive()
+		);
+	}
+
+	private static void printLine(int indentLevels, String text, float value, float minValidValue) {
+		if (value < minValidValue) {
+			return;
+		}
+		printLine(indentLevels, text, Float.toString(value));
+	}
+
+	private static void printLine(int indentLevels, String text, int value, int minValidValue) {
+		if (value >= minValidValue) {
+			printLine(indentLevels, text, Integer.toString(value));
+		}
+	}
+
+	private static void printLine(int indentLevels, String text, String value) {
+		if (value == null || value.length() == 0) {
+			return;
+		}
+		while (indentLevels-- > 0) {
+			System.out.print("\t");
+		}
+		if (text != null && text.length() > 0) {
+			System.out.print(text);
+			System.out.print(" ");
+		}
+		System.out.println(value);
+	}
+
+	private static void printVerbose(String sourceName, ImageInfo ii) {
+		printLine(0, null, sourceName);
+		printLine(1, "File format: ", ii.getFormatName());
+		printLine(1, "MIME type: ", ii.getMimeType());
+		printLine(1, "Width (pixels): ", ii.getWidth(), 1);
+		printLine(1, "Height (pixels): ", ii.getHeight(), 1);
+		printLine(1, "Bits per pixel: ", ii.getBitsPerPixel(), 1);
+		printLine(1, "Progressive: ", ii.isProgressive() ? "yes" : "no");
+		printLine(1, "Number of images: ", ii.getNumberOfImages(), 1);
+		printLine(1, "Physical width (dpi): ", ii.getPhysicalWidthDpi(), 1);
+		printLine(1, "Physical height (dpi): ", ii.getPhysicalHeightDpi(), 1);
+		printLine(1, "Physical width (inches): ", ii.getPhysicalWidthInch(), 1.0f);
+		printLine(1, "Physical height (inches): ", ii.getPhysicalHeightInch(), 1.0f);
+		int numComments = ii.getNumberOfComments();
+		printLine(1, "Number of textual comments: ", numComments, 1);
+		if (numComments > 0) {
+			for (int i = 0; i < numComments; i++) {
+				printLine(2, null, ii.getComment(i));
+			}
+		}
+	}
+
+	private int read() throws IOException {
+		if (in != null) {
+			return in.read();
+		} else {
+			return din.readByte();
+		}
+	}
+
+	private int read(byte[] a) throws IOException {
+		if (in != null) {
+			return in.read(a);
+		} else {
+			din.readFully(a);
+			return a.length;
+		}
+	}
+
+	private int read(byte[] a, int offset, int num) throws IOException {
+		if (in != null) {
+			return in.read(a, offset, num);
+		} else {
+			din.readFully(a, offset, num);
+			return num;
+		}
+	}
+
+	private String readLine() throws IOException {
+		return readLine(new StringBuffer());
+	}
+
+	private String readLine(StringBuffer sb) throws IOException {
+		boolean finished;
+		do {
+			int value = read();
+			finished = (value == -1 || value == 10);
+			if (!finished) {
+				sb.append((char)value);
+			}
+		} while (!finished);
+		return sb.toString();
+	}
+
+	private static void run(String sourceName, InputStream in, ImageInfo imageInfo, boolean verbose) {
+		imageInfo.setInput(in);
+		imageInfo.setDetermineImageNumber(true);
+		imageInfo.setCollectComments(verbose);
+		if (imageInfo.check()) {
+			print(sourceName, imageInfo, verbose);
+		}
+	}
+
+	/**
+	 * Specify whether textual comments are supposed to be extracted from input.
+	 * Default is <code>false</code>.
+	 * If enabled, comments will be added to an internal list.
+	 * @param newValue if <code>true</code>, this class will read comments
+	 * @see #getNumberOfComments
+	 * @see #getComment
+	 */
+	public void setCollectComments(boolean newValue)
+	{
+		collectComments = newValue;
+	}
+
+	/**
+	 * Specify whether the number of images in a file is to be
+	 * determined - default is <code>false</code>.
+	 * This is a special option because some file formats require running over
+	 * the entire file to find out the number of images, a rather time-consuming
+	 * task.
+	 * Not all file formats support more than one image.
+	 * If this method is called with <code>true</code> as argument,
+	 * the actual number of images can be queried via 
+	 * {@link #getNumberOfImages()} after a successful call to
+	 * {@link #check()}.
+	 * @param newValue will the number of images be determined?
+	 * @see #getNumberOfImages
+	 */
+	public void setDetermineImageNumber(boolean newValue)
+	{
+		determineNumberOfImages = newValue;
+	}
+
+	/**
+	 * Set the input stream to the argument stream (or file). 
+	 * Note that {@link java.io.RandomAccessFile} implements
+	 * {@link java.io.DataInput}.
+	 * @param dataInput the input stream to read from
+	 */
+	public void setInput(DataInput dataInput) {
+		din = dataInput;
+		in = null;
+	}
+
+	/**
+	 * Set the input stream to the argument stream (or file).
+	 * @param inputStream the input stream to read from
+	 */
+	public void setInput(InputStream inputStream) {
+		in = inputStream;
+		din = null;
+	}
+
+	private void setPhysicalHeightDpi(int newValue) {
+		physicalWidthDpi = newValue;
+	}
+
+	private void setPhysicalWidthDpi(int newValue) {
+		physicalHeightDpi = newValue;
+	}
+
+    private void skip(int num) throws IOException {
+        while (num > 0) {
+            long result;
+            if (in != null) {
+                result = in.skip(num);
+            } else {
+                result = din.skipBytes(num);
+            }
+            if (result > 0) {
+                num -= result;
+            } else {
+                if (in != null) {
+                    result = in.read();
+                } else {
+                    result = din.readByte();
+                }
+                if (result == -1) {
+                	throw new IOException("Premature end of input.");
+                } else {
+                	num--;
+                }
+            }
+        }
+    }
+}
