0

I'm new to TIFF and LibTiff.Net, I want to read tiled TIFF file and write it to other TIFF file. How can I achieve this? Also any material on Tiff files would be greatly helpful. Thanks in advance.

Aks
  • 13
  • 4

1 Answers1

1

This code will iterate the tiles in a tiff image and then the rows in the tile. The resulting data is stored in the data byte array - from this you can create a bitmapsource. The below code will work with 16bit, 8bit, 1bit gray tile tiff images.

_tiff = Tiff.Open(filepath, "r");

var data = new byte[header.ImageWidth * header.ImageHeight];
var buffer = new byte[_tiff.TileSize()];        

for (var row = 0; row < header.ImageHeight; row += header.TileHeight)
{
    for (var col = 0; col < header.ImageWidth; col += header.TileWidth)
    {
        // Read the tile
        if (_tiff.ReadTile(buffer, 0, col, row, 0, 0) < 0)
        {
            throw new Exception("Error reading data");
        }

        var index = 0;

        // Iterate the rows in the tile
        for (var i = 0; i < header.TileHeight && i + row < header.ImageHeight; i++)
        {
            var length = header.TileWidth;

            // Index of the first position in the row
            var position = (row + i) * data.Width + col;

            // Check we are not outside the image
            if (col + length > header.ImageWidth)
            {
                length = header.ImageWidth - col;
            }

            switch (header.BitsPerPixel)
            {
                case 1:
                {
                    for (var p = 0; p < (length + 7) / 8; p++)
                    {
                        // Unpack the pixels
                        for (var b = 0; b < 8; b++)
                        {
                            data[position + p * 8 + (7 - b)] = (buffer[index / 8 + p] & (1 << b)) != 0 ? byte.MaxValue : byte.MinValue;
                        }
                    }

                    break;
                }
                case 8:
                {
                    for (var p = 0; p < length; p++)
                    {
                        data[position + p] = buffer[index + p];
                    }

                    break;
                }
                case 16:
                {
                    for (var p = 0; p < length; p++)
                    {
                        data[position + p] = buffer[index * 2 + p * 2];
                    }

                    break;
                }
                default:
                {
                    throw new NotImplementedException();
                }
            }

            index += header.TileWidth;
        }
    }
}

You can read the tiff header like this:

public class TaggedImageHeader
{
    public int BitsPerPixel { get; private set; }
    public int Components { get; private set; }
    public string Compression { get; private set; }
    public int ImageHeight { get; private set; }
    public int ImageWidth { get; private set; }
    public string PlanarConfig { get; private set; }
    public int TileHeight { get; private set; }
    public int TileWidth { get; private set; }

    internal static TaggedImageHeader Read(Tiff tiff)
    {
        var imageWidth = tiff.GetField(TiffTag.IMAGEWIDTH);
        var imageHeight = tiff.GetField(TiffTag.IMAGELENGTH);
        var bitsPerPixel = tiff.GetField(TiffTag.BITSPERSAMPLE);
        var components = tiff.GetField(TiffTag.SAMPLESPERPIXEL);
        var tileWidth = tiff.GetField(TiffTag.TILEWIDTH);
        var tileHeight = tiff.GetField(TiffTag.TILELENGTH);
        var compression = tiff.GetField(TiffTag.COMPRESSION);
        var planarConfig = tiff.GetField(TiffTag.PLANARCONFIG);

        return new TaggedImageHeader
        {
            ImageWidth = imageWidth?[0].ToInt() ?? 0,
            ImageHeight = imageHeight?[0].ToInt() ?? 0,
            BitsPerPixel = bitsPerPixel?[0].ToInt() ?? 0,
            Components = components?[0].ToInt() ?? 0,
            TileWidth = tileWidth?[0].ToInt() ?? 0,
            TileHeight = tileHeight?[0].ToInt() ?? 0,
            Compression = compression?[0].ToString() ?? "",
            PlanarConfig = planarConfig?[0].ToString() ?? ""
        };
    }
}

Make sure you call _tiff.SetFrame(frame) if your working with pyramid like tiff files before reading the header.

You can write directly to a WriteableBitmap back buffer by replacing the data array with the code below. This removes an unnecessary copy.

var data = new UnsafeBuffer((byte*)bitmap.BackBuffer, bitmap.BackBufferStride, bitmap.PixelHeight); 

The UnsafeBuffer:

public unsafe class UnsafeBuffer : Buffer
{
    private readonly byte* _data;

    public UnsafeBuffer(byte* data, int x, int y) : base(x, y)
    {
        _data = data;
    }

    public override byte this[int x, int y]
    {
        get => this[y * Width + x];
        set => this[y * Width + x] = value;
    }

    public override byte this[int index]
    {
        get => _data[index];
        set => _data[index] = value;
    }
}

public abstract class Buffer
{
    protected Buffer(int width, int height)
    {
        Width = width;
        Height = height;
    }

    public int Height { get; private set; }
    public int Length => Width * Height;
    public int Width { get; private set; }

    public abstract byte this[int x, int y] { get; set; }

    public abstract byte this[int index] { get; set; }
}

A useful resource would be the documentation which has samples in it. I also found this link quite useful to get me started.

Adam H
  • 561
  • 1
  • 9
  • 20