【问题标题】:GDI Write Bitmap to File in C#GDI 在 C# 中将位图写入文件
【发布时间】:2013-04-07 02:25:00
【问题描述】:

我正在尝试从给定句柄/DC 的窗口中捕获图像。我想捕获具有所有透明度和像素的图像,所以我决定使用 GDI/GDI+。

下面的代码是我所拥有的,但是当它“写入”位图时,它写错了:S 换句话说,文件不可读。它是空白的。然而它的大小是 20kb :S

有什么想法吗?

Imports.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace Imaging
{
    static class Imports
    {
        public enum ObjectType : uint
        {
            OBJ_PEN = 1,
            OBJ_BRUSH = 2,
            OBJ_DC = 3,
            OBJ_METADC = 4,
            OBJ_PAL = 5,
            OBJ_FONT = 6,
            OBJ_BITMAP = 7,
            OBJ_REGION = 8,
            OBJ_METAFILE = 9,
            OBJ_MEMDC = 10,
            OBJ_EXTPEN = 11,
            OBJ_ENHMETADC = 12,
            OBJ_ENHMETAFILE = 13
        }

        public enum TernaryRasterOperations : uint
        {
            SRCCOPY = 0x00CC0020,
            SRCPAINT = 0x00EE0086,
            SRCAND = 0x008800C6,
            SRCINVERT = 0x00660046,
            SRCERASE = 0x00440328,
            NOTSRCCOPY = 0x00330008,
            NOTSRCERASE = 0x001100A6,
            MERGECOPY = 0x00C000CA,
            MERGEPAINT = 0x00BB0226,
            PATCOPY = 0x00F00021,
            PATPAINT = 0x00FB0A09,
            PATINVERT = 0x005A0049,
            DSTINVERT = 0x00550009,
            BLACKNESS = 0x00000042,
            WHITENESS = 0x00FF0062,
            CAPTUREBLT = 0x40000000
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct Rect
        {
            public int Left, Top, Right, Bottom;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct BITMAPFILEHEADER
        {
            public ushort bfType;
            public uint bfSize;
            public ushort bfReserved1;
            public ushort bfReserved2;
            public uint bfOffBits;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct BITMAPINFOHEADER
        {
            public uint biSize;
            public int biWidth;
            public int biHeight;
            public ushort biPlanes;
            public ushort biBitCount;
            public uint biCompression;
            public uint biSizeImage;
            public int biXPelsPerMeter;
            public int biYPelsPerMeter;
            public uint biClrUsed;
            public uint biClrImportant;

            public void Init()
            {
                biSize = (uint)Marshal.SizeOf(this);
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct BITMAPINFO
        {
            public uint biSize;
            public int biWidth;
            public int biHeight;
            public ushort biPlanes;
            public ushort biBitCount;
            public uint biCompression;
            public uint biSizeImage;
            public int biXPelsPerMeter;
            public int biYPelsPerMeter;
            public uint biClrUsed;
            public uint biClrImportant;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
            public uint[] cols;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct BITMAP
        {
            public int bmType;
            public int bmWidth;
            public int bmHeight;
            public int bmWidthBytes;
            public ushort bmPlanes;
            public ushort bmBitsPixel;
            public IntPtr bmBits;
        };

        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

        [DllImport("user32.dll")]
        public static extern IntPtr GetDC(IntPtr WindowHandle);

        [DllImport("user32.dll")]
        public static extern void ReleaseDC(IntPtr WindowHandle, IntPtr DC);

        [DllImport("user32.dll")]
        public static extern IntPtr GetWindowRect(IntPtr WindowHandle, ref Rect rect);

        [DllImport("user32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool PrintWindow(IntPtr hwnd, IntPtr DC, uint nFlags);

        [DllImport("user32.dll")]
        public static extern int SetForegroundWindow(IntPtr hWnd);

        [DllImport("user32.dll")]
        public static extern IntPtr ShowWindow(IntPtr hWnd, int nCmdShow);

        [DllImport("user32.dll")]
        public static extern int GetWindowRgn(IntPtr hWnd, IntPtr hRgn);

        [DllImport("gdi32.dll")]
        public static extern IntPtr GetCurrentObject(IntPtr DC, ObjectType uObjectType);

        [DllImport("gdi32.dll")]
        public static extern int GetObject(IntPtr hObject, int nCount, ref BITMAP lpObject);

        [DllImport("gdi32.dll", SetLastError = true)]
        public static extern IntPtr CreateCompatibleDC(IntPtr DC);

        [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
        public static extern bool DeleteDC(IntPtr DC);

        [DllImport("gdi32.dll")]
        public static extern IntPtr CreateCompatibleBitmap(IntPtr DC, int nWidth, int nHeight);

        [DllImport("gdi32.dll", ExactSpelling = true, PreserveSig = true, SetLastError = true)]
        public static extern IntPtr SelectObject(IntPtr DC, IntPtr hgdiobj);

        [DllImport("gdi32.dll")]
        public static extern bool DeleteObject(IntPtr hObject);

        [DllImport("gdi32.dll")]
        public static extern IntPtr CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);

        [DllImport("gdi32.dll")]
        static extern bool FillRgn(IntPtr DC, IntPtr hrgn, IntPtr hbr);

        [DllImport("gdi32.dll")]
        public static extern IntPtr CreateSolidBrush(uint crColor);

        [DllImport("gdi32.dll")]
        public static extern int GetDIBits(IntPtr DC, IntPtr hbmp, uint uStartScan, uint cScanLines, [Out] byte[] lpvBits, ref BITMAPINFO lpbmi, uint uUsage);

        [DllImport("gdi32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool BitBlt(IntPtr DC, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr SrcDC, int nXSrc, int nYSrc, TernaryRasterOperations dwRop);
    }
}

我的 PBitmap 类:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace Imaging
{
    class PBitmap
    {
        private Bitmap Img = null;
        private Imports.BITMAPINFO Info;
        private Imports.BITMAPFILEHEADER bFileHeader;

        public PBitmap(IntPtr Window, int X, int Y, uint BitsPerPixel = 24)
        {
            IntPtr DC = Imports.GetDC(Window);
            Imports.BITMAP Bmp = new Imports.BITMAP();
            IntPtr hBmp = Imports.GetCurrentObject(DC, Imports.ObjectType.OBJ_BITMAP);

            if (Imports.GetObject(hBmp, Marshal.SizeOf(Bmp), ref Bmp) == 0)
                throw new Exception("ERROR_INVALID_WINDOW_HANDLE.");

            Imports.Rect BMBox = new Imports.Rect();
            Imports.GetWindowRect(Window, ref BMBox);   //GetClientRect
            int width = BMBox.Right - BMBox.Left;
            int height = BMBox.Bottom - BMBox.Top;

            IntPtr MemDC = Imports.GetDC(IntPtr.Zero);
            IntPtr SDC = Imports.CreateCompatibleDC(MemDC);
            IntPtr hSBmp = Imports.CreateCompatibleBitmap(MemDC, width, height);
            Imports.DeleteObject(Imports.SelectObject(SDC, hSBmp));

            Imports.BitBlt(SDC, 0, 0, width, height, DC, X, Y, Imports.TernaryRasterOperations.SRCCOPY);
            long size = ((width * BitsPerPixel + 31) / 32) * 4 * height;

            Info = new Imports.BITMAPINFO();
            bFileHeader = new Imports.BITMAPFILEHEADER();

            Info.biSize = (uint)Marshal.SizeOf(Info);
            Info.biWidth = width;
            Info.biHeight = height;
            Info.biPlanes = 1;
            Info.biBitCount = (ushort)BitsPerPixel;
            Info.biCompression = 0;
            Info.biSizeImage = (uint)size;
            bFileHeader.bfType = 0x4D42;
            bFileHeader.bfOffBits = (uint)(Marshal.SizeOf(bFileHeader) + Marshal.SizeOf(Info));
            bFileHeader.bfSize = (uint)(bFileHeader.bfOffBits + size);

            byte[] ImageData = new byte[size];
            Imports.GetDIBits(SDC, hSBmp, 0, (uint)height, ImageData, ref Info, 0);

            var bw = new BinaryWriter(File.Open("C:/Users/*******/Desktop/Foo.bmp", FileMode.OpenOrCreate));

            bw.Write((short)0x4D42);
            bw.Write((uint)(bFileHeader.bfOffBits + size));
            bw.Write((uint)0);
            bw.Write((uint)54);
            bw.Write((uint)Info.biSize);
            bw.Write(width);
            bw.Write(height);
            bw.Write((short)Info.biPlanes);
            bw.Write(BitsPerPixel);
            bw.Write((uint)0);
            bw.Write((uint)size);
            bw.Write((uint)0);
            bw.Write((uint)0);
            bw.Write((uint)0);
            bw.Write((uint)0);
            bw.Write(ImageData);

            bw.Flush();
            bw.Close();
            bw.Dispose();

            Imports.DeleteDC(SDC);
            Imports.DeleteObject(hSBmp);
            Imports.ReleaseDC(IntPtr.Zero, MemDC);
            Imports.ReleaseDC(Window, DC);
        }
    }
}

【问题讨论】:

  • 内存中的数据是否正确?
  • 我编辑了这篇文章。我将它从 Marshal.StructToPtr 更改为 bw.Write,我在其中编写了结构的所有参数。这似乎可行,只是现在文件大小为 3.92 兆字节!它看起来像:i.imgur.com/vrSqo9x.png 它的大小是 1366x744 我不太确定为什么它是 3.92mb!在 C++ 中,相同的代码可以正常工作,图像最多为 100-200kb。
  • 如果你得到一个 200kB 的文件,显然你没有在 C++ 中使用相同的方法。您正在存储GetDIBits 的(未压缩)结果。仅位图数据的大小(不包括标题)的快速估计为 1366*744*4 = 4065216 字节 = 3.88 MB
  • 但我是:pastebin.com/VYwQwY5P 完全相同的计算、相同的功能和顺序。这就是我要移植的代码。它在代码块中运行良好,并且可以很好地保存图像。
  • 嗯,有些不同。根据您的 C++ 代码 size = ((width * BitsPerPixel + 31) / 32) * 4 * height; 中的这一行,我会说 200kB 文件中的图像尺寸非常不同。

标签: c# bitmap gdi+ gdi


【解决方案1】:

如果您正在寻找一个快速的创可贴修复来完成这项工作(它只需要工作,不经常使用等),请考虑将图像加载回 .NET 并在启用 PNG 压缩的情况下重新保存它. .NET 框架将为您处理此问题,文件大小将更加优化。当然,如果这是针对实时应用程序,这并不理想。

【讨论】:

  • 这是个好主意,但我想我只是将 C++ 代码放入 DLL 并加载它。我真的希望单独用 C# 完成它。我只想完成它,因为我想做在 C# 中保持透明度的屏幕截图。但我想暂时不会。无论如何,谢谢。
猜你喜欢
  • 2012-10-26
  • 1970-01-01
  • 1970-01-01
  • 2011-04-10
  • 2021-12-31
  • 2021-06-07
  • 2015-02-19
  • 1970-01-01
  • 2012-01-25
相关资源
最近更新 更多