zoukankan      html  css  js  c++  java
  • 降低颜色深度及调色板处理

    上一次讲到哪了,说了下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”的位图。 有些部分没完善只起到抛砖引玉的作用 见谅 自己去搞:

    示例文件及代码

  • 相关阅读:
    团队项目——站立会议DAY14
    团队项目——站立会议DAY13
    团队项目——站立会议DAY12
    团队项目——站立会议 DAY11
    团队项目——站立会议DAY10
    团队项目——站立会议DAY9
    团队项目——站立会议 DAY8
    团队项目——站立会议DAY7
    团队项目——站立会议DAY6
    作业五:团队项目——项目启动及需求分析
  • 原文地址:https://www.cnblogs.com/assassinx/p/2537147.html
Copyright © 2011-2022 走看看