Image.Clone的OutOfMemoryException - 仅在Windows 2003上

时间:2010-03-23 15:22:48

标签: .net vb.net gdi+

所以这是我的问题。我有一个我需要缩小的图像。原始图像是灰度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。

有关正在发生的事情的任何想法,或者可能是将图像转换为B& W的更好解决方案吗?

4 个答案:

答案 0 :(得分:2)

以下是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

答案 1 :(得分:1)

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上测试了这段代码。

答案 2 :(得分:0)

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

但是将PixelFormats.Gray32Float更改为PixelFormats.BlackWhite

答案 3 :(得分:0)

我立即看到的一个问题是你的代码有资源泄漏:

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