上一次講到哪了,說(shuō)了下bmp位圖格式以及圖像處理入門。門也入了 搞點(diǎn)別的吧,好 我們繼續(xù)接著折騰。bmp格式的數(shù)據(jù)就放在內(nèi)存里 你愛(ài)折騰不折騰他就在那
總之一句話 搞清楚他的結(jié)構(gòu) 用你清晰的邏輯去處理它。
我們這次要做的事情是降低顏色深度及調(diào)色板處理,反正我是找了園子里也沒(méi)看見(jiàn)類似的東西 都是C++或者其他什么的??傊覀円龅倪@兩個(gè)事情都要用到調(diào)色板。
要想取得一個(gè)圖像的調(diào)色板的所有顏色I(xiàn)mage.Palette.Entries 就可以了 得到的是一個(gè)Color數(shù)組。有些固定顏色深度的圖像 都有默認(rèn)的調(diào)色板比如4位(16色)8位(256色)等。 你用過(guò)win31 或者win95沒(méi)裝顯卡驅(qū)動(dòng)時(shí)一定見(jiàn)過(guò)那些豬肝色的圖像 。我們初始化一個(gè)4位的bmp圖像(16色)來(lái)看看他默認(rèn)的調(diào)色板都有哪些顏色,添加一個(gè)叫“顏色樣本”的按鈕 這是他的代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | //顯示windows默認(rèn)顏色樣本 void makeColor() { Bitmap bmp = new Bitmap(1, 1, PixelFormat.Format4bppIndexed); Color[] colors = bmp.Palette.Entries; Graphics gph = Graphics.FromHwnd( this .Handle); int j = 0; for ( int i = 0; i < 4; i++) { for ( int k = 0; k < 4; k++) { gph.FillRectangle( new SolidBrush(colors[j]), new Rectangle( new Point(k * 100, i * 100), new Size(100, 100))); gph.DrawString(colors[j].ToArgb().ToString( "X" ).Substring(2), new Font( new FontFamily( "黑體" ), 15), Brushes.Lavender, new Point(k * 100, i * 100)); j++; } } bmp.Dispose(); } |
顏色表有了(就是上面代碼里抓出來(lái)的顏色表),然后是怎樣讓每個(gè)像素的顏色去根據(jù)調(diào)色板匹配最近似的顏色呢 http://dev.gameres.com/Program/Visual/Other/256color.htm 這是一個(gè)C++的實(shí)現(xiàn) 不過(guò)我沒(méi)看懂 - -! 用了另外一種傻瓜的方式去實(shí)現(xiàn),添加一個(gè)名為“調(diào)色板算法”的按鈕 這是他的代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | //256色調(diào)色板的匹配處理 http://dev.gameres.com/Program/Visual/Other/256color.htm //沒(méi)看懂不過(guò)我實(shí)現(xiàn)了他的另外一個(gè)效率很低的算法 //調(diào)色板匹配/降低顏色深度的 算法 效率有點(diǎn)低 原理是對(duì)的 //出來(lái)的結(jié)果跟畫(huà)圖板另存為16色 是一模一樣的 要256色同理 void tranTo16() { Bitmap img = (Bitmap)Bitmap.FromFile( "mm.bmp" ); //準(zhǔn)備調(diào)色板 這個(gè)是從畫(huà)圖板新建16色位圖文件里抓出來(lái)的 Color[] colors = new Color[16]; colors[0] = Color.FromArgb(0x00, 0x00, 0x00); colors[1] = Color.FromArgb(0x80, 0x00, 0x00); colors[2] = Color.FromArgb(0x00, 0x80, 0x00); colors[3] = Color.FromArgb(0x80, 0x80, 0x00); colors[4] = Color.FromArgb(0x00, 0x00, 0x80); colors[5] = Color.FromArgb(0x80, 0x00, 0x80); colors[6] = Color.FromArgb(0x00, 0x80, 0x80); colors[7] = Color.FromArgb(0x80, 0x80, 0x80); colors[8] = Color.FromArgb(0xc0, 0xc0, 0xc0); colors[9] = Color.FromArgb(0xff, 0x00, 0x00); colors[10] = Color.FromArgb(0x00, 0xff, 0x00); colors[11] = Color.FromArgb(0xff, 0xff, 0x00); colors[12] = Color.FromArgb(0x00, 0x00, 0xff); colors[13] = Color.FromArgb(0xff, 0x00, 0xff); colors[14] = Color.FromArgb(0x00, 0xff, 0xff); colors[15] = Color.FromArgb(0xff, 0xff, 0xff); Graphics gph = Graphics.FromHwnd( this .Handle); int tmp; for ( int i = 0; i < img.Height; i++) { for ( int j = 0; j < img.Width; j++) { tmp = 255 * 3; Color tmpCol = Color.FromArgb(255, 255, 255); for ( int k = 0; k < colors.Length; k++) { Color src = colors[k]; Color des = img.GetPixel(j, i); //原理就是檢查每個(gè)像素的rgb跟調(diào)色板中的比較 選中差值最小的那個(gè),那么就一定是"最相近"的顏色了 int val = Math.Abs(des.R - src.R) + Math.Abs(des.G - src.G) + Math.Abs(des.B - src.B); if (val < tmp) { tmp = val; tmpCol = src; } } img.SetPixel(j, i, tmpCol); } } gph.DrawImage(img, new Point(0, 0)); gph.Dispose(); img.Dispose(); } |
怎么樣試下吧 用畫(huà)圖板轉(zhuǎn)存為16色位圖 看下是不是跟他是一模一樣的,說(shuō)明咱的方法是對(duì)的 要的就是這個(gè)效果。 看下這個(gè)圖像的畫(huà)面真的慘不忍睹啊
有時(shí)候win95下有些很神奇的程序他們用4位的模式竟然也可以顯示出不算太差的圖像(抖動(dòng)算法 這個(gè)俺還不會(huì) ╮(╯﹏╰)╭)
有沒(méi)有既稍微保留畫(huà)面質(zhì)量又降低數(shù)據(jù)量的方法呢。有啊 改調(diào)色板不就得了嘛 讓調(diào)色板的顏色盡量跟畫(huà)面整體匹配,上面說(shuō)了取得調(diào)色板跟調(diào)色板里的顏色都很簡(jiǎn)單Image.Palette 但是試了就知道通過(guò)給調(diào)色板的顏色賦值 或者直接更改調(diào)色板根本都不起作用的,如果有哪位哥們兒知道 告訴我下。參考上一篇第一個(gè)例子 我們知道 這個(gè)得用非常手段 根據(jù)格式直接對(duì)bmp內(nèi)存數(shù)據(jù)進(jìn)行操作,本文只討論方法這里就不寫(xiě)調(diào)色板操作的代碼了,通過(guò)一段偷懶的代碼見(jiàn)證他的可行性。把對(duì)colors數(shù)組賦值的16行改成這樣:
1 2 3 4 | Random rdm = new Random(); for ( int i = 0; i < colors.Length; i++) colors[i] = img.GetPixel(rdm.Next(0, img.Width - 1), rdm.Next(0, img.Height - 1)); |
這個(gè)圖像像是16色的圖像嗎 不會(huì)吧再怎么看著也比16色好很多啊,怎么樣調(diào)色板的神奇之處 瞬間圖像質(zhì)量就有很大提升吧。你可以寫(xiě)更好的算法對(duì)圖像的色調(diào)進(jìn)行分析生成更智能的調(diào)色板 以前的老游戲由于發(fā)色數(shù)有限 調(diào)色板應(yīng)用非常廣泛。
該結(jié)尾了我們來(lái)實(shí)際寫(xiě)個(gè)24位真彩色bmp圖像轉(zhuǎn)8位256色的例子,添加一個(gè)叫“降低顏色深度”的按鈕 這是對(duì)應(yīng)的代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | void tranTo256() { Bitmap srcImg = new Bitmap( "mm.bmp" ); Bitmap desImg = new Bitmap(srcImg.Width, srcImg.Height, PixelFormat.Format8bppIndexed); MemoryStream desImgData = new MemoryStream(); desImg.Save(desImgData, ImageFormat.Bmp); Color[] pal = desImg.Palette.Entries; BitmapData data = srcImg.LockBits( new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.ReadWrite, srcImg.PixelFormat); unsafe { byte * ptr = ( byte *)data.Scan0; int tmp; //掃描的時(shí)候是由上到下,存儲(chǔ)的時(shí)候是由下到上 for ( int i =0 ; i <srcImg.Height ; i++) //(int i = srcImg.Height - 1; i > 0; i--) { int offSet = data.Stride * i; for ( int j = 0; j < srcImg.Width * 3; j += 3) { tmp = 255 * 3; byte palIndx = 0; for ( int k = 0; k < pal.Length; k++) { Color src = pal[k]; Color des = Color.FromArgb(ptr[offSet + j + 2], ptr[offSet + j + 1], ptr[offSet + j]); int val = Math.Abs(des.R - src.R) + Math.Abs(des.G - src.G) + Math.Abs(des.B - src.B); if (val < tmp) { tmp = val; palIndx = ( byte )k; } } desImgData.WriteByte(palIndx); } } } desImg = (Bitmap)Bitmap.FromStream(desImgData); srcImg.UnlockBits(data); Graphics.FromHwnd( this .Handle).DrawImage(desImg, new Point(0, 0)); desImg.Save( "gs.bmp" ); desImgData.Close(); srcImg.Dispose(); desImg.Dispose(); } |
貌似什么都對(duì)的就是出不來(lái)呢 一個(gè)純黑背景的圖片,因?yàn)閷?xiě)數(shù)據(jù)之前游標(biāo)沒(méi)有到達(dá)指定位置 在unsafe前面加上這句就ok了:
1 | desImgData.Seek(54 + 256 * 4, SeekOrigin.Begin); //數(shù)據(jù)開(kāi)始位置,參考位圖文件格式說(shuō)明 |
看下效果吧:
哇 怎么回事倒了 怎么會(huì)倒了呢 看見(jiàn)咱代碼里注釋的提示了沒(méi) //掃描的時(shí)候是由上到下,存儲(chǔ)的時(shí)候是由下到上
原理不多說(shuō) ,把外層for循環(huán)圓括號(hào)里部分用注釋的那段替換就行了
這就沒(méi)問(wèn)題了 真的么 真的就沒(méi)問(wèn)題了嗎
用畫(huà)圖板打開(kāi)bin目錄那個(gè)叫"mm.bmp"圖像 ,然后 單擊“圖像”菜單->屬性 現(xiàn)在寬度是不是400 ,請(qǐng)改成399 按ctr+s保存
然后再運(yùn)行程序:
為什么400可以399就不行呢。又涉及4倍字節(jié)數(shù)這個(gè)扯淡的問(wèn)題。
我們像素寬是400對(duì)吧 8位256色 一個(gè)像素一個(gè)字節(jié) 對(duì)吧 正好400字節(jié) 能被4整除
如果399像素 就需要補(bǔ)齊一個(gè)字節(jié) 在最外層循環(huán)加上這句就ok了:
1 | desImgData.WriteByte(0x00); |
不行 咱得讓他更智能點(diǎn) 把剛剛那段代碼替換成這段:
1 2 3 4 5 6 | int fill = ((srcImg.Width * 8 + 31) / 32 * 4) - srcImg.Width; if (fill > 0) { byte [] fills = new byte [fill]; desImgData.Write(fills, 0, fills.Length); } |
好了折騰夠了 大功告成,對(duì)應(yīng)目錄已經(jīng)生成了一個(gè)8位名字“gs.bmp”的位圖。 有些部分沒(méi)完善只起到拋磚引玉的作用 見(jiàn)諒 自己去搞:
聯(lián)系客服