LibTiff.Net
| Language | C# (.NET Standard 2.0, .NET Framework 4.6.1+, .NET 5+) |
|---|---|
| License | New BSD |
| Repository | github.com/BitMiracle/libtiff.net |
| Documentation | API reference and samples |
| Depends on | nothing (pure managed code) |
| Read / write | yes / yes, multi-page, strips and tiles, all bit depths, LZW, Deflate, PackBits, JPEG, CCITT Group 3/4 |
LibTiff.Net is a line-by-line port of libtiff to C#. The API mirrors libtiff: Tiff.Open, GetField, SetField, ReadScanline, WriteScanline, ReadDirectory, WriteDirectory. Code and documentation written for libtiff translate almost directly. It runs everywhere .NET runs, with no native libraries.
It works with raw samples and tags, not with a bitmap object: you get bytes or packed RGBA integers and convert them to your image type. It doesn’t read BigTIFF. Releases are infrequent, but the code is stable.
Installation#
Shell
dotnet add package BitMiracle.LibTiff.NET
Basic usage: read a TIFF as RGBA#
C#
using System;
using BitMiracle.LibTiff.Classic;
using Tiff tif = Tiff.Open("in.tif", "r");
if (tif == null) {
Console.Error.WriteLine("cannot open file");
return;
}
int width = tif.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
int height = tif.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
int[] raster = new int[width * height];
if (!tif.ReadRGBAImageOriented(width, height, raster, Orientation.TOPLEFT)) {
Console.Error.WriteLine("cannot decode image");
return;
}
int p = raster[0];
Console.WriteLine($"{width} x {height}, first pixel R={Tiff.GetR(p)} G={Tiff.GetG(p)} B={Tiff.GetB(p)} A={Tiff.GetA(p)}");
How to write an RGB TIFF with LZW compression#
C#
using BitMiracle.LibTiff.Classic;
static void WriteRgb(string path, byte[] pixels, int width, int height) {
using Tiff tif = Tiff.Open(path, "w");
tif.SetField(TiffTag.IMAGEWIDTH, width);
tif.SetField(TiffTag.IMAGELENGTH, height);
tif.SetField(TiffTag.SAMPLESPERPIXEL, 3);
tif.SetField(TiffTag.BITSPERSAMPLE, 8);
tif.SetField(TiffTag.PHOTOMETRIC, Photometric.RGB);
tif.SetField(TiffTag.PLANARCONFIG, PlanarConfig.CONTIG);
tif.SetField(TiffTag.ORIENTATION, Orientation.TOPLEFT);
tif.SetField(TiffTag.COMPRESSION, Compression.LZW);
tif.SetField(TiffTag.ROWSPERSTRIP, tif.DefaultStripSize(0));
byte[] row = new byte[width * 3];
for (int y = 0; y < height; y++) {
Buffer.BlockCopy(pixels, y * width * 3, row, 0, row.Length);
tif.WriteScanline(row, y);
}
}
How to iterate the pages of a multi-page TIFF#
C#
using Tiff tif = Tiff.Open("scan.tif", "r");
short pages = tif.NumberOfDirectories();
for (short i = 0; i < pages; i++) {
tif.SetDirectory(i);
int w = tif.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
int h = tif.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
Console.WriteLine($"page {i}: {w} x {h}");
}
How to write a multi-page TIFF#
Write each page’s tags and rows, then call WriteDirectory():
C#
using Tiff tif = Tiff.Open("pages.tif", "w");
for (int page = 0; page < pageCount; page++) {
tif.SetField(TiffTag.SUBFILETYPE, FileType.PAGE);
tif.SetField(TiffTag.PAGENUMBER, page, pageCount);
// ... set IMAGEWIDTH, IMAGELENGTH, BITSPERSAMPLE etc. and WriteScanline each row ...
tif.WriteDirectory();
}
How to read tags#
GetField returns null when the tag is missing:
C#
FieldValue[] software = tif.GetField(TiffTag.SOFTWARE);
if (software != null)
Console.WriteLine($"Software: {software[0].ToString()}");
FieldValue[] compression = tif.GetFieldDefaulted(TiffTag.COMPRESSION);
Console.WriteLine($"Compression: {(Compression)compression[0].ToInt()}");
FieldValue[] xres = tif.GetField(TiffTag.XRESOLUTION);
if (xres != null)
Console.WriteLine($"X resolution: {xres[0].ToFloat()}");
Tips#
- For black-and-white scans set
BITSPERSAMPLEto 1,PHOTOMETRICtoPhotometric.MINISWHITEandCOMPRESSIONtoCompression.CCITTFAX4; pack 8 pixels per byte. Tiff.ClientOpenreads from and writes to anyStream, e.g. aMemoryStream.- The repository’s Samples folder has examples for tiles, JPEG, conversion to and from
System.Drawing.Bitmapand more.