package com.idega.graphics;
/*
* 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, http://schmidt.devlib.org/image-info/>.
*
* Contributed to the Public Domain.
*
* Adopted to idegaWeb, eiki@idega.is
*/
/*
*
* 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.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Vector;
/**
*
* Get file format, image resolution, number of bits per pixel and optionally
*
* number of images, comments and physical resolution from
*
* JPEG, GIF, BMP, PCX, PNG, IFF, RAS, PBM, PGM, PPM and PSD files
*
* (or input streams).
*
* <p>
*
* Use the class like this:
*
* <pre>
* ImageInfo ii = new ImageInfo();
* ii.setInput(in); // in can be InputStream or RandomAccessFile
* ii.setDetermineImageNumber(true); // default is false
* ii.setCollectComments(true); // default is false
* if (!ii.check()) {
* System.err.println("Not a supported image file format.");
* return;
* }
* System.out.println(ii.getFormatName() + ", " + ii.getMimeType() + ", " + ii.getWidth() + " x " + ii.getHeight()
* + " pixels, " + ii.getBitsPerPixel() + " bits per pixel, " + ii.getNumberOfImages() + " image(s), "
* + ii.getNumberOfComments() + " comment(s).");
* // there are other properties, check out the API documentation
* </pre>
*
* You can also use this class as a command line program.
*
* Call it with a number of image file names and URLs as parameters:
*
* <pre>
*
* java ImageInfo *.jpg *.png *.gif http://somesite.tld/image.jpg
*
* </pre>
*
* or call it without parameters and pipe data to it:
*
* <pre>
*
* java ImageInfo < image.jpg
*
* </pre>
*
* <p>
*
* Known limitations:
*
* <ul>
*
* <li>When the determination of the number of images is turned off, GIF bits
*
* per pixel are only read from the global header.
*
* For some GIFs, local palettes change this to a typically larger
*
* value. To be certain to get the correct color depth, call
*
* setDetermineImageNumber(true) before calling check().
*
* The complete scan over the GIF file will take additional time.</li>
*
* <li>Transparency information is not included in the bits per pixel count.
*
* Actually, it was my decision not to include those bits, so it's a feature!
* ;-)</li>
*
* </ul>
*
* <p>
*
* Requirements:
*
* <ul>
*
* <li>Java 1.1 or higher</li>
*
* </ul>
*
* <p>
*
* The latest version can be found at <a
* href="http://schmidt.devlib.org/image-info/">http://schmidt.devlib.org/image-info/</a>.
*
* <p>
*
* Written by Marco Schmidt.
*
* <p>
*
* This class is contributed to the Public Domain.
*
* Use it at your own risk.
*
* <p>
*
* Last modification 2005-07-26.
*
* <p>
*
* <a name="history">History</a>:
*
* <ul>
*
* <li><strong>2001-08-24</strong> Initial version.</li>
*
* <li><strong>2001-10-13</strong> Added support for the file formats BMP and
* PCX.</li>
*
* <li><strong>2001-10-16</strong> Fixed bug in read(int[], int, int) that
* returned
*
* <li><strong>2002-01-22</strong> Added support for file formats Amiga IFF
* and Sun Raster (RAS).</li>
*
* <li><strong>2002-01-24</strong> Added support for file formats Portable
* Bitmap / Graymap / Pixmap (PBM, PGM, PPM) and Adobe Photoshop (PSD).
*
* Added new method getMimeType() to return the MIME type associated with a
* particular file format.</li>
*
* <li><strong>2002-03-15</strong> Added support to recognize number of images
* in file. Only works with GIF.
*
* Use {@link #setDetermineImageNumber} with <code>true</code> as argument to
* identify animated GIFs
* ({@link #getNumberOfImages()} will return a value larger than
* <code>1</code>).</li>
*
* <li><strong>2002-04-10</strong> Fixed a bug in the feature 'determine
* number of images in animated GIF' introduced with version 1.1.
*
* Thanks to Marcelo P. Lima for sending in the bug report.
*
* Released as 1.1.1.</li>
*
* <li><strong>2002-04-18</strong> Added {@link #setCollectComments(boolean)}.
*
* That new method lets the user specify whether textual comments are to be
*
* stored in an internal list when encountered in an input image file / stream.
*
* Added two methods to return the physical width and height of the image in
* dpi:
*
* {@link #getPhysicalWidthDpi()} and {@link #getPhysicalHeightDpi()}.
*
* If the physical resolution could not be retrieved, these methods return
* <code>-1</code>.
*
* </li>
*
* <li><strong>2002-04-23</strong> Added support for the new properties
* physical resolution and
*
* comments for some formats. Released as 1.2.</li>
*
* <li><strong>2002-06-17</strong> Added support for SWF, sent in by Michael
* Aird.
*
* Changed checkJpeg() so that other APP markers than APP0 will not lead to a
* failure anymore.
*
* Released as 1.3.</li>
*
* <li><strong>2003-07-28</strong> Bug fix - skip method now takes return
* values into consideration.
*
* Less bytes than necessary may have been skipped, leading to flaws in the
* retrieved information in some cases.
*
* Thanks to Bernard Bernstein for pointing that out.
*
* Released as 1.4.</li>
*
* <li><strong>2004-02-29</strong> Added support for recognizing progressive
* JPEG and
*
* interlaced PNG and GIF. A new method {@link #isProgressive()} returns whether
* ImageInfo
*
* has found that the storage type is progressive (or interlaced).
*
* Thanks to Joe Germuska for suggesting the feature.
*
* Bug fix: BMP physical resolution is now correctly determined.
*
* Released as 1.5.</li>
*
* <li><strong>2004-11-30</strong> Bug fix: recognizing progressive GIFs
*
* (interlaced in GIF terminology) did not work (thanks to Franz Jeitler for
*
* pointing this out). Now it should work, but only if the number of images is
* determined.
*
* This is because information on interlacing is stored in a local image header.
*
* In theory, different images could be stored interlaced and non-interlaced in
* one
*
* file. However, I think that's unlikely. Right now, the last image in the GIF
* file
*
* that is examined by ImageInfo is used for the "progressive" status.</li>
*
* <li><strong>2005-01-02</strong> Some code clean up (unused methods and
* variables
*
* commented out, missing javadoc comments, etc.). Thanks to George Sexton for a
* long list.
*
* Removed usage of Boolean.toString because
*
* it's a Java 1.4+ feature (thanks to Gregor Dupont).
*
* Changed delimiter character in compact output from semicolon to tabulator
*
* (for better integration with cut(1) and other Unix tools).
*
* Added some points to the <a
* href="http://schmidt.devlib.org/image-info/index.html#knownissues">'Known
*
* issues' section of the website</a>.
*
* Released as 1.6.</li>
*
* <li><strong>2005-07-26</strong> Removed code to identify Flash (SWF) files.
*
* Has repeatedly led to problems and support requests, and I don't know the
*
* format and don't have the time and interest to fix it myself.
*
* I repeatedly included fixes by others which didn't work for some people.
*
* I give up on SWF. Please do not contact me about it anymore.
*
* Set package of ImageInfo class to org.devlib.schmidt.imageinfo (a package
*
* was repeatedly requested by some users).
*
* Released as 1.7.</li>
*
* </ul>
*
* @author Marco Schmidt
*
*/
public class ImageInfo {
/**
*
* Return value of {@link #getFormat()} for JPEG streams.
*
* ImageInfo can extract physical resolution and comments
*
* from JPEGs (only from APP0 headers).
*
* Only one image can be stored in a file.
*
* It is determined whether the JPEG stream is progressive
*
* (see {@link #isProgressive()}).
*
*/
public static final int FORMAT_JPEG = 0;
/**
*
* Return value of {@link #getFormat()} for GIF streams.
*
* ImageInfo can extract comments from GIFs and count the number
*
* of images (GIFs with more than one image are animations).
*
* It is determined whether the GIF stream is interlaced (see
* {@link #isProgressive()}).
*
*/
public static final int FORMAT_GIF = 1;
/**
*
* Return value of {@link #getFormat()} for PNG streams.
*
* PNG only supports one image per file.
*
* Both physical resolution and comments can be stored with PNG,
*
* but ImageInfo is currently not able to extract those.
*
* It is determined whether the PNG stream is interlaced (see
* {@link #isProgressive()}).
*
*/
public static final int FORMAT_PNG = 2;
/**
*
* Return value of {@link #getFormat()} for BMP streams.
*
* BMP only supports one image per file.
*
* BMP does not allow for comments.
*
* The physical resolution can be stored.
*
*/
public static final int FORMAT_BMP = 3;
/**
*
* Return value of {@link #getFormat()} for PCX streams.
*
* PCX does not allow for comments or more than one image per file.
*
* However, the physical resolution can be stored.
*
*/
public static final int FORMAT_PCX = 4;
/**
*
* Return value of {@link #getFormat()} for IFF streams.
*
*/
public static final int FORMAT_IFF = 5;
/**
*
* Return value of {@link #getFormat()} for RAS streams.
*
* Sun Raster allows for one image per file only and is not able to
*
* store physical resolution or comments.
*
*/
public static final int FORMAT_RAS = 6;
/** Return value of {@link #getFormat()} for PBM streams. */
public static final int FORMAT_PBM = 7;
/** Return value of {@link #getFormat()} for PGM streams. */
public static final int FORMAT_PGM = 8;
/** Return value of {@link #getFormat()} for PPM streams. */
public static final int FORMAT_PPM = 9;
/** Return value of {@link #getFormat()} for PSD streams. */
public static final int FORMAT_PSD = 10;
/*
* public static final int COLOR_TYPE_UNKNOWN = -1;
*
* public static final int COLOR_TYPE_TRUECOLOR_RGB = 0;
*
* public static final int COLOR_TYPE_PALETTED = 1;
*
* public static final int COLOR_TYPE_GRAYSCALE= 2;
*
* public static final int COLOR_TYPE_BLACK_AND_WHITE = 3;
*/
/**
*
* The names of all supported file formats.
*
* The FORMAT_xyz int constants can be used as index values for
*
* this array.
*
*/
private static final String[] FORMAT_NAMES = { "JPEG", "GIF", "PNG", "BMP", "PCX", "IFF", "RAS", "PBM", "PGM",
"PPM", "PSD" };
/**
*
* The names of the MIME types for all supported file formats.
*
* The FORMAT_xyz int constants can be used as index values for
*
* this array.
*
*/
private static final String[] MIME_TYPE_STRINGS = { "image/jpeg", "image/gif", "image/png", "image/bmp",
"image/pcx", "image/iff", "image/ras", "image/x-portable-bitmap", "image/x-portable-graymap",
"image/x-portable-pixmap", "image/psd" };
private int width;
private int height;
private int bitsPerPixel;
// private int colorType = COLOR_TYPE_UNKNOWN;
private boolean progressive;
private int format;
private InputStream in;
private DataInput din;
private boolean collectComments = true;
private Vector comments;
private boolean determineNumberOfImages;
private int numberOfImages;
private int physicalHeightDpi;
private int physicalWidthDpi;
//private int bitBuf;
//private int bitPos;
private void addComment(String s) {
if (this.comments == null) {
this.comments = new Vector();
}
this.comments.addElement(s);
}
/**
*
* Call this method after you have provided an input stream or file
*
* using {@link #setInput(InputStream)} or {@link #setInput(DataInput)}.
*
* If true is returned, the file format was known and information
*
* on the file's content can be retrieved using the various getXyz methods.
*
* @return if information could be retrieved from input
*
*/
public boolean check() {
this.format = -1;
this.width = -1;
this.height = -1;
this.bitsPerPixel = -1;
this.numberOfImages = 1;
this.physicalHeightDpi = -1;
this.physicalWidthDpi = -1;
this.comments = null;
try {
int b1 = read() & 0xff;
int b2 = read() & 0xff;
if (b1 == 0x47 && b2 == 0x49) {
return checkGif();
}
else if (b1 == 0x89 && b2 == 0x50) {
return checkPng();
}
else if (b1 == 0xff && b2 == 0xd8) {
return checkJpeg();
}
else if (b1 == 0x42 && b2 == 0x4d) {
return checkBmp();
}
else if (b1 == 0x0a && b2 < 0x06) {
return checkPcx();
}
else if (b1 == 0x46 && b2 == 0x4f) {
return checkIff();
}
else if (b1 == 0x59 && b2 == 0xa6) {
return checkRas();
}
else if (b1 == 0x50 && b2 >= 0x31 && b2 <= 0x36) {
return checkPnm(b2 - '0');
}
else if (b1 == 0x38 && b2 == 0x42) {
return checkPsd();
}
else {
return false;
}
}
catch (IOException ioe) {
return false;
}
}
private boolean checkBmp() throws IOException {
byte[] a = new byte[44];
if (read(a) != a.length) {
return false;
}
this.width = getIntLittleEndian(a, 16);
this.height = getIntLittleEndian(a, 20);
if (this.width < 1 || this.height < 1) {
return false;
}
this.bitsPerPixel = getShortLittleEndian(a, 26);
if (this.bitsPerPixel != 1 && this.bitsPerPixel != 4 && this.bitsPerPixel != 8 && this.bitsPerPixel != 16 && this.bitsPerPixel != 24
&& this.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);
}
this.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;
}
this.format = FORMAT_GIF;
this.width = getShortLittleEndian(a, 4);
this.height = getShortLittleEndian(a, 6);
int flags = a[8] & 0xff;
this.bitsPerPixel = ((flags >> 4) & 0x07) + 1;
// progressive = (flags & 0x02) != 0;
if (!this.determineNumberOfImages) {
return true;
}
// skip global color palette
if ((flags & 0x80) != 0) {
int tableSize = (1 << ((flags & 7) + 1)) * 3;
skip(tableSize);
}
this.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;
this.progressive = (flags & 0x40) != 0;
/*
* int locWidth = getShortLittleEndian(a, 4);
*
* int locHeight = getShortLittleEndian(a, 6);
*
* System.out.println("LOCAL: " + locWidth + " x " +
* locHeight);
*/
int localBitsPerPixel = (flags & 0x07) + 1;
if (localBitsPerPixel > this.bitsPerPixel) {
this.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);
this.numberOfImages++;
break;
}
case (0x21): // extension
{
int extensionType = read();
if (this.collectComments && extensionType == 0xfe) {
StringBuffer sb = new StringBuffer();
int n;
do {
n = read();
if (n == -1) {
return false;
}
if (n > 0) {
for (int i = 0; i < n; i++) {
int ch = read();
if (ch == -1) {
return false;
}
sb.append((char) ch);
}
}
}
while (n > 0);
}
else {
int n;
do {
n = read();
if (n > 0) {
skip(n);
}
else if (n == -1) {
return false;
}
}
while (n > 0);
}
break;
}
case (0x3b): // end of file
{
break;
}
default: {
return false;
}
}
}
while (blockType != 0x3b);
return true;
}
private boolean checkIff() throws IOException {
byte[] a = new byte[10];
// read remaining 2 bytes of file id, 4 bytes file size
// and 4 bytes IFF subformat
if (read(a, 0, 10) != 10) {
return false;
}
final byte[] IFF_RM = { 0x52, 0x4d };
if (!equals(a, 0, IFF_RM, 0, 2)) {
return false;
}
int type = getIntBigEndian(a, 6);
if (type != 0x494c424d && // type must be ILBM...
type != 0x50424d20) { // ...or PBM
return false;
}
// loop chunks to find BMHD chunk
do {
if (read(a, 0, 8) != 8) {
return false;
}
int chunkId = getIntBigEndian(a, 0);
int size = getIntBigEndian(a, 4);
if ((size & 1) == 1) {
size++;
}
if (chunkId == 0x424d4844) { // BMHD chunk
if (read(a, 0, 9) != 9) {
return false;
}
this.format = FORMAT_IFF;
this.width = getShortBigEndian(a, 0);
this.height = getShortBigEndian(a, 2);
this.bitsPerPixel = a[8] & 0xff;
return (this.width > 0 && this.height > 0 && this.bitsPerPixel > 0 && this.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)) {
// System.out.println("data 7=" + data[7]);
if (data[7] == 1) {
setPhysicalWidthDpi(getShortBigEndian(data, 8));
setPhysicalHeightDpi(getShortBigEndian(data, 10));
}
else if (data[7] == 2) {
int x = getShortBigEndian(data, 8);
int y = getShortBigEndian(data, 10);
setPhysicalWidthDpi((int) (x * 2.54f));
setPhysicalHeightDpi((int) (y * 2.54f));
}
}
skip(size - 14);
}
else if (this.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;
}
this.format = FORMAT_JPEG;
this.bitsPerPixel = (data[0] & 0xff) * (data[5] & 0xff);
this.progressive = marker == 0xffc2 || marker == 0xffc6 || marker == 0xffca || marker == 0xffce;
this.width = getShortBigEndian(data, 3);
this.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;
}
this.width = x2 - x1 + 1;
this.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
this.bitsPerPixel = bits;
}
else if (planes == 3 && bits == 8) {
// RGB truecolor
this.bitsPerPixel = 24;
}
else {
return false;
}
setPhysicalWidthDpi(getShortLittleEndian(a, 10));
setPhysicalHeightDpi(getShortLittleEndian(a, 10));
this.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;
}
this.format = FORMAT_PNG;
this.width = getIntBigEndian(a, 14);
this.height = getIntBigEndian(a, 18);
this.bitsPerPixel = a[22] & 0xff;
int colorType = a[23] & 0xff;
if (colorType == 2 || colorType == 6) {
this.bitsPerPixel *= 3;
}
this.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 };
this.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 (this.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 {
this.width = Integer.parseInt(widthString);
this.height = Integer.parseInt(heightString);
}
catch (NumberFormatException nfe) {
return false;
}
if (this.width < 1 || this.height < 1) {
return false;
}
if (this.format == FORMAT_PBM) {
this.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))) {
this.bitsPerPixel = i + 1;
if (this.format == FORMAT_PPM) {
this.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;
}
this.format = FORMAT_PSD;
this.width = getIntBigEndian(a, 16);
this.height = getIntBigEndian(a, 12);
int channels = getShortBigEndian(a, 10);
int depth = getShortBigEndian(a, 20);
this.bitsPerPixel = channels * depth;
return (this.width > 0 && this.height > 0 && this.bitsPerPixel > 0 && this.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;
}
this.format = FORMAT_RAS;
this.width = getIntBigEndian(a, 2);
this.height = getIntBigEndian(a, 6);
this.bitsPerPixel = getIntBigEndian(a, 10);
return (this.width > 0 && this.height > 0 && this.bitsPerPixel > 0 && this.bitsPerPixel <= 24);
}
/**
*
* Run over String list, return false iff at least one of the arguments
*
* equals <code>-c</code>.
*
* @param args
* string list to check
*
*/
private static boolean determineVerbosity(String[] args) {
if (args != null && args.length > 0) {
for (int i = 0; i < args.length; i++) {
if ("-c".equals(args[i])) {
return false;
}
}
}
return true;
}
private static boolean equals(byte[] a1, int offs1, byte[] a2, int offs2, int num) {
while (num-- > 0) {
if (a1[offs1++] != a2[offs2++]) {
return false;
}
}
return true;
}
/**
*
* If {@link #check()} was successful, returns the image's number of bits
* per pixel.
*
* Does not include transparency information like the alpha channel.
*
* @return number of bits per image pixel
*
*/
public int getBitsPerPixel() {
return this.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 (this.comments == null || index < 0 || index >= this.comments.size()) {
throw new IllegalArgumentException("Not a valid comment index: " + index);
}
return (String) this.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 this.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 (this.format >= 0 && this.format < FORMAT_NAMES.length) {
return FORMAT_NAMES[this.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 this.height;
}
private static int getIntBigEndian(byte[] a, int offs) {
return (a[offs] & 0xff) << 24 | (a[offs + 1] & 0xff) << 16 | (a[offs + 2] & 0xff) << 8 | a[offs + 3] & 0xff;
}
private static int getIntLittleEndian(byte[] a, int offs) {
return (a[offs + 3] & 0xff) << 24 | (a[offs + 2] & 0xff) << 16 | (a[offs + 1] & 0xff) << 8 | a[offs] & 0xff;
}
/**
*
* If {@link #check()} was successful, returns a String with the
*
* MIME type of the format.
*
* @return MIME type, e.g. <code>image/jpeg</code>
*
*/
public String getMimeType() {
if (this.format >= 0 && this.format < MIME_TYPE_STRINGS.length) {
if (this.format == FORMAT_JPEG && this.progressive) {
return "image/pjpeg";
}
return MIME_TYPE_STRINGS[this.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 >= 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 (this.comments == null) {
return 0;
}
else {
return this.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 this.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 this.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 this.physicalWidthDpi;
}
/**
*
* Returns the physical width of an image in inches, or
*
* <code>-1.0f</code> if width information is not available.
*
* Assumes that {@link #check} has been called successfully.
*
* @return physical width in inches or <code>-1.0f</code> on failure
*
* @see #getPhysicalWidthDpi
*
* @see #getPhysicalHeightInch
*
*/
public float getPhysicalWidthInch() {
int w = getWidth();
int pw = getPhysicalWidthDpi();
if (w > 0 && pw > 0) {
return ((float) w) / ((float) pw);
}
else {
return -1.0f;
}
}
private static int getShortBigEndian(byte[] a, int offs) {
return (a[offs] & 0xff) << 8 | (a[offs + 1] & 0xff);
}
private static int getShortLittleEndian(byte[] a, int offs) {
return (a[offs] & 0xff) | (a[offs + 1] & 0xff) << 8;
}
/**
*
* If {@link #check()} was successful, returns one the image's horizontal
*
* resolution in pixels.
*
* @return image width in pixels
*
*/
public int getWidth() {
return this.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 this.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 (this.in != null) {
return this.in.read();
}
else {
return this.din.readByte();
}
}
private int read(byte[] a) throws IOException {
if (this.in != null) {
return this.in.read(a);
}
else {
this.din.readFully(a);
return a.length;
}
}
private int read(byte[] a, int offset, int num) throws IOException {
if (this.in != null) {
return this.in.read(a, offset, num);
}
else {
this.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 <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) {
this.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) {
this.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) {
this.din = dataInput;
this.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) {
this.in = inputStream;
this.din = null;
}
private void setPhysicalHeightDpi(int newValue) {
this.physicalWidthDpi = newValue;
}
private void setPhysicalWidthDpi(int newValue) {
this.physicalHeightDpi = newValue;
}
private void skip(int num) throws IOException {
while (num > 0) {
long result;
if (this.in != null) {
result = this.in.skip(num);
}
else {
result = this.din.skipBytes(num);
}
if (result > 0) {
num -= result;
}
}
}
}