Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 39 additions & 46 deletions LibTiff/Internal/Tiff_DirWrite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ partial class Tiff
// extended to RewriteDirectory() etc.
private ulong PenultimateDirectoryOffset { get; set; }

private ulong m_subifdArrayOffset = 0; // Dateioffset des SubIFD-Offsets-Arrays
private int m_subifdElemSize = 0; // 4 (Classic TIFF) oder 8 (BigTIFF)
private int m_subifdWriteIndex = 0; // 0 .. (td_nsubifd - 1)

private ulong insertData(TiffType type, int v)
{
int t = (int)type;
Expand Down Expand Up @@ -137,7 +141,7 @@ private bool writeDirectory(bool done)
// and link it into the existing directory structure.
if (m_diroff == 0 && !linkDirectory())
return false;

// Size the directory so that we can calculate offsets for the data
// items that aren't kept in-place in each field.
nfields = 0;
Expand Down Expand Up @@ -165,6 +169,28 @@ private bool writeDirectory(bool done)
m_curdir++;
int dir = 0;

if (m_dir.td_nsubifd > 0)
{
m_subifdElemSize = (m_header.tiff_version == TIFF_BIGTIFF_VERSION) ? 8 : 4;
m_subifdWriteIndex = 0;
m_subifdArrayOffset = m_dataoff;

ulong total = (ulong)m_dir.td_nsubifd * (ulong)m_subifdElemSize;
if (total > 0)
{
byte[] zeros = new byte[total];
seekFile((long)m_subifdArrayOffset, SeekOrigin.Begin);
if (!writeOK(zeros, 0, (int)total))
{
ErrorExt(this, m_clientdata, m_name, "Error reserving SubIFD offset array");
return false;
}

m_dataoff += (ulong)((total + 1UL) & ~1UL); // 2-Byte Alignment
seekFile((long)m_dataoff, SeekOrigin.Begin);
}
}

// Setup external form of directory entries and write data items.
int[] fields = new int[FieldBit.SetLongs];
Buffer.BlockCopy(m_dir.td_fieldsset, 0, fields, 0, FieldBit.SetLongs * sizeof(int));
Expand Down Expand Up @@ -317,51 +343,18 @@ private bool writeDirectory(bool done)
break;
case FieldBit.SubIFD:
data[dir].tdir_tag = fip.Tag;
data[dir].tdir_count = (int)m_dir.td_nsubifd;

// Total hack: if this directory includes a SubIFD
// tag then force the next <n> directories to be
// written as "sub directories" of this one. This
// is used to write things like thumbnails and
// image masks that one wants to keep out of the
// normal directory linkage access mechanism.
data[dir].tdir_count = m_dir.td_nsubifd;
data[dir].tdir_type = (m_header.tiff_version == TIFF_BIGTIFF_VERSION)
? TiffType.IFD8
: TiffType.LONG;
data[dir].tdir_offset = m_subifdArrayOffset;

if (data[dir].tdir_count > 0)
{
m_flags |= TiffFlags.INSUBIFD;
m_nsubifd = (short)data[dir].tdir_count;
if (data[dir].tdir_count > 1)
{
m_subifdoff = data[dir].tdir_offset;
}
else
{
if ((m_flags & TiffFlags.ISBIGTIFF) == TiffFlags.ISBIGTIFF)
{
m_subifdoff = m_diroff + sizeof(long) +
(ulong)dir * (ulong)TiffDirEntry.SizeInBytes(m_header.tiff_version == TIFF_BIGTIFF_VERSION) +
sizeof(short) * 2 + sizeof(long);
}
else
{
m_subifdoff = m_diroff + sizeof(short) +
(ulong)dir * (ulong)TiffDirEntry.SizeInBytes(m_header.tiff_version == TIFF_BIGTIFF_VERSION) +
sizeof(short) * 2 + sizeof(int);
}
}
}

if ((m_flags & TiffFlags.ISBIGTIFF) == TiffFlags.ISBIGTIFF)
{
data[dir].tdir_type = TiffType.IFD8;
if (!writeLong8Array(ref data[dir], m_dir.td_subifd))
return false;
}
else
{
data[dir].tdir_type = TiffType.LONG;
if (!writeLongArray(ref data[dir], LongToInt(m_dir.td_subifd)))
return false;
}
break;
default:
// XXX: Should be fixed and removed.
Expand Down Expand Up @@ -1783,21 +1776,21 @@ private bool linkDirectory()

if ((m_flags & TiffFlags.INSUBIFD) == TiffFlags.INSUBIFD)
{
seekFile((long)m_subifdoff, SeekOrigin.Begin);
ulong target = m_subifdArrayOffset + (ulong)(m_subifdWriteIndex * m_subifdElemSize);
seekFile((long)target, SeekOrigin.Begin);
if (!writeDirOffOK((long)diroff, m_header.tiff_version == TIFF_BIGTIFF_VERSION))
{
ErrorExt(this, m_clientdata, module,
"{0}: Error writing SubIFD directory link", m_name);
return false;
}

// Advance to the next SubIFD or, if this is the last one
// configured, revert back to the normal directory linkage.
// Advance to the next SubIFD slot or, if this is the last one,
// revert back to the normal directory linkage.
m_subifdWriteIndex++;
--m_nsubifd;

if (m_nsubifd != 0)
m_subifdoff += sizeof(int);
else
if (m_nsubifd == 0)
m_flags &= ~TiffFlags.INSUBIFD;

return true;
Expand Down
111 changes: 111 additions & 0 deletions UnitTests/PyramidTiffTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System;
using System.IO;
using BitMiracle.LibTiff.Classic;
using NUnit.Framework;

namespace UnitTests
{
public static class TestImages
{
public static byte[] CreateTileBytes(int tileSize, int channels)
{
// Size * size pixels * channels, interleaved
var arr = new byte[tileSize * tileSize * channels];
for (int i = 0; i < arr.Length; i++)
arr[i] = (byte)(i % 256);
return arr;
}
}

[TestFixture]
public class PyramidTiffTests
{
private const int TileSize = 256;

[TestCase(1)]
[TestCase(2)]
public void CanCreateAndReadPyramidalTiffWithSubIfds_ParameterizedChannels(int channelCount)
{
string path = Path.Combine(TestContext.CurrentContext.WorkDirectory,
$"pyramid_test_{channelCount}ch.tif");

if (File.Exists(path))
File.Delete(path);

// ---------- WRITE ----------
using (var tiff = Tiff.Open(path, "w"))
{
// IFD0
WriteLevel(tiff, 1024, 1024, channelCount);
tiff.SetField(TiffTag.SUBIFD, 2, new long[2]);
tiff.WriteDirectory();

// SubIFD #1
tiff.CreateDirectory();
WriteLevel(tiff, 512, 512, channelCount);
tiff.WriteDirectory();

// SubIFD #2
tiff.CreateDirectory();
WriteLevel(tiff, 256, 256, channelCount);
tiff.WriteDirectory();
}

// ---------- READ ----------
using var read = Tiff.Open(path, "r");

// IFD0
Assert.AreEqual(1024, read.GetField(TiffTag.IMAGEWIDTH)[0].ToInt());
Assert.AreEqual(1024, read.GetField(TiffTag.IMAGELENGTH)[0].ToInt());
Assert.AreEqual(channelCount, read.GetField(TiffTag.SAMPLESPERPIXEL)[0].ToInt(),
"IFD0 channel count mismatch");

var field = read.GetField(TiffTag.SUBIFD);
Assert.NotNull(field, "Missing SUBIFD tag");

long[] subIfds = (long[])field[1].Value;
Assert.AreEqual(2, subIfds.Length);

// SubIFD1
read.SetSubDirectory(subIfds[0]);
Assert.AreEqual(512, read.GetField(TiffTag.IMAGEWIDTH)[0].ToInt());
Assert.AreEqual(512, read.GetField(TiffTag.IMAGELENGTH)[0].ToInt());
Assert.AreEqual(channelCount, read.GetField(TiffTag.SAMPLESPERPIXEL)[0].ToInt(),
"SubIFD1 channel count mismatch");

// SubIFD2
read.SetSubDirectory(subIfds[1]);
Assert.AreEqual(256, read.GetField(TiffTag.IMAGEWIDTH)[0].ToInt());
Assert.AreEqual(256, read.GetField(TiffTag.IMAGELENGTH)[0].ToInt());
Assert.AreEqual(channelCount, read.GetField(TiffTag.SAMPLESPERPIXEL)[0].ToInt(),
"SubIFD2 channel count mismatch");
}

private static void WriteLevel(Tiff tiff, int width, int height, int channelCount)
{
tiff.SetField(TiffTag.IMAGEWIDTH, width);
tiff.SetField(TiffTag.IMAGELENGTH, height);
tiff.SetField(TiffTag.BITSPERSAMPLE, 8);
tiff.SetField(TiffTag.SAMPLESPERPIXEL, channelCount);
tiff.SetField(TiffTag.PHOTOMETRIC, Photometric.MINISBLACK);
tiff.SetField(TiffTag.PLANARCONFIG, PlanarConfig.CONTIG);

tiff.SetField(TiffTag.TILEWIDTH, TileSize);
tiff.SetField(TiffTag.TILELENGTH, TileSize);

int tilesX = (width + TileSize - 1) / TileSize;
int tilesY = (height + TileSize - 1) / TileSize;

byte[] tile = TestImages.CreateTileBytes(TileSize, channelCount);

for (int ty = 0; ty < tilesY; ty++)
{
for (int tx = 0; tx < tilesX; tx++)
{
int tileIndex = tiff.ComputeTile(tx * TileSize, ty * TileSize, 0, 0);
tiff.WriteEncodedTile(tileIndex, tile, tile.Length);
}
}
}
}
}