All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.gov.nasa.worldwind.formats.tiff.TIFFReader Maven / Gradle / Ivy

Go to download

World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.

There is a newer version: 2.0.0-986
Show newest version
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */

package gov.nasa.worldwind.formats.tiff;

import gov.nasa.worldwind.util.Logging;

import java.io.IOException;
import java.nio.*;
import java.nio.channels.FileChannel;

/**
 * This is a package private class that contains methods of reading TIFF structures
 *
 *
 * @author Lado Garakanidze
 * @version $Id: TIFFReader.java 1171 2013-02-11 21:45:02Z dcollins $
 */
class TIFFReader
{
    private static final int CLEAR_CODE = 256;
    private static final int EOI_CODE = 257;

    private static final int DOUBLE_SIZEOF = Double.SIZE / Byte.SIZE;
    private static final int FLOAT_SIZEOF = Float.SIZE / Byte.SIZE;
    private static final int INTEGER_SIZEOF = Integer.SIZE / Byte.SIZE;
    private static final int SHORT_SIZEOF = Short.SIZE / Byte.SIZE;


    private FileChannel theChannel;
    private ByteOrder   tiffFileOrder;

    public TIFFReader( FileChannel fileChannel, ByteOrder byteOrder )
    {
        this.theChannel = fileChannel;
        this.tiffFileOrder = byteOrder;
    }

    public TIFFReader( FileChannel fileChannel )
    {
        this( fileChannel,ByteOrder.BIG_ENDIAN );
    }

    public void setByteOrder(ByteOrder byteOrder)
    {
        this.tiffFileOrder = byteOrder;
    }

    public ByteOrder getByteOrder()
    {
        return this.tiffFileOrder;
    }


   /*
    *
    *
    */
    public byte[] readLZWCompressed(int width, int height, long offset, int samplesPerPixel,
        boolean differencing, long[] stripOffsets, long[] stripCounts)
        throws IOException
    {
        this.theChannel.position(offset);
        byte[] pixels = new byte[width * height * samplesPerPixel];
        int base = 0;
        for (int i = 0; i < stripOffsets.length; i++)
        {
            if (i > 0)
            {
                long skip = stripOffsets[i] - stripOffsets[i - 1] - stripCounts[i - 1];
                if (skip > 0)
                {
                    //in.skip(skip);
                    this.theChannel.position(this.theChannel.position() + skip);
                }
            }
            byte[] byteArray = new byte[(int) stripCounts[i]];
            ByteBuffer bBuffer = ByteBuffer.wrap(byteArray);
            int read = 0, left = byteArray.length;
            while (left > 0)
            {
                long r = this.theChannel.read(bBuffer);
                if (r == -1)
                {
                    break;
                }
                read += r;
                left -= r;
            }
            byteArray = lzwUncompress(byteArray, (width * samplesPerPixel));
            if (differencing)
            {
                for (int b = 0; b < byteArray.length; b++)
                {
                    if (b / samplesPerPixel % width == 0)
                        continue;
                    byteArray[b] += byteArray[b - samplesPerPixel];
                }
            }
            int k = 0;
            int bytesToRead = byteArray.length;
            bytesToRead = bytesToRead - (bytesToRead % width);
            int pmax = base + bytesToRead;
            if (pmax > width * height * samplesPerPixel)
                pmax = width * height * samplesPerPixel;

            for (int j = base; j < pmax; j++)
            {
                pixels[j] = byteArray[k++];
            }

            base += bytesToRead;
        }

        return pixels;
    }



