This project has moved. For the latest updates, please go here.

TIFF Correction (color space & bit depth)

Nov 6, 2014 at 1:48 PM
I'm trying to write a function that will convert any TIFF into a set format acceptable by third party imaging software. The accepted formats are bitonal, 8-bit grayscale, and 24-bit RGB. In particular, I'd like to change 32-bit and 16-bit to the expected bit depths as well as non-RGB color spaces (specifically CMYK) to RGB. Here's my current code:
using (var frames = new MagickImageCollection(source))
{
    var isCmyk = frames[0].ColorSpace == ColorSpace.CMYK;
    int depth = frames[0].BitDepth();

    if (depth == 32 || isCmyk)
    {
        foreach (var frame in frames)
        {
            frame.Format = MagickFormat.Tif;
            frame.CompressionMethod = CompressionMethod.LZW;
            frame.ColorSpace = ColorSpace.sRGB;
            frame.Depth = 24;
        }

        frames.RePage();

        return frames.ToByteArray();
    }
    else if (depth == 16)
    {
        foreach (var frame in frames)
        {
            frame.Format = MagickFormat.Tif;
            frame.CompressionMethod = CompressionMethod.LZW;
            frame.ColorSpace = ColorSpace.GRAY;
            frame.Depth = 8;
        }

        frames.RePage();

        return frames.ToByteArray();
    }
    else
    {
        return source;
    }
}
From what I can tell, the problem is that the color space is always sRGB, and the bit depth is always 8, regardless of what I see in the detail properties of the image file.

Am I going about this conversion the wrong way?
Coordinator
Nov 8, 2014 at 9:34 AM
The 'issue' is with this part:
int depth = frames[0].BitDepth();
This method actually calculates the bit depth instead of returning the value that was read. I have updated the IntelliSense help to explain this better. You should use the Depth property of the image if you want the bit depth that was read:
int depth = frames[0].Depth;
Nov 10, 2014 at 11:57 AM
Unfortunately, the Depth property also has a value of 8.
Coordinator
Nov 10, 2014 at 12:14 PM
Can you post a sample image the I can use to reproduce the problem?
Nov 10, 2014 at 12:16 PM
Certainly. How does one attach a file to a post? I have my test images locally stored rather than anywhere that can be reached with a web URL.
Coordinator
Nov 10, 2014 at 1:21 PM
Edited Nov 10, 2014 at 1:27 PM
I use DropBox to share files. You can also contact me through CodePlex to send it by e-mail.
Nov 11, 2014 at 1:46 PM
I've sent you a link to the file through contact. In the meantime, I've changed my approach and have something that seems to work for my needs:
/// <summary>
/// Converts an unspecified image to a format supported by Kofax Capture.
/// </summary>
/// <param name="source">The original image bytes.</param>
/// <returns>The converted TIFF bytes.</returns>
public static byte[] MakeValidKofaxTiff(byte[] source, bool ccittBitonal = false)
{
    using (var frames = new MagickImageCollection(source))
    {
        var colorType = frames[0].ColorType;

        if (colorType == ColorType.Bilevel)
        {
            if (!ccittBitonal)
            {
                SetTiffProfile(frames, ColorSpace.GRAY, ColorType.Bilevel, 1, CompressionMethod.LZW);
            }
            else
            {
                SetTiffProfile(frames, ColorSpace.GRAY, ColorType.Bilevel, 1, CompressionMethod.Group4);
            }
        }
        else if (colorType == ColorType.Grayscale)
        {
            SetTiffProfile(frames, ColorSpace.GRAY, ColorType.Grayscale, 8, CompressionMethod.LZW);
        }
        else
        {
            SetTiffProfile(frames, ColorSpace.sRGB, ColorType.TrueColor, 24, CompressionMethod.LZW);
        }

        frames.RePage();

        return frames.ToByteArray();
    }
}

