在c#中将double值转换为RGB Color

时间:2013-11-21 12:16:06

标签: c# colors

我正在尝试将double值(介于0和1之间)转换为RGB Color。在下面的代码中,您可以看到我尝试做什么,但我觉得这个算法有问题。我没有得到所有的颜色。当我从double转换为int或者我不确定时,可能有松散的信息...但是请看看它,如果你有任何建议或任何其他方法(经过验证的方法),请告诉我:

    private Color generateRGB(double X)
    {
        Color color;
        if (X >= 0.5) //red and half of green colors
        {
            int Red = (int)((2 * X - 1) * 255);
            int Green = (int)((2 - 2 * X) * 255);
            int Blue = 0;
            color = Color.FromArgb(Red, Green, Blue);
        }
        else  // blue and half of green colors
        {
            int Red = 0;
            int Green = (int)((2 * X) * 255);
            int Blue = (int)((1 - 2 * X) * 255);
            color = Color.FromArgb(Red, Green, Blue);
        }
        return color;
    }

这是表达我想要做的最好的图像。

https://www.dropbox.com/s/bvs3a9m9nc0rk5e/20131121_143044%20%281%29.jpg

[更新]

这就是我的表现,这似乎是一个很好的解决方案。请看看它并告诉我天气这是更好的代表性(也许那些对色彩空间有更好了解的人可以给出反馈)

我从这里使用了HSVtoRGB转换算法:http://www.splinter.com.au/converting-hsv-to-rgb-colour-using-c/。 知道我的值是[0,1]间隔,我m extending this interval to [0, 360] in order to use the algorithm for converting HSV to RGB. I m使用s和v等于1在我的情况下。以下是更好解释的代码。

        private Color generateRGB(double X)
        {
            Color color;

            int red;
            int green;
            int blue;
            HsvToRgb(X*360,1,1,out red,out green,out blue);

            color = Color.FromArgb(red, green, blue);

            return color;
        }

2 个答案:

答案 0 :(得分:11)

在你的一条评论中,你说:" no我的目的是包括所有的颜色,我不想赞成他们中的任何一种。简单地说,我想要将双值转换为RGB颜色的最佳方法"

因此,您并不关心doubleColor之间的实际关系,并且您不希望对double中的Color值进行操作一种与他们的Color同行一致的方式。 在这种情况下,事情比你预期的要容易。

我可能会提醒您,RGB颜色由3个字节组成,但由于组合原因,.NET BCL类int提供3个组件作为double值。

所以你有3个字节! float占用8个字节。 如果我的假设是正确的,那么在这个答案的最后你可能会考虑StructLayoutAttribute作为一个更好的候选人(当然,如果一个较小的足迹对你很重要)。

足够的聊天,对实际问题。 我即将布局的方法与数学和内存管理和编码没有那么多联系。

您是否听说过FieldOffsetAttribute属性及其随行人员CommonDenominatorBetweenColoursAndDoubles属性? 万一你没有被他们敬畏。

假设你有一个结构,我们称之为public struct CommonDenominatorBetweenColoursAndDoubles { public byte R; public byte G; public byte B; public double AsDouble; } 。 让我们说它包含4个公共字段,如下所示:

R

现在,假设您希望以这样的方式编排编译器和即将发生的运行时,以便GBAsDouble字段(每个占用1个字节)连续布局并且struct字段在它们的前3个字节中重叠它们并继续它自己的,仅保留5个字节。你是怎么做到的?

