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 java.awt.color.*;
import javax.imageio.*;
import javax.imageio.spi.*;
import javax.imageio.stream.*;
import javax.imageio.metadata.*;
public class BMPImageWriter extends ImageWriter implements BMPConstants{
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 bitCount = 24; /* Bits per Pixel, default = RGB */
private int compression = BI_RGB; /* 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;
protected BMPImageWriter(ImageWriterSpi originatingProvider){
super(originatingProvider);
}
public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param){
return null;
}
public IIOMetadata convertStreamMetadata(IIOMetadata inData,ImageWriteParam param){
return null;
}
public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType,ImageWriteParam param){
return null;
}
public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param){
return null;
}
public boolean canInsertImage(int imageIndex)throws IOException{
super.canInsertImage(imageIndex);
return (imageIndex==0); // can deal only with one image
}
int getColorsInPalette(){
if(clrUsed!=0){ // as in info header
return clrUsed;
}else if(bitCount<16){ // 2, 16, 256 colours
return 1<<bitCount;
}else{ // 0 for 16,24,32 bits per pixel
return 0;
}
}
int getHeaderSize(){
int size=14+40; // fileheader + infoheader
size+=getColorsInPalette()*4; // either colour table
size+=(compression==BI_BITFIELDS)?12:0; // or colour masks or none of them
return size;
}
private void writeHeader(ImageOutputStream out,BufferedImage image,IIOMetadata md)throws IOException{
width=image.getWidth();
height=image.getHeight();
if(md!=null){
if(md instanceof BMPMetadata){
BMPMetadata bmd=(BMPMetadata)md;
xPelsPerMeter=bmd.getXPixelsPerMeter();
yPelsPerMeter=bmd.getYPixelsPerMeter();
}
}
offset=getHeaderSize(); // file header
sizeImage=((((width*bitCount)+31)>>5)<<2)*height;
size=offset+sizeImage;
out.setByteOrder(ByteOrder.LITTLE_ENDIAN);
out.writeByte('B'); // file header
out.writeByte('M');
out.writeInt(size); // size of
out.writeShort(0); // reserved
out.writeShort(0); // reserved
out.writeInt(offset);
out.writeInt(headerSize); // info header
out.writeInt(width);
out.writeInt(height);
out.writeShort(planes);
out.writeShort(bitCount);
out.writeInt(compression);
out.writeInt(sizeImage);
out.writeInt(xPelsPerMeter);
out.writeInt(yPelsPerMeter);
out.writeInt(clrUsed);
out.writeInt(clrImportant);
}
public void write(ImageOutputStream out,BufferedImage image,IIOMetadata md)throws IOException{
writeHeader(out,image,md);
int rawData[] = new int[width*height];
PixelGrabber grabber = new PixelGrabber(image, 0, 0, width, height, rawData, 0, width);
try{
grabber.grabPixels();
}catch(InterruptedException e){
throw new IOException(getClass().getName()+".write : Couldn't grab pixels from image !");
}
ColorModel model = grabber.getColorModel();
int x=0,y=0,i=0,j=0,k=0;
int w=(((width*bitCount)+31)>>5)<<2; // dword align width
int h=height-1;
byte[] bitmap = new byte[w*height];
for (y=0;y<height;y++) {
i=(h-y)*width;
j=y*w;
for (x=0;x<width;x++) {
k=i+x;
bitmap[j++]=(byte)((model.getBlue(rawData[k]))&0xFF);
bitmap[j++]=(byte)((model.getGreen(rawData[k]))&0xFF);
bitmap[j++]=(byte)((model.getRed(rawData[k]))&0xFF);
}
}
out.write(bitmap);
}
void writeColorTable(ImageOutputStream out,IndexColorModel icm)throws IOException{
byte[] reds=new byte[clrUsed],greens=new byte[clrUsed],blues=new byte[clrUsed];
icm.getReds(reds);icm.getGreens(greens);icm.getBlues(blues);
for(int i=0;i<reds.length;i++){
out.write(blues[i]);out.write(greens[i]);out.write(reds[i]);out.write(0);
}
}
void writeImage(ImageOutputStream out,byte[] buffer,int bytesPerLine)throws IOException{
int padding=((((width*bitCount)+31)>>5)<<2)-bytesPerLine;
for(int pos=(height-1)*bytesPerLine;pos>=0;pos-=bytesPerLine){
out.write(buffer,pos,bytesPerLine);
for(int p=0;p<padding;p++){out.write(0);}
}
}
private void write01bit(ImageOutputStream out,BufferedImage image,IIOMetadata md,IndexColorModel icm)throws IOException{
System.out.println("Save as 1 bit BMP");
bitCount=1; // Save as 1 bit BMP
compression=BI_RGB; // no colour masks
clrUsed=icm.getMapSize(); // two colour table
writeHeader(out,image,md);
writeColorTable(out,icm);
WritableRaster raster=image.getRaster();
DataBufferByte buffer=(DataBufferByte)raster.getDataBuffer();
writeImage(out,buffer.getData(),(width+7)>>3);
}
private void write04bit(ImageOutputStream out,BufferedImage image,IIOMetadata md,IndexColorModel icm)throws IOException{
System.out.println("Save as 4 bit BMP");
bitCount=4; // Save as 4 bit BMP
compression=BI_RGB; // no colour masks
clrUsed=icm.getMapSize(); // max 16 colour table
writeHeader(out,image,md);
writeColorTable(out,icm);
WritableRaster raster=image.getRaster();
DataBufferByte buffer=(DataBufferByte)raster.getDataBuffer();
writeImage(out,buffer.getData(),(width+1)>>1);
}
private void write08bit(ImageOutputStream out,BufferedImage image,IIOMetadata md,IndexColorModel icm)throws IOException{
System.out.println("Save as 8 bit BMP");
bitCount=8; // Save as 8 bit BMP
compression=BI_RGB; // no colour masks
clrUsed=icm.getMapSize(); // max 256 colour table
writeHeader(out,image,md);
writeColorTable(out,icm);
WritableRaster raster=image.getRaster();
DataBufferByte buffer=(DataBufferByte)raster.getDataBuffer();
writeImage(out,buffer.getData(),width);
}
private void write16bit(ImageOutputStream out,BufferedImage image,IIOMetadata md)throws IOException{
System.out.println("Save as 8 bit BMP");
bitCount=16; // Save as 16 bit BMP
compression=BI_BITFIELDS; // colour masks
clrUsed=0; // no colour table
writeHeader(out,image,md);
out.writeInt(0x00007C00); // default colour masks
out.writeInt(0x000003E0);
out.writeInt(0x0000001F);
WritableRaster raster=image.getRaster();
DataBufferUShort buffer=(DataBufferUShort)raster.getDataBuffer();
short[] imgBuf=buffer.getData();
int padding=(((width*bitCount+31)>>5)<<2)-(width<<1);
for(int pos=(height-1)*width;pos>=0;pos-=width){
out.writeShorts(imgBuf,pos,width);
if(padding!=0){out.write(0);out.write(0);}
}
}
private void write24bit(ImageOutputStream out,BufferedImage image,IIOMetadata md)throws IOException{
bitCount=24; // Save as 24 bit BMP
compression=BI_RGB; // no colour masks
clrUsed=0; // no colour table
writeHeader(out,image,md);
WritableRaster raster=image.getRaster();
DataBufferByte buffer=(DataBufferByte)raster.getDataBuffer();
writeImage(out,buffer.getData(),width*3);
}
private void write32bit(ImageOutputStream out,BufferedImage image,IIOMetadata md)throws IOException{
bitCount=32; // Save as 32 bit BMP
compression=BI_BITFIELDS; // colour masks
clrUsed=0; // no colour table
writeHeader(out,image,md);
out.writeInt(0x00FF0000); // default colour masks
out.writeInt(0x0000FF00);
out.writeInt(0x000000FF);
WritableRaster raster=image.getRaster();
DataBufferInt buffer=(DataBufferInt)raster.getDataBuffer();
int[] imgBuf=buffer.getData();
for(int pos=(height-1)*width;pos>=0;pos-=width){
out.writeInts(imgBuf,pos,width);
}
}
public void write(IIOMetadata streamMetadata,IIOImage img,ImageWriteParam param)throws IOException{
ImageOutputStream out=(ImageOutputStream)getOutput();
if(!(img.getRenderedImage() instanceof BufferedImage)){
throw new IOException(getClass().getName()+"write:\n\tCan only write BufferedImage objects");
}
IIOMetadata md = img.getMetadata();
BufferedImage image = (BufferedImage)img.getRenderedImage();
ColorModel cm = image.getColorModel();
if(cm instanceof IndexColorModel){
IndexColorModel icm =(IndexColorModel)cm;
if((image.getType()==BufferedImage.TYPE_BYTE_BINARY)&&(cm.getPixelSize()==1)){
write01bit(out,image,md,icm);
}else if((image.getType()==BufferedImage.TYPE_BYTE_BINARY)&&(cm.getPixelSize()==4)){
write04bit(out,image,md,icm);
}else if((image.getType()==BufferedImage.TYPE_BYTE_INDEXED)&&(cm.getPixelSize()<=8)){
write08bit(out,image,md,icm);
}else{
write(out,image,md);
}
}else{
switch(image.getType()){
case BufferedImage.TYPE_USHORT_555_RGB: write16bit(out,image,md);break;
case BufferedImage.TYPE_3BYTE_BGR: write24bit(out,image,md);break;
case BufferedImage.TYPE_INT_RGB: write32bit(out,image,md);break;
default: write(out,image,md);break;
}
}
}
public ImageWriteParam getDefaultWriteParam(){
return new ImageWriteParam(getLocale());
}
}