    public byte[] lzwUncompress(byte[] input, int rowNumPixels)
    {
        if (input == null || input.length == 0)
            return input;
        byte[][] symbolTable = new byte[4096][1];
        int bitsToRead = 9; //default
        int nextSymbol = 258;
        int code;
        int oldCode = -1;

        ByteBuffer out = java.nio.ByteBuffer.allocate(rowNumPixels);
        CodeReader bb = new CodeReader(input);

        while (true)
        {
            code = bb.getCode(bitsToRead);

            if (code == EOI_CODE || code == -1)
                break;
            if (code == CLEAR_CODE)
            {
                // initialize symbol table
                for (int i = 0; i < 256; i++)
                {
                    symbolTable[i][0] = (byte) i;
                }
                nextSymbol = 258;
                bitsToRead = 9;
                code = bb.getCode(bitsToRead);

                if (code == EOI_CODE || code == -1)
                    break;

                out.put(symbolTable[code]);
                oldCode = code;
            }
            else
            {
                if (code < nextSymbol)
                {
                    out.put(symbolTable[code]);
                    ByteBuffer symbol = java.nio.ByteBuffer.allocate((symbolTable[oldCode].length + 1));
                    symbol.put(symbolTable[oldCode]);
                    symbol.put(symbolTable[code][0]);
                    symbolTable[nextSymbol] = symbol.array();
                    oldCode = code;
                    nextSymbol++;
                }
                else
                {
                    int size = symbolTable[oldCode].length + 1;
                    ByteBuffer symbol = java.nio.ByteBuffer.allocate(size);
                    symbol.put(symbolTable[oldCode]);
                    symbol.put(symbolTable[oldCode][0]);
                    byte[] outString = symbol.array();

                    out.put(outString);

                    symbolTable[nextSymbol] = outString;
                    oldCode = code;
                    nextSymbol++;
                }
                if (nextSymbol == 511)
                {
                    bitsToRead = 10;
                }
                if (nextSymbol == 1023)
                {
                    bitsToRead = 11;
                }
                if (nextSymbol == 2047)
                {
                    bitsToRead = 12;
                }
            }
        }
        return out.array();
    }

    /*
     * Reads BYTE image data organized as a singular image plane (and pixel interleaved, in the case of color images).
     *
     */
    public byte[][] readPixelInterleaved8(int width, int height, int samplesPerPixel,
        long[] stripOffsets, long[] stripCounts) throws IOException
    {
        byte[][] data = new byte[1][width * height * samplesPerPixel];
        int offset = 0;

        ByteBuffer buff = ByteBuffer.wrap(data[0]);
        for (int i = 0; i < stripOffsets.length; i++)
        {
            this.theChannel.position(stripOffsets[i]);
            int len = (int) stripCounts[i];
            if ((offset + len) >= data[0].length)
                len = data[0].length - offset;
            buff.limit(offset + len);
            this.theChannel.read(buff);
            offset += stripCounts[i];
        }

        return data;
    }

    /*
    * Reads BYTE image data organized as separate image planes.
    *
    */
    public byte[][] readPlanar8(int width, int height, int samplesPerPixel,
        long[] stripOffsets, long[] stripCounts, long rowsPerStrip) throws IOException
    {
        byte[][] data = new byte[samplesPerPixel][width * height];
        int band = 0;
        int offset = 0;
        int numRows = 0;

        ByteBuffer buff = ByteBuffer.wrap(data[band]);
        for (int i = 0; i < stripOffsets.length; i++)
        {
            this.theChannel.position(stripOffsets[i]);
            int len = (int) stripCounts[i];
            if ((offset + len) >= data[band].length)
                len = data[band].length - offset;
            buff.limit(offset + len);
            this.theChannel.read(buff);
            offset += stripCounts[i];
            numRows += rowsPerStrip;
            if (numRows >= height && band < (data.length - 1))
            {
                buff = ByteBuffer.wrap(data[++band]);
                numRows = 0;
                offset = 0;
            }
        }

        return data;
    }

/*
     * Reads SHORT image data organized as PIXEL interleaved
     * b1p1, b2p1, b3p1, b4p1, b1p2, b2p2, b3p2, b4p2, b1p3, ...
     *
     */
    public short[] read16bitPixelInterleavedImage(int band, int width, int height, int samplesPerPixel,
        long[] stripOffsets, long[] stripCounts, long rowsPerStrip) throws IOException
    {
        short[] data = new short[width * height];
        int numRows = 0;

        ByteBuffer buff = null;

        int dataOffset = 0;
        for (int i = 0; i < stripOffsets.length; i++)
        {
            this.theChannel.position( stripOffsets[i] );

            int stripSize = (int) stripCounts[i];

            if( null == buff || buff.capacity() < stripSize )
            {
                buff = ByteBuffer.allocateDirect( stripSize );
                buff.order( this.getByteOrder() );
            }
            buff.clear().rewind();

            buff.limit( stripSize );

            this.theChannel.read( buff );

            buff.flip();
            ShortBuffer sb = buff.asShortBuffer();

            int b = 0;
            while(sb.hasRemaining())
            {
                if( band == (b++ % samplesPerPixel ))
                {
                    data[ dataOffset] = (short)(0xFFFF & sb.get());
                    dataOffset++;
                }
                else
                    sb.get();
            }
        }

        return data;
    }

/*
     * Reads SHORT image data organized as separate image planes.
     *
     */
    public short[][] readPlanar16(int width, int height, int samplesPerPixel,
        long[] stripOffsets, long[] stripCounts, long rowsPerStrip) throws IOException
    {
        short[][] data = new short[samplesPerPixel][width * height];
        int band = 0;
        int numRows = 0;

        ByteBuffer buff = ByteBuffer.allocateDirect(width * height * SHORT_SIZEOF);
        buff.order(this.getByteOrder());

        for (int i = 0; i < stripOffsets.length; i++)
        {
            this.theChannel.position(stripOffsets[i]);
            int len = (int) stripCounts[i];
            if ((buff.position() + len) > data[band].length * SHORT_SIZEOF)
                len = data[band].length * SHORT_SIZEOF - buff.position();
            buff.limit(buff.position() + len);
            this.theChannel.read(buff);
            numRows += rowsPerStrip;
            if (numRows >= height)
            {
                buff.flip();
                ShortBuffer sbuff = buff.asShortBuffer();
                sbuff.get(data[band]);
                buff.clear();
                ++band;
                numRows = 0;
            }
        }

        return data;
    }

