public static Dimension getImageDimension(File file) throws IOException
{
RandomAccessFile raf = new RandomAccessFile(file, "r");
try
{
int header = raf.readUnsignedShort();
if (header == 0x8950)
{
// PNG!
raf.seek(8 + 4 + 4); // thanks, Abuse
return new Dimension(raf.readInt(), raf.readInt());
}
if (header == 0xffd8)
{
// JPG!
// (see below)
}
else if (header == 0x424D)
{
// BMP!
raf.seek(0x0012);
int w = raf.read() | (raf.read() << 8) | (raf.read() << 16) | (raf.read() << 24);
int h = raf.read() | (raf.read() << 8) | (raf.read() << 16) | (raf.read() << 24);
return new Dimension(w, h);
}
else if (header == (('G' << 8) | ('I' << 0)))
{
// GIF!
raf.seek(0x0006);
int w = raf.read() | (raf.read() << 8);
int h = raf.read() | (raf.read() << 8);
return new Dimension(w, h);
}
else
{
throw new IllegalStateException("unexpected header: " + Integer.toHexString(header));
}
while (true) // JPG is not so easy...
{
int marker = raf.readUnsignedShort();
switch (marker)
{
case 0xffd8: // SOI
case 0xffd0: // RST0
case 0xffd1: // RST1
case 0xffd2: // RST2
case 0xffd3: // RST3
case 0xffd4: // RST4
case 0xffd5: // RST5
case 0xffd6: // RST6
case 0xffd7: // RST7
case 0xffd9: // EOI
break;
case 0xffdd: // DRI
raf.readUnsignedShort();
break;
case 0xffe0: // APP0
case 0xffe1: // APP1
case 0xffe2: // APP2
case 0xffe3: // APP3
case 0xffe4: // APP4
case 0xffe5: // APP5
case 0xffe6: // APP6
case 0xffe7: // APP7
case 0xffe8: // APP8
case 0xffe9: // APP9
case 0xffea: // APPa
case 0xffeb: // APPb
case 0xffec: // APPc
case 0xffed: // APPd
case 0xffee: // APPe
case 0xffef: // APPf
case 0xfffe: // COM
case 0xffdb: // DQT
case 0xffc4: // DHT
case 0xffda: // SOS
raf.readFully(new byte[raf.readUnsignedShort() - 2]);
break;
case 0xffc0: // SOF0
case 0xffc2: // SOF2
raf.readUnsignedShort();
raf.readByte();
int height = raf.readUnsignedShort();
int width = raf.readUnsignedShort();
return new Dimension(width, height);
default:
throw new IllegalStateException("invalid jpg marker: " + Integer.toHexString(marker));
}
}
}
finally
{
raf.close();
}
}
}
This version works on InputStream
s too, which is required for loading your images through a classloader.
public static Dimension getImageDimension(File file) throws IOException
{
return ImageUtil.getImageDimension(new FileInputStream(file));
}
public static Dimension getImageDimension(InputStream in) throws IOException
{
DataInputStream dis = new DataInputStream(in);
try
{
int header = dis.readUnsignedShort();
if (header == 0x8950)
{
// PNG!
dis.readFully(new byte[(8 - 2) + 4 + 4]); // thanks Abuse
return new Dimension(dis.readInt(), dis.readInt());
}
if (header == 0xffd8)
{
// JPG!
// (see below)
}
else if (header == 0x424D)
{
// BMP!
dis.readFully(new byte[16]);
int w = dis.read() | (dis.read() << 8) | (dis.read() << 16) | (dis.read() << 24);
int h = dis.read() | (dis.read() << 8) | (dis.read() << 16) | (dis.read() << 24);
return new Dimension(w, h);
}
else if (header == (('G' << 8) | ('I' << 0))) // GIF
{
// GIF!
dis.readFully(new byte[4]);
int w = dis.read() | (dis.read() << 8);
int h = dis.read() | (dis.read() << 8);
return new Dimension(w, h);
}
else
{
throw new IllegalStateException("unexpected header: " + Integer.toHexString(header));
}
while (true)
{
int marker = dis.readUnsignedShort();
switch (marker)
{
case 0xffd8: // SOI
case 0xffd0: // RST0
case 0xffd1: // RST1
case 0xffd2: // RST2
case 0xffd3: // RST3
case 0xffd4: // RST4
case 0xffd5: // RST5
case 0xffd6: // RST6
case 0xffd7: // RST7
case 0xffd9: // EOI
break;
case 0xffdd: // DRI
dis.readUnsignedShort();
break;
case 0xffe0: // APP0
case 0xffe1: // APP1
case 0xffe2: // APP2
case 0xffe3: // APP3
case 0xffe4: // APP4
case 0xffe5: // APP5
case 0xffe6: // APP6
case 0xffe7: // APP7
case 0xffe8: // APP8
case 0xffe9: // APP9
case 0xffea: // APPa
case 0xffeb: // APPb
case 0xffec: // APPc
case 0xffed: // APPd
case 0xffee: // APPe
case 0xffef: // APPf
case 0xfffe: // COM
case 0xffdb: // DQT
case 0xffc4: // DHT
case 0xffda: // SOS
dis.readFully(new byte[dis.readUnsignedShort() - 2]);
break;
case 0xffc0: // SOF0
case 0xffc2: // SOF2
dis.readUnsignedShort();
dis.readByte();
int height = dis.readUnsignedShort();
int width = dis.readUnsignedShort();
return new Dimension(width, height);
default:
throw new IllegalStateException("invalid jpg marker: " + Integer.toHexString(marker));
}
}
}
finally
{
dis.close();
}
}
Wouldn’t it be nice if DataInput defined methods for reading primitives in LSB order as well as Java’s chosen convention of MSB.
It seems such a simple yet incredibly useful thing to have missed off; and given the choice of zip for code package distribution it must have been an I/O necessity from the very beginning! (DEFLATE stores block lengths in LSB order)
Yup. Judging by how the naming convetions at Sun are (LayoutManager => LayoutManager2, NIO => NIO2) we could expect DataInput2 any day now.
You could use a FileChannel. Then when you create your ByteBuffer for reading from the channel you set the order to ByteOrder.LITTLE_ENDIAN or ByteOrder.BIG_ENDIAN. Then from the ByteBuffer getFloat,getInt,getLong etc…
Yeah… but I’m working with InputStreams…
This is very useful, thanks. May I ask, what are you using it for?
I have this webservice, that must scale huge images (8K * 4K pixels) to thumbnails. I use this code to determine whether the webservice (which has a small heap to keep FullGC down to a minimum) can handle it, or it should launch a new JVM to do it – which might run out of memory, without taking down the webservice.
Sounds like what you really want is a progressive decoder for all of said image formats
Indeed. Me lazy.
BMP and PNG (RGB24/ARGB32) are trivial. JPG not so simple. JAI did not do it (dispite the promise), and I didn’t want to spend more time on it, so this was a quick hack, that will probably be used for a few years. :persecutioncomplex:
Feed the bytes into a ByteBuffer yourself. Then you can still take advantage of the ByteOrder of a ByteBuffer.
Interesting. Clever solution. How annoying that JAI api can’t do it.
And the resulting code would have been… better?
JAI does it for TIFFs - I’ve used it to process a 22k x 22k image without exceeding 64MB heap. Not sure how you were going about it, but what I used was
ImageReader ir=ImageIO.getImageReadersByFormatName("tiff").next(); // Hacky
ImageInputStream iis=new FileImageInputStream(new RandomAccessFile(file,"r"));
ir.setInput(iis);
for (y) {
for (x) {
ImageReadParam irp=new ImageReadParam();
irp.setSourceRegion(new Rectangle(x,y,w,h));
BufferedImage bi=ir.read(0,irp);
// Do stuff with bi
}
}
If Sun’s JPG reader doesn’t handle that then there must be a third party one which does, although finding and getting it approved for use will, of course, be much hassle.
Thanks, turned out I used JAI totally different, and appearantly that resulted in loading the whole image.
Anyway, I got it to work with JPG (trivial, with your code sample), but man… it’s slow!
the image is 8K*4K
ImageIO.read(new File("something.jpg")); // 3sec
the code below takes almost 40 (!!) seconds. Just the loading of the tiles, not even scaling! So I’m going to stick with my current (external JVM) solution.
ImageReader ir = ImageIO.getImageReadersByFormatName("jpeg").next();
ImageInputStream iis = new FileImageInputStream(new RandomAccessFile(file, "r"));
ir.setInput(iis);
int w = ir.getWidth(0);
int h = ir.getHeight(0);
int dim = 512;
for (int y = 0; y < h - dim; y += dim)
{
for (int x = 0; x < w - dim; x += dim)
{
ImageReadParam irp = new ImageReadParam();
irp.setSourceRegion(new Rectangle(x, y, dim, dim));
BufferedImage bi = ir.read(0, irp);
bi.flush();
}
}