24位真彩色圖像轉換為16位高彩色圖像的實現方法及效果改進


     本文是對多年前作者的一篇博文的重新整理和書寫。

一、前言     

     高彩色位圖像即我們常說16位圖像,每個像素占用兩個字節,相比於24位真彩色來說,在保持一定的圖像質量的前提下可以節省1 /3的內存空間,在游戲編程中以及一些移動設備上常使用這種格式,一般PC機上似乎很少涉及,因此這方面的資料也不是特別多。

     真彩色轉換為高彩色是一個信息量降低的過程,如果使得整個信息量的損失降低到最少(特別是對人眼來說),基本上沒有引起什么人的注意,包括一些世界一流的圖像軟件的最新版本,也沒有在這個方面下工夫,而更多的圖像軟件則是沒有這個功能,比如PS CS6的轉換效果就不盡人意:

     

     

              原圖                                PS轉換后的結果(博客園不支持BMP ,使用PNG上傳的)

  請睜大眼睛仔細觀察上面的右圖,可以看到在天空等顏色比較單調的位置會有明顯的紋理出現,原圖可是沒有的,而這種瑕疵的出現對於諸如游戲這類會有大量相似區域出現的圖片來說是個致命的弱點。因此研究如何改進效果是有重要意義的。     

二、實現

     要實現真彩色轉換為高彩色,比如常用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             *7
1 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位的R5G6B5X4R4G4B4格式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  轉載請保留本行信息*************************

   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM