当前位置:编程学习 > C#/ASP.NET >>

「玩一玩」量化GDI+:快速Bitmap读写像素——到底有多快?

发个帖子然后蒸馒头吃

--------

这是一个古老的技巧:
使用Bitmap类时经常会用到GetPixel和SetPixel,但是这两个方法直接使用都比较慢,所以一般都会使用LockBits/UnlockBits将位图在内存中锁定,以加快操作速度。
MSDN上的标准参考是这样的:
    private void LockUnlockBitsExample(PaintEventArgs e)
        {

            // Create a new bitmap.创建位图
            Bitmap bmp = new Bitmap("c:\\fakePhoto.jpg");

            // Lock the bitmap's bits.  锁定位图
            Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
            System.Drawing.Imaging.BitmapData bmpData =
                bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
                bmp.PixelFormat);

            // Get the address of the first line.获取首行地址
            IntPtr ptr = bmpData.Scan0;

            // Declare an array to hold the bytes of the bitmap.定义数组保存位图
            int bytes  = Math.Abs(bmpData.Stride) * bmp.Height;
            byte[] rgbValues = new byte[bytes];

            // Copy the RGB values into the array.复制RGB值到数组
            System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);

            // Set every third value to 255. A 24bpp bitmap will look red.  把每像素第3个值设为255.24bpp的位图将变红
            for (int counter = 2; counter < rgbValues.Length; counter += 3)
                rgbValues[counter] = 255;

            // Copy the RGB values back to the bitmap 把RGB值拷回位图
            System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);

            // Unlock the bits.解锁
            bmp.UnlockBits(bmpData);

            // Draw the modified image.绘制更新了的位图
            e.Graphics.DrawImage(bmp, 0, 150);
        }

因为我比较闲,所以我在想这样的问题:加快之后到底有多快?
为此,我稍微调整了下之前用过的BitmapEx类(记得应该是人脸识别还是什么代码里用过),改成FastBitmap,然后创建了测试程序,搜集了一系列测试用例。(点击左上图片框打开图片文件,无异常处理)

测试用例如下:

为了保证不受文件格式影响,统一使用24bpp的bmp格式。(感谢科技发展,内存白菜价,不然单个文件将近200MB可真要让我麻烦一番。)

考察分为GetPixel和SetPixel两个部分,把读写分开。测试代码(以GetPixel为例)非常简单,如下:
for (int y = 0; y < h; y++)
{
    for (int x = 0; x < w; x++)
    {
        tmp = bmp.GetPixel(x, y);
    }
}

其中bmp分别为Bitmap和FastBitmap。
为了专注于对比结果,虽然逐像素遍历图像非常耗费时间,但并没有刻意使用并行计算,使用单个CPU内核完成。所以如果你打算用这个程序对特别巨大的图片(10000×10000数量级以上)进行测试,还请慎重。

经过测试,得到了这样的测试结果:

从测试结果来看,号称「Fast」果然有两把刷子,平均提升效率在90%~95%,也就说性能提高了10~20倍。
这个结果,虽然还不算很快,但我觉得基本到了GDI+的极限了(剩下的就是机器性能的提升了),如果再要提升,可以试试并行计算、C++ native、直接调用MMX/SSE指令、CUDA之类的技术。
我不知道现在技术发展下还有多少用到Bitmap的场合,只是觉得:追求开发效率和性能平衡的时候,Bitmap也能成为一个不错的选择。

测试程序下载:点击下载

--------------

顺便求解答:
1.C++ Native/Managed的测试结果
2.葱油饼/状元饼的做法
3.怎样揉面能让馒头更好吃 --------------------编程问答-------------------- --------------------编程问答-------------------- 没进行过类似处理,关注。 --------------------编程问答-------------------- --------------------编程问答-------------------- 学习C#,收藏。 --------------------编程问答-------------------- --------------------编程问答-------------------- 野比就是野人牛比。 --------------------编程问答-------------------- 不错,mark --------------------编程问答-------------------- 学习。。 --------------------编程问答-------------------- 不错,学习! --------------------编程问答-------------------- 直接内存操作确实快。。。 --------------------编程问答-------------------- 微软的那个例子(见连接)可能有问题
http://msdn.microsoft.com/zh-cn/library/system.drawing.imaging.bitmapdata.aspx