    /*
     * Reads FLOAT image data organized as separate image planes.
     *
     */
    public float[][] readPlanarFloat32(int width, int height, int samplesPerPixel,
        long[] stripOffsets, long[] stripCounts, long rowsPerStrip) throws IOException
    {
        float[][] data = new float[samplesPerPixel][width * height];
        int band = 0;
        int numRows = 0;

        ByteBuffer buff = ByteBuffer.allocateDirect(width * height * FLOAT_SIZEOF);
        buff.order(this.getByteOrder());

        for (int i = 0; i < stripOffsets.length; i++)
        {
            this.theChannel.position(stripOffsets[i]);
            int len = (int) stripCounts[i];
            if ((buff.position() + len) >= data[band].length * FLOAT_SIZEOF)
                len = data[band].length * FLOAT_SIZEOF - buff.position();
            buff.limit(buff.position() + len);
            this.theChannel.read(buff);
            numRows += rowsPerStrip;
            if (numRows >= height)
            {
                buff.flip();
                FloatBuffer fbuff = buff.asFloatBuffer();
                fbuff.get(data[band]);
                buff.clear();
                ++band;
                numRows = 0;
            }
        }

        return data;
    }

    /*
     * Reads a ColorMap.
     *
     */
    public byte[][] readColorMap(TiffIFDEntry colorMapEntry) throws IOException
    {
        if (null == colorMapEntry)
        {
            String message = Logging.getMessage("GeotiffReader.MissingColormap");
            Logging.logger().severe(message);
            throw new IOException(message);
        }

        // NOTE: TIFF gives total number of cmap values, which is 3 times the size of cmap table...
        // CLUT is composed of shorts, but we'll read as bytes (thus, the factor of 2)...
        int numEntries = (int) colorMapEntry.count / 3;
        byte[][] tmp = new byte[3][numEntries * 2];

        this.theChannel.position(colorMapEntry.asLong());

        // Unroll the loop; the TIFF spec says "...3 is the number of the counting, and the counting shall be 3..."
        // TIFF spec also says that all red values precede all green, which precede all blue.
        ByteBuffer buff = ByteBuffer.wrap(tmp[0]);
        this.theChannel.read(buff);
        buff = ByteBuffer.wrap(tmp[1]);
        this.theChannel.read(buff);
        buff = ByteBuffer.wrap(tmp[2]);
        this.theChannel.read(buff);

        // TIFF gives a ColorMap composed of unsigned shorts. Java's IndexedColorModel wants unsigned bytes.
        // Something's got to give somewhere...we'll do our best.
        byte[][] cmap = new byte[3][numEntries];
        for (int i = 0; i < 3; i++)
        {
            buff = ByteBuffer.wrap(tmp[i]);
            buff.order(this.getByteOrder());
            for (int j = 0; j < numEntries; j++)
            {
                cmap[i][j] = (byte) (0x00ff & buff.getShort());
            }
        }

        return cmap;
    }

    public static int getUnsignedShort(ByteBuffer b)
    {
        return 0xffff & (int) b.getShort();
    }

