package uk.co.mmscomputing.imageio.tiff;
import java.io.*;
import java.awt.image.*;
import javax.imageio.stream.*;
import uk.co.mmscomputing.io.BitSwapInputStream;
import uk.co.mmscomputing.io.RLEBitInputStream;
import uk.co.mmscomputing.io.ModHuffmanInputStream;
import uk.co.mmscomputing.io.ModREADInputStream;
import uk.co.mmscomputing.io.ModModREADInputStream;
import uk.co.mmscomputing.io.RLEBit1OutputStream;
import uk.co.mmscomputing.io.ModHuffmanOutputStream;
import uk.co.mmscomputing.io.ModREADOutputStream;
import uk.co.mmscomputing.io.ModModREADOutputStream;
/*
Photometric Interpretation 0,1 : F Profile for Facsimile or TIFF class F images
required fields bilevel images
imageWidth,imageLength,Compression,PhotometricInterpretation,
StripOffsets,RowsPerStrip,StripByteCounts,
XResolution,YResolution,ResolutionUnit
1] CCITT Group 3 T.4 1-Dimensional MH Modified Huffman
2] CCITT Group 3 T.4 2-Dimensional MR Modified READ (Relative Element Address Designate)
3] CCITT Group 4 T.6 2-Dimensional MMR Modified Modified READ (Relative Element Address Designate)
RFC 2306
*/
public class TIFFClassFFactory implements TIFFConstants{
static final String cn="uk.co.mmscomputing.imageio.tiff.TIFFClassFFactory";
static public IFD writeImage(ImageOutputStream out,BufferedImage image,int mode,TIFFImageWriteParam param)throws IOException{
// todo: need to supply resolutions and pagenumbers
try{
ColorModel cm =image.getColorModel();
if((image.getType()!=BufferedImage.TYPE_BYTE_BINARY)||(cm.getPixelSize()!=1)){
throw new IOException(cn+".writeImage:\n\tPlease convert image to black and white picture [TYPE_BYTE_BINARY,1 bps]");
}
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 bps=new DEFactory.BitsPerSampleDE(1);
bps.setBitsPerSample(0,1); // one bit per sample
ifd.add(bps); // 258
switch(mode){
case compT4MH:case compT4MR:
ifd.add(new DEFactory.CompressionDE(CCITTFAXT4));break; // 259
case compT6MMR:
ifd.add(new DEFactory.CompressionDE(CCITTFAXT6));break; // 259
}
ifd.add(new DEFactory.PhotometricInterpretationDE(WhiteIsZero)); // 262
ifd.add(new DEFactory.FillOrderDE(2)); // 266 2 = Least Significant Bit first
DEFactory.StripOffsetsDE offsets = new DEFactory.StripOffsetsDE(1);
ifd.add(offsets); // 273
ifd.add(new DEFactory.OrientationDE(1)); // 274 0,0 = top/left
ifd.add(new DEFactory.SamplesPerPixelDE(1)); // 277
ifd.add(new DEFactory.RowsPerStripDE(height)); // 278
DEFactory.StripByteCountsDE counts=new DEFactory.StripByteCountsDE(1);
ifd.add(counts); // 279
if(param==null){
ifd.add(new DEFactory.XResolutionDE(204.0)); // 282
ifd.add(new DEFactory.YResolutionDE(196.0)); // 283
}else{
ifd.add(new DEFactory.XResolutionDE(param.getXResolution())); // 282
ifd.add(new DEFactory.YResolutionDE(param.getYResolution())); // 283
}
switch(mode){
case compT4MH: ifd.add(new DEFactory.T4OptionsDE(4));break; // 292 MH; byte aligned EOLs
case compT4MR: ifd.add(new DEFactory.T4OptionsDE(5));break; // 292 MR; byte aligned EOLs
case compT6MMR:ifd.add(new DEFactory.T6OptionsDE(0));break; // 293 MMR
}
ifd.add(new DEFactory.ResolutionUnitDE(Inch)); // 296
// ifd.add(new DEFactory.PageNumberDE(pagenumber,totalnumber)); // 297
// Each strip: count run length
// encode into MH (modified huffman) codes
// save into byte array
// write to image file
WritableRaster raster=image.getRaster();
DataBufferByte buffer=(DataBufferByte)raster.getDataBuffer();
byte[] imgdata=(byte[])buffer.getData();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ModHuffmanOutputStream mhos;
switch(mode){
case compT4MH: mhos = new ModHuffmanOutputStream(baos);break;
case compT4MR: mhos = new ModREADOutputStream(baos,width);break;
case compT6MMR:mhos = new ModModREADOutputStream(baos,width);break;
default: throw new IOException(cn+".writeImage: Internal Error: Unknown compression = "+mode); // Should never get here.
}
copyout(mhos,imgdata,width,height);
byte[] data=baos.toByteArray();
counts.setCount(0,data.length); // update ifd strip counter array
offsets.setOffset(0,out.getStreamPosition()); // update ifd image data offset array
out.write(data); // write to image stream
return ifd;
}catch(Exception e){
e.printStackTrace();
throw new IOException(cn+".writeImage:\n\t"+e.getMessage());
}
}
static private void copyout(ModHuffmanOutputStream mhos,byte[] imgdata,int width,int height)throws IOException{
RLEBit1OutputStream rlos = new RLEBit1OutputStream(mhos);
int len=width>>3; // eight pixel per byte
int end=8-(width&0x07); // how many bits of last byte represent image data
int off=0;
if(end==8){ // image row ends at byte boundary
for(int y=0;y<height;y++){
rlos.setStartCodeWord(0x0001); // white run first; White is Zero: 1s in image => 0s in compressed data
mhos.writeEOL(); // T.6: we don't write EOL code, we just set up buffers here
rlos.write(imgdata,off,len);
rlos.flush();
off+=len;
}
}else{
for(int y=0;y<height;y++){
rlos.setStartCodeWord(0x0001); // white run first; White is Zero: 1s in image => 0s in compressed data
mhos.writeEOL(); // T.6: we don't write EOL code, we just set up buffers here
rlos.write(imgdata,off,len);
rlos.writeBits(imgdata[off+len],7,end); // write end of line pixel
rlos.flush();
off+=len+1;
}
}
if(mhos instanceof ModModREADOutputStream){ // T.6: write EOFB after every strip
((ModModREADOutputStream)mhos).writeEOFB();
}
rlos.close();
}
static public BufferedImage readImage(ImageInputStream in,IFD ifd)throws IOException{
BufferedImage image=null;
try{
int width =ifd.getWidth();
int height =ifd.getHeight();
image=new BufferedImage(width,height,BufferedImage.TYPE_BYTE_BINARY);
WritableRaster raster=image.getRaster();
DataBufferByte buffer=(DataBufferByte)raster.getDataBuffer();
byte[] imgdata=(byte[])buffer.getData();
long[] offsets =ifd.getStripOffsets();
long[] counts =ifd.getStripByteCounts();
int h=0,off=0;
for(int i=0;i<offsets.length;i++){
/*
For each strip:
get chunk of data MH || MR || MMR code
[swap bits]
evaluate runlength
convert into runs
copy to image buffer
*/
in.seek(offsets[i]);
byte[] data=new byte[(int)counts[i]];
in.read(data);
ByteArrayInputStream bais=new ByteArrayInputStream(data);
ModHuffmanInputStream mhis;
if(ifd.getFillOrder()==LowColHighBit){ // baseline tiff default
mhis=getDecoder(new BitSwapInputStream(bais),ifd);
}else{ // fax devices usually code low pixel col low bit positions
mhis=getDecoder(bais,ifd);
}
off=copyin(imgdata,off,mhis,width,ifd.getPhotometricInterpretation()!=WhiteIsZero);
}
return image;
}catch(Exception e){
System.out.println(cn+".readImage:\n\t"+e.getMessage());
e.printStackTrace();
return image;
}
}
static private ModHuffmanInputStream getDecoder(InputStream is,IFD ifd)throws IOException{
switch(ifd.getCompression()){
case CCITTFAXT4:
int t4o=ifd.getT4Options();
/*
System.out.println("Tiff Class F T.4");
System.out.println(((t4o&0x00000001)==0)?"1-dim (MH)":"2-dim (MR)");
System.out.println(((t4o&0x00000002)==0)?"Compressed":"Uncompressed mode (not allowed in rfc 2306)");
System.out.println(((t4o&0x00000004)==0)?"non-byte-aligned":"byte-aligned");
*/
if((t4o&0x00000001)==0){
return new ModHuffmanInputStream(is);
}else{
return new ModREADInputStream(is,ifd.getWidth());
}
case CCITTFAXT6:
int t6o=ifd.getT6Options(); // Assume: T6Options == 0 => compressed MMR coding
/*
System.out.println("Tiff Class F T.6");
System.out.println(((t6o&0x00000001)==0)?"2-Dimensional (MMR)":"Unknown Compression Mode");
System.out.println(((t6o&0x00000002)==0)?"Compressed":"Uncompressed mode (not allowed in rfc 2306)");
*/
return new ModModREADInputStream(is,ifd.getWidth());
}
throw new IOException(cn+".getCoder: Internal Error: Unknown compression."); // Should never get here.
}
static private int copyin(byte[] imgdata,int off,ModHuffmanInputStream is,int width,boolean invert)throws IOException{
RLEBitInputStream rlis=new RLEBitInputStream(is);
rlis.setInvert(invert);
if((width&0x0007)==0){
byte[] buf=new byte[width>>3];int len=0;
while(true){
rlis.resetToStartCodeWord(); // start next line with white
is.readEOL(); // set settings for a new line
try{
len=rlis.read(buf); // read one image line
if(len==-1){break;} // end of page
System.arraycopy(buf,0,imgdata,off,len); // copy line to image buffer
}catch(ModHuffmanInputStream.ModHuffmanCodingException mhce){
System.out.println(cn+".copyin:\n\t"+mhce);
}
off+=len;
}
}else{
byte[] buf=new byte[(width+7)>>3];int len=0,ecw=8-(width&0x0007),bits;
while(true){
rlis.resetToStartCodeWord(); // start next line with white
is.readEOL(); // set settings for a new line
try{
len=rlis.read(buf,0,buf.length-1); // read one image line
if(len==-1){break;} // end of page
bits=rlis.readBits(7,ecw);
buf[len]=(byte)bits;
System.arraycopy(buf,0,imgdata,off,len+1); // copy line to image buffer
}catch(ModHuffmanInputStream.ModHuffmanCodingException mhce){
System.out.println(cn+".copyin:\n\t"+mhce);
}
off+=len+1;
}
}
return off;
}
}