Алгоритм аддитивного смешивания цветов для значений RGB


Я ищу алгоритм для аддитивного смешивания цветов для значений RGB.

это так же просто, как добавление значений RGB вместе до максимума 256?

(r1, g1, b1) + (r2, g2, b2) =
    (min(r1+r2, 256), min(g1+g2, 256), min(b1+b2, 256))  
9 57

9 ответов:

это зависит от того, что вы хотите, и это может помочь увидеть, что результаты различных методов.

если вы хотите

Red + Black        = Red
Red + Green        = Yellow
Red + Green + Blue = White
Red + White        = White 
Black + White      = White

затем добавление с зажимом работает (например min(r1 + r2, 255)) Это больше похоже на модель Света, которую вы упомянули.

если вы хотите

Red + Black        = Dark Red
Red + Green        = Dark Yellow
Red + Green + Blue = Dark Gray
Red + White        = Pink
Black + White      = Gray

затем вам нужно будет усреднить значения (например,(r1 + r2) / 2) это работает лучше для осветления / затемнения цветов и создания градиентов.

для смешивания с использованием Альфа-каналов можно использовать следующие формулы:

r = new Color();
r.A = 1 - (1 - fg.A) * (1 - bg.A);
if (r.A < 1.0e-6) return r; // Fully transparent -- R,G,B not important
r.R = fg.R * fg.A / r.A + bg.R * bg.A * (1 - fg.A) / r.A;
r.G = fg.G * fg.A / r.A + bg.G * bg.A * (1 - fg.A) / r.A;
r.B = fg.B * fg.A / r.A + bg.B * bg.A * (1 - fg.A) / r.A;

fg - это цвет краски. bg - это фон. r результирующий цвет. 1.0e-6 это просто очень малое количество, чтобы компенсировать ошибки округления.

Примечание: все переменные, используемые здесь находятся в диапазоне [0.0, 1.0]. Вы должны разделить или умножить на 255, если вы хотите использовать значения в диапазоне [0, 255].

например, 50% красный поверх 50% зеленый:

// background, 50% green
var bg = new Color { R = 0.00, G = 1.00, B = 0.00, A = 0.50 };
// paint, 50% red
var fg = new Color { R = 1.00, G = 0.00, B = 0.00, A = 0.50 };
// The result
var r = new Color();
r.A = 1 - (1 - fg.A) * (1 - bg.A); // 0.75
r.R = fg.R * fg.A / r.A + bg.R * bg.A * (1 - fg.A) / r.A; // 0.67
r.G = fg.G * fg.A / r.A + bg.G * bg.A * (1 - fg.A) / r.A; // 0.33
r.B = fg.B * fg.A / r.A + bg.B * bg.A * (1 - fg.A) / r.A; // 0.00

результирующий цвет-это: (0.67, 0.33, 0.00, 0.75), или 75% коричневый (или темно-оранжевый).


вы также можете изменить эти формулы:

var bg = new Color();
if (1 - fg.A <= 1.0e-6) return null; // No result -- 'fg' is fully opaque
if (r.A - fg.A < -1.0e-6) return null; // No result -- 'fg' can't make the result more transparent
if (r.A - fg.A < 1.0e-6) return bg; // Fully transparent -- R,G,B not important
bg.A = 1 - (1 - r.A) / (1 - fg.A);
bg.R = (r.R * r.A - fg.R * fg.A) / (bg.A * (1 - fg.A));
bg.G = (r.G * r.A - fg.G * fg.A) / (bg.A * (1 - fg.A));
bg.B = (r.B * r.A - fg.B * fg.A) / (bg.A * (1 - fg.A));

или

var fg = new Color();
if (1 - bg.A <= 1.0e-6) return null; // No result -- 'bg' is fully opaque
if (r.A - bg.A < -1.0e-6) return null; // No result -- 'bg' can't make the result more transparent
if (r.A - bg.A < 1.0e-6) return bg; // Fully transparent -- R,G,B not important
fg.A = 1 - (1 - r.A) / (1 - bg.A);
fg.R = (r.R * r.A - bg.R * bg.A * (1 - fg.A)) / fg.A;
fg.G = (r.G * r.A - bg.G * bg.A * (1 - fg.A)) / fg.A;
fg.B = (r.B * r.A - bg.B * bg.A * (1 - fg.A)) / fg.A;

формулы рассчитают, что цвет фона или краски должен быть для получения заданного результирующего цвета.


если ваш фон непрозрачен, результат также будет непрозрачным. Затем цвет переднего плана может принимать диапазон значений с различными альфа-значение. Для каждого канала (красный, зеленый и синий) вы должны проверить, какой диапазон букв приводит к допустимым значениям (0 - 1).

забавный факт: значения RGB компьютера получены из квадратного корня потока фотонов. Поэтому в качестве общей функции ваша математика должна учитывать это. Общая функция для этого для данного канала:

blendColorValue(a, b, t)
    return sqrt((1 - t) * a^2 + t * b^2)

где A и b-цвета для смешивания, а t-число от 0-1, представляющее точку в смеси, которую вы хотите между a и b.

альфа-канал отличается; он не представляет интенсивность фотонов, а только процент фона, который должен показать до конца; поэтому при смешивании Альфа-значений достаточно линейного среднего:

blendAlphaValue(a, b, t)
    return (1-t)*a + t*b;

Итак, чтобы обрабатывать смешивание двух цветов, используя эти две функции, следующий псевдокод должен вам помочь:

blendColors(c1, c2, t)
    ret
    [r, g, b].each n ->
        ret[n] = blendColorValue(c1[n], c2[n], t)
    ret.alpha = blendAlphaValue(c1.alpha, c2.alpha, t)
    return ret

кстати, я стремлюсь к языку программирования и клавиатуре, которые позволяют представлять математику, которая (или более) чисто (объединяющий символ unicode overline не работает для надстроек, символов и огромного массива других символов) и интерпретирует ее правильно. sqrt ((1-t)*pow(a, 2) + t * pow (b, 2)) просто не читается как чистый.

несколько пунктов:

  • Я думаю, что вы хотите использовать min вместо max
  • Я думаю, что вы хотите использовать 255 вместо 256

Это даст:

(r1, g1, b1) + (r2, g2, b2) = (min(r1+r2, 255), min(g1+g2, 255), min(b1+b2, 255))

однако, "естественный" способ смешивания цветов заключается в использовании среднего, а затем вам не нужно min:

(r1, g1, b1) + (r2, g2, b2) = ((r1+r2)/2, (g1+g2)/2, (b1+b2)/2)

функция Javascript для смешивания цветов rgba

c1, c2 и результат-JSON, как c1={r:0.5,g: 1, b: 0,a:0.33}

    var rgbaSum = function(c1, c2){
       var a = c1.a + c2.a*(1-c1.a);
       return {
         r: (c1.r * c1.a  + c2.r * c2.a * (1 - c1.a)) / a,
         g: (c1.g * c1.a  + c2.g * c2.a * (1 - c1.a)) / a,
         b: (c1.b * c1.a  + c2.b * c2.a * (1 - c1.a)) / a,
         a: a
       }
     } 

PYTHONцветадля смешиваниячерездополнениена CMYKпробел

один из возможных способов сделать это, чтобы сначала преобразовать цвета CMYK формат, добавьте их там, а затем повторно преобразуйте в RGB.

вот пример кода на Python:

rgb_scale = 255
cmyk_scale = 100


def rgb_to_cmyk(self,r,g,b):
    if (r == 0) and (g == 0) and (b == 0):
        # black
        return 0, 0, 0, cmyk_scale

    # rgb [0,255] -> cmy [0,1]
    c = 1 - r / float(rgb_scale)
    m = 1 - g / float(rgb_scale)
    y = 1 - b / float(rgb_scale)

    # extract out k [0,1]
    min_cmy = min(c, m, y)
    c = (c - min_cmy) 
    m = (m - min_cmy) 
    y = (y - min_cmy) 
    k = min_cmy

    # rescale to the range [0,cmyk_scale]
    return c*cmyk_scale, m*cmyk_scale, y*cmyk_scale, k*cmyk_scale

def cmyk_to_rgb(self,c,m,y,k):
    """
    """
    r = rgb_scale*(1.0-(c+k)/float(cmyk_scale))
    g = rgb_scale*(1.0-(m+k)/float(cmyk_scale))
    b = rgb_scale*(1.0-(y+k)/float(cmyk_scale))
    return r,g,b

def ink_add_for_rgb(self,list_of_colours):
    """input: list of rgb, opacity (r,g,b,o) colours to be added, o acts as weights.
    output (r,g,b)
    """
    C = 0
    M = 0
    Y = 0
    K = 0

    for (r,g,b,o) in list_of_colours:
        c,m,y,k = rgb_to_cmyk(r, g, b)
        C+= o*c
        M+=o*m
        Y+=o*y 
        K+=o*k 

    return cmyk_to_rgb(C, M, Y, K)

результат на ваш вопрос будет тогда (предполагая половину-половину смеси из ваших двух цветов:

r_mix, g_mix, b_mix = ink_add_for_rgb([(r1,g1,b1,0.5),(r2,g2,b2,0.5)])

где 0.5 там сказать, что мы смешиваем 50% первого цвета с 50% второго цвета.

Да, это так просто. Другой вариант-найти среднее значение (для создания градиентов).

Это действительно просто зависит от эффекта, который вы хотите достичь.

однако, когда Альфа добавляется, это становится сложным. Существует несколько различных методов смешивания с использованием Альфа.

пример простого альфа-смешивания: http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending

вот высокооптимизированный, автономный класс c++, public domain, с плавающей точкой и двумя по-разному оптимизированными 8-битными механизмами смешивания как в функциональных, так и в макроформатах, а также техническое обсуждение как проблемы, так и того, как и важность оптимизации этой проблемы:

https://github.com/fyngyrz/colorblending

написал/использовал что-то вроде @Markus Jarderot ' s sRGB ответ на смешивание (который не исправляется гамма, так как это наследие по умолчанию) с использованием C++

//same as Markus Jarderot's answer
float red, green, blue;
alpha = (1.0 - (1.0 - back.alpha)*(1.0 - front.alpha));
red   = (front.red   * front.alpha / result.alpha + back.red   * back.alpha * (1.0 - front.alpha));
green = (front.green * front.alpha / result.alpha + back.green * back.alpha * (1.0 - front.alpha));
blue  = (front.blue  * front.alpha / result.alpha + back.blue  * back.alpha * (1.0 - front.alpha));

//faster but equal output
alpha = (1.0 - (1.0 - back.alpha)*(1.0 - front.alpha));
red   = (back.red   * (1.0 - front.alpha) + front.red   * frontAlpha);
green = (back.green * (1.0 - front.alpha) + front.green * frontAlpha);
blue  = (back.blue  * (1.0 - front.alpha) + front.blue  * frontAlpha);

//even faster but only works when all values are in range 0 to 255
int red, green, blue;
alpha = (255 - (255 - back.alpha)*(255 - front.alpha));
red   = (back.red   * (255 - front.alpha) + front.red   * frontAlpha) / 255;
green = (back.green * (255 - front.alpha) + front.green * frontAlpha) / 255;
blue  = (back.blue  * (255 - front.alpha) + front.blue  * frontAlpha) / 255;

подробнее: что-каждый-кодер-должен-знать-о-гамма