Stream (and convert?) multi-page TIFF using ASP.NET

toddk picture toddk · Aug 10, 2009 · Viewed 7.9k times · Source

I have built a simple image viewer in .NET and have the requirement to display multi-frame TIFF images in the browser. Presently, I have a (ashx) handler setup to stream back JPEGs that are co-mingled in the same database as the multi-frame TIFF's and it's worth mentioning that this handler will also return the first frame of the TIFF file in its current state. In the VB.NET code below (part of the handler) I am able to identify if a TIFF file has multiple frames and I started attempting to stitch the frames together but have not had any success yet. Has anyone returned multi-frame TIFF's using a similar approach? Note: I used the How to open a multi-frame TIFF image as a reference when developing the code below.

        context.Response.Cache.SetCacheability(HttpCacheability.NoCache)
        context.Response.Cache.SetNoStore()
        context.Response.Cache.SetExpires(DateTime.MinValue)

        imageList = GetPhoto(picid)
        If (imageList IsNot Nothing) Then
            Dim img As Image
            Dim prevImageHeight = 0
            For Each img In imageList
                Dim imgGraphics As Graphics = Graphics.FromImage(img)
                imgGraphics.DrawImage(img, 0, prevImageHeight, img.Width, img.Height * imageList.Count)
                prevImageHeight += img.Height
                img.Save(context.Response.OutputStream, ImageFormat.Jpeg)
                img.Dispose()
            Next img
        Else
            ' Return 404
            context.Response.StatusCode = 404
            context.Response.End()
        End If

Here is the code for the GetPhoto function:

Public Function GetPhoto(ByVal id As String) As List(Of Image)
    Dim db As New UtilDb
    Dim imageLocation As String
    Dim errMsg As String = ""
    Dim imageList As New List(Of Image)
    Dim returnImage As Bitmap = Nothing
    imageLocation = GetFileName(id)

    If (imageLocation IsNot Nothing) Then
        Dim iFile As Image = Image.FromFile(imageLocation)
        If (imageLocation.ToUpper.EndsWith("TIF")) Then
            Dim frameCount As Integer = iFile.GetFrameCount(FrameDimension.Page)
            Dim i As Integer
            If (frameCount > 1) Then
                For i = 0 To frameCount - 1
                    iFile.SelectActiveFrame(FrameDimension.Page, i)
                    returnImage = New Bitmap(iFile, iFile.Width * 0.4, iFile.Height * 0.4)
                    imageList.Add(returnImage)
                Next i
            Else
                returnImage = New Bitmap(iFile, iFile.Width * 0.4, iFile.Height * 0.4)
                imageList.Add(returnImage)
            End If

        Else
            Dim scaledWidth As Integer = (iFile.Width / iFile.Height) * 480
            returnImage = New Bitmap(iFile, scaledWidth, 480)
            imageList.Add(returnImage)
        End If
        iFile.Dispose()
    End If
    Return imageList
End Function

Is it possible to place each frame of a multi-frame TIFF in a contiguous image and send it back to the browser? Should I be focusing my energy on converting the mutli-frame TIFF to another format such as PDF? I essentially do not have a budget for purchasing conversion packages...Any help or guidance would be greatly appreciated!

Answer

toddk picture toddk · Aug 11, 2009

So the solution ended up being really simple - I realized that saving each frame individually to the response stream was the primary reason why the top frame was the only frame being rendered in the browser.

Here's a snippet from the function I wrote to gather all of the required parameters from an image (albeit multi-frame TIFF's, single-frame TIFF's, or JPEGs):

Dim iFile As Image = Image.FromFile(imageLocation)
Dim frameCount As Integer = iFile.GetFrameCount(FrameDimension.Page)
Dim totalWidth, totalHeight As Integer

If (imageLocation.ToUpper.EndsWith("TIF")) Then
    Dim i As Integer
    If (frameCount > 1) Then
        totalWidth = 0
        totalHeight = 0
        For i = 0 To frameCount - 1
            iFile.SelectActiveFrame(FrameDimension.Page, i)
            imageStructure.totalWidth = Math.Max(totalWidth, (iFile.Width * 0.4))
            imageStructure.totalHeight += (iFile.Height * 0.4)
            returnImage = New Bitmap(iFile, iFile.Width * 0.4, iFile.Height * 0.4)
            imageList.Add(returnImage)
        Next i
     Else
        returnImage = New Bitmap(iFile, iFile.Width * 0.4, iFile.Height * 0.4)
        imageStructure.totalWidth = (iFile.Width * 0.4)
        imageStructure.totalHeight = (iFile.Height * 0.4)
        imageList.Add(returnImage)
     End If

 Else
    Dim scaledWidth As Integer = (iFile.Width / iFile.Height) * defaultHeight
    returnImage = New Bitmap(iFile, scaledWidth, defaultHeight)
    imageStructure.totalWidth = scaledWidth
    imageStructure.totalHeight = defaultHeight
    imageList.Add(returnImage)
 End If
 iFile.Dispose()
 imageStructure.frameCount = frameCount
 imageStructure.frameList = imageList

Here's a snippet from the code that renders the images:

If (imageStructure.frameCount > 1) Then
   'We know we have a multi-frame TIFF
   Dim appendedImage As Bitmap = New Bitmap(imageStructure.totalWidth, imageStructure.totalHeight)
   imgGraphics = Graphics.FromImage(appendedImage)
   Dim prevHeight As Integer = 0
   For Each img In imageStructure.frameList
         imgGraphics.DrawImage(img, 0, prevHeight, img.Width, img.Height)
         prevHeight += img.Height
         img.Dispose()
   Next
   appendedImage.Save(context.Response.OutputStream, ImageFormat.Jpeg)
   appendedImage.Dispose()
Else
    ' JPEG or single frame TIFF
    img = imageStructure.frameList(0)
    imgGraphics = Graphics.FromImage(img)
    imgGraphics.DrawImage(img, 0, 0, img.Width, img.Height)
    img.Save(context.Response.OutputStream, ImageFormat.Jpeg)
    img.Dispose()
 End If

Note: The imageStructure variable is a trivial structure that stores the total width, height, number of frames, and a list of images representing each frame.

Now I just have some refactoring to do and I'll be all set! I hope someone else finds this useful...