线性颜色渐变不起作用

时间:2015-03-31 19:59:53

标签: c++ colors

我正在尝试为我的Mandelbrot Set资源管理器创建一个颜色渐变类。

它从文本文件中读取颜色约束(RGBA8888颜色和0到1之间的位置)并将它们添加到矢量中,该矢量稍后用于确定某个位置的颜色。

为了计算颜色,算法从给定位置搜索下一个约束到任一侧,将颜色分成四个单个通道,然后,对于每个通道,搜索两者中的较低者并添加一部分差异等于(x-lpos)/(upos-lpos)与较低颜色的比率。然后,将通道移位并进行“或”运算,然后返回RGBA8888无符号整数。 (参见下面的代码。)

编辑:我完全重写了渐变类,修复了一些问题,并为了调试而使其更具可读性(尽管如此,它变慢了,但是-Os或多或少照顾那个)。然而,它仍然没有像它应该的那样。

class Gradient { //remade, Some irrelevant methods and de-/constructors removed
private:
    map<double, unsigned int> constraints;
public:
    unsigned int operator[](double value) {
        //Forbid out-of-range values, return black
        if (value < 0 || value > 1+1E-10) return 0xff;
        //Find upper and lower constraint
        auto upperC = constraints.lower_bound(value);
        if (upperC == constraints.end()) upperC = constraints.begin();
        auto lowerC = upperC == constraints.begin() ? prev(constraints.end(), 1) : prev(upperC, 1);
        if (value == lowerC->first) return lowerC->second;
        double lpos = lowerC->first;
        double upos = upperC->first;
        if (upos < lpos) upos += 1;
        //lower color channels
        unsigned char lred = (lowerC->second >> 24) & 0xff;
        unsigned char lgreen = (lowerC->second >> 16) & 0xff;
        unsigned char lblue = (lowerC->second >> 8) & 0xff;
        unsigned char lalpha = lowerC->second & 0xff;
        //upper color channels
        unsigned char ured = (upperC->second >> 24) & 0xff;
        unsigned char ugreen = (upperC->second >> 16) & 0xff;
        unsigned char ublue = (upperC->second >> 8) & 0xff;
        unsigned char ualpha = upperC->second & 0xff;
        unsigned char red = 0, green = 0, blue = 0, alpha = 0xff;
        //Compute each channel using
        //  lower color + dist(lower, x)/dist(lower, upper) * diff(lower color, upper color)
        if (lred < ured)
            red = lred + (value - lpos)/(upos - lpos) * (ured - lred);
        else red = ured + (upos - value)/(upos - lpos) * (ured - lred);
        if (lgreen < ugreen)
            green = lgreen + (value - lpos)/(upos - lpos) * (ugreen - green);
        else green = ugreen + (upos - value)/(upos - lpos) * (ugreen - lgreen);
        if (lblue < ublue)
            blue = lblue + (value - lpos)/(upos - lpos) * (ublue - lblue);
        else blue = ublue + (upos - value)/(upos - lpos) * (ublue - lblue);
        if (lalpha < ualpha)
            alpha = lalpha + (value - lpos)/(upos - lpos) * (ualpha - lalpha);
        else alpha = ualpha + (upos - value)/(upos - lpos) * (ualpha - lalpha);
        //Merge channels together and return
        return (red << 24) | (green << 16) | (blue << 8 ) | alpha;
    }
    void addConstraint(unsigned int color, double position) {
        constraints[position] = color;
    }
};

更新方法中的用法:

image[r + rres*i] = grd[ratio];
//With image being a vector<unsigned int>, which is then used as data source for a `SDL_Texture` using `SDL_UpdateTexture`

但它只能部分起作用。当我只使用黑/白渐变时,生成的图像符合预期:

black-white works flawlessly

渐变文件:

2
0 000000ff
1 ffffffff

然而,当我使用更鲜艳的渐变(Ultra Fractal gradient的线性版本,下面的输入文件),图像远离预期的结果图像仍然没有显示所需的颜色:

enter image description here

渐变文件:

5
0       000764ff
.16     206bcbff
.42     edffffff
.6425   ffaa00ff
0.8575  000200ff