原因是跨距(Stride)可以是负数,这种情况下bmpData.Scan0将指向图像最后一行的开头。直接Marshal.Copy会出问题。BmpData.Stride见:
http://msdn.microsoft.com/zh-cn/library/system.drawing.imaging.bitmapdata.stride.aspx --------------------编程问答-------------------- --------------------编程问答--------------------
引用 11 楼  的回复:
微软的那个例子(见连接)可能有问题
http://msdn.microsoft.com/zh-cn/library/system.drawing.imaging.bitmapdata.aspx

原因是跨距(Stride)可以是负数,这种情况下bmpData.Scan0将指向图像最后一行的开头。直接Marshal.Copy会出问题。BmpData.Stride见:
http://msdn.……


感谢。各位请参考11楼。 --------------------编程问答-------------------- 你这 出产速度也太快了啊 --------------------编程问答-------------------- 前段时间试过Cimage里面的GetPixel,直接内存操作,当时效率提升20倍。

想来也差不多了。感谢内存白菜价。 --------------------编程问答-------------------- 在实际的程序里如果出现这样的东西,还是慢。



public unsafe Color GetPixel(int x, int y)
{
    if (this.bmpData.PixelFormat == PixelFormat.Format32bppArgb)
    {
        byte* numPtr = (byte*) ((((void*) this.bmpData.Scan0) + (y * this.bmpData.Stride)) + (x * 4));
        return Color.FromArgb(numPtr[3], numPtr[2], numPtr[1], numPtr[0]);
    }
    if (this.bmpData.PixelFormat == PixelFormat.Format24bppRgb)
    {
        byte* numPtr2 = (byte*) ((((void*) this.bmpData.Scan0) + (y * this.bmpData.Stride)) + (x * 3));
        return Color.FromArgb(numPtr2[2], numPtr2[1], numPtr2[0]);
    }
    return Color.Empty;
}


大量属性的调用,会耗时。实际算法里这只是优化的第一步。


--------------------编程问答--------------------
引用 4 楼  的回复:
学习C#,收藏。
学习了。。。。。 --------------------编程问答--------------------
引用 16 楼  的回复:
在实际的程序里如果出现这样的东西,还是慢。

C# code


