Image format info : ImageReader « 2D Graphics GUI « Java






Image format info

   

/*
#IFNDEF ALT_LICENSE
                           ThinWire(R) RIA Ajax Framework
                 Copyright (C) 2003-2007 Custom Credit Systems

  This library is free software; you can redistribute it and/or modify it under
  the terms of the GNU Lesser General Public License as published by the Free
  Software Foundation; either version 2.1 of the License, or (at your option) any
  later version.

  This library is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
  PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License along
  with this library; if not, write to the Free Software Foundation, Inc., 59
  Temple Place, Suite 330, Boston, MA 02111-1307 USA

  Users who would rather have a commercial license, warranty or support should
  contact the following company who invented, built and supports the technology:
  
                Custom Credit Systems, Richardson, TX 75081, USA.
                email: info@thinwire.com    ph: +1 (888) 644-6405
                          http://www.thinwire.com
#ENDIF
 [ v1.2_RC2 ] 
*/

import java.io.DataInput;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;


/**
 * @author Joshua J. Gertzen
 */
public class ImageInfo {
    public enum Format {GIF,JPEG,PNG}
    
    private String stringValue;
    private String name;
    private Format format;
    private int width;
    private int height;

    public ImageInfo(String name) {
        setName(name);
    }
    
    public int getWidth() {
        return width;
    }
    
    public int getHeight() {
        return height;
    }
    
    public Format getFormat() {
        return format;
    }
    
    public String getName() {
        return name;
    }
    
    private void setName(String name) {        
        try {                       
            InputStream is = new FileInputStream(name);
                
            if (is != null) {
                this.name = name;
                ImageParser ii = new ImageParser();                        
                ii.setInput(is);
                
                if (!ii.check())
                    throw new UnsupportedOperationException("Unknown image file format for file.");
                
                switch (ii.getFormat()) {
                    case ImageParser.FORMAT_GIF:
                        this.format = Format.GIF;                        
                        break;
                        
                    case ImageParser.FORMAT_JPEG:
                        this.format = Format.JPEG;
                        break;
                        
                    case ImageParser.FORMAT_PNG:
                        this.format = Format.PNG; 
                        break;
                    
                    default:
                        throw new UnsupportedOperationException("Unsupported image file format '" + ii.getFormatName() + "'.");
                }

                this.width = ii.getWidth();
                this.height = ii.getHeight();
            } else {
                this.width = -1;
                this.height = -1;
                this.format = null;
                this.name = "";
            }
        } catch (Exception e) {
            if (!(e instanceof RuntimeException)) e = new RuntimeException(e);
            throw (RuntimeException)e;
        }
    }   
    
    public boolean equals(Object o) {
        return o instanceof ImageInfo && toString().equals(o.toString());
    }
    
    public int hashCode() {
        return toString().hashCode();
    }
    
    public String toString() {
        if (stringValue == null) stringValue = "ImageInfo{name:" + name + ",format:" + format + ",width:" + width + ",height:" + height + "}"; 
        return stringValue;
    }
}

/* XXX Note: This class is a public domain class that is used by
 * ImageInfo (above) to determine the width and height of an image.
 * It is made package-private because it is not intended to be used
 * directly.
 */

/*
 * ImageInfo.java
 *
 * Version 1.5
 *
 * A Java class to determine image width, height and color depth for
 * a number of image file formats.
 *
 * Written by Marco Schmidt 
 * <http://www.geocities.com/marcoschmidt.geo/contact.html>.
 *
 * Contributed to the Public Domain.
 *
 * Last modification 2004-02-29
 */
/**
 * 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, PSD and SWF 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://www.geocities.com/marcoschmidt.geo/image-info.html">http://www.geocities.com/marcoschmidt.geo/image-info.html</a>.
 * <p>
 * Written by <a href="http://www.geocities.com/marcoschmidt.geo/contact.html">Marco Schmidt</a>.
 * <p>
 * This class is contributed to the Public Domain.
 * Use it at your own risk.
 * <p>
 * Last modification 2004-02-29.
 * <p>
 * History:
 * <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>
 * </ul>
 */
