【问题标题】:OutOfMemoryException with Image.Clone - Only on Windows 2003带有 Image.Clone 的 OutOfMemoryException - 仅在 Windows 2003 上
【发布时间】:2010-03-23 15:22:48
【问题描述】:

所以这是我的问题。我有一个需要缩小的图像。原始图像是灰度PNG,这不是一个大问题,只是当我将其缩小时,热敏标签打印机会拾取伪影并将它们打印在标签上。所以,我所做的是在调整大小之前将图像更改为黑白(Format1bppIndexed),如下所示:

Dim bte() As Byte = System.Convert.FromBase64String(imgStr)
Dim ms As New IO.MemoryStream(bte)
Dim bmp As New System.Drawing.Bitmap(ms)
Dim monoImage As Drawing.Bitmap = New Drawing.Bitmap(1200, 1800, Drawing.Imaging.PixelFormat.Format1bppIndexed)
Dim rect As New Drawing.Rectangle(0, 0, 1200, 1800)
monoImage = bmp.Clone(rect, Drawing.Imaging.PixelFormat.Format1bppIndexed)

然后我调整它的大小。这段代码在我的 Windows 7 机器上运行良好,但是当我在它称为 home 的 Windows 2003 Server 机器上运行它时,它总是在遇到 bmp.Clone 行时抛出 OutOfMemoryException。

关于正在发生的事情的任何想法,或者将图像转换为黑白的更好解决方案?

【问题讨论】:

  • 你的 Win7 机器和 Win 2003 有多少内存?我们正在谈论的图像的文件大小有多大?
  • 这篇文章可能对你有所帮助:stackoverflow.com/questions/1836632/…
  • Bryan - 我的机器是 x64 的 6GB,服务器是 32 位的 4GB,在任何一种情况下都应该足够了,因为文件只有 120KB 大小。

标签: .net vb.net gdi+


【解决方案1】:

这是this article的一些转换源代码:

Option Explicit On
Option Strict On

