Reputation: 2083
Is there any way to create multipage TIFF with JPEG compression using .NET?
I can create TIFF's with LZW compression, but files are extremely big. Looks like EncoderValue
enumeration (which I use to set up compression) doesn't even have suitable member.
Upvotes: 5
Views: 8051
Reputation: 17035
You can have a look at >> this post, where I explain how to wrap existing JPEGs in a simple multi-page TIFF container.
But there's a variety of other possibilities. FreeImage.Net (freeimage.sourceforge.net) is a very powerful library. You can create a simple JPEG-compressed TIFF like this:
using FreeImageAPI;
// [...]
FIBITMAP image = FreeImage.Load(FREE_IMAGE_FORMAT.FIF_UNKNOWN, "filename", FREE_IMAGE_LOAD_FLAGS.DEFAULT);
FreeImage.Save(FREE_IMAGE_FORMAT.FIF_TIFF, image, "filename", FREE_IMAGE_SAVE_FLAGS.TIFF_JPEG)
Creating multi-page TIFFs is a little more tricky. As far as I know, multi-page editing is only possible on the harddrive. (I've heard there's another way in a newer version though, so maybe play around with it a little bit.) However, you may use sth like this:
using System.IO;
using FreeImageAPI;
// [...]
public byte[] MergeTiffs(List<byte[]> tiffs)
{
byte[] multiPageTiff = null;
string tmpfile = "... chose any file name ...";
MemoryStream singleStream = null;
FIBITMAP fib = FIBITMAP.Zero;
FIMULTIBITMAP fmb = FIMULTIBITMAP.Zero;
using (singleStream = new MemoryStream(tiffs[0])
{
fib = FreeImage.LoadFromStream(singleStream);
FreeImage.SaveEx(fib, dateiname, FREE_IMAGE_FORMAT.FIF_TIFF, FREE_IMAGE_SAVE_FLAGS.TIFF_JPEG);
FreeImage.UnloadEx(ref fib);
}
fmb = FreeImage.OpenMultiBitmap(
FREE_IMAGE_FORMAT.FIF_TIFF,
tmpfile,
false,
false,
false,
FREE_IMAGE_LOAD_FLAGS.TIFF_JPEG);
for (int i = 1; i < tiffs.Count; i++)
{
using (singleStream = new MemoryStream(tiffs[i])
{
fib = FreeImage.LoadFromStream(singleStream);
FreeImage.AppendPage(fmb, fib);
}
}
FreeImage.CloseMultiBitmapEx(ref fmb);
multiPageTiff = File.ReadAllBytes(tmpfile);
File.Delete(tmpfile);
return file;
}
Apart from that, I recently found a simple way to merge existing TIFFs with LibTiff.Net (bitmiracle.com), without changing their raw data (so you neither lose quality nor increase their sizes). There might be an easier way (maybe you find one), but I used the following code (based on this example):
using System.IO
using BitMiracle.LibTiff.Classic;
// [...]
/// <summary>
/// Merges multiple TIFFs into one multi-page TIFF
/// </summary>
/// <param name="tiffs">The TIFFs' raw data (can also be multi-page)</param>
/// <returns></returns>
public static byte[] MergeTiffs(List<byte[]> tiffs)
{
// the byteStream will contain the merged tiff's raw data
MemoryStream byteStream = new MemoryStream();
// create the output-TIFF (empty stream)
using (Tiff output = Tiff.ClientOpen("InMemory", "w", byteStream, new TiffStream()))
{
for (short i = 0; i < tiffs.Count; i++)
{
// provide input-TIFF as custom TiffStream, with byteStream (output-TIFF) as output
TiffStreamForBytes tiffStream = new TiffStreamForBytes(tiffs[i]);
using (Tiff input = Tiff.ClientOpen("bytes", "r", null, tiffStream))
{
// *** now copy all the TIFF-data: ***
// copy all directories (= all pages)
int numberOfDirectories = input.NumberOfDirectories();
for (short d = 0; d < numberOfDirectories; ++d)
{
// set this as the current directory (to work in)
input.SetDirectory(d);
// copy all tags
for (ushort t = ushort.MinValue; t < ushort.MaxValue; ++t)
{
TiffTag tag = (TiffTag)t;
FieldValue[] tagValue = input.GetField(tag);
if (tagValue != null)
output.GetTagMethods().SetField(output, tag, tagValue);
}
// copy all strips
int numberOfStrips = input.NumberOfStrips();
int stripSize = input.StripSize();
for (int s = 0; s < numberOfStrips; ++s)
{
// buffer for the current strip
byte[] stripData = new byte[stripSize];
// read strip from input image (not decompressed)
int length = input.ReadRawStrip(s, stripData, 0, stripData.Length);
// write strip to output image (uncompressed)
output.WriteRawStrip(s, stripData, 0, length);
}
// add the new directory to output image
output.WriteDirectory();
}
}
}
}
// return the new TIFF as byte array
return byteStream.ToArray();
}
/// <summary>
/// Custom read-only stream for byte buffer that can be used
/// with Tiff.ClientOpen method.
/// </summary>
private class TiffStreamForBytes : TiffStream
{
private byte[] m_bytes;
private int m_position;
public TiffStreamForBytes(byte[] bytes)
{
m_bytes = bytes;
m_position = 0;
}
public override int Read(object clientData, byte[] buffer, int offset, int count)
{
if ((m_position + count) > m_bytes.Length)
return -1;
Buffer.BlockCopy(m_bytes, m_position, buffer, offset, count);
m_position += count;
return count;
}
public override void Write(object clientData, byte[] buffer, int offset, int count)
{
throw new InvalidOperationException("This stream is read-only");
}
public override long Seek(object clientData, long offset, SeekOrigin origin)
{
switch (origin)
{
case SeekOrigin.Begin:
if (offset > m_bytes.Length)
return -1;
m_position = (int)offset;
return m_position;
case SeekOrigin.Current:
if ((offset + m_position) > m_bytes.Length)
return -1;
m_position += (int)offset;
return m_position;
case SeekOrigin.End:
if ((m_bytes.Length - offset) < 0)
return -1;
m_position = (int)(m_bytes.Length - offset);
return m_position;
}
return -1;
}
public override void Close(object clientData)
{
// nothing to do
}
public override long Size(object clientData)
{
return m_bytes.Length;
}
}
Upvotes: 7