    public static long getUnsignedInt(ByteBuffer b)
    {
        return 0xffffffffL & (long)b.getInt();
    }

    /*
     * Reads and returns an array of bytes from the file.
     *
     */
    public byte[] readBytes(TiffIFDEntry entry) throws IOException
    {
        byte[] bytes = new byte[(int) entry.count];
        ByteBuffer buff = ByteBuffer.wrap(bytes);
        this.theChannel.position(entry.asOffset());
        this.theChannel.read(buff);
        return bytes;
    }

    public String readString(TiffIFDEntry entry)
    {
        try
        {
            if( null != entry && entry.type == Tiff.Type.ASCII )
            {
                return new String( this.readBytes( entry ));
            }
        }
        catch(Exception e)
        {
            Logging.logger().severe(e.getMessage());
        }
        return null;
    }


    /*
     * Utility method intended to read the array of StripOffsets or StripByteCounts.
     */
//    public long[] readOffsetsAsLongs(TiffIFDEntry entry) throws IOException
//    {
//        long[] offsets = new long[(int) entry.count];
//        if (entry.count == 1)
//        {
//            // this is a special case, and it *does* happen!
//            offsets[0] = entry.asLong();
//        }
//        else
//        {
//            long fileOffset = entry.asLong();
//            this.theChannel.position(fileOffset);
//            if (entry.type == Tiff.Type.SHORT)
//            {
//                ByteBuffer buff = ByteBuffer.allocateDirect(offsets.length * SHORT_SIZEOF);
//                this.theChannel.read(buff);
//                buff.order(this.getByteOrder()).flip();
//                for (int i = 0; i < entry.count; i++)
//                {
//                    offsets[i] = getUnsignedShort(buff);
//                }
//            }
//            else
//            {
//                ByteBuffer buff = ByteBuffer.allocateDirect(offsets.length * INTEGER_SIZEOF);
//                this.theChannel.read(buff);
//                buff.order(this.getByteOrder()).flip();
//                for (int i = 0; i < entry.count; i++)
//                {
//                    offsets[i] = getUnsignedInt(buff);
//                }
//            }
//        }
//
//        return offsets;
//    }

        //Inner class for reading individual codes during decompression
    private class CodeReader
    {
        private int currentByte;
        private int currentBit;
        private byte[] byteBuffer;
        private int bufferLength;
        private int[] backMask = new int[] {0x0000, 0x0001, 0x0003, 0x0007,
            0x000F, 0x001F, 0x003F, 0x007F};
        private int[] frontMask = new int[] {0x0000, 0x0080, 0x00C0, 0x00E0,
            0x00F0, 0x00F8, 0x00FC, 0x00FE};
        private boolean atEof;

        public CodeReader(byte[] byteBuffer)
        {
            //todo validate byteBuffer
            this.byteBuffer = byteBuffer;
            currentByte = 0;
            currentBit = 0;
            bufferLength = byteBuffer.length;
        }

        public int getCode(int numBitsToRead)
        {
            if (numBitsToRead < 0)
                return 0;
            if (atEof)
                return -1; //end of file
            int returnCode = 0;
            while (numBitsToRead != 0 && !atEof)
            {
                if (numBitsToRead >= 8 - currentBit)
                {
                    if (currentBit == 0) //get first
                    {
                        returnCode = returnCode << 8;
                        int cb = ((int) byteBuffer[currentByte]);
                        returnCode += (cb < 0 ? 256 + cb : cb);
                        numBitsToRead -= 8;
                        currentByte++;
                    }
                    else
                    {
                        returnCode = returnCode << (8 - currentBit);
                        returnCode += ((int) byteBuffer[currentByte]) & backMask[8 - currentBit];
                        numBitsToRead -= (8 - currentBit);
                        currentBit = 0;
                        currentByte++;
                    }
                }
                else
                {
                    returnCode = returnCode << numBitsToRead;
                    int cb = ((int) byteBuffer[currentByte]);
                    cb = (cb < 0 ? 256 + cb : cb);
                    returnCode += ((cb) & (0x00FF - frontMask[currentBit])) >> (8 - (currentBit + numBitsToRead));
                    currentBit += numBitsToRead;
                    numBitsToRead = 0;
                }
                if (currentByte == bufferLength)  //at eof
                {
                    atEof = true;
                    return returnCode;
                }
            }
            return returnCode;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy