【问题标题】:Linear color gradient not working线性颜色渐变不起作用
【发布时间】:2015-06-05 07:33:32
【问题描述】:

我目前正在尝试为我的 Mandelbrot Set explorer 创建一个颜色渐变类。

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

为了计算颜色,算法从给定位置搜索到任一侧的下一个约束,将颜色分成四个单独的通道,然后,对于每个通道,搜索两者中较低的一个并添加一部分差异等于(x-lpos)/(upos-lpos) 与较低颜色的比率。之后,通道被移位和 ORed 一起,然后以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`

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

渐变文件:

2
0 000000ff
1 ffffffff

但是,当我使用更彩色的渐变(Ultra Fractal gradient 的线性版本,下面的输入文件)时,图像与预期结果相差甚远图像仍然没有显示想要的颜色:

渐变文件:

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

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

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

【问题讨论】:

  • 你真的应该把很多代码从operator[]移到读取约束的方法中。使用已排序的 vector 而不是 map 来保存约束,并尽早将它们解析为单独的 RGBA 组件。
  • 哦,如果您将 RGBA 组件存储在它自己的类中,您可能应该将对这些颜色进行基本数学运算的方法放在该类中。
  • 为什么地图会有问题?我之前实际上使用了一个向量,但后来切换到了一个映射,因为映射中的值按键升序排序,这在初始化时给出了对数访问时间。
  • RGBA 只是一个无符号整数,operator[] 实际上是读取约束的方法。
  • 糟糕,您对正在排序的地图是正确的。我的观点仍然是该函数中的代码过多。

标签: c++ colors


【解决方案1】:

您的问题是由于插值函数过于复杂。

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

result = a + r * (b - a)

如果r == 0 这被简单地显示为a,如果r == 1a - a 取消,只留下b。同样,如果r == 0.5 则结果为(a + b) / 2a &gt; b 或反之亦然无关紧要。

在您的情况下,首选公式是:

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 类,将钳位操作放在构造函数中,而不是放在单个运算符中。

【讨论】:

    【解决方案2】:

    经过大量的反复试验,我终于设法让它发挥作用。 (此时非常感谢@Alnitak,他建议使用单独的 RGBA 颜色类。)

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

    下面是RGBA类和固定渐变类的实现:

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

    class Gradient {
    private:
        map<double, RGBA> constraints;
    public:
        Gradient() {
            constraints[0] = RGBA(0x007700ff);
            constraints[1] = RGBA(0xffffffff);
        }
        ~Gradient() {}
        void addConstraint(RGBA color, double position) {
            constraints[position] = color;
        }
        void reset() {
            constraints.clear();
        }
        unsigned int operator[](double value) {
            if (value < 0 || value > 1+1E-10) return 0xff;
            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;
            RGBA lower = lowerC->second;
            RGBA upper = upperC->second;
            RGBA color = RGBA::mid(lower, upper, (value-lpos)/(upos-lpos));
            return color;
        }
        size_t size() {
            return constraints.size();
        }
    };
    

    这是结果:

    【讨论】:

    • 您实际上不需要比较开始和结束组件来插入它们。如果r 是插值度(范围为 0 .. 1),那么您只需要val = (1 - r) * start + r * end。事实上你的整个mid(a, b, r) 函数可以变成return (1 - r) * a + r * b
    猜你喜欢
    • 2021-07-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-12
    • 2019-10-06
    • 1970-01-01
    • 1970-01-01
    • 2019-02-22
    相关资源
    最近更新 更多