001    /*
002     * TIFFDecoder
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 java.io.RandomAccessFile;
013    import net.sourceforge.jiu.codecs.InvalidFileStructureException;
014    import net.sourceforge.jiu.color.conversion.CMYKConversion;
015    import net.sourceforge.jiu.color.conversion.LogLuvConversion;
016    import net.sourceforge.jiu.data.BilevelImage;
017    import net.sourceforge.jiu.data.ByteChannelImage;
018    import net.sourceforge.jiu.data.RGBIndex;
019    import net.sourceforge.jiu.data.ShortChannelImage;
020    import net.sourceforge.jiu.ops.MissingParameterException;
021    import net.sourceforge.jiu.util.ArrayConverter;
022    
023    /**
024     * The abstract base class for a TIFF decoder, a class that decompresses one tile or
025     * strip of image data and understands one or more compression types.
026     * Each child class implements the decoding of a particular TIFF compression type
027     * in its {@link #decode} method.
028     * <p>
029     * This class does all the work of storing decompressed data (given as a byte array)
030     * in the image object.
031     * Given the many variants (sample order, color depth, color space etc.) this is 
032     * a larger portion of code. 
033     * @author Marco Schmidt
034     * @since 0.7.0
035     */
036    public abstract class TIFFDecoder
037    {
038            private TIFFCodec codec;
039            private TIFFImageFileDirectory ifd;
040            private int currentRow;
041            private int leftColumn;
042            private int rightColumn;
043            private int topRow;
044            private int bottomRow;
045            private byte[] rowBuffer;
046            private int bufferIndex;
047            private int tileIndex;
048            private int processedTileRows;
049            private int totalTileRows;
050    
051            public TIFFDecoder()
052            {
053                    tileIndex = -1;
054            }
055    
056            /**
057             * Decode data from input and write the decompressed pixel data to
058             * the image associated with this decoder.
059             * Child classes must override this method to implement the decoding
060             * for a particular compression type.
061             */
062            public abstract void decode() throws 
063                    InvalidFileStructureException,
064                    IOException;
065    
066            /**
067             * Returns the number of bytes per row for the strip or tile
068             * that this decoder deals with.
069             * So with a tiled TIFF and an image width of 500 and a tile width of 100,
070             * for an eight bit grayscale image this would return 100 (not 500).
071             * @return number of bytes per row
072             */
073            public int getBytesPerRow()
074            {
075                    return ifd.getBytesPerRow();
076            }
077    
078            /**
079             * Returns the codec from which this decoder is used.
080             * @return TIFFCodec object using this decoder
081             */
082            public TIFFCodec getCodec()
083            {
084                    return codec;
085            }
086    
087            /**
088             * Returns an array with Integer values of all compression types supported by
089             * this decoder (see the COMPRESSION_xyz constants in {@link TIFFConstants}.
090             * Normally, this is only one value, but some compression types got assigned more than one constant
091             * (e.g. deflated).
092             * Also, a decoder could be capable of dealing with more than one type of compression
093             * if the compression types are similar enough to justify that.
094             * However, typically a decoder can only deal with one type of compression.
095             * @return array with Integer objects of all TIFF compression constants supported by this decoder
096             */
097            public abstract Integer[] getCompressionTypes();
098    
099            /**
100             * Returns the IFD for the image this decoder is supposed to uncompress
101             * (partially).
102             * @return IFD object
103             */
104            public TIFFImageFileDirectory getImageFileDirectory()
105            {
106                    return ifd;
107            }
108    
109            /**
110             * Returns the input stream from which this decoder is supposed 
111             * to read data.
112             */
113            public DataInput getInput()
114            {
115                    return codec.getRandomAccessFile();
116            }
117    
118            /**
119             * Returns the zero-based index of the tile or strip this decoder
120             * is supposed to be decompressing.
121             * @return tile index
122             */
123            public int getTileIndex()
124            {
125                    return tileIndex;
126            }
127    
128            /**
129             * Returns the leftmost column of the image strip / tile to be read 
130             * by this decoder.
131             */
132            public int getX1()
133            {
134                    return leftColumn;
135            }
136    
137            /**
138             * Returns the rightmost column of the image strip / tile to be read 
139             * by this decoder.
140             */
141            public int getX2()
142            {
143                    return rightColumn;
144            }
145    
146            /**
147             * Returns the top row of the image strip / tile to be read 
148             * by this decoder.
149             */
150            public int getY1()
151            {
152                    return topRow;
153            }
154    
155            /**
156             * Returns the bottom row of the image strip / tile to be read 
157             * by this decoder.
158             */
159            public int getY2()
160            {
161                    return bottomRow;
162            }
163    
164            /**
165             * Check if all necessary parameters have been given to this decoder
166             * and initialize several internal fields from them.
167             * Required parameters are a TIFFCodec object, a TIFFImageFileDirectory object and
168             * a tile index.
169             */
170            public void initialize() throws 
171                    IOException, 
172                    MissingParameterException
173            {
174                    if (tileIndex < 0)
175                    {
176                            throw new MissingParameterException("Tile index was not initialized.");
177                    }
178                    if (codec == null)
179                    {
180                            throw new MissingParameterException("No TIFFCodec object was given to this decoder.");
181                    }
182                    if (ifd == null)
183                    {
184                            throw new MissingParameterException("No TIFFImageFileDirectory object was given to this decoder.");
185                    }
186    
187                    RandomAccessFile raf = codec.getRandomAccessFile();
188                    long offset = ifd.getTileOffset(tileIndex) & 0x00000000ffffffffL;
189                    raf.seek(offset);
190    
191                    leftColumn = ifd.getTileX1(tileIndex);
192                    rightColumn = ifd.getTileX2(tileIndex);
193                    topRow = ifd.getTileY1(tileIndex);
194                    bottomRow = ifd.getTileY2(tileIndex);
195                    currentRow = topRow;
196                    processedTileRows = tileIndex * ifd.getTileHeight();
197                    totalTileRows = ifd.getTileHeight() * ifd.getNumTiles();
198                    rowBuffer = new byte[ifd.getBytesPerRow()];
199            }
200    
201            /**
202             * Adds a number of bytes to the internal row buffer.
203             * If the row buffer gets full (a complete line is available)
204             * that data will be copied to the image.
205             * Note that more than one line, exactly one line or only part
206             * of a line can be stored in the <code>number</code> bytes
207             * in <code>data</code>.
208             * @param data byte array with image data that has been decoded
209             * @param offset int index into data where the first byte to be stored is situated
210             * @param number int number of bytes to be stored
211             */
212            public void putBytes(byte[] data, int offset, int number)
213            {
214                    // assert(bufferIndex < rowBuffer.length);
215                    while (number > 0)
216                    {
217                            int remaining = rowBuffer.length - bufferIndex;
218                            int numCopy;
219                            if (number > remaining)
220                            {
221                                    numCopy = remaining;
222                            }
223                            else
224                            {
225                                    numCopy = number;
226                            }
227                            System.arraycopy(data, offset, rowBuffer, bufferIndex, numCopy);
228                            number -= numCopy;
229                            offset += numCopy;
230                            bufferIndex += numCopy;
231                            if (bufferIndex == getBytesPerRow())
232                            {
233                                    storeRow(rowBuffer, 0);
234                                    bufferIndex = 0;
235                            }
236                    }
237            }
238    
239            /**
240             * Specify the codec to be used with this decoder.
241             * This is a mandatory parameter - without it, {@link #initialize}
242             * will throw an exception.
243             * @param tiffCodec TIFFCodec object to be used by this decoder
244             * @see #getCodec
245             */
246            public void setCodec(TIFFCodec tiffCodec)
247            {
248                    codec = tiffCodec;
249            }
250    
251            /**
252             * Specify the IFD to be used with this decoder.
253             * This is a mandatory parameter - without it, {@link #initialize}
254             * will throw an exception.
255             * @param tiffIfd object to be used by this decoder
256             * @see #getImageFileDirectory
257             */
258            public void setImageFileDirectory(TIFFImageFileDirectory tiffIfd)
259            {
260                    ifd = tiffIfd;
261            }
262    
263            /**
264             * Specify the zero-based tile index for the tile or strip to be decompressed
265             * by this decoder.
266             * This is a mandatory parameter - without it, {@link #initialize}
267             * will throw an exception.
268             * @param index zero-based tile / strip index
269             * @see #getTileIndex
270             */
271            public void setTileIndex(int index)
272            {
273                    if (index < 0)
274                    {
275                            throw new IllegalArgumentException("Tile index must be 0 or larger.");
276                    }
277                    tileIndex = index;
278            }
279    
280            private void storeRow(byte[] data, int offset)
281            {
282                    codec.setProgress(processedTileRows++, totalTileRows);
283                    // get current row number and increase field currentRow by one
284                    int y = currentRow++;
285                    // buffer index field is reset to zero so that putBytes will start at the beginning of the buffer next time
286                    bufferIndex = 0;
287                    // leave if we don't need that row because of bounds
288                    if (!codec.isRowRequired(y))
289                    {
290                            return;
291                    }
292                    // adjust y so that it will be in bounds coordinate space
293                    y -= codec.getBoundsY1();
294                    // get leftmost and rightmost pixel index of the current tile
295                    int x1 = getX1();
296                    int x2 = getX2();
297                    // compute number of pixels, adjust for bounds
298                    int numPixels = x2 - x1 + 1;
299                    int leftPixels = 0;
300                    if (getX1() < codec.getBoundsX1())
301                    {
302                            leftPixels = codec.getBoundsX1() - getX1();
303                    }
304                    int rightPixels = 0;
305                    if (getX2() > codec.getBoundsX2())
306                    {
307                            rightPixels = getX2() - codec.getBoundsX2();
308                    }
309                    numPixels -= (rightPixels + leftPixels);
310                    switch(ifd.getImageType())
311                    {
312                            case(TIFFImageFileDirectory.TYPE_BILEVEL_BYTE):
313                            {
314                                    BilevelImage image = (BilevelImage)codec.getImage();
315                                    int index = offset + leftPixels;
316                                    int x = getX1() - codec.getBoundsX1() + leftPixels;
317                                    while (numPixels-- > 0)
318                                    {
319                                            if (data[index++] == (byte)BilevelImage.BLACK)
320                                            {
321                                                    image.putBlack(x++, y);
322                                            }
323                                            else
324                                            {
325                                                    image.putWhite(x++, y);
326                                            }
327                                    }
328                                    break;
329                            }
330                            case(TIFFImageFileDirectory.TYPE_BILEVEL_PACKED):
331                            {
332                                    BilevelImage image = (BilevelImage)codec.getImage();
333                                    int x = getX1() - codec.getBoundsX1() + leftPixels;
334                                    image.putPackedBytes(x, y, numPixels, data, offset + (leftPixels / 8), leftPixels % 8);
335                                    break;
336                            }
337                            case(TIFFImageFileDirectory.TYPE_GRAY4):
338                            {
339                                    ByteChannelImage image = (ByteChannelImage)codec.getImage();
340                                    byte[] dest = new byte[data.length * 2];
341                                    ArrayConverter.decodePacked4Bit(data, 0, dest, 0, data.length);
342                                    for (int i = 0; i < dest.length; i++)
343                                    {
344                                            int value = dest[i] & 15;
345                                            value = (value << 4) | value;
346                                            dest[i] = (byte)value;
347                                    }
348                                    image.putByteSamples(0, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, dest, offset + leftPixels);
349                                    break;
350                            }
351                            case(TIFFImageFileDirectory.TYPE_PALETTED4):
352                            {
353                                    ByteChannelImage image = (ByteChannelImage)codec.getImage();
354                                    byte[] dest = new byte[data.length * 2];
355                                    ArrayConverter.decodePacked4Bit(data, 0, dest, 0, data.length);
356                                    image.putByteSamples(0, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, dest, offset + leftPixels);
357                                    break;
358                            }
359                            case(TIFFImageFileDirectory.TYPE_GRAY8):
360                            case(TIFFImageFileDirectory.TYPE_PALETTED8):
361                            {
362                                    ByteChannelImage image = (ByteChannelImage)codec.getImage();
363                                    image.putByteSamples(0, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, data, offset + leftPixels);
364                                    break;
365                            }
366                            case(TIFFImageFileDirectory.TYPE_CMYK32_INTERLEAVED):
367                            {
368                                    ByteChannelImage image = (ByteChannelImage)codec.getImage();
369                                    byte[] dest = new byte[data.length];
370                                    int numSamples = ifd.getTileWidth();
371                                    CMYKConversion.convertCMYK32InterleavedToRGB24Planar(
372                                            data, 0,
373                                            dest, 0,
374                                            dest, numSamples,
375                                            dest, numSamples * 2,
376                                            numSamples);
377                                    image.putByteSamples(RGBIndex.INDEX_RED, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, dest, leftPixels);
378                                    image.putByteSamples(RGBIndex.INDEX_GREEN, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, dest, numSamples + leftPixels);
379                                    image.putByteSamples(RGBIndex.INDEX_BLUE, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, dest, 2 * numSamples + leftPixels);
380                                    break;
381                            }
382    /*                      case(TIFFImageFileDirectory.TYPE_CMYK32_PLANAR):
383                            {
384                                    ByteChannelImage image = (ByteChannelImage)codec.getImage();
385                                    byte[] dest = new byte[data.length];
386                                    int numSamples = ifd.getTileWidth();
387                                    CMYKConversion.convertCMYK32PlanarToRGB24Planar(
388                                            data, 0,
389                                            data, numPixels,
390                                            data, numPixels * 2,
391                                            data, numPixels * 3,
392                                            dest, 0,
393                                            dest, numSamples,
394                                            dest, numSamples * 2,
395                                            numSamples);
396                                    image.putByteSamples(RGBIndex.INDEX_RED, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, dest, leftPixels);
397                                    image.putByteSamples(RGBIndex.INDEX_GREEN, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, dest, numSamples + leftPixels);
398                                    image.putByteSamples(RGBIndex.INDEX_BLUE, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, dest, 2 * numSamples + leftPixels);
399                                    break;
400                            }*/
401                            case(TIFFImageFileDirectory.TYPE_RGB24_INTERLEAVED):
402                            {
403                                    ByteChannelImage image = (ByteChannelImage)codec.getImage();
404                                    offset += leftPixels * 3;
405                                    for (int i = 0, x = getX1() - codec.getBoundsX1() + leftPixels; i < numPixels; i++, x++)
406                                    {
407                                            image.putByteSample(RGBIndex.INDEX_RED, x, y, data[offset++]);
408                                            image.putByteSample(RGBIndex.INDEX_GREEN, x, y, data[offset++]);
409                                            image.putByteSample(RGBIndex.INDEX_BLUE, x, y, data[offset++]);
410                                    }
411                                    break;
412                            }
413                            case(TIFFImageFileDirectory.TYPE_RGB48_INTERLEAVED):
414                            {
415                                    ShortChannelImage image = (ShortChannelImage)codec.getImage();
416                                    offset += leftPixels * 3;
417                                    short[] triplet = new short[3];
418                                    boolean littleEndian = codec.getByteOrder() == TIFFCodec.BYTE_ORDER_INTEL;
419                                    for (int i = 0, x = getX1() - codec.getBoundsX1() + leftPixels; i < numPixels; i++, x++)
420                                    {
421                                            for (int j = 0; j < 3; j++, offset += 2)
422                                            {
423                                                    if (littleEndian)
424                                                    {
425                                                            triplet[j] = ArrayConverter.getShortLE(data, offset);
426                                                    }
427                                                    else
428                                                    {
429                                                            triplet[j] = ArrayConverter.getShortBE(data, offset);
430                                                    }
431                                            }
432                                            image.putShortSample(RGBIndex.INDEX_RED, x, y, triplet[0]);
433                                            image.putShortSample(RGBIndex.INDEX_GREEN, x, y, triplet[1]);
434                                            image.putShortSample(RGBIndex.INDEX_BLUE, x, y, triplet[2]);
435                                    }
436                                    break;
437                            }
438                            case(TIFFImageFileDirectory.TYPE_LOGLUV32_INTERLEAVED):
439                            {
440                                    if (getImageFileDirectory().getCompression() == TIFFConstants.COMPRESSION_SGI_LOG_RLE)
441                                    {
442                                            ByteChannelImage image = (ByteChannelImage)codec.getImage();
443                                            int numSamples = ifd.getTileWidth();
444                                            byte[] red = new byte[numSamples];
445                                            byte[] green = new byte[numSamples];
446                                            byte[] blue = new byte[numSamples];
447                                            LogLuvConversion.convertLogLuv32InterleavedtoRGB24Planar(data, red, green, blue, numSamples);
448                                            image.putByteSamples(RGBIndex.INDEX_RED, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, red, leftPixels);
449                                            image.putByteSamples(RGBIndex.INDEX_GREEN, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, green, leftPixels);
450                                            image.putByteSamples(RGBIndex.INDEX_BLUE, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, blue, leftPixels);
451                                    }
452                                    else
453                                    if (getImageFileDirectory().getCompression() == TIFFConstants.COMPRESSION_SGI_LOG_24_PACKED)
454                                    {
455                                            ByteChannelImage image = (ByteChannelImage)codec.getImage();
456                                            int numSamples = ifd.getTileWidth();
457                                            byte[] red = new byte[numSamples];
458                                            byte[] green = new byte[numSamples];
459                                            byte[] blue = new byte[numSamples];
460                                            LogLuvConversion.convertLogLuv24InterleavedtoRGB24Planar(data, red, green, blue, numSamples);
461                                            image.putByteSamples(RGBIndex.INDEX_RED, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, red, leftPixels);
462                                            image.putByteSamples(RGBIndex.INDEX_GREEN, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, green, leftPixels);
463                                            image.putByteSamples(RGBIndex.INDEX_BLUE, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, blue, leftPixels);
464                                    }
465                                    break;
466                            }
467                            case(TIFFImageFileDirectory.TYPE_LOGL):
468                            {
469                                    ByteChannelImage image = (ByteChannelImage)codec.getImage();
470                                    int numSamples = ifd.getTileWidth();
471                                    byte[] gray = new byte[numSamples];
472                                    LogLuvConversion.convertLogL16toGray8(data, gray, numSamples);
473                                    image.putByteSamples(0, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, gray, leftPixels);
474                                    break;
475                            }
476                    }
477            }
478    }