24位真彩色图像转换为16位高彩色图像的实现方法及效果改进

 本文是对多年前作者的一篇博文的重新整理和书写。

一、前言  

     高彩色位图像即我们常说16位图像,每个像素占用两个字节,相比于24位真彩色来说,在保持一定的图像质量的前提下可以节省1 /3的内存空间,在游戏编程中以及一些移动设备上常使用这种格式,一般PC机上似乎很少涉及,因此这方面的资料也不是特别多。

     真彩色转换为高彩色是一个信息量降低的过程,如果使得整个信息量的损失降低到最少(特别是对人眼来说),基本上没有引起什么人的注意,包括一些世界一流的图像软件的最新版本,也没有在这个方面下工夫,而更多的图像软件则是没有这个功能,比如PS CS6的转换效果就不尽人意:

               原图

             PS转换后的结果

               原图

             PS转换后的结果

  请睁大眼睛仔细观察上面的右图,可以看到在天空等颜色比较单调的位置会有明显的纹理出现,原图可是没有的,而这种瑕疵的出现对于诸如游戏这类会有大量相似区域出现的图片来说是个致命的弱点。因此研究如何改进效果是有重要意义的。     

二、实现

     要实现真彩色转换为高彩色,比如常用R5G5B5格式,我们只需要取原先的各颜色分量的高5位充当新的颜色分量就可以了,但是,涉及到如何把这些数据保存到文件,则需要一番努力。

     首先,我们需要注意的是16位BMP也有多种格式,PS中为我们列举了X1R5G5B5、R5G6B5、A1R5G5B5、X4R4G4B4、A4R4G4B4等,这些只是一些常用的组合罢了。实际上我们也可以自创一些格式,比如A3R4G5B4,只要相互之间的掩码不相互重合就可以了。

     16位BMP在结构上与其他的几种位深的BMP有一定的不同,他是由以下几个部分组成的:

       BITMAPFILEHEADER

       BITMAPINFOHEADER

       BITMASK

       IMAGEDATA

     这里的BITMASK是24位或8位以下色所没有的,他表明了后面的数据部分各颜色分量所使用的蒙版。值得注意的是, X1R5G5B5是个特例,他没有BITMASK部分的数据,并且其位图信息结构中的biCompression为BI_RGB,而其他几种格式都为BI_bitfields。还有一点就是X1R5G5B5的文件头的biSize为40,其他的都为56。

     为了操作方便,在我们转换真彩色图像时,我们定义一个合适大小的integer数组(VB6下),按照不同的子格式把真彩色的3中颜色分量合成到一个integer中,这里我们简单的以R5G5B5为例说明一下。

      比如原始的R=45、G=129、B=234,我们分别取各颜色分量的高五位部分,在VB6中要实现这个过程可以用一下语句实现:

NewR=R And &HF8
NewG=G And &HF8
NewB=B And &HF8

  &HF8的二进制展开形式为11111000,和我们的原始颜色进行或操作则可以得到高五位分量。

     注意,由于VB的变量在内存中的位置存放的特殊性,我们需要把B5部分的数据放在integer变量的低5位,G5居中,R5为最高位。这里的变量合成可以用逻辑运算符OR实现,即:

Integer= (NewB \ 8)  Or  (NewG * 4)  Or  (NewR * 128)

  VB中没有移位运算符,因此左移只能用乘法代替,右移用右除来代替,开启高级优化这些会被优化为移位的。

     遍历彩色图像中的每一个像素,用上述算法计算对应的integer值,则得到R5G5B5格式所需要的图像数据。

     由于VB中除了byte类型外,没有其他无符号数据类型,因此对于R5G6B5这种利用了最高位的格式处理时,一定要小心。当我们计算出NewR的最高位的值为1时,如果直接把他用OR运算合成到integer中,则生成的integer在VB中表示的为负数了。因此要把这一位作为特殊情况予以处理。

     如果直接按照上述方式写入图像数据,对于颜色丰富的图像转换的图像在清晰度的降低上是不明显的。但是对于游戏编程中常见到的天空、大海之类的有着较为平滑过渡的渐变区域图像来说,结果可能惨不忍睹。弥补这个缺点的方法就是利用抖动,最常用的方式就是误差扩散以及顺序抖动,误差扩散算法通过将误差传递到周围像素而减轻其造成的视觉误差,这有利于提高图像的可视性。

     抖动在真彩色转换为索引色或者索引色转换为Bitmap模式时,最为常用,把他引入到真彩色转换为高彩色似乎就没有什么记录,这里就涉及到一个问题,如何确定这个误差的计算方式。

     在真彩色转换为索引色时,针对每一个像素点,所谓的误差是类似于以下的计算:

Entry = GetNearestPaletteIndex(Palette,RGB(ImageDataC(Speed+2),ImageDataC(Speed+1),ImageDataC(Speed))
ErrB = ImageDataC(Speed) + 0& - Palette(Entry).Blue
ErrG = ImageDataC(Speed + 1) + 0& - Palette(Entry).Green
ErrR = ImageDataC(Speed + 2) + 0& - Palette(Entry).Red
    GetNearestPaletteIndex是用于计算和原始颜色值最为相似的颜色在颜色表中索引的一个方法,ImageDataC(Speed)为原始像素的Blue值,Palette(Entry).Blue则是直接查找到的映射值,这两个值之间的差异,即误差,这个按照一定的规则向某一个方向传递到原始的颜色信息中,比如常用的传播方式有如下一些(*的位置表示当前像素,一般都是向右下角传播的):   *2           *3             *71  1  0  /4      0  3  2   /8       3  5  1  /16  以第三组为例,则对应的Blue值的传播的相关参考代码如下所示:
NewB = ImageDataC(Speed) + 7 * ErrB \ 16                        ' 当前位置
ImageDataC(Speed) = (NewB And (NewB >= 0) Or (NewB >= 256)) And &HFF
NewB = ImageDataC(Speed + Stride - 3) + 3 * ErrB \ 16           ' 下一行左侧
ImageDataC(Speed + Stride - 3) = (NewB And (NewB >= 0) Or (NewB >= 256)) And &HFF
NewB = ImageDataC(Speed + Stride) + 5 * ErrB \ 16               ' 下一行正中
ImageDataC(Speed + Stride) = (NewB And (NewB >= 0) Or (NewB >= 256)) And &HFF
NewB = ImageDataC(Speed + Stride + 3) + ErrB \ 16               ' 下一行右侧
ImageDataC(Speed + Stride + 3) = (NewB And (NewB >= 0) Or (NewB >= 256)) And &HFF

  其中的(NewB And (NewB >= 0) Or (NewB >= 256)) And &HFF代码是用将值现在在0到255之间的快速方法。

     这样将误差就传递到了ImageDataC中,在求下一个像素的Entry时就会受到前面的误差的影响。

     在真彩色转换为高彩色的过程,其基本的过程和上述其实是完全一样的,只是这个误差的描述会有所区别,很直观的,我们将在此情况下的误差定义为:

Blue = ImageDataC(Speed) And &HF8
Green = ImageDataC(Speed + 1) And &HF8
Red = ImageDataC(Speed + 2) And &HF8
ErrB = ImageDataC(Speed) - Blue
ErrG = ImageDataC(Speed + 1) - Green
ErrR = ImageDataC(Speed + 2) - Red

  OK,其他的过程请模仿常规的操作了吧,就这么简单。

三、效果

 就是经过这么简单的设计和处理,效果会有很大的改善,同样的对于上面的图像,依旧采用X1R5G5B5格式,和PS的处理的相比效果如下:

  经过处理的图片和原始的相比基本看不出有什么大的差异了。

四、关于16位图像的其他说明

     看到有人在帖子说在读取16位格式的图像时也要进行抖动,这是一个很大的错误,对于图像,如果他的数据一定,那么应该说不管你什么读图软件去读他,显示的结果应该是一样的,如果在读的时候有抖动,那如果大家利用的抖动算法不一致,结果也就不一致,这是明显的矛盾。其实在读的时候,就是一个量化的过程,比如我读取的某点的红色分量的2进制 表示为10110,对应十进制为22,则在显示器上输出的颜色即为22*8*255/248,解释下:这里成8表示左移三位,246的由来是因为11111000表示的十进制数。*255表示量化到0到255之间。

     如果要显示不同格式的16位的图像数据,其实也很简单,有两中方法,第一,是修改CreateDIBSection函数的一个参数类型pBitmapInfo ,把这个默认参数BITMAPINFO修改为BITMAPV4HEADER,这个结构是比较新的BMP信息头,我们稍微修改他的一些成员结构,即修改为如下形式:

Private Type BITMAPV4HEADER
    Size                As Long
    Width               As Long
    Height              As Long
    Planes              As Integer
    BitCount            As Integer
    Compression         As Long
    SizeImage           As Long
    XPelsPerMeter       As Long
    YPelsPerMeter       As Long
    ClrUsed             As Long
    ClrImportant        As Long
    RedMask             As Long
    GreenMask           As Long
    BlueMask            As Long
    AlphaMask           As Long
End Type

     和BITMAPINFO结构相比,他只是多了几个蒙版成员,如果我们实现知道了我们要创建的16位图像的格式,则填充入对应的mask数据,然后在创建DIBSection,显示的时候直接调用Bitblt函数就可以。

     第二种方法依旧是在创建DIBSection时,使用修改后的结构体参数,但不填充mask内容,在显示的时候在修改mask,然后调用SetDIBitsToDevice 函数来显示他,当然也要修改SetDIBitsToDevice 的对应的那个参数声明,这种方法实用于先创建一个空白的16位图像,然后由其他高彩色图像向这个空白图像填充数据的情况。

     另外一个现象就是:16位的R5G6B5、X4R4G4B4格式XP自带的图片和传真查看器打不开,windows自带的画图板确可以打开,windows也不知道是如何考虑的。

五、参考资料

http://blog.csdn.net/yangdelong/archive/2008/04/26/2330748.aspx

http://www.pscode.com/vb/scripts/ShowCode.asp?txtCodeId=42376&lngWId=1

http://www.vbaccelerator.com/home/vb/code/vbMedia/Image_Processing/Floyd-Stucci_Colour_Reduction_Methods_and_Gray_Scaling/article.asp

 六、转换用工具

这么好的效果,当然得给大家一个转换工具了: 真彩色转高彩色

*********************************作者: laviewpbt   时间: 2013.12.2   联系QQ:  33184777  转载请保留本行信息*************************

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端新视界

使用 SVG 和 JS 创建一个由星形变心形的动画

序言:首先,这是一篇学习 SVG 及 JS 动画不可多得的优秀文章。我非常喜欢 Ana Tudor 写的教程。在她的教程中有大量使用 SVG 制作的图解以及实...

4525
来自专栏Code_iOS

OpenGL ES 2.0 (iOS)[06-1]:基础纹理

Texture 在 OpenGL 里面有很多种类,但在 ES 版本中就两种——Texture_2D + Texture_CubeMap;

1172
来自专栏mathor

搜索(4)

804
来自专栏MixLab科技+设计实验室

【图像处理篇】自动识别手写数字web应用05

往期的4篇已经把Docker+Keras+Flask+JS的全栈+深度学习介绍完整了: 自己动手做一个识别手写数字的web应用01 自己动手做一个识别手写数字的...

3746
来自专栏tkokof 的技术,小趣及杂念

小聊聊NGUI中Panel的Clip功能(之二)

  上篇简单聊了一下NGUI中Panel裁剪的实现原理,总结来看其实比较简单,就是通过Shader计算fragment关于Panel裁剪区域的相对位置,然后通过...

982
来自专栏数据结构与算法

Day5网络流

算法 无源汇上下界可行流 ?  先强制流过l的流量 从s到每个正权点连流量为l的流量  从每个负权点向t连-l的流量 如果容量为0,则不连边 有源汇上下界最大流...

2889
来自专栏算法+

快速双边滤波 附完整C代码

很早之前写过《双边滤波算法的简易实现bilateralFilter》。 当时学习参考的代码来自cuda的样例。 相关代码可以参阅: https://github...

92610
来自专栏CDA数据分析师

【图表大师三】仿gartner清爽圆角矩阵图

在Gartner的报告中,常看到如下图的清爽圆角矩阵图。 ? 我很喜欢这种清爽的图表风格,其特点有:干净清爽的颜色,优雅的圆角绘图区,个性的XY坐标轴。...

1836
来自专栏数据结构与算法

网络最大流算法—EK算法

前言 EK算法是求网络最大流的最基础的算法,也是比较好理解的一种算法,利用它可以解决绝大多数最大流问题。 但是受到时间复杂度的限制,这种算法常常有TLE的风险 ...

2838
来自专栏hightopo

基于 HTML5 的 WebGL 3D 版俄罗斯方块

1333

扫码关注云+社区