public unsafe Color GetPixel(int x, int y)
{
    if (this.bmpData.PixelFormat == PixelFormat.Format32bppArgb)
    {
        byte* numPtr = (byte*) ((((void*) this.……


我能保证你发的这段代码效率远比你想象中高。至于算法中其他优化那是具体的设计,我也没办法考虑。 --------------------编程问答-------------------- 一直是听说,直接 Pixel 操作速度会很慢,一直都没接触过,现在感觉速度还能接受啊 --------------------编程问答-------------------- 类时经常会用到GetPixel和SetPixel,但是这两个方法直接使用都比较慢,所以一般都会使用LockBits/UnlockBits将位图在内存中锁定,以加快操作速 --------------------编程问答-------------------- 野比兄,下次发帖前,可否先贴一张个人清晰大头照哇 --------------------编程问答-------------------- 不错啊 --------------------编程问答-------------------- 学习了,野哥速度太快了! --------------------编程问答-------------------- 内存法也慢,用指针法吧,至少比内存法快10倍! --------------------编程问答-------------------- --------------------编程问答-------------------- C#图像处理有三种方法,1是GetPixel,SetPixel方法,这种方法最慢,简直就是看幻灯片;2是内存法,就是你介绍的那个方法,这个方法比方法1快了N倍;3是指针法,虽然C#中指针属于不安全因素,但是,这种方法的速度确实是内存法的10倍以上!建议如果做大型图像处理操作,还是用指针法好! --------------------编程问答--------------------
引用 24 楼  的回复:
内存法也慢,用指针法吧,至少比内存法快10倍!


求实验数据。 --------------------编程问答-------------------- 我一般是用unsafe的,回头试试Marshal~ --------------------编程问答-------------------- 没积分了啊 郁闷 --------------------编程问答-------------------- --------------------编程问答-------------------- 顶起,还看不懂 --------------------编程问答-------------------- 1024 --------------------编程问答-------------------- 不错,谢谢 --------------------编程问答-------------------- --------------------编程问答-------------------- 不就是个buffer吧,直接定位到那里写就行了,没这么多事! --------------------编程问答-------------------- 野比就是野人牛比
引用 6 楼  的回复:
野比就是野人牛比。
--------------------编程问答-------------------- --------------------编程问答--------------------
引用 27 楼  的回复:
引用 24 楼  的回复:

内存法也慢,用指针法吧,至少比内存法快10倍!


求实验数据。

这个不需要实验,把两个copy去掉,它不但浪费了两次copy的时间,还付出了空间的代价。在下面的代码中
for (int counter = 2; counter < rgbValues.Length; counter += 3)
     rgbValues[counter] = 255;
替换为指针操作,或者不用指针操作,直接用Marshal.WriteByte --------------------编程问答-------------------- 顺便再讨论下将图片输出到屏幕的高效的方法呗 --------------------编程问答-------------------- --------------------编程问答--------------------
引用 6 楼  的回复:
野比就是野人牛比。
牛比 --------------------编程问答--------------------
引用 38 楼  的回复:
引用 27 楼  的回复:

引用 24 楼  的回复:

内存法也慢,用指针法吧,至少比内存法快10倍!


求实验数据。

这个不需要实验,把两个copy去掉,它不但浪费了两次copy的时间,还付出了空间的代价。在下面的代码中
for (int counter = 2; counter < rgbValues.Length; counter += 3)
     rgb……


我明白你说的「内存法」就是指Copy了。。从来不用。。其实,MSDN那段我只是用来说明下Lock/Unlock的用法,和测试代码无关。测试是用指针逐像素读写的。。参考16楼。。 --------------------编程问答-------------------- 珍爱生命 远离gdi+ --------------------编程问答--------------------     GDIPLUS这种 LockBits是临时性的把图像的数据读到内存,是不适合于做专业的图像处理软件的,专业做的话一个图像加载后在内存呢中的格式应该是固定的,这样做算法也就是直接访问这段内存的数据。
    LockBits的存在有点类似于GDI的 GetDibits,他能够简单的把图像数据在不同格式之间进行转换。
    GetPixel之类的函数的存在也不是为了专业的图像处理的,而是对类似于屏幕取色或DC取色这样小批量数据时方便处理。
    要玩速度,图像处理方面的算法先是用普通语言写出来,对算法的核心尽心优化,如果速度还不行,考虑用汇编进一步优化,越简单的算法,用汇编优化的速度能提高的倍数越高,比如,最简单的反色算法,3000*4000*24的图像,一般的语言要100ms左右的处理时间,用汇编的话20ms够了,不过复杂的算法,一般汇编能提升的档次不会有这么明显。




--------------------编程问答--------------------
引用 42 楼  的回复:
引用 38 楼  的回复:

引用 27 楼  的回复:

引用 24 楼  的回复:

内存法也慢,用指针法吧,至少比内存法快10倍!


求实验数据。

这个不需要实验,把两个copy去掉,它不但浪费了两次copy的时间,还付出了空间的代价。在下面的代码中
for (int counter = 2; counter < rgbValues.Length; counte……

我没说“内存法”,那是另一个人。不过我还是测试了一下指针和copy的速度比较,比如那段代码改为
byte* p = (byte*)ptr;
byte* q = p + rgbValues.Length;
for (byte* counter = p + 2; counter < q; counter += 3)
                *counter = 255;
把两个copy去掉测试一下时间,除了时间之外还有空间的优势。
Marshal.WriteByte我说的有误,速度不快,特此更正,不好意思,哈哈。 --------------------编程问答-------------------- 葱油饼做法 http://home.meishichina.com/recipe-22694.html
关键还是面粉跟发酵 --------------------编程问答--------------------
引用 44 楼  的回复:
    GDIPLUS这种 LockBits是临时性的把图像的数据读到内存,是不适合于做专业的图像处理软件的,专业做的话一个图像加载后在内存呢中的格式应该是固定的,这样做算法也就是直接访问这段内存的数据。
    LockBits的存在有点类似于GDI的 GetDibits,他能够简单的把图像数据在不同格式之间进行转换。
    GetPixel之类的函数的存在也不是为了专业的图像处理的,而……


是的。GDI+做点简单图像做界面,真正处理还是需要更精简的方式。

刚优化了下,反色 4096x4096@24bpp 的图像,用时299 ms,我已经黔驴技穷了,大家继续。





PS: 3000x4000@24bpp 的图像用时 213 ms

Size:3000 x 4000
Processing FastBitmap...
Processed fast invert in 213 ms.
Processed FastBitmap in 213 ms. --------------------编程问答-------------------- to lizhibin11:

收到。


to laviewpbt & all:

最新优化,时间缩短52ms



to KarasCanvas:

正点!我都是用的自发粉。先试试吧,不行再买酵母。 --------------------编程问答-------------------- 3000x4000@24bpp 的图像用时 176 ms

Size:3000 x 4000
Processing FastBitmap...
Processed fast invert in 176 ms.
Processed FastBitmap in 176 ms. --------------------编程问答-------------------- 关注,前些时间也在做这一块,不过没有这么快 --------------------编程问答-------------------- 你们都是高手啊!啊啊啊啊啊 --------------------编程问答-------------------- 不粗不粗,没测试过. --------------------编程问答-------------------- 我有个1亿像素的图像,谁有兴趣,可以测试一下!!!留下邮箱我发给你 --------------------编程问答-------------------- 我要,laviewpbt@sina.com --------------------编程问答--------------------
引用 53 楼  的回复:
我有个1亿像素的图像,谁有兴趣,可以测试一下!!!留下邮箱我发给你


……  1亿像素是指什么? 1亿 x 1亿?还是10000x10000? 

如果是前者,24bpp的按照90%压缩率文件大小都有2728 TB,地球上还没有邮箱装得下。

如果是后者,你可以看我测试用例#8,8192x8192,6710万像素,1亿的像素也没什么必要测试了,结果差不多的。

你也可以自己下载测试程序试一试。欢迎把你的测试结果贴在这里。 --------------------编程问答-------------------- 我把测试用例#7(压缩版本)传上来,有兴趣的可以自行下载。#8太大了一直上传失败。

#7:4096x4096,1.6 MB
http://img.my.csdn.net/uploads/201207/11/1342000091_6512.jpg --------------------编程问答-------------------- --------------------编程问答-------------------- --------------------编程问答-------------------- --------------------编程问答-------------------- <<windows图形编程>>一书中详细说明了GDI库的GetPixe()为什么慢,以及他们存在的理由.GetPixe()它考虑的太全面了 --------------------编程问答-------------------- --------------------编程问答-------------------- --------------------编程问答-------------------- 楼主你想多了 --------------------编程问答-------------------- Me.Mark() --------------------编程问答--------------------
引用 63 楼  的回复:
楼主你想多了


我也觉得……抱歉 --------------------编程问答-------------------- --------------------编程问答--------------------
引用 39 楼  的回复:
顺便再讨论下将图片输出到屏幕的高效的方法呗


双倍缓冲输出图片到屏幕可以吗 --------------------编程问答-------------------- GDI+就是慢的代名词,GetPixel、SetPixel根本就是不能用。
--------------------编程问答--------------------
引用 68 楼  的回复:
GDI+就是慢的代名词,GetPixel、SetPixel根本就是不能用。


个人习惯,随意。 --------------------编程问答-------------------- --------------------编程问答-------------------- 2ms...的撸过
模板化的C++代码,没有刻意优化.
i7 860@2.93GHz,12G PC1333内存.Windows7 64位.别的参数不重要了吧.


struct __vfxblt_not{
template<class T>
inline static void op(T & _o,const T & _i){
_o = ~_i;
}
inline static void op(_VFMT0888 & _o,const _VFMT0888 & _i){
_o.r = ~_i.r;_o.g = ~_i.g;_o.b = ~_i.b;
}
};

template<class T>
inline void __not_page(const T * _i,int i_picth,T * _o,int o_picth,size_t line,size_t page){
vfxblt_leach_page<T,__vfxblt_not>::op(_i,i_picth,_o,o_picth,line,page);
}
template<class FMT,class OP>
struct vfxblt_leach_page{
typedef typename __vfxleach_size<FMT>::value_type value_type;
typedef OP op_type;
typedef vfxblt_operator_page<value_type,op_type> blt_type;
inline static void op(const FMT * _i,intptr_t i_picth,FMT * _o,intptr_t o_picth,size_t line,size_t page){
blt_type::op((value_type*)_i,i_picth,(value_type*)_o,o_picth,line,page);
}
}
};

--------------------编程问答--------------------

template<class FMT,class OP>
struct __vfxblt_operator_line{
inline static void op(const FMT * _i,FMT * _o,size_t line){
for(size_t i=0;i<line;++i)
OP::op(_o[i],_i[i]);
}
};

template<class FMT,class OP>
struct vfxblt_operator_page{
inline static void op(const FMT * _i,intptr_t i_picth,FMT * _o,intptr_t o_picth,size_t line,size_t page)
{
for(;page;--page)
{
__vfxblt_operator_line<FMT,OP>::op(_i,_o,line);
_i = (FMT *)((INT_PTR)_i + i_picth);
_o = (FMT *)((INT_PTR)_o + o_picth);
}
}
};

--------------------编程问答--------------------
引用 71 楼  的回复:
2ms...的撸过
模板化的C++代码,没有刻意优化.
i7 860@2.93GHz,12G PC1333内存.Windows7 64位.别的参数不重要了吧.

C/C++ code

struct __vfxblt_not{
    template<class T>
    inline static void op(T & _o,const T & _i){
  ……


你可以和#44楼交流下,他的汇编程序反色在我机器上25ms左右 (4096x4096x24bit,48MB)。

话说你机器确实好。 --------------------编程问答-------------------- 临时写了点代码,SSE2指令,启用内部函数。
图片大小:4096 * 4096,32位色
机器配置:i7 860@2.93GHz,12G PC1333内存.Windows7 64位.
测试100次,平均下来,每轮耗时:0.39ms


__m128i * pdwData = (__m128i *)m_Image[0];
DWORD dwCount = m_Image.GetWidth() * m_Image.GetHeight() / 4;
__m128i * pdwEnd = pdwData + dwCount;
__m128i dwff = {0};
dwff = _mm_cmpeq_epi32(dwff,dwff);
for(;pdwData < pdwEnd; ++pdwData)
{
*pdwData = _mm_andnot_si128(*pdwData,dwff);
}
--------------------编程问答-------------------- VS2010编译的。重要的不是VS2010,主要是这个东西对SSE2指令支持得很好。不用内部函数,直接汇编也能写,就是麻烦点,不利于阅读。
dwff = _mm_cmpeq_epi32(dwff,dwff);
使得128位的dwff值每个位都是1。
c = _mm_andnot_si128(a,b)
做如下运算
c = ~a & b;
SSE2没有单独的not指令 --------------------编程问答--------------------
引用 75 楼  的回复:
VS2010编译的。重要的不是VS2010,主要是这个东西对SSE2指令支持得很好。不用内部函数,直接汇编也能写,就是麻烦点,不利于阅读。
dwff = _mm_cmpeq_epi32(dwff,dwff);
使得128位的dwff值每个位都是1。
c = _mm_andnot_si128(a,b)
做如下运算
c = ~a & b;
SSE2没有单独的not指令


楼已经跑歪,偏到优化速度这条不归路上。

我来试试优化下。不过主题要改成「C#下实现快速位图处理」,必须要放弃GDI+了。。 --------------------编程问答-------------------- 不好意思,各位。刚才的数据有误。
因为是在一个现成的程序里添加临时代码进行测试的,那个程序把图片加载近来后,已经把图片缩放到适合窗口的大小了。临时代码是在缩小代码后面添加的。所以,尺寸不对。
这次测试的结果是:
C++模板代码:19ms
C的SSE2指令:12.09ms --------------------编程问答-------------------- --------------------编程问答-------------------- 学习中。。。。。。。 --------------------编程问答-------------------- --------------------编程问答--------------------
引用 74 楼  的回复:
临时写了点代码,SSE2指令,启用内部函数。
图片大小:4096 * 4096,32位色
机器配置:i7 860@2.93GHz,12G PC1333内存.Windows7 64位.
测试100次,平均下来,每轮耗时:0.39ms

C/C++ code

__m128i * pdwData = (__m128i *)m_Image[0];
DWORD dwCount = m_Ima……



绝对不相信这个的测试速度。 --------------------编程问答--------------------
引用 81 楼  的回复:
引用 74 楼  的回复:

临时写了点代码,SSE2指令,启用内部函数。
图片大小:4096 * 4096,32位色
机器配置:i7 860@2.93GHz,12G PC1333内存.Windows7 64位.
测试100次,平均下来,每轮耗时:0.39ms

C/C++ code

__m128i * pdwData = (__m128i *)m_Image[0];
DW……


他下面改正了(77楼)

这次测试的结果是:
C++模板代码:19ms
C的SSE2指令:12.09ms

再感叹下,他机器真好。。 --------------------编程问答-------------------- 使用多线程,加快了一点速度。

4096x4096x24用时进入140ms



100ms是真难啊,继续用GDI+的话。。 --------------------编程问答-------------------- 好吧,各种大神齐聚一堂啊 --------------------编程问答-------------------- --------------------编程问答-------------------- 你代码怎么写的? --------------------编程问答-------------------- 问一下Delphi的使用者,性能又会比C#高出N倍了 --------------------编程问答--------------------
引用 87 楼  的回复:
问一下Delphi的使用者,性能又会比C#高出N倍了


欢迎Delphi爱好者加入。

C#一向不以速度见长。看77楼。我的本意只是测试一下。从76楼以后就完全是闲着没事,来试试在C#下到底能跑到多快。 --------------------编程问答-------------------- 尼玛。。让我进100ms要死啊?


真不想用汇编SIMD之类的。。


--------------------编程问答-------------------- 我不懂c#,但首页的我常来看看。发现这个区的都是这一个人发的 --------------------编程问答-------------------- 我擦!终于进100ms了。下一步,试试看能不能进50ms。。

测试用例:7.bmp:49153 KB,4096x4096x24bit
测试机:i3 380M@2.53GHz,2.92G DDR3-1333,Windows 7 32位



--------------------编程问答-------------------- 恩。。还是先来个对比:

都是对 4096x4096x24bit 图像进行反色处理。

44楼:
语言:C#、汇编
测试机:(我的)i3 380M@2.53GHz,2.92G DDR3-1333,Windows 7 32位
速度:25ms

77楼:
语言:C++调用SSE指令
测试机:i7 860@2.93GHz,12G PC1333内存,Windows7 64位
速度:
C++模板代码:19ms
C的SSE2指令:12.09ms

我的:总算进入50ms了
语言:C#、C#指针,基于System.Drawing.Bitmap类(FastBitmap是个包装)
测试机:i3 380M@2.53GHz,2.92G DDR3-1333,Windows 7 32位
速度:约50ms




另外,和xiaotie交流,他使用完全重写的自定义图像库(彻底抛弃LockBits之类的)和C#指针实现了33ms左右速度(4096x4096x24)。 --------------------编程问答-------------------- 膜拜。 --------------------编程问答-------------------- 不错 mark。。。 --------------------编程问答-------------------- 用C#写图像处理程序还是可以的,但是GDI+在其中的主要作用应该是图像读取和显示以及一些简单的算法。图像数据一定要一次性分配读到内存并固化。用C#写的最著名的图像软件Paint.net中可以看到很多GDI函数的身影,就是因为GDI在很多相同功能的速度上要比GDI+快很多。 --------------------编程问答-------------------- --------------------编程问答--------------------
引用 95 楼  的回复:
用C#写图像处理程序还是可以的,但是GDI+在其中的主要作用应该是图像读取和显示以及一些简单的算法。图像数据一定要一次性分配读到内存并固化。用C#写的最著名的图像软件Paint.net中可以看到很多GDI函数的身影,就是因为GDI在很多相同功能的速度上要比GDI+快很多。


肯定的。追求性能也许必须换方法了。你的ImgWizard速度就非常快。我想试试GDI+的极限。从之前300ms优化到50ms左右,再往下前进一步都难。如果不修改数据格式,感觉是不可能进2、30ms级别的。 --------------------编程问答--------------------
引用 92 楼  的回复:
另外,和xiaotie交流,他使用完全重写的自定义图像库(彻底抛弃LockBits之类的)和C#指针实现了33ms左右速度(4096x4096x24)。

求实现方法,绘制动画时这个LockBits耗时还是很可观的 --------------------编程问答-------------------- 所谓图像,只不过是内存中一块连续的区域罢了。任何图像(静态的)在内存中的格式都是Bitmap.

那为什么不把它当成byte[]来搞呢?然后按位取反,不用休休答答地用什么GDI+之类的。

别喷我,我只是打酱油的。 --------------------编程问答-------------------- 楼上讲的是重点,但是GDI+中偏偏不是这样做的。
补充:.NET技术 ,  C#
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,