Qué Padre
Qué Padre

Reputation: 2083

Create multipage TIFF with JPEG compression using .NET

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

Answers (1)

marsze
marsze

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

Related Questions