老白在开发WhiteNautilus Magnify (一个具有放大屏幕指定区域并反色显示该区域等功能的屏幕放大程序)时,为了较好地实现反色效果,特地跑到 Google 上用关键字“C# 反色”简单搜索了一下,发现位于搜索结果前几位的方法都大同小异[1][2]:大意是进行二重循环,对 Bitmap 的每个像素调用 GetPixel() 以获取其颜色值,然后对该像素颜色的 R, G 和 B 每个分量都用 255 减去原分量值,将得到的新值再用 SetPixel() 设置回去。老白照猫画虎,照葫芦画瓢,照着张飞画李逵……地照做了,然后遭遇了明显的性能问题。
可能也赶上程序有两点比较特殊:第一,放大镜有一种模式是窗体的位置始终跟踪鼠标指针(用了 Timer 以定时更新窗体位置);第二,窗体是使用 Region 裁切过的异形窗体。如果再加上那个二重循环,其整体性能简直让人无法忍受(咱这奔腾 M 1.5G 按说也不是闹着玩的)……没办法,谁让老白已经滥用了很多系统资源呢,只好自己再想办法……
虽然接触 .NET 有段时间了,也曾经参与过使用 ASP.NET 技术的动态网站的开发、测试,但是对整个 Class Library 还是没有完整的概念,GDI+ 就更甭提了。但是老白总有一种感觉,Class Library 应该具备处理类似问题的能力。浏览了 MSDN 中几个相关类的文档后,偶然发现了 Recoloring Images 这一专题。听起来挺对路子,那就赶紧看看吧~
果然,其中的几个小专题分别介绍了利用“颜色变换矩阵”(Color Matrix,请原谅老白擅自增加了“变换”二字)进行颜色的平移、缩放、扭曲等操作(这就是抽象的力量:将颜色(R, G, B 和 A)、三维空间中的点(X, Y 和 Z)和方向等都抽象成带有多个分量的向量,对其的操作比如平移、旋转和缩放,都是将其乘以一个特定的矩阵就完事儿)。老白想,既然 .NET 提供了这种重新着色的方式,估计是做了优化的(恨自己当年没好好学 Win32 API 编程,只听说过 BitBlt 云云),可以尝试一下,于是开始琢磨怎么设计这个颜色变换矩阵。
不知正在阅读本文的您是否学习过线性代数,学过的是否还记得矩阵相乘?这个……相关知识可以参考教科书或者在网上进行搜索,恕不赘述。但为了避免您频繁切换于本文和参考文献之间,老白还是准备在这里稍微引述一下相关内容,有时间和兴趣的读者亦可直接参看上文提及的 MSDN 中的专题 Recoloring Images,其中从颜色的向量表示法到颜色变换矩阵等,对所需的基础知识都有比较系统的介绍。
我们知道,一种颜色可以使用 R, G, B 和 A 一组共四个数来表示,其中每个数的取值范围可能是 0~255 或 0.0~1.0(至于 Web 开发习惯使用八位十六进制等形式表示,则不在讨论之列)。为了参与矩阵操作,还需增加一个所谓“齐次分量”,在下文中我们将可以看到齐次分量的重要作用。这样的话,不透明的纯红色就可以使用 [1.0, 0.0, 0.0, 1.0, 1.0] 这个向量来表示(选择 0.0~1.0 这种表示法是为了方便矩阵运算),其中的分量从左到右依次是红色、绿色、蓝色、Alpha(不透明度)和齐次分量。
参照上文提到的反色计算方法,我们需要对 R, G 和 B 每个分量进行“255 - 原值 = 新值”或“1.0 - 原值 = 新值”,而这种运算如何通过乘以一个矩阵来完成呢?老白就不卖关子了,直接拿最后使用的颜色变换矩阵举例了:
|
|
|