Public Class BitmapEncoder
    ''' <summary>
    ''' Copies a bitmap into a 1bpp bitmap of the same dimensions, fast
    ''' </summary>
    ''' <param name="b">original bitmap</param>
    ''' <returns>a 1bpp copy of the bitmap</returns>
    Public Shared Function ConvertBitmapTo1bpp(ByVal b As System.Drawing.Bitmap) As System.Drawing.Bitmap
        ' Plan: built into Windows GDI is the ability to convert
        ' bitmaps from one format to another. Most of the time, this
        ' job is actually done by the graphics hardware accelerator card
        ' and so is extremely fast. The rest of the time, the job is done by
        ' very fast native code.
        ' We will call into this GDI functionality from C#. Our plan:
        ' (1) Convert our Bitmap into a GDI hbitmap (ie. copy unmanaged->managed)
        ' (2) Create a GDI monochrome hbitmap
        ' (3) Use GDI "BitBlt" function to copy from hbitmap into monochrome (as above)
        ' (4) Convert the monochrone hbitmap into a Bitmap (ie. copy unmanaged->managed)

        Dim w As Integer = b.Width, h As Integer = b.Height
        Dim hbm As IntPtr = b.GetHbitmap()
        ' this is step (1)
        '
        ' Step (2): create the monochrome bitmap.
        ' "BITMAPINFO" is an interop-struct which we define below.
        ' In GDI terms, it's a BITMAPHEADERINFO followed by an array of two RGBQUADs
        Dim bmi As New BITMAPINFO()
        bmi.biSize = 40
        ' the size of the BITMAPHEADERINFO struct
        bmi.biWidth = w
        bmi.biHeight = h
        bmi.biPlanes = 1
        ' "planes" are confusing. We always use just 1. Read MSDN for more info.
        bmi.biBitCount = CShort(1)
        ' ie. 1bpp or 8bpp
        bmi.biCompression = BI_RGB
        ' ie. the pixels in our RGBQUAD table are stored as RGBs, not palette indexes
        bmi.biSizeImage = CUInt((((w + 7) And &HFFFFFFF8) * h / 8))
        bmi.biXPelsPerMeter = 1000000
        ' not really important
        bmi.biYPelsPerMeter = 1000000
        ' not really important
        ' Now for the colour table.
        Dim ncols As UInteger = CUInt(1) << 1
        ' 2 colours for 1bpp; 256 colours for 8bpp
        bmi.biClrUsed = ncols
        bmi.biClrImportant = ncols
        bmi.cols = New UInteger(255) {}
        ' The structure always has fixed size 256, even if we end up using fewer colours

        bmi.cols(0) = MAKERGB(0, 0, 0)
        bmi.cols(1) = MAKERGB(255, 255, 255)
        ' 
        ' Now create the indexed bitmap "hbm0"
        Dim bits0 As IntPtr
        ' not used for our purposes. It returns a pointer to the raw bits that make up the bitmap.
        Dim hbm0 As IntPtr = CreateDIBSection(IntPtr.Zero, bmi, DIB_RGB_COLORS, bits0, IntPtr.Zero, 0)
        '
        ' Step (3): use GDI's BitBlt function to copy from original hbitmap into monocrhome bitmap
        ' GDI programming is kind of confusing... nb. The GDI equivalent of "Graphics" is called a "DC".
        Dim sdc As IntPtr = GetDC(IntPtr.Zero)
        ' First we obtain the DC for the screen
        ' Next, create a DC for the original hbitmap
        Dim hdc As IntPtr = CreateCompatibleDC(sdc)
        SelectObject(hdc, hbm)
        ' and create a DC for the monochrome hbitmap
        Dim hdc0 As IntPtr = CreateCompatibleDC(sdc)
        SelectObject(hdc0, hbm0)
        ' Now we can do the BitBlt:
        BitBlt(hdc0, 0, 0, w, h, hdc, 0, 0, SRCCOPY)
        ' Step (4): convert this monochrome hbitmap back into a Bitmap:
        Dim b0 As System.Drawing.Bitmap = System.Drawing.Bitmap.FromHbitmap(hbm0)
        '
        ' Finally some cleanup.
        DeleteDC(hdc)
        DeleteDC(hdc0)
        ReleaseDC(IntPtr.Zero, sdc)
        DeleteObject(hbm)
        DeleteObject(hbm0)
        '
        Return b0
    End Function


    Private Shared SRCCOPY As Integer = &HCC0020
    Private Shared BI_RGB As UInteger = 0
    Private Shared DIB_RGB_COLORS As UInteger = 0
    <System.Runtime.InteropServices.DllImport("gdi32.dll")> _
    Private Shared Function DeleteObject(ByVal hObject As IntPtr) As Boolean
    End Function

    <System.Runtime.InteropServices.DllImport("user32.dll")> _
    Private Shared Function GetDC(ByVal hwnd As IntPtr) As IntPtr
    End Function

    <System.Runtime.InteropServices.DllImport("gdi32.dll")> _
    Private Shared Function CreateCompatibleDC(ByVal hdc As IntPtr) As IntPtr
    End Function

    <System.Runtime.InteropServices.DllImport("user32.dll")> _
    Private Shared Function ReleaseDC(ByVal hwnd As IntPtr, ByVal hdc As IntPtr) As Integer
    End Function

    <System.Runtime.InteropServices.DllImport("gdi32.dll")> _
    Private Shared Function DeleteDC(ByVal hdc As IntPtr) As Integer
    End Function

    <System.Runtime.InteropServices.DllImport("gdi32.dll")> _
    Private Shared Function SelectObject(ByVal hdc As IntPtr, ByVal hgdiobj As IntPtr) As IntPtr
    End Function

    <System.Runtime.InteropServices.DllImport("gdi32.dll")> _
    Private Shared Function BitBlt(ByVal hdcDst As IntPtr, ByVal xDst As Integer, ByVal yDst As Integer, ByVal w As Integer, ByVal h As Integer, ByVal hdcSrc As IntPtr, _
     ByVal xSrc As Integer, ByVal ySrc As Integer, ByVal rop As Integer) As Integer
    End Function


    <System.Runtime.InteropServices.DllImport("gdi32.dll")> _
    Private Shared Function CreateDIBSection(ByVal hdc As IntPtr, ByRef bmi As BITMAPINFO, ByVal Usage As UInteger, ByRef bits As IntPtr, ByVal hSection As IntPtr, ByVal dwOffset As UInteger) As IntPtr
    End Function

    <System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)> _
    Private Structure BITMAPINFO
        Public biSize As UInteger
        Public biWidth As Integer, biHeight As Integer
        Public biPlanes As Short, biBitCount As Short
        Public biCompression As UInteger, biSizeImage As UInteger
        Public biXPelsPerMeter As Integer, biYPelsPerMeter As Integer
        Public biClrUsed As UInteger, biClrImportant As UInteger
        <System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst:=256)> _
        Public cols As UInteger()
    End Structure

    Private Shared Function MAKERGB(ByVal r As Integer, ByVal g As Integer, ByVal b As Integer) As UInteger
        Return CUInt((b And 255)) Or CUInt(((r And 255) << 8)) Or CUInt(((g And 255) << 16))
    End Function
    Private Sub New()

    End Sub
End Class

调用它使用:

    Dim myFile = "c:\test.jpg"

    Using Input As New Bitmap(myFile)
        Using Output = BitmapEncoder.ConvertBitmapTo1bpp(Input)
            Output.Save("c:\test.bmp")
        End Using
    End Using

【讨论】:

  • 这绝对有效!似乎需要很多代码来完成这样一个简单的任务;)谢谢,克里斯!
  • 我知道,我一开始还以为你疯了,但后来我一直在挖掘,发现每个人都有同样的问题,唯一的解决方案就是去低级。很奇怪。
【解决方案2】:

GDI+ 异常消息非常悲惨,如果您传递的矩形超出图像边界,则可以在 Clone() 方法中引发 OutOfMemoryException。与内存不足无关。这很容易在这里发生,源位图不太可能是 1200 x 1800。让你的代码看起来像这样:

Dim bte() As Byte = System.Convert.FromBase64String(imgStr)
Dim ms As New IO.MemoryStream(bte)
Dim monoImage As Drawing.Bitmap
Using bmp As New Drawing.Bitmap(ms)
  Dim rect As New Drawing.Rectangle(0, 0, bmp.Width, bmp.Height)
  monoImage = bmp.Clone(rect, Drawing.Imaging.PixelFormat.Format1bppIndexed)
End Using

不太确定生成的位图是否好看或是否可用,GDI+ 对索引像素格式的支持很差。 Win7 的 gdiplus.dll 版本与 Windows 2003 不同。我仅在 Win7 上测试了此代码。

【讨论】:

  • 我刚刚在 Vista 上执行此操作并收到相同的错误消息。我什至尝试了一个 1,1,10,10 的矩形并得到了错误。
  • 这里也一样。同样,这在我的 Win7 计算机上运行良好,但在 2003 年就崩溃了。
【解决方案3】:

看看这个:http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.formatconvertedbitmap.aspx

但是将PixelFormats.Gray32Float改为PixelFormats.BlackWhite

【讨论】:

    【解决方案4】:

    我马上看到的一个问题是您的代码存在资源泄漏:

    Dim monoImage As Drawing.Bitmap = New Drawing.Bitmap(1200, 1800,
        Drawing.Imaging.PixelFormat.Format1bppIndexed)
    ...
    monoImage = bmp.Clone(rect, Drawing.Imaging.PixelFormat.Format1bppIndexed)
    

    您正在创建一个全新的位图并将其分配给monoImage,然后几乎立即将其丢弃,而从未处理掉原始的Bitmap。如果您经常这样做,就会泄漏大量 GDI 资源并因此收到各种错误(包括 OOM)。

    只需将声明移动到与“新”赋值相同的行:

    Dim monoImage As Drawing.Bitmap = bmp.Clone(rect,
        Drawing.Imaging.PixelFormat.Format1bppIndexed)
    

    您还需要确保您正在处理其他 BitmapMemoryStream 等等;所有这些都实现了IDisposable,所以只需将它们包装在Using 中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-06
      • 2013-01-28
      • 2010-09-12
      • 2016-04-02
      相关资源
      最近更新 更多