/* * ImageInfo.java * * Version 1.7 * * 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. */ 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). *
* Use the class like this: *
* 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
*
* You can also use this class as a command line program.
* Call it with a number of image file names and URLs as parameters:
* * java ImageInfo *.jpg *.png *.gif http://somesite.tld/image.jpg ** or call it without parameters and pipe data to it: *
* java ImageInfo < image.jpg **
* Known limitations: *
* Requirements: *
* The latest version can be found at http://schmidt.devlib.org/image-info/. *
* Written by Marco Schmidt. *
* This class is contributed to the Public Domain. * Use it at your own risk. *
* Last modification 2005-07-26. *
* History: *
true as argument to identify animated GIFs
* ({@link #getNumberOfImages()} will return a value larger than 1).-1.
* -c.
* @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. image/jpeg
*/
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
* true as argument, returns the number of comments retrieved
* from the input image stream / file.
* Any number >= 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 setDetermineImageNumber(true); was called before
* a successful call to {@link #check()}.
* This value can currently be only different from 1 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 -1 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
* -1.0f if width information is not available.
* Assumes that {@link #check} has been called successfully.
* @return physical width in inches or -1.0f 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 {
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 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 integer value from input.
* @param numBits number of bits to read
*/
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;
}
/* private void synchBits()
{
bitBuf = 0;
bitPos = 0;
}*/
/* private String readLine(int firstChar) throws IOException {
StringBuffer result = new StringBuffer();
result.append((char)firstChar);
return readLine(result);
}*/
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 false.
* If enabled, comments will be added to an internal list.
* @param newValue if true, 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 false.
* 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 true 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;
}
}
}
}