使用上述属性指定:

  1. 您控制R布局的事实(小心,功能强大,责任重大)
  2. GBstructbyte内的第0,1第1和第2个字节开始的事实(因为我们知道AsDouble占据了struct 1个字节)mscorlib.dll也从System.Runtime.InteropServices内的第0个字节开始。
  3. 属性位于[StructLayout(LayoutKind.Explicit)] public struct CommonDenominatorBetweenColoursAndDoubles { [FieldOffset(0)] public byte R; [FieldOffset(1)] public byte G; [FieldOffset(2)] public byte B; [FieldOffset(0)] public double AsDouble; } 命名空间下的struct,您可以在此处StructLayoutFieldOffset了解这些属性。

    所以你可以实现所有这些:

    public static double ToDouble(this Color @this) {
    
        CommonDenominatorBetweenColoursAndDoubles denom = new CommonDenominatorBetweenColoursAndDoubles ();
    
        denom.R = (byte)@this.R;
        denom.G = (byte)@this.G;
        denom.B = (byte)@this.B;
    
        double result = denom.AsDouble;
        return result;
    
    }
    
    public static Color ToColor(this double @this) {
    
        CommonDenominatorBetweenColoursAndDoubles denom = new CommonDenominatorBetweenColoursAndDoubles ();
    
        denom.AsDouble = @this;
    
        Color color = Color.FromArgb (
            red: denom.R,
            green: denom.G,
            blue: denom.B
        );
        return color;
    
    }
    

    这里的for (int x = 0; x < 255; x++) { for (int y = 0; y < 255; y++) { for (int z = 0; z < 255; z++) { var c1 = Color.FromArgb (x, y, z); var d1 = c1.ToDouble (); var c2 = d1.ToColor (); var x2 = c2.R; var y2 = c2.G; var z2 = c2.B; if ((x != x2) || (y != y2) || (z != z2)) Console.Write ("1 error"); } } } (有点)实例中的内存是什么样的:

    Diagram showing memory details of converter struct

    除了几种扩展方法之外,有什么更好的方式来包装它:

    double

    我也对此进行了测试,以确保它具有防弹功能,据我所知,您不必担心任何事情:

    0

    完成后不会产生任何错误。

    修改

    在我开始编辑之前:如果你研究double encoding standard一点(这在所有语言,框架和最可能的大多数处理器之间是通用的)你将得出结论(我也测试过)通过迭代通过8字节双精度的3个最低有效字节(24个最低有效位)的所有组合,这就是我们在这里正在做的事情,你将得到double.Epsilon * (256 * 3 - 1)值,这些值在数学上受到0位于低端,double.Epsilon * (256 * 3 - 1)位于另一端(包含)。当然,如果剩下的更重要的5个字节用2²⁴ s填充,那就是这样。

    如果它已经不清楚,double是一个非常小的数字,人们甚至无法发音。 你对发音的最佳看法是:它是0和最小正8.28904556439245E-317大于256 * 3(非常小)之间的产品,或者它是否更适合你:2²⁴

    在该范围内,您会发现您的double正是0&#34;连续&#34; double值,以2²⁴开头,以最小0 .. double.Epsilon * (2²⁴ - 1)距离分隔。

    通过数学(逻辑值)操作(而不是通过直接内存寻址),您可以轻松地将0 .. 1个数字范围从原始double.Epsilon扩展到ε

    这就是我所说的:

    Linear transformation

    不要将e(或double.Epsilon)误认为是指数字母0N在某种程度上是它的微积分对应物的表示,它可能意味着最大实数大于N

    所以,为了确保我们为编码做好准备,让我们回顾一下这里发生的事情:

    我们2²⁴double0ε * (N-1)个数字从ε开始,以double.Epsilon结尾(其中double },或0最小struct大于double[] allDoubles = new double[256 * 256 * 256]; double cursor = 0; int index = 0; for (int r = 0; r < 256; r++) for (int g = 0; g < 256; g++) for (int b = 0; b < 256; b++) { allDoubles[index] = cursor; index++; cursor += double.Epsilon; } )。

    从某种意义上说,我们创建的struct实际上只是帮助我们做到这一点:

    N

    那么,为什么我们会遇到R的所有麻烦? 因为它更快,因为它不涉及任何数学运算,并且我们能够根据G,{{1}随机访问B值中的任何一个}}和0输入。

    现在,转到线性转换位。

    我们现在要做的只是一些数学运算(因为它涉及浮点运算需要更长时间才能计算,但是会成功地将我们的双精度范围扩展到1和{之间的均匀分布范围。 {1}}):

    在我们之前创建的struct中,我们将重命名AsDouble字段,将其设为私有,并创建一个名为AsDouble的新属性来处理转换(两者都有)方式):

    [StructLayout(LayoutKind.Explicit)]
    public struct CommonDenominatorBetweenColoursAndDoubles {
    
        [FieldOffset(0)]
        public byte R;
        [FieldOffset(1)]
        public byte G;
        [FieldOffset(2)]
        public byte B;
    
        // we renamed this field in order to avoid simple breaks in the consumer code
        [FieldOffset(0)]
        private double _AsDouble;
    
        // now, a little helper const
        private const int N_MINUS_1 = 256 * 256 * 256 - 1;
    
        // and maybe a precomputed raw range length
        private static readonly double RAW_RANGE_LENGTH = double.Epsilon * N_MINUS_1;
    
        // and now we're adding a property called AsDouble
        public double AsDouble {
            get { return this._AsDouble / RAW_RANGE_LENGTH; }
            set { this._AsDouble = value * RAW_RANGE_LENGTH; }
        }
    
    }
    

    您会惊喜地发现我在 EDIT 之前提出的测试仍然正常工作,这个新增功能,因此您有0%的信息丢失,现在双打的范围是同等地延伸到0 .. 1

答案 1 :(得分:1)

如上文评论中所述,您绘制的公式不满足您统一跨越整个颜色范围的条件。我相信这应该有效(不是迄今为止唯一可行的解​​决方案):

* 编辑:修正了公式,之前没有生成所有可能的颜色

int red = Math.Min((int)(X * 256), 255);
int green = Math.Min((int)((X * 256 - red) * 256), 255);
int blue = Math.Min((int)(((X * 256 - red) * 256 - green) * 256), 255);

Math.Min用于将边界方案修复为X -> 1D