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<height;y+=maxrps){
/*
Assume bilevel image (black/white[=-1])
Each strip: count run length
encode into modified hufman codes
swap bits
save in byte array
write to image file
*/
ByteArrayOutputStream baos=new ByteArrayOutputStream();
BitSwapOutputStream bsos=new BitSwapOutputStream(baos);
ModHuffmanOutputStream mhos=new ModHuffmanOutputStream(bsos);
RLEOutputStream rlos=new RLEOutputStream(mhos,3); // rgb = 3 bytes per sample code word (not needed here)
for(int i=0;i<maxrps;i++){
if((y+i)==height){break;} // last strip might have less rows
rlos.setStartCodeWord(-1); // white run first
for(int x=0;x<width;x++){
rlos.write(image.getRGB(x,y+i));
}
rlos.flush(); // write padding after ever image row
}
rlos.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
index++;
}
return ifd;
}catch(Exception e){
e.printStackTrace();
throw new IOException(getClass().getName()+".writeBModHufImage:\n\t"+e.getMessage());
}
}
/*
Photometric Interpretation 1 : BlackIsZero
required fields
imageWidth,imageLength,Compression,PhotometricInterpretation,
StripOffsets,RowsPerStrip,StripByteCounts,
XResolution,YResolution,ResolutionUnit,
BitsPerSample
SamplesPerPixel
*/
private IFD writeGrayImage(ImageOutputStream out,BufferedImage image,int comp,TIFFImageWriteParam param)throws IOException{
image = convert(image,BufferedImage.TYPE_BYTE_GRAY);
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(1);
bpss.setBitsPerSample(0,8); // gray scale
ifd.add(bpss); // 258
ifd.add(new DEFactory.CompressionDE(comp)); // 259
ifd.add(new DEFactory.PhotometricInterpretationDE(BlackIsZero)); // 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;
}
if(comp==JPEG){
maxrps=((maxrps+8-1)/8)*8;
maxstripes=(height+maxrps-1)/maxrps;
}
DEFactory.StripOffsetsDE offsets = new DEFactory.StripOffsetsDE(maxstripes);
ifd.add(offsets); // 273
ifd.add(new DEFactory.SamplesPerPixelDE(1)); // 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
ByteArrayOutputStream baos =new ByteArrayOutputStream();
OutputStream os =baos;
JPEGOutputStream jpegos =null;
if(comp==JPEG){ // add JPEGTables tag
jpegos=new JPEGOutputStream(baos);
int quality=(param==null)?50:(int)(param.getCompressionQuality()*100);
jpegos.setZZQuantizationTable(0,JPEGConstants.LQT,quality);
jpegos.setRawDCHuffmanTable(0,JPEGConstants.HLDCTable);
jpegos.setRawACHuffmanTable(0,JPEGConstants.HLACTable);
jpegos.defineQuantizationTables();
jpegos.defineHuffmanTables();
jpegos.close();
DEFactory.JPEGTablesDE jpegtables=new DEFactory.JPEGTablesDE(baos.toByteArray());
ifd.add(jpegtables); // 347
baos.reset();
os=jpegos;
}
WritableRaster raster=image.getRaster();
DataBufferByte buffer=(DataBufferByte)raster.getDataBuffer();
byte[] imgdata=(byte[])buffer.getData();
int index=0;
for(int y=0;y<height;y+=maxrps){
/*
Assume rgb image.
Each strip: evaluate gray scale colour
save in byte array
write to image file
*/
if((height-y)<maxrps){maxrps=height-y;}
if(jpegos!=null){ // jpeg: SOI,SOF,SOS marker
jpegos.startOfImage();
int[] hv={0x11}; // (Hi<<4)|Vi
int[] q={0}; // quantization table 0
jpegos.startOfFrame(maxrps,width,hv,q);
int[] sel={0}; // DC,AC code table 0
jpegos.startOfScan(sel);
}
os.write(imgdata,y*width,maxrps*width);
os.close(); // jpeg EOF: end of frame
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 2 : baseline TIFF RGB images or TIFF class R images
required fields
imageWidth,imageLength,Compression,PhotometricInterpretation,
StripOffsets,RowsPerStrip,StripByteCounts,
XResolution,YResolution,ResolutionUnit,
BitsPerSample
SamplesPerPixel
*/
private IFD writeRGBImage(ImageOutputStream out,BufferedImage image,int comp,TIFFImageWriteParam param)throws IOException{
image = convert(image,BufferedImage.TYPE_INT_RGB);
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(3);
bpss.setBitsPerSample(0,8); // red
bpss.setBitsPerSample(1,8); // green
bpss.setBitsPerSample(2,8); // blue
ifd.add(bpss); // 258
ifd.add(new DEFactory.CompressionDE(comp)); // 259
ifd.add(new DEFactory.PhotometricInterpretationDE(RGB)); // 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;
}
if(comp==JPEG){
maxrps=((maxrps+8-1)/8)*8;
maxstripes=(height+maxrps-1)/maxrps;
}
DEFactory.StripOffsetsDE offsets = new DEFactory.StripOffsetsDE(maxstripes);
ifd.add(offsets); // 273
ifd.add(new DEFactory.SamplesPerPixelDE(3)); // 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
ByteArrayOutputStream baos =new ByteArrayOutputStream();
OutputStream os =baos;
JPEGOutputStream jpegos =null;
if(comp==JPEG){ // add JPEGTables tag
jpegos=new JPEGOutputStream(baos);
int quality=(param==null)?50:(int)(param.getCompressionQuality()*100);
jpegos.setZZQuantizationTable(0,JPEGConstants.LQT,quality);
jpegos.setRawDCHuffmanTable(0,JPEGConstants.HLDCTable);
jpegos.setRawACHuffmanTable(0,JPEGConstants.HLACTable);
jpegos.defineQuantizationTables();
jpegos.defineHuffmanTables();
jpegos.close();
DEFactory.JPEGTablesDE jpegtables=new DEFactory.JPEGTablesDE(baos.toByteArray());
ifd.add(jpegtables); // 347
baos.reset();
os=jpegos;
}
WritableRaster raster=image.getRaster();
DataBufferInt buffer=(DataBufferInt)raster.getDataBuffer();
int[] imgdata=(int[])buffer.getData();
int index=0;
for(int y=0;y<height;y+=maxrps){
/*
Assume rgb image.
Each strip: evaluate r g b colour
save in byte array
write to image file
*/
if((height-y)<maxrps){maxrps=height-y;}
if(jpegos!=null){ // jpeg: SOI,SOF,SOS marker
jpegos.startOfImage();
int[] hv={0x11,0x11,0x11}; // (Hi<<4)|Vi
int[] q={0,0,0}; // quantization table 0
jpegos.startOfFrame(maxrps,width,hv,q);
int[] sel={0,0,0}; // DC,AC code table 0
jpegos.startOfScan(sel);
}
for(int i=0;i<maxrps;i++){ // write RGB data
for(int x=0;x<width;x++){
int c = imgdata[x+(y+i)*width];
os.write((c>>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<height;y+=maxrps){
/*
Assume rgb image.
Each strip: evaluate c m y k colour
save in byte array
write to image file
*/
ByteArrayOutputStream baos=new ByteArrayOutputStream();
for(int i=0;i<maxrps;i++){
if((y+i)==height){break;} // last strip might have less rows
for(int x=0;x<width;x++){
int c=image.getRGB(x,y+i);
int R=(c>>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<K){K=M;}if(Y<K){K=Y;}
C=((C-K)/(1.0-K))*255.0;
M=((M-K)/(1.0-K))*255.0;
Y=((Y-K)/(1.0-K))*255.0;
K*=255.0;
baos.write((int)C);
baos.write((int)M);
baos.write((int)Y);
baos.write((int)K);
}
}
}
baos.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
index++;
}
return ifd;
}catch(Exception e){
e.printStackTrace();
throw new IOException(getClass().getName()+".writeCMYKImage:\n\t"+e.getMessage());
}
}
/*
Photometric Interpretation 6 : TIFF YCbCr images
required fields
imageWidth,imageLength,Compression,PhotometricInterpretation,
StripOffsets,RowsPerStrip,StripByteCounts,
XResolution,YResolution,ResolutionUnit,
BitsPerSample
SamplesPerPixel
*/
private IFD writeYCbCrImage(ImageOutputStream out,BufferedImage image,int comp,TIFFImageWriteParam param)throws IOException{
image = convert(image,BufferedImage.TYPE_INT_RGB);
try{
int width=image.getWidth();
int height=image.getHeight();
IFD ifd=new IFD(); // entries need to be in tag order !
int ss=(param==null)?0x22:param.getSubSampling();
int ssh=(ss>>4)&0x0F;
int ssv= ss &0x0F;
if(ssh<ssv){ // YCbCrSubsampleVert shall always be less than or equal to YCbCrSubsampleHoriz.
throw new IOException("Internal error: YCbCrSubsampleVert is not less than YCbCrSubsampleHoriz.");
}
// int ww=((width +ssh-1)/ssh)*ssh; // [1] p.92
// int hh=((height+ssv-1)/ssv)*ssv;
int ww=width;int hh=height;
ifd.add(new DEFactory.NewSubfileTypeDE(2)); // 254 single page of multipage file
ifd.add(new DEFactory.ImageWidthDE (ww)); // 256
ifd.add(new DEFactory.ImageLengthDE(hh)); // 257
DEFactory.BitsPerSampleDE bpss = new DEFactory.BitsPerSampleDE(3);
bpss.setBitsPerSample(0,8); // Y
bpss.setBitsPerSample(1,8); // Cb
bpss.setBitsPerSample(2,8); // Cr
ifd.add(bpss); // 258
ifd.add(new DEFactory.CompressionDE(comp)); // 259
ifd.add(new DEFactory.PhotometricInterpretationDE(YCbCr)); // 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;
}
if(comp==JPEG){
maxrps=((maxrps+8*ssv-1)/(8*ssv))*(8*ssv);
maxstripes=(height+maxrps-1)/maxrps;
}
DEFactory.StripOffsetsDE offsets = new DEFactory.StripOffsetsDE(maxstripes);
ifd.add(offsets); // 273
ifd.add(new DEFactory.SamplesPerPixelDE(3)); // 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
ByteArrayOutputStream baos =new ByteArrayOutputStream();
OutputStream os =baos;
JPEGOutputStream jpegos =null;
if(comp==JPEG){
jpegos=new JPEGOutputStream(baos);
int quality=(param==null)?50:(int)(param.getCompressionQuality()*100);
jpegos.setZZQuantizationTable(0,JPEGConstants.LQT,quality);
jpegos.setZZQuantizationTable(1,JPEGConstants.CQT,quality);
jpegos.setRawDCHuffmanTable(0,JPEGConstants.HLDCTable);
jpegos.setRawACHuffmanTable(0,JPEGConstants.HLACTable);
jpegos.setRawDCHuffmanTable(1,JPEGConstants.HCDCTable);
jpegos.setRawACHuffmanTable(1,JPEGConstants.HCACTable);
jpegos.defineQuantizationTables();
jpegos.defineHuffmanTables();
jpegos.close();
DEFactory.JPEGTablesDE jpegtables=new DEFactory.JPEGTablesDE(baos.toByteArray());
ifd.add(jpegtables); // 347
baos.reset();
os=jpegos;
}
// CCIR Recommendation 601-1 LumaRed=299/1000 LumaGreen=587/1000 LumeBlue=114/1000
// Y = ( LumaRed * R + LumaGreen * G + LumaBlue * B )
// Cb = ( B - Y ) / ( 2 - 2 * LumaBlue )
// Cr = ( R - Y ) / ( 2 - 2 * LumaRed )
double LumaRed = 299.0/1000.0;
double LumaGreen = 587.0/1000.0;
double LumaBlue = 114.0/1000.0;
DEFactory.YCbCrCoefficientsDE YCbCrCoeff = new DEFactory.YCbCrCoefficientsDE();
YCbCrCoeff.setLumaRed (LumaRed); // Y
YCbCrCoeff.setLumaGreen(LumaGreen); // Cb
YCbCrCoeff.setLumaBlue (LumaBlue); // Cr
ifd.add(YCbCrCoeff); // 529
DEFactory.YCbCrSubSamplingDE YCbCrSubSampling = new DEFactory.YCbCrSubSamplingDE();
YCbCrSubSampling.setHoriz(ssh);
YCbCrSubSampling.setVert(ssv);
ifd.add(YCbCrSubSampling); // 530
double RfBY =0; double RfWY =255;
double RfBCb =128; double RfWCb =255;
double RfBCr =128; double RfWCr =255;
DEFactory.ReferenceBlackWhiteDE ReferenceBlackWhite = new DEFactory.ReferenceBlackWhiteDE();
ReferenceBlackWhite.setY(RfBY,RfWY);
ReferenceBlackWhite.setCb(RfBCb,RfWCb);
ReferenceBlackWhite.setCr(RfBCr,RfWCr);
ifd.add(ReferenceBlackWhite); // 532
TIFFYCbCrOutputStream ycbcros;
if(jpegos==null){
ycbcros=new TIFFYCbCrOutputStream(os,width,ssv,ssh);
os=new TIFFSubSamplingOutputStream(ycbcros,width,ssv,ssh);
}else{
ycbcros=new TIFFYCbCrOutputStream(os,width,1,1); // jpeg does own subsampling
os=ycbcros;
}
ycbcros.setPositioning(1);
ycbcros.setColourCoefficients(LumaRed,LumaGreen,LumaBlue);
ycbcros.setRfBWY (RfBY,RfWY);
ycbcros.setRfBWCb(RfBCb,RfWCb);
ycbcros.setRfBWCr(RfBCr,RfWCr);
WritableRaster raster=image.getRaster();
DataBufferInt buffer=(DataBufferInt)raster.getDataBuffer();
int[] imgdata=(int[])buffer.getData();
int c=0,index=0;
for(int y=0;y<height;y+=maxrps){
if((height-y)<maxrps){maxrps=height-y;}
if(jpegos!=null){
jpegos.startOfImage();
int[] hv={(ssh<<4)|ssv,0x11,0x11}; // (Hi<<4)|Vi
int[] q={0,1,1}; // quantization table Y=0, Cb=Cr=1
// jpegos.startOfFrame(((maxrps+ssv-1)/ssv)*ssv,ww,hv,q);
jpegos.startOfFrame(maxrps,ww,hv,q);
int[] sel={0,1,1}; // DC,AC code table Y=0, Cb=Cr=1
jpegos.startOfScan(sel);
}
for(int i=0;i<maxrps;i++){
int x=0;
while(x<width){
c = imgdata[x+(y+i)*width];
// c = image.getRGB(x,y+i);
os.write((c>>16)&0x000000FF);
os.write((c>> 8)&0x000000FF);
os.write( c &0x000000FF);
x++;
}
while(x<ww){
os.write((c>>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;
}
}