除非我使用Image.FromFile,为什么Image.SelectActiveFrame在多页TIFF的最后一帧失败(“在GDI +中出现一般错误”)?

时间:2017-11-10 18:21:02

标签: .net gdi+ tiff

Related SO Answer(这是为什么?)

我正在调用一个webservice(我没有访问源代码),它返回给我一个字节数组,该数组应该代表一个多页TIFF图像。 (方法名称改为保留就业......)

var tiffBytes = _webServiceClient.GetTiff("someIdentifier");

如果我使用标准FileStream将tiffBytes写入文件,然后打开生成的文件,知道什么是TIFF,没有问题,我可以正确浏览页面。当我直接从这个文件创建一个Image对象(Image.FromFile(...))时,我的其余代码也没有问题。这使我相信存储在tiffBytes中的字节实际上是有效的。

如果我将tiffBytes写入MemoryStream,事情会变得奇怪:

//Note that this code works just fine if I create "img" with Image.FromFile()
var frameCount = img.GetFrameCount(FrameDimension.Page); // frameCount = 2
img.SelectActiveFrame(FrameDimension.Page, 0); // First frame selected with no issue
img.SelectActiveFrame(FrameDimension.Page, 1); // Exception: "A generic error occured in GDI+"

我认为我错误地创建了我的MemoryStream,但我真的不认为我是。我尝试了几种不同的方法,结果相同;生成的Image对象将显示并正常运行,直到我尝试移动到最后一页:

方法1:

using(var ms = new MemoryStream(tiffBytes))
{
    img = Image.FromStream(ms);
}

方法2:

using(var ms = new MemoryStream(tiffBytes.Length))
{
    ms.Write(tiffBytes, 0, tiffBytes.Length);
    img = Image.FromStream(ms);
}

所以...我的问题是为什么Image.FromFile()和Image.FromStream()之间的区别使得TIFF的最后一页/帧在加载时会导致异常?

附带问题...是否有某种win32 api可以访问以确切了解该异常是什么?

1 个答案:

答案 0 :(得分:0)

Hans Passant的评论很有用,但我没有找到完整的解决方案。

尤其是当您打开多个文件并创建合并的输出时,图像处理非常麻烦。

我战斗了几天,有了一项功能,该功能可以使用现有的多页tif并附加其他页面。这些其他页面可以是单页tif,多页tif或图像文件。

输出将替换原始的多页tif,然后删除已附加的“新”文件(您可能不想删除源文件。我这样做是因为它是文档管理系统的一部分)。

打开/关闭文件时以及在代码中应分别执行操作以避免文件锁定时出现问题。

以下解决方案对我来说效果很好。这是用VB.net 4.6(注释显示为//,因为VB注释“格式不正确”)使用老式的win形式编写的,尽管此代码位于业务层对象中。

请注意每个文件的打开以及“使用”块的使用。如果您尝试对Using块之外的文件执行任何操作,则会遇到麻烦。即使我将代码将每个页面图像加载到图像列表中,如果我在创建最终合并输出的代码块之前放置End,也无法访问图像页面。

 Public Shared Function AppendPage(OriginalDocPath As String, NewPageFilePath As String) As Boolean

    //Define return value, default as fail
    Dim bRet As Boolean = False

    Try
        //get the codec for tiff file
        Dim info As ImageCodecInfo = Nothing
        Dim ice As ImageCodecInfo

        //find TIF in the encoder list
        For Each ice In ImageCodecInfo.GetImageEncoders()

            If ice.MimeType = "image/tiff" Then
                info = ice
            End If

        Next ice //use the 'save' encoder

        //List to store all output pages
        Dim imageList As New List(Of Image)
        Dim frameCount As Integer = 0

        //Set up the 'Save' encoder for multi page tif
        Dim enc As Encoder = Encoder.SaveFlag
        Dim ep As New EncoderParameters(1)
        ep.Param(0) = New EncoderParameter(enc, CLng(EncoderValue.MultiFrame))

        //control for original tif that I want to append to
        Dim oldDoc As Image

        Using fs As Stream = File.OpenRead(OriginalDocPath)

            //read the image file from a Stream
            oldDoc = Bitmap.FromStream(fs)

            //count the pages
            frameCount = oldDoc.GetFrameCount(FrameDimension.Page)
            Dim objGuid As Guid = (oldDoc.FrameDimensionsList(0))
            Dim objDimension As FrameDimension = New FrameDimension(objGuid)

            //load each page into the Image List
            For i As Integer = 0 To frameCount - 1 Step 1

                oldDoc.SelectActiveFrame(objDimension, i)
                Dim nextFrame As Bitmap = New Bitmap(oldDoc)
                imageList.Add(nextFrame)

            Next

            //the End Using will close the first image file
        End Using

        //variables for the appending image file
        Dim newDoc As Image
        Dim bmpFullDoc As Bitmap

        //Open file to append
        Using nStr As Stream = File.OpenRead(NewPageFilePath)

            //read using a Stream
            newDoc = Bitmap.FromStream(nStr)

            //Count the pages in the image (works for tif, jpg, png)
            frameCount = newDoc.GetFrameCount(FrameDimension.Page)
            Dim objGuid As Guid = (newDoc.FrameDimensionsList(0))
            Dim objDimension As FrameDimension = New FrameDimension(objGuid)

            //add each page to the Image List
            For i As Integer = 0 To frameCount - 1 Step 1

                newDoc.SelectActiveFrame(objDimension, i)
                Dim nextFrame As Bitmap = New Bitmap(newDoc)
                imageList.Add(nextFrame)

            Next

            //make a backup of the original file
            File.Copy(OriginalDocPath, OriginalDocPath & "-old", True)
            //delete the original file
            File.Delete(OriginalDocPath)

            Dim firstFrame As Boolean = True

            //create new file with all pages
            For Each im As Image In imageList

                //for the first page we need to actually create the file
                If firstFrame Then
                    //add the first image to the output
                    bmpFullDoc = im

                    //save the first frame
                    bmpFullDoc.Save(OriginalDocPath, info, ep)
                Else
                    //for subsequent pages we need to change the Encoder 'Page'
                    ep.Param(0) = New EncoderParameter(enc, CLng(EncoderValue.FrameDimensionPage))
                    //append next page
                    bmpFullDoc.SaveAdd(im, ep)

                End If
                //tell the loop we've saved the first page already
                firstFrame = False

            Next
        End Using

        //destroy the temp bitmap object
        bmpFullDoc.Dispose()
        //destroy the encoder parameters object
        ep.Dispose()

        //finally, delete the temp and source files
        File.Delete(OriginalDocPath & "-old")
        File.Delete(NewPageFilePath)

        //Set Return value to Success
        bRet = True

    Catch ex As Exception
        //Log error or throw exception
    End Try

    //return success or fail
    Return bRet

End Function