原始图像:
PS中保存为X1R5G5B5格式的效果,可以看到有很明显的不光滑区域。
我写的无抖动的效果,和PS差不多,但不是完全一样,因此PS还是对标准的过程进行了改动。
采用有序抖动的结果,效果要好很多了。
采用经典的Floyd-Steinberg抖动,效果一般会很好。
(一般来说,R5G6B5格式可以在无抖动的时候也实现比较好的效果。)
彩色位图像即我们常说16位图像,每个像素占用两个字节,相比于24位真彩色来说,在保持一定的图像质量的前提下可以节省1 /3的内存空间,在游戏编程中经使用这种格式。
要实现真彩色转换为高彩色,,比如常用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数组,按照不同的子格式把真彩色的3中颜色分量合成到一个integer中,这里我们简单的以R5G5B5为例说明一下。
比如原始的R=45、G=129、B=234,我们分别取各颜色分量的高五位部分,在VB中要实现这个过程可以用一下语句实现:
NewR=R And &HF8
NewG=G And &HF8
NewB=B And &HF8
&HF8的二进制展开形式为11111000,和我们的原始颜色进行或操作则可以得到高五位分量。
注意,由于VB的变量在内存中的位置存放的特殊性,我们需要把G5部分的数据放在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中表示的为负数了。因此要把这一位作为特殊情况予以处理。
如果直接按照上述方式写入图像数据,对于颜色丰富的图像转换的图像在清晰度的降低上是不明显的。但是对于游戏编程中常见到的天空、大海之类的有着较为平滑过渡的渐变区域图像来说,结果可能惨不忍睹。结果这个缺点的方法就是利用抖动,也就是我们常看到的误差扩散,误差扩散算法通过将误差传递到周围像素而减轻其造成的视觉误差,这有利于提高图像的可视性。
看到有人在帖子说在读取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位图像,然后由其他高彩色图像向这个空白图像填充数据的情况。
说明一下:PS中的保存为16位色的效果虽然做了一定的处理,但是他的处理效果并不是很好,对于很多图像会产生比较明显的瘢迹,过渡不好,我们自己用Floyd-Steinberg抖动或者有序抖动都可以得到比他好的效果。大家可以用我们附带的程序比较下PS的效果。
补充: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.cnblogs.com/laviewpbt/archive/2009/07/20/1526954.html