zoukankan      html  css  js  c++  java
  • 自定义(手动调整).net中图像的调色板(How to adjust (customize) image's palette in .net?)

    作者:王先荣

    引言

        昨天在测试各种图像处理类库及方法的性能时,想试试看自己写的灰度化性能如何,结果发现自己通过写BitmapData生成的灰度图看起来依然色彩斑斓。通过跟踪调试,图像的数据部分是正确的,问题出在了调色板部分,调色板中的颜色是Windows默认的web流行色,而非我所期望的256级灰度颜色。

    发现问题

        既然是调色板不对,那么首先想到的就是调整图像的调色板。

        1.我用以下代码循环修改图像的调色板为灰度颜色

    for (int i = 0; i < image.Palette.Entries.Length; i++)
        image.Palette.Entries[i] 
    = Color.FromArgb(255, i, i, i);

    然后显示图像,图像依然是彩色的,说明调色板的颜色改过来了,但是没有起作用。

         2.于是用Google大法到网上搜索,发现了《C#画8位彩色图片(自定义调色板)》这篇文章,地址在:http://www.china-code.net/article/7/3/93645/cc0OCr1G1.html。

        该文章的核心方法是:创建一个新的调色板,修改调色板的颜色,然后再用新的调色板替换图像原有的调色板。值得注意的是ColorPalette类没有构造函数,不能直接创建,他这里用了一个取巧的办法——创建一个1X1的内存图像,获取其中的调色板,释放再内存图像。经过我稍微修改之后的创建调色板方法如下:

    创建调色板
    /// <summary>
    /// 创建图像格式对应的调色板
    /// </summary>
    /// <param name="pixelFormat">图像格式,只能是Format1bppIndexed,Format1bppIndexed,Format1bppIndexed</param>
    /// <returns>返回调色板;如果创建失败或者图像格式不支持,返回null。</returns>
    private ColorPalette CreateColorPalette(PixelFormat pixelFormat)
    {
        ColorPalette palette 
    = null;
        
    if (pixelFormat == PixelFormat.Format1bppIndexed || pixelFormat == PixelFormat.Format4bppIndexed || pixelFormat == PixelFormat.Format8bppIndexed)
        {
            
    //因为ColorPalette类没有构造函数,所以这里创建一个1x1的位图,然后抓取该位图的调色板
            Bitmap temp = new Bitmap(11, pixelFormat);
            palette 
    = temp.Palette;
            temp.Dispose();
        }
        
    return palette;
    }

    /// <summary>
    /// 根据颜色深度,创建对应的调色板
    /// </summary>
    /// <param name="depth">颜色深度,即表示颜色所用的位数</param>
    /// <returns>返回调色板</returns>
    private ColorPalette CreateColorPalette(int depth)
    {
        
    //根据颜色数,决定使用什么样的调色板
        PixelFormat pixelFormat = PixelFormat.Format1bppIndexed;
        
    if (depth > 2)
            pixelFormat 
    = PixelFormat.Format4bppIndexed;
        
    if (depth > 16)
            pixelFormat 
    = PixelFormat.Format8bppIndexed;
        
    return CreateColorPalette(pixelFormat);
    }

    /// <summary>
    /// 创建256级灰度调色板
    /// </summary>
    /// <returns>返回调色板</returns>
    private ColorPalette CreateGrayscalePalette()
    {
        ColorPalette palette 
    = CreateColorPalette(PixelFormat.Format8bppIndexed);
        
    for (int i = 0; i < palette.Entries.Length; i++)
            palette.Entries[i] 
    = Color.FromArgb(255, i, i, i);
        
    return palette;
    }

        使用上述方法创建好调色板,按需要修改调色板的颜色之后,用它来替换图像原有的调色板就可以了。

        3.为什么不能直接修改调色板?

        今天中午无聊,想看看为什么不能直接修改调色板。于是用Relector查看Image.Palette属性的设置部分 ,相关代码如下:

    Image.Palette属性的设置部分
    1.Image.Palette属性的set部分
    public void set_Palette(ColorPalette value)
    {
        
    this._SetColorPalette(value);
    }

    2.设置调色板
    private void _SetColorPalette(ColorPalette palette)
    {
        
    //将调色板对象转换成内存块
        IntPtr ptr = palette.ConvertToMemory();
        
    //调用API函数设置图像的调色板
        int status = SafeNativeMethods.Gdip.GdipSetImagePalette(new HandleRef(thisthis.nativeImage), ptr);
        
    if (ptr != IntPtr.Zero)
        {
            Marshal.FreeHGlobal(ptr);
        }
        
    if (status != 0)
        {
            
    throw SafeNativeMethods.Gdip.StatusException(status);
        }
    }

        通过这里我们可以发现,GDI+中的Image是对API的封装,直接修改调色板中的颜色并不会调用到GdipSetImagePalette这个API函数,当然不会生效了。

        我们的任务就是要让GdipSetImagePalette运行一次,使修改过的调色板生效。

    解决问题

        我们可以这么处理:(1)将调色板保存到临时变量palette内;(2)修改调色板中的颜色;(3)将palette赋值给Image.Palette以调用GdipSetImagePalette。这么处理之后,效果跟方法2一样,但是不用在内存中创建临时图像,效率更高。代码如下:

    //创建灰度图像
    Bitmap bCustom = new Bitmap(22, PixelFormat.Format8bppIndexed);
    //修改调色板为256级灰色
    ColorPalette palette = bCustom.Palette;    //用临时变量保存调色板
    for (int i = 0; i < palette.Entries.Length; i++)
        palette.Entries[i] 
    = Color.FromArgb(255, i, i, i);
    //让修改过的调色板生效
    bCustom.Palette = palette;
    //设置图像的值,从上至下依次为:黑、浅黑、灰、白
    BitmapData data = bCustom.LockBits(new Rectangle(0022), mageLockMode.WriteOnly, bCustom.PixelFormat);
    unsafe
    {
        
    byte* p = (byte*)data.Scan0.ToPointer();
        
    *= 0;
        
    *(p + 1= 85;
        
    *(p + data.Stride) = 170;
        
    *(p + data.Stride + 1= 255;
    }
    bCustom.UnlockBits(data);
    //保存图像到文件,便于查看校验结果
    bCustom.Save("b.jpg");
    //释放图像
    bCustom.Dispose();

        感谢您的耐心阅读,希望本文对您有所帮助。

        (注意:本文于2010年1月25日有改动,修改了其中的一处BUG。)

  • 相关阅读:
    Android 2.2 r1 API 中文文档系列(11) —— RadioButton
    Android API 中文 (15) —— GridView
    Android 中文 API (16) —— AnalogClock
    Android2.2 API 中文文档系列(7) —— ImageButton
    Android2.2 API 中文文档系列(6) —— ImageView
    Android 2.2 r1 API 中文文档系列(12) —— Button
    Android2.2 API 中文文档系列(8) —— QuickContactBadge
    [Android1.5]TextView跑马灯效果
    [Android1.5]ActivityManager: [1] Killed am start n
    Android API 中文(14) —— ViewStub
  • 原文地址:https://www.cnblogs.com/xrwang/p/AdjustdotnetColorPalette.html
Copyright © 2011-2022 走看看