package uk.co.mmscomputing.imageio.jpeg; import java.io.*; import uk.co.mmscomputing.io.*; public class JPEGInputStream extends IntFilterInputStream implements JPEGConstants{ JPEGBitInputStream in; protected int bps; // bits per sample: DCT compression [8,12] or lossless [2..16] protected int height,width; protected int maxHor,maxVert; protected int mcuHeight,mcuWidth,mcuRows,mcuCols; protected int spp; // samples per pixel private int[][] qts = new int[4][]; // available quantization tables protected JPEGHuffmanInputStream[] dcins = new JPEGHuffmanInputStream[4]; protected JPEGHuffmanInputStream[] acins = new JPEGHuffmanInputStream[4]; private int[] cqts = {-1,-1,-1,-1}; // match compins with qts protected JPEGComponentInputStream[] compins = new JPEGComponentInputStream[4]; protected JPEGComponentInputStream[] scanins = new JPEGComponentInputStream[4]; protected int Ri; // restart segment: how many MCUs in interval; public JPEGInputStream(InputStream input)throws IOException{ super(null); Ri=0;maxHor=0;maxVert=0; in=new JPEGBitInputStream(input,this); ((JPEGBitInputStream)in).start(); } public JPEGInputStream( InputStream input, int[][] qts, JPEGHuffmanInputStream[] dcins, JPEGHuffmanInputStream[] acins )throws IOException{ super(null); Ri=0;maxHor=0;maxVert=0; this.qts =qts; this.dcins=dcins; this.acins=acins; in=new JPEGBitInputStream(input,this); ((JPEGBitInputStream)in).start(); for(int i=0;(dcins[i]!=null)&&(i<dcins.length);i++){dcins[i].setInputStream(in);} for(int i=0;(acins[i]!=null)&&(i<acins.length);i++){acins[i].setInputStream(in);} } public int[][] getQTs() {return qts;} public JPEGHuffmanInputStream[] getDCIns(){return dcins;} public JPEGHuffmanInputStream[] getACIns(){return acins;} public int getHeight(){return height;} public int getWidth(){ return width;} public int getNumComponents(){return spp;} protected int readIn(InputStream in)throws IOException{ int b=in.read(); if(b==-1){ IOException ioe=new IOException(getClass().getName()+"readIn:\n\tUnexpected end of file."); ioe.printStackTrace(); throw ioe; } return b; } public void startOfFrame(InputStream in,int mode)throws IOException{ // 0xC0,0xC1 // System.out.println("3\bStart Of Frame"); bps =readIn(in); height=(readIn(in)<<8)|readIn(in); // System.out.println("Height="+height); width =(readIn(in)<<8)|readIn(in); // System.out.println("Width="+width); spp =readIn(in); // System.out.println("3\bspp="+spp); if(qts[0]==null){ // System.err.println(getClass().getName()+".startOfFrame:\n\tNo qts!"); initQuantizationTables(); // preset with standard quantization tables } for(int i=0;i<spp;i++){ int cid = readIn(in); // Component id can be between 0..255 compins[i]=new JPEGComponentInputStream(cid); compins[i].setBitsPerSample(bps); compins[i].setDimensions(height,width); int b = readIn(in); int Hi = ((b>>4)&0x0F); // 1..4 int Vi = (b &0x0F); // 1..4 if(spp==1){ // [1]p.25 order left to right and top to bottom whatever the Hi,Vi values maxHor=1;maxVert=1; compins[i].setSamplingRate(1,1); }else{ if(Hi>maxHor){maxHor=Hi;} if(Vi>maxVert){maxVert=Vi;} compins[i].setSamplingRate(Vi,Hi); } int qtId = readIn(in); // read index of quantization table compins[i].setQuantizationTable(qts[qtId]); // assign one of the available QTs to a component cqts[qtId] = i; // buffer compins <-> qts need this if qts haven't been read yet // System.out.println(compins[i].toString()); } for(int i=0;i<spp;i++){ compins[i].setMaxSamplingRate(maxVert,maxHor); } mcuHeight = maxVert*DCTSize; mcuWidth = maxHor *DCTSize; mcuRows =(height+mcuHeight-1)/mcuHeight; mcuCols =(width +mcuWidth -1)/mcuWidth ; /* System.out.println("maxHor="+maxHor); System.out.println("maxVert="+maxVert); System.out.println("mcuHeight="+mcuHeight); System.out.println("mcuWidth="+mcuWidth); System.out.println("mcuRows="+mcuRows); System.out.println("mcuCols="+mcuCols); */ } public void defineHuffmanTables(InputStream tables)throws IOException{ // 0xC4 // System.out.println("3\bDefine Huffman Tables"); for(int n=0;n<8;n++){ // max 8 tables possible int b=tables.read(); switch((b>>4)&0x0F){ // table class case 0: dcins[b&0x0F]=new JPEGHuffmanInputStream(in,tables);break; // DC table case 1: acins[b&0x0F]=new JPEGHuffmanInputStream(in,tables);break; // AC table default: return; } } } public void defineArithmeticConditioning(InputStream in)throws IOException{ // 0xC8 System.out.println("3\bDefine Arithmetic Conditioning"); } public void restartIntervalTermination(int no)throws IOException{ // 0xD0 .. 0xD7; no = 0..7 for(int c=0;c<spp;c++){scanins[c].restart();} // System.out.println("3\bRestart Interval Termination: no = "+no); } public void startOfImage(){ // 0xD8 // System.out.println("3\bStart Of Image"); } public void endOfImage(){ // 0xD9 // System.out.println("3\bEnd Of Image"); } public void startOfScan(InputStream in)throws IOException{ // 0xDA spp=readIn(in); // number of components: max 4 for(int i=0;i<spp;i++){ int c=readIn(in); // Component id can be between 0..255 for(int j=0;(compins[j]!=null)&&(j<4);j++){ if(compins[j].getId()==c){scanins[i]=compins[j];break;} } int b=readIn(in); scanins[i].setHuffmanTables(dcins[(b>>4)&0x0F],acins[b&0x0F]); switch(i){ case 0: scanins[i].setShift(16); break; // R Y case 1: scanins[i].setShift( 8); break; // G Cb case 2: scanins[i].setShift( 0); break; // B Cr case 3: scanins[i].setShift(24); break; // alpha } } int ss=readIn(in); int se=readIn(in); int b=readIn(in); int ah =((b>>4)&0x0F); int al = (b &0x0F); } private void initQuantizationTables()throws IOException{ // used only if no qts are defined yet (start of frame) int[] qt; ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(0); qt = JPEGConstants.LQT2; for(int i=0;i<qt.length;i++){baos.write(qt[i]);} baos.write(1); qt = JPEGConstants.CQT2; for(int i=0;i<qt.length;i++){baos.write(qt[i]);} baos.write(2); for(int i=0;i<qt.length;i++){baos.write(qt[i]);} baos.write(3); for(int i=0;i<qt.length;i++){baos.write(qt[i]);} ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); defineQuantizationTables(bais); } public void defineQuantizationTables(InputStream in)throws IOException{ // 0xDB // System.out.println("3\bDefine Quantization Tables"); int[] qt; for(int n=0;n<4;n++){ // max 4 tables int b=in.read(); // System.out.println("b="+b); int t=(b>>4)&0x0F; // System.out.println("t="+t); int c=b&0x0F; // System.out.println("c="+c); switch(t){ // table class case 0: // 8bit table qt=new int[64];for(int i=0;i<64;i++){qt[i]=readIn(in);} // are in zigzag scan order [1]p.40 break; case 1: // 16bit table qt=new int[64];for(int i=0;i<64;i++){qt[i]=(readIn(in)<<8)|readIn(in);} break; default: return; } JPEGFastDCTInputStream.normalize(qt); // comment this out if you want to use JPEGDCTInputStream.inverseDCT qts[c]=qt; if(cqts[n]!=-1){ // if qts are defined after start of frame /* Does T.81 allow you to define the qts after start of frame ? */ compins[cqts[n]].setQuantizationTable(qt); // assign quantization table } } } public void defineNumberOfLines(InputStream in)throws IOException{ // 0xDC Not allowed in TIFF file height = (readIn(in)<<8)|readIn(in); System.out.println("3\bDefine Number of Lines: height="+height); } public void defineRestartInterval(InputStream in)throws IOException{ // 0xDD Ri = (readIn(in)<<8)|readIn(in); // System.out.println("3\bDefine Restart Interval: Ri = "+Ri); } public void defineHierarchicalProgression(InputStream in)throws IOException{ // 0xDE throw new IOException(getClass().getName()+"defineHierarchicalProgression:\n\tDo not support 'Hierarchical Progression' mode."); } public void expandReferenceComponents(InputStream in)throws IOException{ // 0xDF throw new IOException(getClass().getName()+"defineHierarchicalProgression:\n\tDo not support 'expand reference component(s)'."); } protected void dump(InputStream in)throws IOException{ int b,i=0; while((b=in.read())!=-1){ System.out.println("appl["+i+"] 0x"+Integer.toHexString(b)+" "+(char)((b>=' ')?b:' ')+" "+b);i++; } } public void app0(InputStream in)throws IOException{dump(in);} // 0xE0 JFIF public void app1(InputStream in)throws IOException{dump(in);} // 0xE1 Exif public void app2(InputStream in)throws IOException{dump(in);} // 0xE2 public void app3(InputStream in)throws IOException{dump(in);} // 0xE3 public void app4(InputStream in)throws IOException{dump(in);} // 0xE4 public void app5(InputStream in)throws IOException{dump(in);} // 0xE5 public void app6(InputStream in)throws IOException{dump(in);} // 0xE6 public void app7(InputStream in)throws IOException{dump(in);} // 0xE7 public void app8(InputStream in)throws IOException{dump(in);} // 0xE8 public void app9(InputStream in)throws IOException{dump(in);} // 0xE9 public void app10(InputStream in)throws IOException{dump(in);} // 0xEA public void app11(InputStream in)throws IOException{dump(in);} // 0xEB public void app12(InputStream in)throws IOException{dump(in);} // 0xEC public void app13(InputStream in)throws IOException{dump(in);} // 0xED public void app14(InputStream in)throws IOException{dump(in);} // 0xEE public void app15(InputStream in)throws IOException{dump(in);} // 0xEF public void comment(InputStream in)throws IOException{dump(in);} // 0xFE public int read()throws IOException{ throw new IOException(getClass().getName()+".read():\nInternal Error: Don't support simple read()."); } public int read(int[] buf, int off, int len)throws IOException{ int y=0; for(int row=0;row<mcuRows;row++){ int x=0; for(int col=0;col<mcuCols;col++){ for(int c=0;c<spp;c++){ scanins[c].read( buf, off+y*width+x, (mcuHeight<(height-y))?mcuHeight:(height-y), (mcuWidth <(width -x))?mcuWidth: (width-x) ); } x+=mcuWidth; } y+=mcuHeight; } return len; } // call only if one component and BufferedImage has grayscale type public int read(byte[] buf, int off, int len)throws IOException{ int y=0; for(int row=0;row<mcuRows;row++){ int x=0; for(int col=0;col<mcuCols;col++){ scanins[0].read( buf, off+y*width+x, (mcuHeight<(height-y))?mcuHeight:(height-y), (mcuWidth <(width -x))?mcuWidth: (width-x) ); x+=mcuWidth; } y+=mcuHeight; } return len; } } // [1]'JPEG' : ISO/IEC IS 10918-1 // ITU-T Recommendation T.81 // http://www.w3.org/Graphics/JPEG/itu-t81.pdf