Create Multipage Tiff in a loop

dgorka

Well-known member
Joined
Dec 27, 2006
Messages
88
Programming Experience
5-10
So I have an app that walks directories and creates multipage tiffs based on what the directory is named. They're named with a 3 letter prefix _ a random number. I'm trying to create the multipage tiff named with the 3 letter prefix. Here is my code:

VB.NET:
Private Sub appendTiffs(ByVal p_OutputPath As String, ByVal p_Tiff As String, ByVal p_docType As String)

        Dim tiff As Image = Image.FromFile(p_Tiff)
        MasterBitmap = New Bitmap(tiff.Width, tiff.Height, PixelFormat.Format1bppIndexed)

        For Each ice As ImageCodecInfo In ImageCodecInfo.GetImageEncoders
            If ice.MimeType = "image/tiff" Then
                info = ice
            End If
        Next

          ep.Param(0) = New EncoderParameter(enc, CType(EncoderValue.MultiFrame, Long))
        If p_docType <> oldDocType Then
            MasterBitmap = CType(tiff, Bitmap)
            MasterBitmap.Save(p_OutputPath & "\" & p_docType & ".tif", info, ep)
            MasterBitmap.Dispose()
        Else
            MasterBitmap = CType(Image.FromFile(p_OutputPath & "\" & p_docType & ".tif"), Bitmap)
            [COLOR="Red"]MasterBitmap.Save(p_OutputPath & "\" & p_docType & ".tif", info, ep)[/COLOR]
            ep.Param(0) = New EncoderParameter(enc, CType(EncoderValue.FrameDimensionPage, Long))
            MasterBitmap.SaveAdd(CType(Image.FromFile(p_Tiff), Bitmap), ep)
            ep.Param(0) = New EncoderParameter(enc, CType(EncoderValue.Flush, Long))
            MasterBitmap.SaveAdd(ep)
        End If

        oldDocType = p_docType

        tiff.Dispose()

    End Sub

The line that's red is the line thats erroring out. I think its because I'm trying to load the image, then save to that same place. I'm not really sure how to fix this problem. Anyone have any ideas on how I could fix this?

Oh, this sub is called on every iteration of the loop when it finds a tiff.

I almost forgot to add this, each folder has 1 singlepage tiff in it. And there can be multiple folders with the same 3 letter prefix in a directory, hence why I'm looping this and checking the docType variables.
 
Last edited:
maybe MasterBitmap.Dispose() too ?
 
Do you mean to add in another one? Or that the one in the first part of the IF statement may be causing problems?
 
I don't see that you have disposed MasterBitmap, I think you should.
 
I see what you're saying. The only problem is that its erroring out the first time it comes to the Else, and I've disposed of MasterBitmap on the previous itteration, so this time it should already be nothing.

Just to try it out I added it as the first thing that happens in the Else, and it still errored. I really wish GDI would give an error other than "A generic error has occured in GDI+".
 
Just to try it out I added it as the first thing that happens in the Else
It is not smart to dispose a resource just before you need to use it ;) You dispose when you are finished using the resource.

Here's some code that works in my test, it's freely extracted from Adding frames to a Multi-Frame TIFF. It's really amazing how much Generic Error you can get when working with Tiffs. I'm not really sure what your method is supposed to do, it's named "appendTiffs" and you have code there both for first frame (new image) and adding frames. My thought was that you wanted a method that added an image frame to a tiff file, if the file didn't exist you created a new file, if it existed you added to the frames. Since all else result in "Generic Error" and I have found no particular documentation for this, I do similar to the mentioned link, if file exist I extract all frames, add the new image frame, then write out the full multiframe file like the 'standard' examples you find all over.
VB.NET:
'Imports System.Drawing.Imaging

Sub SaveAddTiff(ByVal img As Image, ByVal filename As String)
    If Not IO.File.Exists(filename) Then
        img.Save(filename, Imaging.ImageFormat.Tiff)
    Else
        Dim frames As List(Of Image) = getFrames(filename)
        frames.Add(img)
        SaveMultiTiff(frames.ToArray, filename)
    End If
    img.Dispose()
End Sub

Sub SaveMultiTiff(ByVal frames() As Image, ByVal filename As String)
    Dim codec As ImageCodecInfo = getTiffCodec()
    Dim enc As Encoder = Encoder.SaveFlag
    Dim ep As New EncoderParameters(2)
    ep.Param(0) = New EncoderParameter(enc, CLng(EncoderValue.MultiFrame))
    ep.Param(1) = New EncoderParameter(Encoder.Compression, CLng(EncoderValue.CompressionNone))
    Dim tiff As Image = frames(0)
    tiff.Save(filename, codec, ep)
    ep.Param(0) = New EncoderParameter(enc, CLng(EncoderValue.FrameDimensionPage))
    For i As Integer = 1 To frames.Length - 1
        tiff.SaveAdd(frames(i), ep)
        frames(i).Dispose()
    Next
    ep.Param(0) = New EncoderParameter(enc, CLng(EncoderValue.Flush))
    tiff.SaveAdd(ep)
    tiff.Dispose()
End Sub

Function getTiffCodec() As ImageCodecInfo
    For Each ice As ImageCodecInfo In ImageCodecInfo.GetImageEncoders()
        If ice.MimeType = "image/tiff" Then
            Return ice
        End If
    Next
    Return Nothing
End Function

Function getFrames(ByVal filename) As List(Of Image)
    Dim frames As New List(Of Image)
    Dim img As Image = Image.FromFile(filename)
    For i As Integer = 0 To img.GetFrameCount(Imaging.FrameDimension.Page) - 1
        img.SelectActiveFrame(Imaging.FrameDimension.Page, i)
        Dim tmp As New Bitmap(img.Width, img.Height)
        Dim g As Graphics = Graphics.FromImage(tmp)
        g.CompositingQuality = Drawing2D.CompositingQuality.HighQuality
        g.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic
        g.PixelOffsetMode = Drawing2D.PixelOffsetMode.HighQuality
        g.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
        g.TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAliasGridFit
        g.DrawImageUnscaled(img, 0, 0)
        frames.Add(tmp)
        g.Dispose()
    Next
    img.Dispose()
    Return frames
End Function
If you happen to have access to all images that is to be written to the multiframe tiff during the runtime, you can just stack them up in a collection and write them out flat when ready, it will save a little resources if you don't have to read the frames from an existing file before each frame you add. Anyway the codes above should get you some ideas.
 
Last edited:
Thanks John!

I'll have to tweak this a little bit, but I think it will work. I actually got my code from the same place, but I think I was just so focused on trying to get it to work my way that I completely over looked doing it like this. So thanks. :)
 
Finally got around to testing this code out and it works great! Thanks John. I had to add in one line to the getFrames function to preserve the DPI of the images, but otherwise it works great.

Now I need to convert it to group 4 compression, which I have code to do, its just finding where to call it.

Again, thanks John. This was something that I had been struggling with for quite a while.
 
Say, about how fast does yours run? Because mine is only doing around 12.2 pages per minute, and that seems horribly slow to me.
 
Saving 12 high-res images to a multipage-tiff takes 5 seconds here (*). 12 calls to SaveAddTiff method in succession to add the same images takes around a minute, but the results is not comparable as there is no reason to do that. I've edited the code sample in previous post so you can see this more clearly, the pure saving part was put in SaveMultiTiff method, so if you have a collection of images you want to put in a multipage tiff you call this method, not SaveAddTiff which needs to read existing frames first for each call.

(*) I just found that I'm down to 3 seconds and smaller files too if I specify no compression, other compression doesn't seem to work at all here. (I added this to the code also just for reference.)

PS - depending on where the images come from you can save lots of memory too by for example running the Image.FromFile loop inside your save method.
 
John, I know its been a while since we talked about this code, but this morning I realized that it is saving as no compression. If I change ep from accepting 2 to only accepting 1, and get rid of the ep.Param(1) line, it saves them as LZW.

However, if I set it up to save in group 4 like so:

VB.NET:
Sub SaveMultiTiff(ByVal frames() As Image, ByVal filename As String)
    Dim codec As ImageCodecInfo = getTiffCodec()
    Dim enc As Encoder = Encoder.SaveFlag
    Dim ep As New EncoderParameters(2)
    ep.Param(0) = New EncoderParameter(enc, CLng(EncoderValue.MultiFrame))
    [COLOR="Red"][B]ep.Param(1) = New EncoderParameter(Encoder.Compression, CLng(EncoderValue.CompressionCCITT4))[/B][/COLOR]
    Dim tiff As Image = frames(0)
    tiff.Save(filename, codec, ep)
    ep.Param(0) = New EncoderParameter(enc, CLng(EncoderValue.FrameDimensionPage))
    For i As Integer = 1 To frames.Length - 1
        tiff.SaveAdd(frames(i), ep)
        frames(i).Dispose()
    Next
    ep.Param(0) = New EncoderParameter(enc, CLng(EncoderValue.Flush))
    tiff.SaveAdd(ep)
    tiff.Dispose()
End Sub

It gives me a "Parameter is not valid" error on the tiff.Save(filename, codec, ep) line. Any idea how I can get around this and save them as Group4 Tiffs? I have a somewhat lengthy function (as defined here) that I could use, but if it's something that I can change in one or two lines, I'd rather do that.
 
The Ccitt3, Ccitt4, and Rle require that the PixelFormat value be set to BlackWhite.
Is this true for your images?
 
When i run the code and look at tiff.PixelFormat it says its set to Format32bppArgb. I would want it at Format1bppIndexed right?

How can i set this since PixelFormat is read only? Or am i looking at the wrong thing?
 
You can't "set" it to pixelformat, you have to convert the image. Search for "vb.net Format1bppIndexed" and you should be able to find a converter.
 
Very helpful

It is not smart to dispose a resource just before you need to use it ;) You dispose when you are finished using the resource.

Here's some code that works in my test, it's freely extracted from Adding frames to a Multi-Frame TIFF. It's really amazing how much Generic Error you can get when working with Tiffs. I'm not really sure what your method is supposed to do, it's named "appendTiffs" and you have code there both for first frame (new image) and adding frames. My thought was that you wanted a method that added an image frame to a tiff file, if the file didn't exist you created a new file, if it existed you added to the frames. Since all else result in "Generic Error" and I have found no particular documentation for this, I do similar to the mentioned link, if file exist I extract all frames, add the new image frame, then write out the full multiframe file like the 'standard' examples you find all over.
VB.NET:
'Imports System.Drawing.Imaging

Sub SaveAddTiff(ByVal img As Image, ByVal filename As String)
    If Not IO.File.Exists(filename) Then
        img.Save(filename, Imaging.ImageFormat.Tiff)
    Else
        Dim frames As List(Of Image) = getFrames(filename)
        frames.Add(img)
        SaveMultiTiff(frames.ToArray, filename)
    End If
    img.Dispose()
End Sub

Sub SaveMultiTiff(ByVal frames() As Image, ByVal filename As String)
    Dim codec As ImageCodecInfo = getTiffCodec()
    Dim enc As Encoder = Encoder.SaveFlag
    Dim ep As New EncoderParameters(2)
    ep.Param(0) = New EncoderParameter(enc, CLng(EncoderValue.MultiFrame))
    ep.Param(1) = New EncoderParameter(Encoder.Compression, CLng(EncoderValue.CompressionNone))
    Dim tiff As Image = frames(0)
    tiff.Save(filename, codec, ep)
    ep.Param(0) = New EncoderParameter(enc, CLng(EncoderValue.FrameDimensionPage))
    For i As Integer = 1 To frames.Length - 1
        tiff.SaveAdd(frames(i), ep)
        frames(i).Dispose()
    Next
    ep.Param(0) = New EncoderParameter(enc, CLng(EncoderValue.Flush))
    tiff.SaveAdd(ep)
    tiff.Dispose()
End Sub

Function getTiffCodec() As ImageCodecInfo
    For Each ice As ImageCodecInfo In ImageCodecInfo.GetImageEncoders()
        If ice.MimeType = "image/tiff" Then
            Return ice
        End If
    Next
    Return Nothing
End Function

Function getFrames(ByVal filename) As List(Of Image)
    Dim frames As New List(Of Image)
    Dim img As Image = Image.FromFile(filename)
    For i As Integer = 0 To img.GetFrameCount(Imaging.FrameDimension.Page) - 1
        img.SelectActiveFrame(Imaging.FrameDimension.Page, i)
        Dim tmp As New Bitmap(img.Width, img.Height)
        Dim g As Graphics = Graphics.FromImage(tmp)
        g.CompositingQuality = Drawing2D.CompositingQuality.HighQuality
        g.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic
        g.PixelOffsetMode = Drawing2D.PixelOffsetMode.HighQuality
        g.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
        g.TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAliasGridFit
        g.DrawImageUnscaled(img, 0, 0)
        frames.Add(tmp)
        g.Dispose()
    Next
    img.Dispose()
    Return frames
End Function
If you happen to have access to all images that is to be written to the multiframe tiff during the runtime, you can just stack them up in a collection and write them out flat when ready, it will save a little resources if you don't have to read the frames from an existing file before each frame you add. Anyway the codes above should get you some ideas.



Dude this code was so helpful to me but I needed to do it in a memory stream instead of a file. Here is the modified code to use a memory stream. I'm telling you this code saved my life. Thanks so much.

Sub SaveAddTiff(ByVal img As Image, ByVal filename As MemoryStream)
If filename.Length = 0 Then
img.Save(filename, System.Drawing.Imaging.ImageFormat.Tiff)
Else
Dim frames As List(Of Image) = getFrames(filename)
frames.Add(img)
SaveMultiTiff(frames.ToArray, filename)
End If
img.Dispose()
End Sub

Sub SaveMultiTiff(ByVal frames() As Image, ByVal filename As MemoryStream)
Dim codec As ImageCodecInfo = getTiffCodec()
Dim enc As Encoder = Encoder.SaveFlag
Dim ep As New EncoderParameters(2)
ep.Param(0) = New EncoderParameter(enc, CLng(EncoderValue.MultiFrame))
ep.Param(1) = New EncoderParameter(Encoder.Compression, CLng(EncoderValue.CompressionNone))
Dim tiff As Image = frames(0)
tiff.Save(filename, codec, ep)
ep.Param(0) = New EncoderParameter(enc, CLng(EncoderValue.FrameDimensionPage))
For i As Integer = 1 To frames.Length - 1
tiff.SaveAdd(frames(i), ep)
frames(i).Dispose()
Next
ep.Param(0) = New EncoderParameter(enc, CLng(EncoderValue.Flush))
tiff.SaveAdd(ep)
tiff.Dispose()
End Sub

Function getTiffCodec() As ImageCodecInfo
For Each ice As ImageCodecInfo In ImageCodecInfo.GetImageEncoders()
If ice.MimeType = "image/tiff" Then
Return ice
End If
Next
Return Nothing
End Function

Function getFrames(ByVal filename) As List(Of Image)
Dim frames As New List(Of Image)
Dim img As Image = Image.FromStream(filename)
For i As Integer = 0 To img.GetFrameCount(Imaging.FrameDimension.Page) - 1
img.SelectActiveFrame(Imaging.FrameDimension.Page, i)
Dim tmp As New Bitmap(img.Width, img.Height)
Dim g As Graphics = Graphics.FromImage(tmp)
g.CompositingQuality = Drawing2D.CompositingQuality.HighQuality
g.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic
g.PixelOffsetMode = Drawing2D.PixelOffsetMode.HighQuality
g.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
g.TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAliasGridFit
g.DrawImageUnscaled(img, 0, 0)
frames.Add(tmp)
g.Dispose()
Next
img.Dispose()
Return frames
End Function




 
Back
Top