0001: /*
0002: #IFNDEF ALT_LICENSE
0003: ThinWire(R) RIA Ajax Framework
0004: Copyright (C) 2003-2007 Custom Credit Systems
0005:
0006: This library is free software; you can redistribute it and/or modify it under
0007: the terms of the GNU Lesser General Public License as published by the Free
0008: Software Foundation; either version 2.1 of the License, or (at your option) any
0009: later version.
0010:
0011: This library is distributed in the hope that it will be useful, but WITHOUT ANY
0012: WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
0013: PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
0014:
0015: You should have received a copy of the GNU Lesser General Public License along
0016: with this library; if not, write to the Free Software Foundation, Inc., 59
0017: Temple Place, Suite 330, Boston, MA 02111-1307 USA
0018:
0019: Users who would rather have a commercial license, warranty or support should
0020: contact the following company who invented, built and supports the technology:
0021:
0022: Custom Credit Systems, Richardson, TX 75081, USA.
0023: email: info@thinwire.com ph: +1 (888) 644-6405
0024: http://www.thinwire.com
0025: #ENDIF
0026: [ v1.2_RC2 ]
0027: */
0028: package thinwire.util;
0029:
0030: import java.io.DataInput;
0031: import java.io.File;
0032: import java.io.FileInputStream;
0033: import java.io.IOException;
0034: import java.io.InputStream;
0035: import java.util.ArrayList;
0036: import java.util.List;
0037:
0038: import thinwire.ui.Application;
0039:
0040: /**
0041: * @author Joshua J. Gertzen
0042: */
0043: public class ImageInfo {
0044: public enum Format {
0045: GIF, JPEG, PNG
0046: }
0047:
0048: private String stringValue;
0049: private String name;
0050: private Format format;
0051: private int width;
0052: private int height;
0053:
0054: public ImageInfo(String name) {
0055: setName(name);
0056: }
0057:
0058: public int getWidth() {
0059: return width;
0060: }
0061:
0062: public int getHeight() {
0063: return height;
0064: }
0065:
0066: public Format getFormat() {
0067: return format;
0068: }
0069:
0070: public String getName() {
0071: return name;
0072: }
0073:
0074: private void setName(String name) {
0075: try {
0076: InputStream is = Application.getResourceAsStream(name);
0077:
0078: if (is != null) {
0079: this .name = name;
0080: ImageParser ii = new ImageParser();
0081: ii.setInput(is);
0082:
0083: if (!ii.check())
0084: throw new UnsupportedOperationException(
0085: "Unknown image file format for file.");
0086:
0087: switch (ii.getFormat()) {
0088: case ImageParser.FORMAT_GIF:
0089: this .format = Format.GIF;
0090: break;
0091:
0092: case ImageParser.FORMAT_JPEG:
0093: this .format = Format.JPEG;
0094: break;
0095:
0096: case ImageParser.FORMAT_PNG:
0097: this .format = Format.PNG;
0098: break;
0099:
0100: default:
0101: throw new UnsupportedOperationException(
0102: "Unsupported image file format '"
0103: + ii.getFormatName() + "'.");
0104: }
0105:
0106: this .width = ii.getWidth();
0107: this .height = ii.getHeight();
0108: } else {
0109: this .width = -1;
0110: this .height = -1;
0111: this .format = null;
0112: this .name = "";
0113: }
0114: } catch (Exception e) {
0115: if (!(e instanceof RuntimeException))
0116: e = new RuntimeException(e);
0117: throw (RuntimeException) e;
0118: }
0119: }
0120:
0121: public boolean equals(Object o) {
0122: return o instanceof ImageInfo
0123: && toString().equals(o.toString());
0124: }
0125:
0126: public int hashCode() {
0127: return toString().hashCode();
0128: }
0129:
0130: public String toString() {
0131: if (stringValue == null)
0132: stringValue = "ImageInfo{name:" + name + ",format:"
0133: + format + ",width:" + width + ",height:" + height
0134: + "}";
0135: return stringValue;
0136: }
0137: }
0138:
0139: /* XXX Note: This class is a public domain class that is used by
0140: * ImageInfo (above) to determine the width and height of an image.
0141: * It is made package-private because it is not intended to be used
0142: * directly.
0143: */
0144:
0145: /*
0146: * ImageInfo.java
0147: *
0148: * Version 1.5
0149: *
0150: * A Java class to determine image width, height and color depth for
0151: * a number of image file formats.
0152: *
0153: * Written by Marco Schmidt
0154: * <http://www.geocities.com/marcoschmidt.geo/contact.html>.
0155: *
0156: * Contributed to the Public Domain.
0157: *
0158: * Last modification 2004-02-29
0159: */
0160: /**
0161: * Get file format, image resolution, number of bits per pixel and optionally
0162: * number of images, comments and physical resolution from
0163: * JPEG, GIF, BMP, PCX, PNG, IFF, RAS, PBM, PGM, PPM, PSD and SWF files
0164: * (or input streams).
0165: * <p>
0166: * Use the class like this:
0167: * <pre>
0168: * ImageInfo ii = new ImageInfo();
0169: * ii.setInput(in); // in can be InputStream or RandomAccessFile
0170: * ii.setDetermineImageNumber(true); // default is false
0171: * ii.setCollectComments(true); // default is false
0172: * if (!ii.check()) {
0173: * System.err.println("Not a supported image file format.");
0174: * return;
0175: * }
0176: * System.out.println(ii.getFormatName() + ", " + ii.getMimeType() +
0177: * ", " + ii.getWidth() + " x " + ii.getHeight() + " pixels, " +
0178: * ii.getBitsPerPixel() + " bits per pixel, " + ii.getNumberOfImages() +
0179: * " image(s), " + ii.getNumberOfComments() + " comment(s).");
0180: * // there are other properties, check out the API documentation
0181: * </pre>
0182: * You can also use this class as a command line program.
0183: * Call it with a number of image file names and URLs as parameters:
0184: * <pre>
0185: * java ImageInfo *.jpg *.png *.gif http://somesite.tld/image.jpg
0186: * </pre>
0187: * or call it without parameters and pipe data to it:
0188: * <pre>
0189: * java ImageInfo < image.jpg
0190: * </pre>
0191: * <p>
0192: * Known limitations:
0193: * <ul>
0194: * <li>When the determination of the number of images is turned off, GIF bits
0195: * per pixel are only read from the global header.
0196: * For some GIFs, local palettes change this to a typically larger
0197: * value. To be certain to get the correct color depth, call
0198: * setDetermineImageNumber(true) before calling check().
0199: * The complete scan over the GIF file will take additional time.</li>
0200: * <li>Transparency information is not included in the bits per pixel count.
0201: * Actually, it was my decision not to include those bits, so it's a feature! ;-)</li>
0202: * </ul>
0203: * <p>
0204: * Requirements:
0205: * <ul>
0206: * <li>Java 1.1 or higher</li>
0207: * </ul>
0208: * <p>
0209: * The latest version can be found at <a href="http://www.geocities.com/marcoschmidt.geo/image-info.html">http://www.geocities.com/marcoschmidt.geo/image-info.html</a>.
0210: * <p>
0211: * Written by <a href="http://www.geocities.com/marcoschmidt.geo/contact.html">Marco Schmidt</a>.
0212: * <p>
0213: * This class is contributed to the Public Domain.
0214: * Use it at your own risk.
0215: * <p>
0216: * Last modification 2004-02-29.
0217: * <p>
0218: * History:
0219: * <ul>
0220: * <li><strong>2001-08-24</strong> Initial version.</li>
0221: * <li><strong>2001-10-13</strong> Added support for the file formats BMP and PCX.</li>
0222: * <li><strong>2001-10-16</strong> Fixed bug in read(int[], int, int) that returned
0223: * <li><strong>2002-01-22</strong> Added support for file formats Amiga IFF and Sun Raster (RAS).</li>
0224: * <li><strong>2002-01-24</strong> Added support for file formats Portable Bitmap / Graymap / Pixmap (PBM, PGM, PPM) and Adobe Photoshop (PSD).
0225: * Added new method getMimeType() to return the MIME type associated with a particular file format.</li>
0226: * <li><strong>2002-03-15</strong> Added support to recognize number of images in file. Only works with GIF.
0227: * Use {@link #setDetermineImageNumber} with <code>true</code> as argument to identify animated GIFs
0228: * ({@link #getNumberOfImages()} will return a value larger than <code>1</code>).</li>
0229: * <li><strong>2002-04-10</strong> Fixed a bug in the feature 'determine number of images in animated GIF' introduced with version 1.1.
0230: * Thanks to Marcelo P. Lima for sending in the bug report.
0231: * Released as 1.1.1.</li>
0232: * <li><strong>2002-04-18</strong> Added {@link #setCollectComments(boolean)}.
0233: * That new method lets the user specify whether textual comments are to be
0234: * stored in an internal list when encountered in an input image file / stream.
0235: * Added two methods to return the physical width and height of the image in dpi:
0236: * {@link #getPhysicalWidthDpi()} and {@link #getPhysicalHeightDpi()}.
0237: * If the physical resolution could not be retrieved, these methods return <code>-1</code>.
0238: * </li>
0239: * <li><strong>2002-04-23</strong> Added support for the new properties physical resolution and
0240: * comments for some formats. Released as 1.2.</li>
0241: * <li><strong>2002-06-17</strong> Added support for SWF, sent in by Michael Aird.
0242: * Changed checkJpeg() so that other APP markers than APP0 will not lead to a failure anymore.
0243: * Released as 1.3.</li>
0244: * <li><strong>2003-07-28</strong> Bug fix - skip method now takes return values into consideration.
0245: * Less bytes than necessary may have been skipped, leading to flaws in the retrieved information in some cases.
0246: * Thanks to Bernard Bernstein for pointing that out.
0247: * Released as 1.4.</li>
0248: * <li><strong>2004-02-29</strong> Added support for recognizing progressive JPEG and
0249: * interlaced PNG and GIF. A new method {@link #isProgressive()} returns whether ImageInfo
0250: * has found that the storage type is progressive (or interlaced).
0251: * Thanks to Joe Germuska for suggesting the feature.
0252: * Bug fix: BMP physical resolution is now correctly determined.
0253: * Released as 1.5.</li>
0254: * </ul>
0255: */
0256: final class ImageParser {
0257: /**
0258: * Return value of {@link #getFormat()} for JPEG streams.
0259: * ImageInfo can extract physical resolution and comments
0260: * from JPEGs (only from APP0 headers).
0261: * Only one image can be stored in a file.
0262: * It is determined whether the JPEG stream is progressive
0263: * (see {@link #isProgressive()}).
0264: */
0265: public static final int FORMAT_JPEG = 0;
0266:
0267: /**
0268: * Return value of {@link #getFormat()} for GIF streams.
0269: * ImageInfo can extract comments from GIFs and count the number
0270: * of images (GIFs with more than one image are animations).
0271: * If you know of a place where GIFs store the physical resolution
0272: * of an image, please
0273: * <a href="http://www.geocities.com/marcoschmidt.geo/contact.html">send me a mail</a>!
0274: * It is determined whether the GIF stream is interlaced (see {@link #isProgressive()}).
0275: */
0276: public static final int FORMAT_GIF = 1;
0277:
0278: /**
0279: * Return value of {@link #getFormat()} for PNG streams.
0280: * PNG only supports one image per file.
0281: * Both physical resolution and comments can be stored with PNG,
0282: * but ImageInfo is currently not able to extract those.
0283: * It is determined whether the PNG stream is interlaced (see {@link #isProgressive()}).
0284: */
0285: public static final int FORMAT_PNG = 2;
0286:
0287: /**
0288: * Return value of {@link #getFormat()} for BMP streams.
0289: * BMP only supports one image per file.
0290: * BMP does not allow for comments.
0291: * The physical resolution can be stored.
0292: */
0293: public static final int FORMAT_BMP = 3;
0294:
0295: /**
0296: * Return value of {@link #getFormat()} for PCX streams.
0297: * PCX does not allow for comments or more than one image per file.
0298: * However, the physical resolution can be stored.
0299: */
0300: public static final int FORMAT_PCX = 4;
0301:
0302: /**
0303: * Return value of {@link #getFormat()} for IFF streams.
0304: */
0305: public static final int FORMAT_IFF = 5;
0306:
0307: /**
0308: * Return value of {@link #getFormat()} for RAS streams.
0309: * Sun Raster allows for one image per file only and is not able to
0310: * store physical resolution or comments.
0311: */
0312: public static final int FORMAT_RAS = 6;
0313:
0314: /** Return value of {@link #getFormat()} for PBM streams. */
0315: public static final int FORMAT_PBM = 7;
0316:
0317: /** Return value of {@link #getFormat()} for PGM streams. */
0318: public static final int FORMAT_PGM = 8;
0319:
0320: /** Return value of {@link #getFormat()} for PPM streams. */
0321: public static final int FORMAT_PPM = 9;
0322:
0323: /** Return value of {@link #getFormat()} for PSD streams. */
0324: public static final int FORMAT_PSD = 10;
0325:
0326: /** Return value of {@link #getFormat()} for SWF (Shockwave) streams. */
0327: public static final int FORMAT_SWF = 11;
0328:
0329: public static final int COLOR_TYPE_UNKNOWN = -1;
0330: public static final int COLOR_TYPE_TRUECOLOR_RGB = 0;
0331: public static final int COLOR_TYPE_PALETTED = 1;
0332: public static final int COLOR_TYPE_GRAYSCALE = 2;
0333: public static final int COLOR_TYPE_BLACK_AND_WHITE = 3;
0334:
0335: /**
0336: * The names of all supported file formats.
0337: * The FORMAT_xyz int constants can be used as index values for
0338: * this array.
0339: */
0340: private static final String[] FORMAT_NAMES = { "JPEG", "GIF",
0341: "PNG", "BMP", "PCX", "IFF", "RAS", "PBM", "PGM", "PPM",
0342: "PSD", "SWF" };
0343:
0344: /**
0345: * The names of the MIME types for all supported file formats.
0346: * The FORMAT_xyz int constants can be used as index values for
0347: * this array.
0348: */
0349: private static final String[] MIME_TYPE_STRINGS = { "image/jpeg",
0350: "image/gif", "image/png", "image/bmp", "image/pcx",
0351: "image/iff", "image/ras", "image/x-portable-bitmap",
0352: "image/x-portable-graymap", "image/x-portable-pixmap",
0353: "image/psd", "application/x-shockwave-flash" };
0354:
0355: private int width;
0356: private int height;
0357: private int bitsPerPixel;
0358: private boolean progressive;
0359: private int format;
0360: private InputStream in;
0361: private DataInput din;
0362: private boolean collectComments = true;
0363: private List<String> comments;
0364: private boolean determineNumberOfImages;
0365: private int numberOfImages;
0366: private int physicalHeightDpi;
0367: private int physicalWidthDpi;
0368: private int bitBuf;
0369: private int bitPos;
0370:
0371: private void addComment(String s) {
0372: if (comments == null) {
0373: comments = new ArrayList<String>();
0374: }
0375: comments.add(s);
0376: }
0377:
0378: /**
0379: * Call this method after you have provided an input stream or file
0380: * using {@link #setInput(InputStream)} or {@link #setInput(DataInput)}.
0381: * If true is returned, the file format was known and information
0382: * on the file's content can be retrieved using the various getXyz methods.
0383: * @return if information could be retrieved from input
0384: */
0385: public boolean check() {
0386: format = -1;
0387: width = -1;
0388: height = -1;
0389: bitsPerPixel = -1;
0390: numberOfImages = 1;
0391: physicalHeightDpi = -1;
0392: physicalWidthDpi = -1;
0393: comments = null;
0394: try {
0395: int b1 = read() & 0xff;
0396: int b2 = read() & 0xff;
0397: if (b1 == 0x47 && b2 == 0x49) {
0398: return checkGif();
0399: } else if (b1 == 0x89 && b2 == 0x50) {
0400: return checkPng();
0401: } else if (b1 == 0xff && b2 == 0xd8) {
0402: return checkJpeg();
0403: } else if (b1 == 0x42 && b2 == 0x4d) {
0404: return checkBmp();
0405: } else if (b1 == 0x0a && b2 < 0x06) {
0406: return checkPcx();
0407: } else if (b1 == 0x46 && b2 == 0x4f) {
0408: return checkIff();
0409: } else if (b1 == 0x59 && b2 == 0xa6) {
0410: return checkRas();
0411: } else if (b1 == 0x50 && b2 >= 0x31 && b2 <= 0x36) {
0412: return checkPnm(b2 - '0');
0413: } else if (b1 == 0x38 && b2 == 0x42) {
0414: return checkPsd();
0415: } else if (b1 == 0x46 && b2 == 0x57) {
0416: return checkSwf();
0417: } else {
0418: return false;
0419: }
0420: } catch (IOException ioe) {
0421: return false;
0422: }
0423: }
0424:
0425: private boolean checkBmp() throws IOException {
0426: byte[] a = new byte[44];
0427: if (read(a) != a.length) {
0428: return false;
0429: }
0430: width = getIntLittleEndian(a, 16);
0431: height = getIntLittleEndian(a, 20);
0432: if (width < 1 || height < 1) {
0433: return false;
0434: }
0435: bitsPerPixel = getShortLittleEndian(a, 26);
0436: if (bitsPerPixel != 1 && bitsPerPixel != 4 && bitsPerPixel != 8
0437: && bitsPerPixel != 16 && bitsPerPixel != 24
0438: && bitsPerPixel != 32) {
0439: return false;
0440: }
0441: int x = (int) (getIntLittleEndian(a, 36) * 0.0254);
0442: if (x > 0) {
0443: setPhysicalWidthDpi(x);
0444: }
0445: int y = (int) (getIntLittleEndian(a, 40) * 0.0254);
0446: if (y > 0) {
0447: setPhysicalHeightDpi(y);
0448: }
0449: format = FORMAT_BMP;
0450: return true;
0451: }
0452:
0453: private boolean checkGif() throws IOException {
0454: final byte[] GIF_MAGIC_87A = { 0x46, 0x38, 0x37, 0x61 };
0455: final byte[] GIF_MAGIC_89A = { 0x46, 0x38, 0x39, 0x61 };
0456: byte[] a = new byte[11]; // 4 from the GIF signature + 7 from the global header
0457: if (read(a) != 11) {
0458: return false;
0459: }
0460: if ((!equals(a, 0, GIF_MAGIC_89A, 0, 4))
0461: && (!equals(a, 0, GIF_MAGIC_87A, 0, 4))) {
0462: return false;
0463: }
0464: format = FORMAT_GIF;
0465: width = getShortLittleEndian(a, 4);
0466: height = getShortLittleEndian(a, 6);
0467: int flags = a[8] & 0xff;
0468: bitsPerPixel = ((flags >> 4) & 0x07) + 1;
0469: progressive = (flags & 0x02) != 0;
0470: if (!determineNumberOfImages) {
0471: return true;
0472: }
0473: // skip global color palette
0474: if ((flags & 0x80) != 0) {
0475: int tableSize = (1 << ((flags & 7) + 1)) * 3;
0476: skip(tableSize);
0477: }
0478: numberOfImages = 0;
0479: int blockType;
0480: do {
0481: blockType = read();
0482: switch (blockType) {
0483: case (0x2c): // image separator
0484: {
0485: if (read(a, 0, 9) != 9) {
0486: return false;
0487: }
0488: flags = a[8] & 0xff;
0489: int localBitsPerPixel = (flags & 0x07) + 1;
0490: if (localBitsPerPixel > bitsPerPixel) {
0491: bitsPerPixel = localBitsPerPixel;
0492: }
0493: if ((flags & 0x80) != 0) {
0494: skip((1 << localBitsPerPixel) * 3);
0495: }
0496: skip(1); // initial code length
0497: int n;
0498: do {
0499: n = read();
0500: if (n > 0) {
0501: skip(n);
0502: } else if (n == -1) {
0503: return false;
0504: }
0505: } while (n > 0);
0506: numberOfImages++;
0507: break;
0508: }
0509: case (0x21): // extension
0510: {
0511: int extensionType = read();
0512: if (collectComments && extensionType == 0xfe) {
0513: StringBuilder sb = new StringBuilder();
0514: int n;
0515: do {
0516: n = read();
0517: if (n == -1) {
0518: return false;
0519: }
0520: if (n > 0) {
0521: for (int i = 0; i < n; i++) {
0522: int ch = read();
0523: if (ch == -1) {
0524: return false;
0525: }
0526: sb.append((char) ch);
0527: }
0528: }
0529: } while (n > 0);
0530: } else {
0531: int n;
0532: do {
0533: n = read();
0534: if (n > 0) {
0535: skip(n);
0536: } else if (n == -1) {
0537: return false;
0538: }
0539: } while (n > 0);
0540: }
0541: break;
0542: }
0543: case (0x3b): // end of file
0544: {
0545: break;
0546: }
0547: default: {
0548: return false;
0549: }
0550: }
0551: } while (blockType != 0x3b);
0552: return true;
0553: }
0554:
0555: private boolean checkIff() throws IOException {
0556: byte[] a = new byte[10];
0557: // read remaining 2 bytes of file id, 4 bytes file size
0558: // and 4 bytes IFF subformat
0559: if (read(a, 0, 10) != 10) {
0560: return false;
0561: }
0562: final byte[] IFF_RM = { 0x52, 0x4d };
0563: if (!equals(a, 0, IFF_RM, 0, 2)) {
0564: return false;
0565: }
0566: int type = getIntBigEndian(a, 6);
0567: if (type != 0x494c424d && // type must be ILBM...
0568: type != 0x50424d20) { // ...or PBM
0569: return false;
0570: }
0571: // loop chunks to find BMHD chunk
0572: do {
0573: if (read(a, 0, 8) != 8) {
0574: return false;
0575: }
0576: int chunkId = getIntBigEndian(a, 0);
0577: int size = getIntBigEndian(a, 4);
0578: if ((size & 1) == 1) {
0579: size++;
0580: }
0581: if (chunkId == 0x424d4844) { // BMHD chunk
0582: if (read(a, 0, 9) != 9) {
0583: return false;
0584: }
0585: format = FORMAT_IFF;
0586: width = getShortBigEndian(a, 0);
0587: height = getShortBigEndian(a, 2);
0588: bitsPerPixel = a[8] & 0xff;
0589: return (width > 0 && height > 0 && bitsPerPixel > 0 && bitsPerPixel < 33);
0590: } else {
0591: skip(size);
0592: }
0593: } while (true);
0594: }
0595:
0596: private boolean checkJpeg() throws IOException {
0597: byte[] data = new byte[12];
0598: while (true) {
0599: if (read(data, 0, 4) != 4) {
0600: return false;
0601: }
0602: int marker = getShortBigEndian(data, 0);
0603: int size = getShortBigEndian(data, 2);
0604: if ((marker & 0xff00) != 0xff00) {
0605: return false; // not a valid marker
0606: }
0607: if (marker == 0xffe0) { // APPx
0608: if (size < 14) {
0609: return false; // APPx header must be >= 14 bytes
0610: }
0611: if (read(data, 0, 12) != 12) {
0612: return false;
0613: }
0614: final byte[] APP0_ID = { 0x4a, 0x46, 0x49, 0x46, 0x00 };
0615: if (equals(APP0_ID, 0, data, 0, 5)) {
0616: if (data[7] == 1) {
0617: setPhysicalWidthDpi(getShortBigEndian(data, 8));
0618: setPhysicalHeightDpi(getShortBigEndian(data, 10));
0619: } else if (data[7] == 2) {
0620: int x = getShortBigEndian(data, 8);
0621: int y = getShortBigEndian(data, 10);
0622: setPhysicalWidthDpi((int) (x * 2.54f));
0623: setPhysicalHeightDpi((int) (y * 2.54f));
0624: }
0625: }
0626: skip(size - 14);
0627: } else if (collectComments && size > 2 && marker == 0xfffe) { // comment
0628: size -= 2;
0629: byte[] chars = new byte[size];
0630: if (read(chars, 0, size) != size) {
0631: return false;
0632: }
0633: String comment = new String(chars, "iso-8859-1");
0634: comment = comment.trim();
0635: addComment(comment);
0636: } else if (marker >= 0xffc0 && marker <= 0xffcf
0637: && marker != 0xffc4 && marker != 0xffc8) {
0638: if (read(data, 0, 6) != 6) {
0639: return false;
0640: }
0641: format = FORMAT_JPEG;
0642: bitsPerPixel = (data[0] & 0xff) * (data[5] & 0xff);
0643: progressive = marker == 0xffc2 || marker == 0xffc6
0644: || marker == 0xffca || marker == 0xffce;
0645: width = getShortBigEndian(data, 3);
0646: height = getShortBigEndian(data, 1);
0647: return true;
0648: } else {
0649: skip(size - 2);
0650: }
0651: }
0652: }
0653:
0654: private boolean checkPcx() throws IOException {
0655: byte[] a = new byte[64];
0656: if (read(a) != a.length) {
0657: return false;
0658: }
0659: if (a[0] != 1) { // encoding, 1=RLE is only valid value
0660: return false;
0661: }
0662: // width / height
0663: int x1 = getShortLittleEndian(a, 2);
0664: int y1 = getShortLittleEndian(a, 4);
0665: int x2 = getShortLittleEndian(a, 6);
0666: int y2 = getShortLittleEndian(a, 8);
0667: if (x1 < 0 || x2 < x1 || y1 < 0 || y2 < y1) {
0668: return false;
0669: }
0670: width = x2 - x1 + 1;
0671: height = y2 - y1 + 1;
0672: // color depth
0673: int bits = a[1];
0674: int planes = a[63];
0675: if (planes == 1
0676: && (bits == 1 || bits == 2 || bits == 4 || bits == 8)) {
0677: // paletted
0678: bitsPerPixel = bits;
0679: } else if (planes == 3 && bits == 8) {
0680: // RGB truecolor
0681: bitsPerPixel = 24;
0682: } else {
0683: return false;
0684: }
0685: setPhysicalWidthDpi(getShortLittleEndian(a, 10));
0686: setPhysicalHeightDpi(getShortLittleEndian(a, 10));
0687: format = FORMAT_PCX;
0688: return true;
0689: }
0690:
0691: private boolean checkPng() throws IOException {
0692: final byte[] PNG_MAGIC = { 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
0693: byte[] a = new byte[27];
0694: if (read(a) != 27) {
0695: return false;
0696: }
0697: if (!equals(a, 0, PNG_MAGIC, 0, 6)) {
0698: return false;
0699: }
0700: format = FORMAT_PNG;
0701: width = getIntBigEndian(a, 14);
0702: height = getIntBigEndian(a, 18);
0703: bitsPerPixel = a[22] & 0xff;
0704: int colorType = a[23] & 0xff;
0705: if (colorType == 2 || colorType == 6) {
0706: bitsPerPixel *= 3;
0707: }
0708: progressive = (a[26] & 0xff) != 0;
0709: return true;
0710: }
0711:
0712: private boolean checkPnm(int id) throws IOException {
0713: if (id < 1 || id > 6) {
0714: return false;
0715: }
0716: final int[] PNM_FORMATS = { FORMAT_PBM, FORMAT_PGM, FORMAT_PPM };
0717: format = PNM_FORMATS[(id - 1) % 3];
0718: boolean hasPixelResolution = false;
0719: String s;
0720: while (true) {
0721: s = readLine();
0722: if (s != null) {
0723: s = s.trim();
0724: }
0725: if (s == null || s.length() < 1) {
0726: continue;
0727: }
0728: if (s.charAt(0) == '#') { // comment
0729: if (collectComments && s.length() > 1) {
0730: addComment(s.substring(1));
0731: }
0732: continue;
0733: }
0734: if (!hasPixelResolution) { // split "343 966" into width=343, height=966
0735: int spaceIndex = s.indexOf(' ');
0736: if (spaceIndex == -1) {
0737: return false;
0738: }
0739: String widthString = s.substring(0, spaceIndex);
0740: spaceIndex = s.lastIndexOf(' ');
0741: if (spaceIndex == -1) {
0742: return false;
0743: }
0744: String heightString = s.substring(spaceIndex + 1);
0745: try {
0746: width = Integer.parseInt(widthString);
0747: height = Integer.parseInt(heightString);
0748: } catch (NumberFormatException nfe) {
0749: return false;
0750: }
0751: if (width < 1 || height < 1) {
0752: return false;
0753: }
0754: if (format == FORMAT_PBM) {
0755: bitsPerPixel = 1;
0756: return true;
0757: }
0758: hasPixelResolution = true;
0759: } else {
0760: int maxSample;
0761: try {
0762: maxSample = Integer.parseInt(s);
0763: } catch (NumberFormatException nfe) {
0764: return false;
0765: }
0766: if (maxSample < 0) {
0767: return false;
0768: }
0769: for (int i = 0; i < 25; i++) {
0770: if (maxSample < (1 << (i + 1))) {
0771: bitsPerPixel = i + 1;
0772: if (format == FORMAT_PPM) {
0773: bitsPerPixel *= 3;
0774: }
0775: return true;
0776: }
0777: }
0778: return false;
0779: }
0780: }
0781: }
0782:
0783: private boolean checkPsd() throws IOException {
0784: byte[] a = new byte[24];
0785: if (read(a) != a.length) {
0786: return false;
0787: }
0788: final byte[] PSD_MAGIC = { 0x50, 0x53 };
0789: if (!equals(a, 0, PSD_MAGIC, 0, 2)) {
0790: return false;
0791: }
0792: format = FORMAT_PSD;
0793: width = getIntBigEndian(a, 16);
0794: height = getIntBigEndian(a, 12);
0795: int channels = getShortBigEndian(a, 10);
0796: int depth = getShortBigEndian(a, 20);
0797: bitsPerPixel = channels * depth;
0798: return (width > 0 && height > 0 && bitsPerPixel > 0 && bitsPerPixel <= 64);
0799: }
0800:
0801: private boolean checkRas() throws IOException {
0802: byte[] a = new byte[14];
0803: if (read(a) != a.length) {
0804: return false;
0805: }
0806: final byte[] RAS_MAGIC = { 0x6a, (byte) 0x95 };
0807: if (!equals(a, 0, RAS_MAGIC, 0, 2)) {
0808: return false;
0809: }
0810: format = FORMAT_RAS;
0811: width = getIntBigEndian(a, 2);
0812: height = getIntBigEndian(a, 6);
0813: bitsPerPixel = getIntBigEndian(a, 10);
0814: return (width > 0 && height > 0 && bitsPerPixel > 0 && bitsPerPixel <= 24);
0815: }
0816:
0817: // Written by Michael Aird.
0818: private boolean checkSwf() throws IOException {
0819: //get rid of the last byte of the signature, the byte of the version and 4 bytes of the size
0820: byte[] a = new byte[6];
0821: if (read(a) != a.length) {
0822: return false;
0823: }
0824: format = FORMAT_SWF;
0825: int bitSize = (int) readUBits(5);
0826: int maxX = (int) readSBits(bitSize);
0827: int maxY = (int) readSBits(bitSize);
0828: width = maxX / 20; //cause we're in twips
0829: height = maxY / 20; //cause we're in twips
0830: setPhysicalWidthDpi(72);
0831: setPhysicalHeightDpi(72);
0832: return (width > 0 && height > 0);
0833: }
0834:
0835: private boolean equals(byte[] a1, int offs1, byte[] a2, int offs2,
0836: int num) {
0837: while (num-- > 0) {
0838: if (a1[offs1++] != a2[offs2++]) {
0839: return false;
0840: }
0841: }
0842: return true;
0843: }
0844:
0845: /**
0846: * If {@link #check()} was successful, returns the image's number of bits per pixel.
0847: * Does not include transparency information like the alpha channel.
0848: * @return number of bits per image pixel
0849: */
0850: public int getBitsPerPixel() {
0851: return bitsPerPixel;
0852: }
0853:
0854: /**
0855: * Returns the index'th comment retrieved from the image.
0856: * @throws IllegalArgumentException if index is smaller than 0 or larger than or equal
0857: * to the number of comments retrieved
0858: * @see #getNumberOfComments
0859: */
0860: public String getComment(int index) {
0861: if (comments == null || index < 0 || index >= comments.size()) {
0862: throw new IllegalArgumentException(
0863: "Not a valid comment index: " + index);
0864: }
0865: return comments.get(index);
0866: }
0867:
0868: /**
0869: * If {@link #check()} was successful, returns the image format as one
0870: * of the FORMAT_xyz constants from this class.
0871: * Use {@link #getFormatName()} to get a textual description of the file format.
0872: * @return file format as a FORMAT_xyz constant
0873: */
0874: public int getFormat() {
0875: return format;
0876: }
0877:
0878: /**
0879: * If {@link #check()} was successful, returns the image format's name.
0880: * Use {@link #getFormat()} to get a unique number.
0881: * @return file format name
0882: */
0883: public String getFormatName() {
0884: if (format >= 0 && format < FORMAT_NAMES.length) {
0885: return FORMAT_NAMES[format];
0886: } else {
0887: return "?";
0888: }
0889: }
0890:
0891: /**
0892: * If {@link #check()} was successful, returns one the image's vertical
0893: * resolution in pixels.
0894: * @return image height in pixels
0895: */
0896: public int getHeight() {
0897: return height;
0898: }
0899:
0900: private int getIntBigEndian(byte[] a, int offs) {
0901: return (a[offs] & 0xff) << 24 | (a[offs + 1] & 0xff) << 16
0902: | (a[offs + 2] & 0xff) << 8 | a[offs + 3] & 0xff;
0903: }
0904:
0905: private int getIntLittleEndian(byte[] a, int offs) {
0906: return (a[offs + 3] & 0xff) << 24 | (a[offs + 2] & 0xff) << 16
0907: | (a[offs + 1] & 0xff) << 8 | a[offs] & 0xff;
0908: }
0909:
0910: /**
0911: * If {@link #check()} was successful, returns a String with the
0912: * MIME type of the format.
0913: * @return MIME type, e.g. <code>image/jpeg</code>
0914: */
0915: public String getMimeType() {
0916: if (format >= 0 && format < MIME_TYPE_STRINGS.length) {
0917: if (format == FORMAT_JPEG && progressive) {
0918: return "image/pjpeg";
0919: }
0920: return MIME_TYPE_STRINGS[format];
0921: } else {
0922: return null;
0923: }
0924: }
0925:
0926: /**
0927: * If {@link #check()} was successful and {@link #setCollectComments(boolean)} was called with
0928: * <code>true</code> as argument, returns the number of comments retrieved
0929: * from the input image stream / file.
0930: * Any number >= 0 and smaller than this number of comments is then a
0931: * valid argument for the {@link #getComment(int)} method.
0932: * @return number of comments retrieved from input image
0933: */
0934: public int getNumberOfComments() {
0935: if (comments == null) {
0936: return 0;
0937: } else {
0938: return comments.size();
0939: }
0940: }
0941:
0942: /**
0943: * Returns the number of images in the examined file.
0944: * Assumes that <code>setDetermineImageNumber(true);</code> was called before
0945: * a successful call to {@link #check()}.
0946: * This value can currently be only different from <code>1</code> for GIF images.
0947: * @return number of images in file
0948: */
0949: public int getNumberOfImages() {
0950: return numberOfImages;
0951: }
0952:
0953: /**
0954: * Returns the physical height of this image in dots per inch (dpi).
0955: * Assumes that {@link #check()} was successful.
0956: * Returns <code>-1</code> on failure.
0957: * @return physical height (in dpi)
0958: * @see #getPhysicalWidthDpi()
0959: * @see #getPhysicalHeightInch()
0960: */
0961: public int getPhysicalHeightDpi() {
0962: return physicalHeightDpi;
0963: }
0964:
0965: /**
0966: * If {@link #check()} was successful, returns the physical width of this image in dpi (dots per inch)
0967: * or -1 if no value could be found.
0968: * @return physical height (in dpi)
0969: * @see #getPhysicalHeightDpi()
0970: * @see #getPhysicalWidthDpi()
0971: * @see #getPhysicalWidthInch()
0972: */
0973: public float getPhysicalHeightInch() {
0974: int h = getHeight();
0975: int ph = getPhysicalHeightDpi();
0976: if (h > 0 && ph > 0) {
0977: return ((float) h) / ((float) ph);
0978: } else {
0979: return -1.0f;
0980: }
0981: }
0982:
0983: /**
0984: * If {@link #check()} was successful, returns the physical width of this image in dpi (dots per inch)
0985: * or -1 if no value could be found.
0986: * @return physical width (in dpi)
0987: * @see #getPhysicalHeightDpi()
0988: * @see #getPhysicalWidthInch()
0989: * @see #getPhysicalHeightInch()
0990: */
0991: public int getPhysicalWidthDpi() {
0992: return physicalWidthDpi;
0993: }
0994:
0995: /**
0996: * Returns the physical width of an image in inches, or
0997: * <code>-1.0f</code> if width information is not available.
0998: * Assumes that {@link #check} has been called successfully.
0999: * @return physical width in inches or <code>-1.0f</code> on failure
1000: * @see #getPhysicalWidthDpi
1001: * @see #getPhysicalHeightInch
1002: */
1003: public float getPhysicalWidthInch() {
1004: int w = getWidth();
1005: int pw = getPhysicalWidthDpi();
1006: if (w > 0 && pw > 0) {
1007: return ((float) w) / ((float) pw);
1008: } else {
1009: return -1.0f;
1010: }
1011: }
1012:
1013: private int getShortBigEndian(byte[] a, int offs) {
1014: return (a[offs] & 0xff) << 8 | (a[offs + 1] & 0xff);
1015: }
1016:
1017: private int getShortLittleEndian(byte[] a, int offs) {
1018: return (a[offs] & 0xff) | (a[offs + 1] & 0xff) << 8;
1019: }
1020:
1021: /**
1022: * If {@link #check()} was successful, returns one the image's horizontal
1023: * resolution in pixels.
1024: * @return image width in pixels
1025: */
1026: public int getWidth() {
1027: return width;
1028: }
1029:
1030: /**
1031: * Returns whether the image is stored in a progressive (also called: interlaced) way.
1032: * @return true for progressive/interlaced, false otherwise
1033: */
1034: public boolean isProgressive() {
1035: return progressive;
1036: }
1037:
1038: private int read() throws IOException {
1039: if (in != null) {
1040: return in.read();
1041: } else {
1042: return din.readByte();
1043: }
1044: }
1045:
1046: private int read(byte[] a) throws IOException {
1047: if (in != null) {
1048: return in.read(a);
1049: } else {
1050: din.readFully(a);
1051: return a.length;
1052: }
1053: }
1054:
1055: private int read(byte[] a, int offset, int num) throws IOException {
1056: if (in != null) {
1057: return in.read(a, offset, num);
1058: } else {
1059: din.readFully(a, offset, num);
1060: return num;
1061: }
1062: }
1063:
1064: private String readLine() throws IOException {
1065: return readLine(new StringBuilder());
1066: }
1067:
1068: private String readLine(StringBuilder sb) throws IOException {
1069: boolean finished;
1070: do {
1071: int value = read();
1072: finished = (value == -1 || value == 10);
1073: if (!finished) {
1074: sb.append((char) value);
1075: }
1076: } while (!finished);
1077: return sb.toString();
1078: }
1079:
1080: private long readUBits(int numBits) throws IOException {
1081: if (numBits == 0) {
1082: return 0;
1083: }
1084: int bitsLeft = numBits;
1085: long result = 0;
1086: if (bitPos == 0) { //no value in the buffer - read a byte
1087: if (in != null) {
1088: bitBuf = in.read();
1089: } else {
1090: bitBuf = din.readByte();
1091: }
1092: bitPos = 8;
1093: }
1094:
1095: while (true) {
1096: int shift = bitsLeft - bitPos;
1097: if (shift > 0) {
1098: // Consume the entire buffer
1099: result |= bitBuf << shift;
1100: bitsLeft -= bitPos;
1101:
1102: // Get the next byte from the input stream
1103: if (in != null) {
1104: bitBuf = in.read();
1105: } else {
1106: bitBuf = din.readByte();
1107: }
1108: bitPos = 8;
1109: } else {
1110: // Consume a portion of the buffer
1111: result |= bitBuf >> -shift;
1112: bitPos -= bitsLeft;
1113: bitBuf &= 0xff >> (8 - bitPos); // mask off the consumed bits
1114:
1115: return result;
1116: }
1117: }
1118: }
1119:
1120: /**
1121: * Read a signed value from the given number of bits
1122: */
1123: private int readSBits(int numBits) throws IOException {
1124: // Get the number as an unsigned value.
1125: long uBits = readUBits(numBits);
1126:
1127: // Is the number negative?
1128: if ((uBits & (1L << (numBits - 1))) != 0) {
1129: // Yes. Extend the sign.
1130: uBits |= -1L << numBits;
1131: }
1132:
1133: return (int) uBits;
1134: }
1135:
1136: /**
1137: * Specify whether textual comments are supposed to be extracted from input.
1138: * Default is <code>false</code>.
1139: * If enabled, comments will be added to an internal list.
1140: * @param newValue if <code>true</code>, this class will read comments
1141: * @see #getNumberOfComments
1142: * @see #getComment
1143: */
1144: public void setCollectComments(boolean newValue) {
1145: collectComments = newValue;
1146: }
1147:
1148: /**
1149: * Specify whether the number of images in a file is to be
1150: * determined - default is <code>false</code>.
1151: * This is a special option because some file formats require running over
1152: * the entire file to find out the number of images, a rather time-consuming
1153: * task.
1154: * Not all file formats support more than one image.
1155: * If this method is called with <code>true</code> as argument,
1156: * the actual number of images can be queried via
1157: * {@link #getNumberOfImages()} after a successful call to
1158: * {@link #check()}.
1159: * @param newValue will the number of images be determined?
1160: * @see #getNumberOfImages
1161: */
1162: public void setDetermineImageNumber(boolean newValue) {
1163: determineNumberOfImages = newValue;
1164: }
1165:
1166: /**
1167: * Set the input stream to the argument stream (or file).
1168: * Note that {@link java.io.RandomAccessFile} implements
1169: * {@link java.io.DataInput}.
1170: * @param dataInput the input stream to read from
1171: */
1172: public void setInput(DataInput dataInput) {
1173: din = dataInput;
1174: in = null;
1175: }
1176:
1177: /**
1178: * Set the input stream to the argument stream (or file).
1179: * @param inputStream the input stream to read from
1180: */
1181: public void setInput(InputStream inputStream) {
1182: in = inputStream;
1183: din = null;
1184: }
1185:
1186: private void setPhysicalHeightDpi(int newValue) {
1187: physicalWidthDpi = newValue;
1188: }
1189:
1190: private void setPhysicalWidthDpi(int newValue) {
1191: physicalHeightDpi = newValue;
1192: }
1193:
1194: private void skip(int num) throws IOException {
1195: while (num > 0) {
1196: long result;
1197: if (in != null) {
1198: result = in.skip(num);
1199: } else {
1200: result = din.skipBytes(num);
1201: }
1202: if (result > 0) {
1203: num -= result;
1204: }
1205: }
1206: }
1207: }
|