降低颜色深度及调色板处理

上一次讲到哪了,说了下bmp位图格式以及图像处理入门。门也入了 搞点别的吧,好 我们继续接着折腾。bmp格式的数据就放在内存里 你爱折腾不折腾他就在那
总之一句话 搞清楚他的结构 用你清晰的逻辑去处理它。
我们这次要做的事情是降低颜色深度及调色板处理,反正我是找了园子里也没看见类似的东西 都是C++或者其他什么的。总之我们要做的这两个事情都要用到调色板。
要想取得一个图像的调色板的所有颜色Image.Palette.Entries 就可以了 得到的是一个Color数组。有些固定颜色深度的图像 都有默认的调色板比如4位(16色)8位(256色)等。 你用过win31 或者win95没装显卡驱动时一定见过那些猪肝色的图像 。我们初始化一个4位的bmp图像(16色)来看看他默认的调色板都有哪些颜色,添加一个叫“颜色样本”的按钮 这是他的代码:

//显示windows默认颜色样本

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();

}

降低颜色深度及调色板处理

 
颜色表有了(就是上面代码里抓出来的颜色表),然后是怎样让每个像素的颜色去根据调色板匹配最近似的颜色呢 http://dev.gameres.com/Program/Visual/Other/256color.htm  这是一个C++的实现 不过我没看懂 - -! 用了另外一种傻瓜的方式去实现,添加一个名为“调色板算法”的按钮 这是他的代码:

//256色调色板的匹配处理 http://dev.gameres.com/Program/Visual/Other/256color.htm 

//没看懂不过我实现了他的另外一个效率很低的算法

//调色板匹配/降低颜色深度的 算法 效率有点低 原理是对的

//出来的结果跟画图板另存为16色 是一模一样的 要256色同理                 

void tranTo16()

{

    Bitmap img = (Bitmap)Bitmap.FromFile("mm.bmp");



    //准备调色板 这个是从画图板新建16色位图文件里抓出来的

    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);

                //原理就是检查每个像素的rgb跟调色板中的比较 选中差值最小的那个,那么就一定是"最相近"的颜色了

                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();

}

怎么样试下吧 用画图板转存为16色位图 看下是不是跟他是一模一样的,说明咱的方法是对的 要的就是这个效果。  看下这个图像的画面真的惨不忍睹啊

降低颜色深度及调色板处理

有时候win95下有些很神奇的程序他们用4位的模式竟然也可以显示出不算太差的图像(抖动算法 这个俺还不会 ╮(╯﹏╰)╭)
有没有既稍微保留画面质量又降低数据量的方法呢。有啊 改调色板不就得了嘛 让调色板的颜色尽量跟画面整体匹配,上面说了取得调色板跟调色板里的颜色都很简单Image.Palette 但是试了就知道通过给调色板的颜色赋值 或者直接更改调色板根本都不起作用的,如果有哪位哥们儿知道 告诉我下。参考上一篇第一个例子 我们知道 这个得用非常手段 根据格式直接对bmp内存数据进行操作,本文只讨论方法这里就不写调色板操作的代码了,通过一段偷懒的代码见证他的可行性。把对colors数组赋值的16行改成这样:

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));

 

降低颜色深度及调色板处理

这个图像像是16色的图像吗 不会吧再怎么看着也比16色好很多啊,怎么样调色板的神奇之处 瞬间图像质量就有很大提升吧。你可以写更好的算法对图像的色调进行分析生成更智能的调色板  以前的老游戏由于发色数有限 调色板应用非常广泛。

该结尾了我们来实际写个24位真彩色bmp图像转8位256色的例子,添加一个叫“降低颜色深度”的按钮 这是对应的代码:

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;

        //扫描的时候是由上到下,存储的时候是由下到上

        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();

}

貌似什么都对的就是出不来呢 一个纯黑背景的图片,因为写数据之前游标没有到达指定位置 在unsafe前面加上这句就ok了:

desImgData.Seek(54 + 256 * 4, SeekOrigin.Begin);//数据开始位置,参考位图文件格式说明

 看下效果吧:

降低颜色深度及调色板处理

哇 怎么回事倒了 怎么会倒了呢 看见咱代码里注释的提示了没 //扫描的时候是由上到下,存储的时候是由下到上
原理不多说 ,把外层for循环圆括号里部分用注释的那段替换就行了
这就没问题了  真的么 真的就没问题了吗
用画图板打开bin目录那个叫"mm.bmp"图像 ,然后 单击“图像”菜单->属性 现在宽度是不是400 ,请改成399 按ctr+s保存
然后再运行程序:

降低颜色深度及调色板处理

为什么400可以399就不行呢。又涉及4倍字节数这个扯淡的问题。
我们像素宽是400对吧 8位256色 一个像素一个字节 对吧 正好400字节 能被4整除
如果399像素 就需要补齐一个字节 在最外层循环加上这句就ok了:

desImgData.WriteByte(0x00); 

 不行 咱得让他更智能点 把刚刚那段代码替换成这段:

int fill = ((srcImg.Width * 8 + 31) / 32 * 4) - srcImg.Width;

if (fill > 0)

{

    byte[] fills = new byte[fill];

    desImgData.Write(fills, 0, fills.Length);

}

 好了折腾够了 大功告成,对应目录已经生成了一个8位名字“gs.bmp”的位图。 有些部分没完善只起到抛砖引玉的作用 见谅 自己去搞:

降低颜色深度及调色板处理

示例文件及代码

你可能感兴趣的:(处理)