001    /*
002     * TIFFDecoderModifiedHuffman
003     *
004     * Copyright (c) 2002, 2003 Marco Schmidt.
005     * All rights reserved.
006     */
007    
008    package net.sourceforge.jiu.codecs.tiff;
009    
010    import java.io.DataInput;
011    import java.io.IOException;
012    import net.sourceforge.jiu.codecs.tiff.TIFFDecoder;
013    import net.sourceforge.jiu.codecs.InvalidFileStructureException;
014    import net.sourceforge.jiu.data.BilevelImage;
015    import net.sourceforge.jiu.ops.MissingParameterException;
016    
017    /**
018     * A TIFF decoder for files compresseed with the <em>Modified Huffman</em> method
019     * (also known as <em>CCITT 1D Modified Huffman Run Length Encoding</em>).
020     * This compression algorithm has the value <code>2</code> 
021     * in the compression tag of an image file directory.
022     * Only bilevel images can be encoded with that method.
023     * @author Marco Schmidt
024     * @since 0.9.0
025     */
026    public class TIFFDecoderModifiedHuffman extends TIFFDecoder
027    {
028            private DataInput in;
029            private int bitBuffer;
030            private int numBufferedBits;
031    
032            public void decode() throws 
033                    InvalidFileStructureException,
034                    IOException
035            {
036                    byte[] row = new byte[getBytesPerRow()];
037                    for (int y = getY1(); y <= getY2(); y++)
038                    {
039                            decodeRow(row);
040                            putBytes(row, 0, row.length);
041                    }
042            }
043    
044            private int decodeBlackRun() throws
045                    InvalidFileStructureException,
046                    IOException
047            {
048                    return decodeRun(TIFFFaxCodes.BLACK_CODES, TIFFFaxCodes.MIN_BLACK_CODE_SIZE);
049            }
050    
051            private void decodeRow(byte[] row) throws 
052                    InvalidFileStructureException,
053                    IOException
054            {
055                    reset();
056                    boolean black = false;
057                    int index = 0;
058                    do
059                    {
060                            // this will hold the accumulated run length for the current 
061                            // color at the end of this loop iteration
062                            int completeRunLength = 0;
063                            // get run lengths regarding current color until one is smaller than 64
064                            int runLength;
065                            do
066                            {
067                                    if (black)
068                                    {
069                                            runLength = decodeBlackRun();
070                                    }
071                                    else
072                                    {
073                                            runLength = decodeWhiteRun();
074                                    }
075                                    completeRunLength += runLength;
076                            }
077                            while (runLength >= 64);
078                            // pick color value for output row
079                            byte value;
080                            if (black)
081                            {
082                                    value = (byte)BilevelImage.BLACK;
083                            }
084                            else
085                            {
086                                    value = (byte)BilevelImage.WHITE;
087                            }
088                            // fill row buffer with value
089                            while (completeRunLength-- > 0)
090                            {
091                                    row[index++] = value;
092                            }
093                            // switch colors (black to white or vice versa)
094                            black = !black;
095                    }
096                    while (index < row.length);
097            }
098    
099            private int decodeRun(int[][][] codes, int minCodeSize) throws 
100                    InvalidFileStructureException,
101                    IOException
102            {
103                    int code = readBits(minCodeSize);
104                    int currentCodeSize = minCodeSize;
105                    for (int i = 0; i < codes.length; i++)
106                    {
107                            int[][] data = codes[i];
108                            int j = 0;
109                            final int LENGTH = data.length;
110                            while (j < LENGTH)
111                            {
112                                    int[] pair = data[j++];
113                                    if (pair[TIFFFaxCodes.INDEX_CODE_WORD] == code)
114                                    {
115                                            return pair[TIFFFaxCodes.INDEX_CODE_VALUE];
116                                    }
117                            }
118                            code = (code << 1) | readBit();
119                    }
120                    throw new InvalidFileStructureException("Could not identify Huffman code in TIFF file.");
121            }
122    
123            private int decodeWhiteRun() throws
124                    InvalidFileStructureException,
125                    IOException
126            {
127                    return decodeRun(TIFFFaxCodes.WHITE_CODES, TIFFFaxCodes.MIN_WHITE_CODE_SIZE);
128            }
129    
130            public Integer[] getCompressionTypes()
131            {
132                    return new Integer[] {new Integer(TIFFConstants.COMPRESSION_CCITT_GROUP3_1D_MODIFIED_HUFFMAN)};
133            }
134    
135            public void initialize() throws 
136                    IOException, 
137                    MissingParameterException
138            {
139                    super.initialize();
140                    in = getInput();
141            }
142    
143            private int readBit() throws IOException
144            {
145                    int result;
146                    if (numBufferedBits == 0)
147                    {
148                            bitBuffer = in.readUnsignedByte();
149                            if ((bitBuffer & 0x80) == 0)
150                            {
151                                    result = 0;
152                            }
153                            else
154                            {
155                                    result = 1;
156                            }
157                            bitBuffer &= 0x7f;
158                            numBufferedBits = 7;
159                    }
160                    else
161                    {
162                            numBufferedBits--;
163                            result = bitBuffer >> numBufferedBits;
164                            bitBuffer &= (1 << numBufferedBits) - 1;
165                    }
166                    return result;
167            }
168    
169            private int readBits(int number) throws IOException
170            {
171                    // make sure there are at least number bits
172                    while (numBufferedBits < number)
173                    {
174                            int b = in.readUnsignedByte();
175                            bitBuffer = (bitBuffer << 8) | b;
176                            numBufferedBits += 8;
177                    }
178                    numBufferedBits -= number;
179                    int result = bitBuffer >> numBufferedBits;
180                    bitBuffer &= (1 << numBufferedBits) - 1;
181                    return result;
182            }
183    
184            private void reset()
185            {
186                    bitBuffer = 0;
187                    numBufferedBits = 0;
188            }
189    }