Search code examples
asp.netvb.nettiffihttphandler

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


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!


Solution

  • 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...