readTileData (InputStreamMutex *streamData,
              DeepTiledInputFile::Data *ifd,
              int dx, int dy,
              int lx, int ly,
              char *&buffer,
              Int64 &dataSize,
              Int64 &unpackedDataSize)
{
    //
    // Read a single tile block from the file and into the array pointed
    // to by buffer.  If the file is memory-mapped, then we change where
    // buffer points instead of writing into the array (hence buffer needs
    // to be a reference to a char *).
    //

    //
    // Look up the location for this tile in the Index and
    // seek to that position if necessary
    //

    Int64 tileOffset = ifd->tileOffsets (dx, dy, lx, ly);

    if (tileOffset == 0)
    {
        THROW (IEX_NAMESPACE::InputExc, "Tile (" << dx << ", " << dy << ", " <<
                              lx << ", " << ly << ") is missing.");
    }

    //
    // In a multi-part file, the next chunk does not need to
    // belong to the same part, so we have to compare the
    // offset here.
    //

    if ( !isMultiPart(ifd->version) )
    {
        if (streamData->currentPosition != tileOffset)
            streamData->is->seekg(tileOffset);
    }
    else
    {
        //
        // In a multi-part file, the file pointer may be moved by other
        // parts, so we have to ask tellg() where we are.
        //
        if (streamData->is->tellg() != tileOffset)
            streamData->is->seekg (tileOffset);
    }

    //
    // Read the first few bytes of the tile (the header).
    // Verify that the tile coordinates and the level number
    // are correct.
    //

    int tileXCoord, tileYCoord, levelX, levelY;

    if (isMultiPart(ifd->version))
    {
        int partNumber;
        Xdr::read <StreamIO> (*streamData->is, partNumber);
        if (partNumber != ifd->partNumber)
        {
            THROW (IEX_NAMESPACE::ArgExc, "Unexpected part number " << partNumber
                   << ", should be " << ifd->partNumber << ".");
        }
    }

    Xdr::read <StreamIO> (*streamData->is, tileXCoord);
    Xdr::read <StreamIO> (*streamData->is, tileYCoord);
    Xdr::read <StreamIO> (*streamData->is, levelX);
    Xdr::read <StreamIO> (*streamData->is, levelY);

    Int64 tableSize;
    Xdr::read <StreamIO> (*streamData->is, tableSize);

    Xdr::read <StreamIO> (*streamData->is, dataSize);
    Xdr::read <StreamIO> (*streamData->is, unpackedDataSize);


    //
    // Skip the pixel sample count table because we have read this data.
    //

    Xdr::skip <StreamIO> (*streamData->is, tableSize);


    if (tileXCoord != dx)
        throw IEX_NAMESPACE::InputExc ("Unexpected tile x coordinate.");

    if (tileYCoord != dy)
        throw IEX_NAMESPACE::InputExc ("Unexpected tile y coordinate.");

    if (levelX != lx)
        throw IEX_NAMESPACE::InputExc ("Unexpected tile x level number coordinate.");

    if (levelY != ly)
        throw IEX_NAMESPACE::InputExc ("Unexpected tile y level number coordinate.");

    //
    // Read the pixel data.
    //

    if (streamData->is->isMemoryMapped ())
        buffer = streamData->is->readMemoryMapped (dataSize);
    else
    {
        // (TODO) check if the packed data size is too big?
        // (TODO) better memory management here. Don't delete buffer everytime.
        if (buffer != 0) delete[] buffer;
        buffer = new char[dataSize];
        streamData->is->read (buffer, dataSize);
    }

    //
    // Keep track of which tile is the next one in
    // the file, so that we can avoid redundant seekg()
    // operations (seekg() can be fairly expensive).
    //

    streamData->currentPosition = tileOffset + 4 * Xdr::size<int>() +
                                  3 * Xdr::size<Int64>()            +
                                  tableSize                         +
                                  dataSize;
}