package uk.co.mmscomputing.imageio.bmp; import java.io.IOException; import java.nio.ByteOrder; import java.util.Iterator; import java.util.ArrayList; import java.awt.image.*; import javax.imageio.*; import javax.imageio.spi.*; import javax.imageio.stream.*; import javax.imageio.metadata.*; public class BMPImageReader extends ImageReader implements BMPConstants{ private boolean gotHeader = false; private int size=0; private int offset=0; private int headerSize = 40; // InfoHeader Offset private int width; // Width private int height; // Height private int planes = 1; // BitPlanes on Target Device private int bitsPerPixel = 24; // Bits per Pixel, default = RGB private int compression = 0; // Bitmap Compression private int sizeImage; // Bitmap Image Size private int xPelsPerMeter = 2953; // Horiz Pixels Per Meter, default 75 dpi private int yPelsPerMeter = 2953; // Vert Pixels Per Meter, default 75 dpi private int clrUsed = 0; // Number of ColorMap Entries private int clrImportant = 0; // Number of important colours // private int[] colortable=null; private IndexColorModel icm=null; private int redMask=0; // 16bit, 32bit compression private int greenMask=0; private int blueMask=0; protected BMPImageReader(ImageReaderSpi originatingProvider){ super(originatingProvider); } public BufferedImage read(int imageIndex, ImageReadParam param)throws IOException{ checkIndex(imageIndex); return read((ImageInputStream)getInput()); } public int getHeight(int imageIndex)throws IOException{ checkIndex(imageIndex); readHeader((ImageInputStream)getInput()); return height; } public int getWidth(int imageIndex)throws IOException{ checkIndex(imageIndex); readHeader((ImageInputStream)getInput()); return width; } public Iterator getImageTypes(int imageIndex)throws IOException{ //System.err.println("BMPImageReader.getImageTypes "+imageIndex); checkIndex(imageIndex); readHeader((ImageInputStream)getInput()); java.util.List l = new ArrayList(); // l.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_BINARY)); // l.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_INDEXED)); // l.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB)); // l.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR)); // l.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB)); return l.iterator(); } public int getNumImages(boolean allowSearch)throws IOException{ return 1; } public IIOMetadata getImageMetadata(int imageIndex)throws IOException{ BMPMetadata md=new BMPMetadata(); md.setWidth(getWidth(imageIndex)); md.setHeight(getHeight(imageIndex)); // private int planes = 1; // BitPlanes on Target Device md.setBitsPerPixel(bitsPerPixel); md.setCompression(compression); md.setImageSize(sizeImage); md.setXPixelsPerMeter(xPelsPerMeter); md.setYPixelsPerMeter(yPelsPerMeter); md.setColorsUsed(clrUsed); md.setIndexColorModel(icm); md.setColorsImportant(clrImportant); md.setRedMask(redMask);md.setGreenMask(greenMask);md.setBlueMask(blueMask); return md; } public IIOMetadata getStreamMetadata() throws IOException{ return null; } private void checkIndex(int imageIndex) { if (imageIndex != 0) { throw new IndexOutOfBoundsException(getClass().getName()+".checkIndex:\n\tBad index in bmp image reader."); } } int getColorsInPalette(){ if(clrUsed!=0){ // as in info header return clrUsed; }else if(bitsPerPixel<16){ // 2, 16, 256 colours return 1<<bitsPerPixel; }else{ // 0 for 16,24,32 bits per pixel return 0; } } private void readHeader(ImageInputStream in)throws IOException{ if (gotHeader) { return; } gotHeader = true; in.setByteOrder(ByteOrder.LITTLE_ENDIAN); byte[] bm = new byte[2]; in.readFully(bm); if((bm[0]!=(byte)'B')||(bm[1]!=(byte)'M')){ throw new IOException("Invalid BMP 3.0 File."); } size=in.readInt(); in.readUnsignedShort(); in.readUnsignedShort(); offset=in.readInt(); headerSize = in.readInt(); if(headerSize == 12){ width = in.readUnsignedShort(); height = in.readUnsignedShort(); planes = in.readUnsignedShort(); bitsPerPixel = in.readUnsignedShort(); compression = 0; // BI_RGB xPelsPerMeter = 2953; // default 75 dpi yPelsPerMeter = 2953; clrUsed = 0; clrImportant = 0; }else{ width = in.readInt(); height = in.readInt(); planes = in.readUnsignedShort(); // always = 1; device independent bitsPerPixel = in.readUnsignedShort(); // 1,4,8,16,24,32 compression = in.readInt(); // BI_RGB, BI_RLE8, BI_RLE4, BI_BITFIELDS sizeImage = in.readInt(); xPelsPerMeter = in.readInt(); yPelsPerMeter = in.readInt(); clrUsed = in.readInt(); clrImportant = in.readInt(); } if(sizeImage == 0){ sizeImage = (((width*bitsPerPixel+31)>>5)<<2)*height; } int coloursUsed=getColorsInPalette(); if(coloursUsed > 0){ // colortable=new int[coloursUsed]; byte[] redCols =new byte[coloursUsed]; byte[] greenCols=new byte[coloursUsed]; byte[] blueCols =new byte[coloursUsed]; for (int i=0; i < coloursUsed; i++) { blueCols[i] =(byte)in.read(); // blue greenCols[i]=(byte)in.read(); // green redCols[i] =(byte)in.read(); // red in.read(); // reserved // colortable[i]=in.readInt(); } icm=new IndexColorModel(bitsPerPixel,coloursUsed,redCols,greenCols,blueCols); } if(compression==BI_BITFIELDS){ redMask=in.readInt(); greenMask=in.readInt(); blueMask=in.readInt(); }else if(bitsPerPixel==16){ redMask =0x00007C00; greenMask=0x000003E0; blueMask =0x0000001F; }else if(bitsPerPixel==32){ redMask =0x00FF0000; greenMask=0x0000FF00; blueMask =0x000000FF; } int skip=offset-(14+headerSize+coloursUsed*4+((compression==BI_BITFIELDS)?12:0)); if(skip > 0){ in.skipBytes(skip); } if((compression==BI_RLE4)||(compression==BI_RLE8)){ throw new IOException(getClass().getName()+".readHeader:\n\tCannot read Run Length Encoded BMP Files."); } } ///* private BufferedImage read(ImageInputStream in)throws IOException{ readHeader(in); switch(bitsPerPixel){ case 1: return unpack01(in, icm); case 4: return unpack04(in, icm); case 8: return unpack08(in, icm); case 16: return unpack16(in); case 24: return unpack24(in); case 32: return unpack32(in); default: throw new IOException(getClass().getName()+".read:\n Cannot read BMP with depth="+(1<<bitsPerPixel)); } } void copyImage(byte[] imgBuf,int bytesPerLine,ImageInputStream in)throws IOException{ int padding=(((width*bitsPerPixel+31)>>5)<<2)-bytesPerLine; for(int pos=(height-1)*bytesPerLine;pos>=0;pos-=bytesPerLine){ in.read(imgBuf,pos,bytesPerLine); in.skipBytes(padding); } } int getColorValue(int pixel,int mask){ int color=pixel&mask; while((mask&0x80000000)==0){ color<<=1; mask<<=1; } return (color>>24)&0x000000FF; } BufferedImage unpack32(ImageInputStream in)throws IOException{ BufferedImage image =new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB); DataBufferInt buffer =(DataBufferInt)image.getRaster().getDataBuffer(); int[] imgBuf =buffer.getData(); if((redMask==0x00FF0000)&&(greenMask==0x0000FF00)&&(blueMask==0x000000FF)){ for(int pos=(height-1)*width;pos>=0;pos-=width){ in.readFully(imgBuf,pos,width); } }else{ int color, r, g, b; for(int pos=(height-1)*width;pos>=0;pos-=width){ for(int x=0;x<width;x++){ color=in.readInt(); r=getColorValue(color,redMask); g=getColorValue(color,greenMask); b=getColorValue(color,blueMask); imgBuf[pos+x]=(r<<16)|(g<<8)|b; } } } return image; } BufferedImage unpack24(ImageInputStream in)throws IOException{ BufferedImage image=new BufferedImage(width,height,BufferedImage.TYPE_3BYTE_BGR); WritableRaster raster=image.getRaster(); DataBufferByte buffer=(DataBufferByte)raster.getDataBuffer(); copyImage(buffer.getData(),width*3,in); return image; } BufferedImage unpack16(ImageInputStream in)throws IOException{ BufferedImage image=new BufferedImage(width,height,BufferedImage.TYPE_USHORT_555_RGB); DataBufferUShort buffer=(DataBufferUShort)image.getRaster().getDataBuffer(); short[] imgBuf=buffer.getData(); int padding=(((width*bitsPerPixel+31)>>5)<<2)-(width<<1); if((redMask==0x00007C00)&&(greenMask==0x000003E0)&&(blueMask==0x0000001F)){ for(int pos=(height-1)*width;pos>=0;pos-=width){ in.readFully(imgBuf,pos,width); in.skipBytes(padding); } }else{ int color, r, g, b; for(int pos=(height-1)*width;pos>=0;pos-=width){ for(int x=0;x<width;x++){ color=in.readShort(); r=getColorValue(color,redMask) &0x001F; g=getColorValue(color,greenMask)&0x001F; b=getColorValue(color,blueMask) &0x001F; imgBuf[pos+x]=(short)((r<<10)|(g<<5)|b); } in.skipBytes(padding); } } return image; } BufferedImage unpack08(ImageInputStream in, IndexColorModel icm)throws IOException{ BufferedImage image=new BufferedImage(width,height,BufferedImage.TYPE_BYTE_INDEXED,icm); WritableRaster raster=image.getRaster(); DataBufferByte buffer=(DataBufferByte)raster.getDataBuffer(); copyImage(buffer.getData(),width,in); return image; } BufferedImage unpack04(ImageInputStream in, IndexColorModel icm)throws IOException{ BufferedImage image=new BufferedImage(width,height,BufferedImage.TYPE_BYTE_BINARY,icm); WritableRaster raster=image.getRaster(); DataBufferByte buffer=(DataBufferByte)raster.getDataBuffer(); copyImage(buffer.getData(),(width+1)>>1,in); return image; } BufferedImage unpack01(ImageInputStream in, IndexColorModel icm)throws IOException{ BufferedImage image=new BufferedImage(width,height,BufferedImage.TYPE_BYTE_BINARY,icm); WritableRaster raster=image.getRaster(); DataBufferByte buffer=(DataBufferByte)raster.getDataBuffer(); copyImage(buffer.getData(),(width+7)>>3,in); return image; } //*/ /* private BufferedImage read(ImageInputStream in)throws IOException{ readHeader(in); int scanlineSize = ((width*bitsPerPixel+31)/32)*4; byte[] rawData = new byte[scanlineSize*height]; in.readFully(rawData); if(rawData != null){ switch(bitsPerPixel){ case 1: return unpack01(rawData, colortable); case 4: return unpack04(rawData, colortable); case 8: return unpack08(rawData, colortable); case 24: return unpack24(rawData); default: throw new IOException(getClass().getName()+".read:\n Cannot read BMP with depth="+(1<<bitsPerPixel)); } } return null; } BufferedImage unpack24(byte[] data){ BufferedImage image=new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB); int padding = (((width*3)&3)!=0)?4-((width*3)&3):0; int x,y,k=0,len=data.length-3; for(y=height-1;y>=0;y--){ for(x=0;(x<width)&&(k<len);x++){ image.setRGB( x,y,0xFF000000 | (((int)(data[k++])) & 0xFF) | // blue (((int)(data[k++])) & 0xFF) << 8 | // green (((int)(data[k++])) & 0xFF) << 16 // red ); } k+=padding; } return image; } BufferedImage unpack08(byte[] data, int[] colortable){ BufferedImage image=new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB); int padding = ((width&3)!=0)?4-(width&3):0; int x,y,k=0,len=data.length; for(y=height-1;y>=0;y--){ for(x=0;(x<width)&&(k<len);x++){ image.setRGB(x,y,colortable[data[k++]&0x00FF]); } k+=padding; } return image; } BufferedImage unpack04(byte[] data, int[] colortable) { BufferedImage image=new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB); int bytesPerLine = (width+1)>>1; int padding = ((bytesPerLine&3)!=0)?4-(bytesPerLine&3):0; int x,xx,y,k=0,len=data.length;byte b; for(y=height-1;y>=0;y--){ xx=0; for(x=0;(x<bytesPerLine)&&(k<len);x++){ b=data[k++]; if(xx<width){ // last byte in line may have padding bits image.setRGB(xx++,y,colortable[(b>>4)&0x0F]); if(xx<width){ // last byte in line may have padding bits image.setRGB(xx++,y,colortable[b&0x0F]); } } // else ignore padding bytes } k+=padding; } return image; } BufferedImage unpack01(byte[] data, int[] colortable) { BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB); int bytesPerLine = (width+7)>>3; int padding = ((bytesPerLine&3)!=0)?4-(bytesPerLine&3):0; int x,xx,y,k=0,bit,len=data.length;byte b; for(y=height-1;y>=0;y--){ xx=0; for(x=0;(x<bytesPerLine)&&(k<len);x++){ b=data[k++]; for(bit=0;(bit<8)&&(xx<width);bit++){ image.setRGB(xx++,y,colortable[(b>>(7-bit))&0x01]); } } k+=padding; } return image; } */ }