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