我做错了什么?我已多次重写operator[]方法,没有任何改变。

欢迎提出有关我的代码的澄清或一般性评论的问题。

2 个答案:

答案 0 :(得分:2)

您的问题是由于插补功能过于复杂。

使用其他因子a .. b(范围r)在0 .. 1范围内线性插值以指示该范围内的位置时,完全没有必要确定ab更大。无论哪种方式,你都可以使用:

result = a + r * (b - a)

如果r == 0这显然是a,如果r == 1 a - a取消,只留下b。同样,如果r == 0.5则结果为(a + b) / 2。无论是a > b还是反之亦然。

在您的情况下,首选配方,因为它避免了可能达到范围限制限制的b - a减法:

result = (1 - r) * a + r * b;

在您的新*类上给出了适当的+RGBA运算符,可以实现mid函数的简单实现(不需要每个组件操作,因为它们“在这些运营商处理”:

static RGBA mid(const RGBA& a, const RGBA& b, double r) {
    return (1.0 - r)  * a + r * b;
}

参见https://gist.github.com/raybellis/4f69345d8e0c4e83411b,我还重构了你的RGBA类,将钳位操作放在构造函数中,而不是放在各个运算符中。

答案 1 :(得分:1)

经过一番广泛的反复试验后,我终于成功了。 (此时非常感谢@Alnitak,他建议使用单独的RGBA颜色类。)

主要问题是,当上限约束的颜色值低于下限值的颜色值时,我仍然乘以比率(x-l)/(u-l),而我应该使用它的坠饰{{1} 1}},将上约束的颜色称为新约束的基础。

下面是RGBA类和固定梯度类的实现:

1 - (x-l)/(u-l)

class RGBA {
private:
    unsigned int red = 0, green = 0, blue = 0, alpha = 0;
public:
    static RGBA mid(RGBA a, RGBA b, double r) {
        RGBA color;
        if (a.red < b.red) color.red = a.red + (b.red - a.red) * r;
        else color.red = b.red + (a.red - b.red) * (1-r);
        if (a.green < b.green) color.green = a.green + (b.green - a.green) * r;
        else color.green = b.green + (a.green - b.green) * (1-r);
        if (a.blue < b.blue) color.blue = a.blue + (b.blue - a.blue) * r;
        else color.blue = b.blue + (a.blue - b.blue) * (1-r);
        if (a.alpha < b.alpha) color.alpha = a.alpha + (b.alpha - a.alpha) * r;
        else color.alpha = b.alpha + (a.alpha - b.alpha) * (1-r);
        return color;
    }
    RGBA() {};
    RGBA(unsigned char _red, unsigned char _green, unsigned char _blue, unsigned char _alpha) :
            red(_red), green(_green), blue(_blue), alpha(_alpha) {};
    RGBA(unsigned int _rgba) {
        red = (_rgba >> 24) & 0xff;
        green = (_rgba >> 16) & 0xff;
        blue = (_rgba >> 8) & 0xff;
        alpha = _rgba & 0xff;
    };
    operator unsigned int() {
        return (red << 24) | (green << 16) | (blue << 8 ) | alpha;
    }
    RGBA operator+(const RGBA& o) const {
        return RGBA((red + o.red) & 0xff, (green + o.green) & 0xff, (blue + o.blue) & 0xff, (alpha + o.alpha) & 0xff);
    }
    RGBA operator-(const RGBA& o) const {
        return RGBA(min(red - o.red, 0u), min(green - o.green, 0u), min(blue - o.blue, 0u), min(alpha - o.alpha, 0u));
    }
    RGBA operator~() {
        return RGBA(0xff - red, 0xff - green, 0xff - blue, 0xff - alpha);
    }
    RGBA operator*(double _f) {
        return RGBA((unsigned int) min(red * _f, 0.) & 0xff, (unsigned int) min(green * _f, 0.) & 0xff,
                    (unsigned int) min(blue * _f, 0.) & 0xff, (unsigned int) min(alpha * _f, 0.) & 0xff);
    }
};

结果如下:

elephant valley