/// <summary>
/// Sets the TIFF profile for an image.
/// </summary>
/// <param name="frames">A collection of pages for the image.</param>
/// <param name="colorSpace">The color space of the target (eg. RGB, CMYK).</param>
/// <param name="colorType">The color type of the target (eg. Bitonal, Grayscal, Color)</param>
/// <param name="depth">The number of bits used per pixel in the target.</param>
/// <param name="compression">The desired TIFF compression method.</param>
private static void SetTiffProfile(MagickImageCollection frames, ColorSpace colorSpace, ColorType colorType, int depth, CompressionMethod compression)
{
    foreach (var frame in frames)
    {
        frame.Format = MagickFormat.Tif;
        frame.CompressionMethod = compression;
        frame.ColorSpace = colorSpace;
        frame.ColorType = colorType;
        frame.Depth = depth;
    }
}
There's one snag though. One of my test images is a 20MB 32-bit TIFF, and I consistently get a data flush exception on frames.ToByteArray():
ImageMagick.MagickCoderErrorException was unhandled
  HResult=-2146233088
  Message=reci.ImageProcessing.Test.vshost.exe: Error flushing data before directory write. `TIFFWriteDirectorySec' @ error/tiff.c/TIFFErrors/560
  Source=Magick.NET-x86
  StackTrace:
       at ImageMagick.MagickImageCollection.HandleException(MagickException exception)
       at ImageMagick.MagickImageCollection.ToByteArray()
       at reci.ImageProcessing.Magick.ImageTools.MakeValidKofaxTiff(Byte[] source, Boolean ccittBitonal) in C:\TFS\Other\Image Processing\WPF Imaging\codebase\reci.ImageProcessing.Magick\ImageTools.cs:line 61
       at reci.ImageProcessing.Test.TestUtility.<buttonKofaxTiff_Click>b__e(Stream strm) in C:\TFS\Other\Image Processing\WPF Imaging\codebase\reci.ImageProcessing.Test\TestUtility.cs:line 99
       at reci.ImageProcessing.Test.TestUtility.RunCommandOnFile(Func`2 action, String targetExt) in C:\TFS\Other\Image Processing\WPF Imaging\codebase\reci.ImageProcessing.Test\TestUtility.cs:line 118
       at reci.ImageProcessing.Test.TestUtility.buttonKofaxTiff_Click(Object sender, EventArgs e) in C:\TFS\Other\Image Processing\WPF Imaging\codebase\reci.ImageProcessing.Test\TestUtility.cs:line 97
       at System.Windows.Forms.Control.OnClick(EventArgs e)
       at System.Windows.Forms.Button.OnClick(EventArgs e)
       at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
       at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
       at System.Windows.Forms.Control.WndProc(Message& m)
       at System.Windows.Forms.ButtonBase.WndProc(Message& m)
       at System.Windows.Forms.Button.WndProc(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
       at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
       at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
       at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.Run(Form mainForm)
       at reci.ImageProcessing.Test.Program.Main() in C:\TFS\Other\Image Processing\WPF Imaging\codebase\reci.ImageProcessing.Test\Program.cs:line 18
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: ImageMagick.MagickCoderErrorException
       HResult=-2146233088
       Message=reci.ImageProcessing.Test.vshost.exe: IO error writing tag data. `TIFFWriteDirectoryTagData' @ error/tiff.c/TIFFErrors/560
       InnerException: 
Presumably this is due to the size of the file (51 pages at 300x300 DPI, 2550x3300 pixels), but I can easily see such files needing to be run through the conversion. Do you have any tips on how I can do things differently to work around this exception?

Thanks!
Coordinator
Nov 11, 2014 at 2:27 PM
Edited Nov 11, 2014 at 2:27 PM
I apologize for not realizing this earlier but the Depth is per channel, so it is 8 bits per channel * 4 = 32 bits. Is the image you send me the one that is causing you the 'flushing data' problems?
Nov 11, 2014 at 2:45 PM
Ah, per channel makes sense. No, it's a different test image. Unfortunately that file contains personal data from a customer, so I can't share it.
Coordinator
Nov 11, 2014 at 2:58 PM
I am sorry but I cannot help you without a sample image. The error is happening too deep inside the tiff library. Maybe you can edit it in something like Photoshop and remove the sensitive data?
Nov 11, 2014 at 3:05 PM
I found another sample that exhibits the problem and contains generic information. It's in the same FTP folder I gave you earlier, and the name of the file is DocCleanMultipage.tif.
Coordinator
Dec 25, 2014 at 9:49 PM
The problem has been found and will be fixed in the next release of Magick.NET.