final class ImageParser {
    /**
     * 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).
     * If you know of a place where GIFs store the physical resolution
     * of an image, please
     * <a href="http://www.geocities.com/marcoschmidt.geo/contact.html">send me a mail</a>!
     * 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;

    /** Return value of {@link #getFormat()} for SWF (Shockwave) streams. */
    public static final int FORMAT_SWF = 11;

    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", "SWF"};

    /**
     * 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", "application/x-shockwave-flash"};
    

    private int width;
    private int height;
    private int bitsPerPixel;
    private boolean progressive;
    private int format;
    private InputStream in;
    private DataInput din;
    private boolean collectComments = true;
    private List<String> comments;
    private boolean determineNumberOfImages;
    private int numberOfImages;
    private int physicalHeightDpi;
    private int physicalWidthDpi;
    private int bitBuf;
    private int bitPos;

    private void addComment(String s) {
        if (comments == null) {
            comments = new ArrayList<String>();
        }
        comments.add(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
            if (b1 == 0x46 && b2 == 0x57) {
                return checkSwf();
            }
            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;
                    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) {
                        StringBuilder sb = new StringBuilder();
                        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) {
                    return false; // APPx header must be >= 14 bytes
                }
                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)) {
                    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);
    }

    // Written by Michael Aird.
    private boolean checkSwf() throws IOException {
        //get rid of the last byte of the signature, the byte of the version and 4 bytes of the size
        byte[] a = new byte[6];
        if (read(a) != a.length) {
            return false;
        }
        format = FORMAT_SWF;
        int bitSize = (int)readUBits( 5 );
        int maxX = (int)readSBits( bitSize );
        int maxY = (int)readSBits( bitSize );
        width = maxX/20; //cause we're in twips
        height = maxY/20;  //cause we're in twips
        setPhysicalWidthDpi(72);
        setPhysicalHeightDpi(72);
        return (width > 0 && height > 0);
    }

    private 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 image.
     * @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 comments.get(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 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 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 int getShortBigEndian(byte[] a, int offs) {
        return
            (a[offs] & 0xff) << 8 | 
            (a[offs + 1] & 0xff);
    }

    private 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;
    }

    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 StringBuilder());
    }

    private String readLine(StringBuilder 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 long readUBits( int numBits ) throws IOException
    {
        if (numBits == 0) {
            return 0;
        }
        int bitsLeft = numBits;
        long result = 0;
        if (bitPos == 0) { //no value in the buffer - read a byte
            if (in != null) {
                bitBuf = in.read();
            } else {
                bitBuf = din.readByte();
            }
            bitPos = 8;
        }
        
        while( true )
        {
            int shift = bitsLeft - bitPos;
            if( shift > 0 )
            {
                // Consume the entire buffer
                result |= bitBuf << shift;
                bitsLeft -= bitPos;

                // Get the next byte from the input stream
                if (in != null) {
                  bitBuf = in.read();
                } else {
                  bitBuf = din.readByte();
                }
                bitPos = 8;
            }
            else
            {
                // Consume a portion of the buffer
                result |= bitBuf >> -shift;
                bitPos -= bitsLeft;
                bitBuf &= 0xff >> (8 - bitPos); // mask off the consumed bits

                return result;
            }
        }        
    }
    
        /**
     * Read a signed value from the given number of bits
     */
    private int readSBits( int numBits ) throws IOException
    {
        // Get the number as an unsigned value.
        long uBits = readUBits( numBits );

        // Is the number negative?
        if( ( uBits & (1L << (numBits - 1))) != 0 )
        {
            // Yes. Extend the sign.
            uBits |= -1L << numBits;
        }

        return (int)uBits;        
    }  

    /**
     * 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;
            }
        }
    }
}

   
    
    
  








Related examples in the same category

1.Show Image with ImageReader
2.Add Image IO Read Progress Listener to ImageReader
3.Read an Image from a file
4.Read an Image from inputStream
5.Read an Image from URL
6.Determining the Format of an Image in a File
7.Detect the file type of the input stream prior to reading the image
8.List the image formats that can be read and written
9.Get file format, image resolution, number of bits per pixel (JPEG, GIF, BMP, PCX, PNG, IFF, RAS, PBM, PGM, PPM, PSD and SWF files)
10.Image observer blocks until the image is completely loaded. AWT defers the loading of images until they are painted on a graphic.