package uk.co.mmscomputing.imageio.tiff; import java.io.*; import java.util.*; import java.nio.ByteOrder; import java.awt.*; import java.awt.image.*; import java.awt.color.*; import javax.imageio.*; import javax.imageio.spi.*; import javax.imageio.stream.*; import javax.imageio.metadata.*; import uk.co.mmscomputing.io.BitSwapOutputStream; import uk.co.mmscomputing.io.ModHuffmanOutputStream; import uk.co.mmscomputing.io.RLEOutputStream; import uk.co.mmscomputing.imageio.jpeg.*; public class TIFFImageWriter extends ImageWriter implements TIFFConstants{ private long ifdptr=0; protected TIFFImageWriter(ImageWriterSpi originatingProvider){ super(originatingProvider); } public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType,ImageWriteParam param){ // return new TIFFMetadata(); return null; } public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param){ // if(inData instanceof TIFFMetadata){ // We only understand our own metadata // return inData; // } return null; } public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param){ return null; } public IIOMetadata convertStreamMetadata(IIOMetadata inData,ImageWriteParam param){ return null; } public ImageWriteParam getDefaultWriteParam(){ return new TIFFImageWriteParam(getLocale()); } public boolean canInsertImage(int imageIndex)throws IOException{ return (imageIndex==0); // use sequence for more than one picture } public void write(IIOMetadata streamMetadata,IIOImage img,ImageWriteParam param)throws IOException{ prepareWriteSequence(streamMetadata); writeToSequence(img,param); // just one page ! endWriteSequence(); } public boolean canWriteSequence(){ return true; } public void prepareWriteSequence(IIOMetadata streamMetadata)throws IOException{ ImageOutputStream out=(ImageOutputStream)getOutput(); out.setByteOrder(ByteOrder.LITTLE_ENDIAN); out.writeShort(0x00004949); // 0: II = intel = little endian out.writeShort(42); // 2: version, magic value ifdptr=out.getStreamPosition(); // save position (4): write here later the offset to first ifd (ifd linked list) out.writeInt(0); // 4: offset first Image File Directory // 8: header size } public void writeToSequence(IIOImage img,ImageWriteParam param)throws IOException{ ImageOutputStream out=(ImageOutputStream)getOutput(); if(!(img.getRenderedImage() instanceof BufferedImage)){ throw new IOException(getClass().getName()+"writeToSequence:\n\tCan only write BufferedImage objects"); } BufferedImage image=(BufferedImage)img.getRenderedImage(); /* // Attempt to convert metadata, if present IIOMetadata imd = img.getMetadata(); TIFFMetadata metadata = null; if(imd!=null){ ImageTypeSpecifier type=ImageTypeSpecifier.createFromRenderedImage(image); metadata=(TIFFMetadata)convertImageMetadata(imd,type,null); } // Output metadata if present if(metadata != null){ Iterator keywordIter = metadata.keywords.iterator(); Iterator valueIter = metadata.values.iterator(); while(keywordIter.hasNext()){ String keyword = (String)keywordIter.next(); String value = (String)valueIter.next(); System.out.println("9\bKEYWORD: "+keyword); System.out.println("9\bVALUE: "+value); } } */ IFD ifd; int pmi=RGB,comp=compNone,tiffComp=NOCOMPRESSION; TIFFImageWriteParam p=null; if((param!=null)&&(param instanceof TIFFImageWriteParam)){ p=(TIFFImageWriteParam)param; pmi =p.getPhotometricInterpretation(); if(p.getCompressionType().equals("none")){ comp=compNone; tiffComp=NOCOMPRESSION; }else if(p.getCompressionType().equals("mh")){ comp=compBaselineMH; tiffComp=CCITTGROUP3MODHUFFMAN; }else if(p.getCompressionType().equals("t4mh")){ comp=compT4MH; tiffComp=CCITTFAXT4; }else if(p.getCompressionType().equals("t4mr")){ comp=compT4MR; tiffComp=CCITTFAXT4; }else if(p.getCompressionType().equals("t6mmr")){ comp=compT6MMR; tiffComp=CCITTFAXT6; }else if(p.getCompressionType().equals("packbits")){ comp=compPackBits; tiffComp=PACKBITS; }else if(p.getCompressionType().equals("lzw")){ comp=compLZW; tiffComp=LZW; }else if(p.getCompressionType().equals("jpeg")){ comp=compJPEG; tiffComp=JPEG; } // System.out.println("comp = "+p.getCompressionType()+" "+comp); } switch(pmi){ case WhiteIsZero: switch(comp){ case compBaselineMH: ifd=writeBModHufImage(out,image,p);break; case compT4MH: case compT4MR: case compT6MMR: ifd=TIFFClassFFactory.writeImage(out,image,comp,p);break; default: ifd=writeRGBImage(out,image,NOCOMPRESSION,p);break; // write image data as uncompressed rgb data. } break; case BlackIsZero: switch(comp){ default: ifd=writeGrayImage(out,image,tiffComp,p);break; } break; case RGB: // write image data as uncompressed rgb data. switch(comp){ default: ifd=writeRGBImage(out,image,tiffComp,p);break; } break; case CMYK: switch(comp){ default: ifd=writeCMYKImage(out,image,p);break; } break; case YCbCr: switch(comp){ default: ifd=writeYCbCrImage(out,image,tiffComp,p);break; } break; default: ifd=writeRGBImage(out,image,NOCOMPRESSION,p);break; // write image data as uncompressed rgb data. } ifdptr=ifd.write(out,ifdptr); // write ifd contents, entries and set ifd linked list pointer } public void endWriteSequence()throws IOException{ ImageOutputStream out=(ImageOutputStream)getOutput(); } /* Photometric Interpretation 0,1 : baseline TIFF bilevel images or TIFF class B images required fields bilevel images imageWidth,imageLength,Compression,PhotometricInterpretation, StripOffsets,RowsPerStrip,StripByteCounts, XResolution,YResolution,ResolutionUnit No compression, 1 Dimensional Modified Huffman, PackBits */ private IFD writeBModHufImage(ImageOutputStream out,BufferedImage image,TIFFImageWriteParam param)throws IOException{ try{ int width=image.getWidth(); int height=image.getHeight(); IFD ifd=new IFD(); // entries need to be in tag order ! ifd.add(new DEFactory.NewSubfileTypeDE(2)); // 254 single page of multipage file ifd.add(new DEFactory.ImageWidthDE(width)); // 256 ifd.add(new DEFactory.ImageLengthDE(height)); // 257 ifd.add(new DEFactory.CompressionDE(CCITTGROUP3MODHUFFMAN)); // 259 ifd.add(new DEFactory.PhotometricInterpretationDE(WhiteIsZero)); // 262 int maxrps,maxstripes; // max RowsPerStrip if((1<<13)<=width){ maxrps=1;maxstripes=height; // one row per stripe }else{ maxrps=(1<<13)/width; maxstripes=(height+maxrps-1)/maxrps; } DEFactory.StripOffsetsDE offsets = new DEFactory.StripOffsetsDE(maxstripes); ifd.add(offsets); // 273 ifd.add(new DEFactory.RowsPerStripDE(maxrps)); // 278 DEFactory.StripByteCountsDE counts=new DEFactory.StripByteCountsDE(maxstripes); ifd.add(counts); // 279 if(param==null){ ifd.add(new DEFactory.XResolutionDE(72.0)); // 282 ifd.add(new DEFactory.YResolutionDE(72.0)); // 283 }else{ ifd.add(new DEFactory.XResolutionDE(param.getXResolution())); // 282 ifd.add(new DEFactory.YResolutionDE(param.getYResolution())); // 283 } ifd.add(new DEFactory.ResolutionUnitDE(Inch)); // 296 int index=0; for(int y=0;y>16)&0x000000FF); os.write((c>> 8)&0x000000FF); os.write( c &0x000000FF); } } os.close(); // jpeg: EOI marker byte[] data=baos.toByteArray(); counts.setCount(index,data.length); // update ifd strip counter array offsets.setOffset(index,out.getStreamPosition()); // update ifd image data offset array out.write(data); // write to image stream baos.reset(); index++; } return ifd; }catch(Exception e){ e.printStackTrace(); throw new IOException(getClass().getName()+".writeRGBImage:\n\t"+e.getMessage()); } } /* Photometric Interpretation 5 : TIFF CMYK images required fields imageWidth,imageLength,Compression,PhotometricInterpretation, StripOffsets,RowsPerStrip,StripByteCounts, XResolution,YResolution,ResolutionUnit, BitsPerSample SamplesPerPixel */ private IFD writeCMYKImage(ImageOutputStream out,BufferedImage image,TIFFImageWriteParam param)throws IOException{ try{ int width=image.getWidth(); int height=image.getHeight(); IFD ifd=new IFD(); // entries need to be in tag order ! ifd.add(new DEFactory.NewSubfileTypeDE(2)); // 254 single page of multipage file ifd.add(new DEFactory.ImageWidthDE(width)); // 256 ifd.add(new DEFactory.ImageLengthDE(height)); // 257 DEFactory.BitsPerSampleDE bpss = new DEFactory.BitsPerSampleDE(4); bpss.setBitsPerSample(0,8); // cyan bpss.setBitsPerSample(1,8); // magneta bpss.setBitsPerSample(2,8); // yellow bpss.setBitsPerSample(3,8); // key (black) ifd.add(bpss); // 258 ifd.add(new DEFactory.CompressionDE(NOCOMPRESSION)); // 259 ifd.add(new DEFactory.PhotometricInterpretationDE(CMYK)); // 262 int maxrps,maxstripes; // max RowsPerStrip if((1<<13)<=width){ maxrps=1;maxstripes=height; // one row per strip }else{ maxrps=(1<<13)/width; maxstripes=(height+maxrps-1)/maxrps; } DEFactory.StripOffsetsDE offsets = new DEFactory.StripOffsetsDE(maxstripes); ifd.add(offsets); // 273 ifd.add(new DEFactory.SamplesPerPixelDE(4)); // 277 ifd.add(new DEFactory.RowsPerStripDE(maxrps)); // 278 DEFactory.StripByteCountsDE counts=new DEFactory.StripByteCountsDE(maxstripes); ifd.add(counts); // 279 if(param==null){ ifd.add(new DEFactory.XResolutionDE(72.0)); // 282 ifd.add(new DEFactory.YResolutionDE(72.0)); // 283 }else{ ifd.add(new DEFactory.XResolutionDE(param.getXResolution())); // 282 ifd.add(new DEFactory.YResolutionDE(param.getYResolution())); // 283 } ifd.add(new DEFactory.ResolutionUnitDE(Inch)); // 296 int index=0; for(int y=0;y>16)&0x00FF; int G=(c>> 8)&0x00FF; int B=(c )&0x00FF; if((R==255)&&(G==255)&&(B==255)){ baos.write(0); baos.write(0); baos.write(0); baos.write(0); }else{ double C=1.0-R/255.0; double M=1.0-G/255.0; double Y=1.0-B/255.0; double K=C;if(M>4)&0x0F; int ssv= ss &0x0F; if(ssh>16)&0x000000FF); os.write((c>> 8)&0x000000FF); os.write( c &0x000000FF); x++; } while(x>16)&0x000000FF); os.write((c>> 8)&0x000000FF); os.write( c &0x000000FF); x++; } } os.close(); byte[] data=baos.toByteArray(); counts.setCount(index,data.length); // update ifd strip counter array offsets.setOffset(index,out.getStreamPosition()); // update ifd image data offset array out.write(data); // write to image stream baos.reset(); index++; } return ifd; }catch(Exception e){ e.printStackTrace(); throw new IOException(getClass().getName()+".writeYCbCrImage:\n\t"+e.getMessage()); } } private BufferedImage convert(BufferedImage image, int imageType){ if(image.getType()==imageType){return image;} int w=image.getWidth(); int h=image.getHeight(); BufferedImage newImg=new BufferedImage(w,h,imageType); ColorSpace srcSpace=image.getColorModel().getColorSpace(); ColorSpace newSpace=newImg.getColorModel().getColorSpace(); ColorConvertOp convert=new ColorConvertOp(srcSpace,newSpace,null); convert.filter(image,newImg); return newImg; } }