Russian Qt Forum

Qt => 2D и 3D графика => Тема начата: navrocky от Июль 25, 2011, 15:07



Название: Объединение двух QColor
Отправлено: navrocky от Июль 25, 2011, 15:07
Есть задача: надо наложить один QColor с прозрачностью поверх другого QColor, как будто происходило рисование точки одним цветом и  поверх рисование точки другим цветом. Не нашел в доке функций/методов для этого.  (

Сейчас вижу только один способ - самостоятельно просчитать цвет результирующей точки с учетом прозрачности. Но хотелось бы не изобретать лисапедов.


Название: Re: Объединение двух QColor
Отправлено: Ground от Июль 25, 2011, 15:13
Немного не понял вопроса, нужно смешать цвет до рисования?
Можно посмотреть QPainter::setCompositionMode


Название: Re: Объединение двух QColor
Отправлено: navrocky от Июль 25, 2011, 15:20
Да. Именно смешать два цвета, получить результирующий, при рисовании которым, результат должен совпадать с результатом последовательного рисования двумя цветами.


Название: Re: Объединение двух QColor
Отправлено: Igors от Июль 26, 2011, 10:29
Да. Именно смешать два цвета, получить результирующий, при рисовании которым, результат должен совпадать с результатом последовательного рисования двумя цветами.
Это неоднозначно, напр

- просто интерполировать 2 цвета с учетом их alpha (порядок слагаемых не имеет значения)
- второй цвет виден "сквозь" первый (здесь уже результат разный в зависимости от того кто первый)


Название: Re: Объединение двух QColor
Отправлено: m_ax от Июль 26, 2011, 12:32
По-моему это уже где-то обсуждалось)

То что результат будет зависеть от порядка наложения цветов, это понятно.
Предлагаю такую реализацию:

Код
C++ (Qt)
QColor mix2clr(const QColor &clr1, const QColor &clr2) {
   qreal r = clr1.redF() + (clr2.redF() - clr1.redF()) * clr2.alphaF();
   qreal g = clr1.greenF() + (clr2.greenF() - clr1.greenF()) * clr2.alphaF();
   qreal b = clr1.blueF() + (clr2.blueF() - clr1.blueF()) * clr2.alphaF();
   return QColor(r, g, b, qMax(clr1.alphaF(), clr2.alphaF()));
}
 


Название: Re: Объединение двух QColor
Отправлено: navrocky от Июль 26, 2011, 12:50
У меня получилось так:

Код
C++ (Qt)
inline qreal __blend(qreal s, qreal d, qreal a)
{
   return s + (d - s) * a;
}
 
QColor color_blend(const QColor& c1, const QColor& c2)
{
   QColor res;
   res.setRedF(__blend(c1.redF(), c2.redF(), c2.alphaF()));
   res.setGreenF(__blend(c1.greenF(), c2.greenF(), c2.alphaF()));
   res.setBlueF(__blend(c1.blueF(), c2.blueF(), c2.alphaF()));
   res.setAlphaF((1 - c1.alphaF()) * c2.alphaF() + c1.alphaF());
   return res;
}

Основное отличие от предыдущего поста получилось в просчете альфа-канала.


Название: Re: Объединение двух QColor
Отправлено: Igors от Июль 26, 2011, 13:25
То что результат будет зависеть от порядка наложения цветов, это понятно.
Лучше если человек скажет что ему нужно

Код
C++ (Qt)
   ...
   return QColor(r, g, b, qMax(clr1.alphaF(), clr2.alphaF()))
 
Если один цвет виден сквозь другой, прозрачность падает по зкспоненте
Код
C++ (Qt)
QColor CombineTrans( const QColor & fore, const QColor & back )
{
float foreA = fore.alphaF();
float backA = back.alphaF();
if (foreA + backA <= 0.0f) return QColor(0, 0, 0, 0); // full transparent
 
float newA = backA * (1.0f - foreA);  // added alpha
float sumA = foreA + newA;             // combined alpha
 
// Red component
float foreR = fore.redF();
float backR = back.redF();
float sumR = (foreR * foreA + backR * newA) / sumA;
 
// Green & Blue components
 ...
 
QColor result;
result.setRgbF(sumR, sumG, sumB, sumA);
return result;
}
 
Не пойму откуда такая велосипедо-боязнь?  :)


Название: Re: Объединение двух QColor
Отправлено: m_ax от Июль 26, 2011, 15:54
Цитировать
Не пойму откуда такая велосипедо-боязнь?
Да какая там велосипедобоязнь)) У меня таблетки от велосипедофобии)

Вот непонятно, зачем так накручивать:
Код
C++ (Qt)
// Red component
float foreR = fore.redF();
float backR = back.redF();
float sumR = (foreR * foreA + backR * newA) / sumA;
 

И что Вы всё float, да float? qreal - это ж double.

А по-поводу альфа-канала, согласен с тем, что здесь больше зависит от постановки задачи. В зависимости от желаемого поведения, короче)


Название: Re: Объединение двух QColor
Отправлено: Igors от Июль 27, 2011, 11:00
Да какая там велосипедобоязнь)) У меня таблетки от велосипедофобии)
У Вас с велосипедами все отлично, вот только проверять нужно тщательнее

Код
C++ (Qt)
   qreal r = clr1.redF() + (clr2.redF() - clr1.redF()) * clr2.alphaF();
 
Пусть clr1.alpha = 1.0f. Тогда выходной цвет должен быть = clr1 (позади полностью непрозрачного цвета ничего нового мы не увидим). А у Вас?

Вот непонятно, зачем так накручивать:
Ну вообще-то это называется "взвешивать"  :)

Edit: кстати о птичках
Код
C++ (Qt)
inline qreal __blend(qreal s, qreal d, qreal a)
{
   return s + (d - s) * a;
}
 
Этот весьма популярный inline (а иногда и макрос) часто называют lerp


Название: Re: Объединение двух QColor
Отправлено: m_ax от Июль 27, 2011, 16:23
Цитировать
Пусть clr1.alpha = 1.0f. Тогда выходной цвет должен быть = clr1 (позади полностью непрозрачного цвета ничего нового мы не увидим). А у Вас?
Нет, clr1 - это цвет который кладут первым. Результирующий цвет зависит как от прозрачности этого первого clr1.alphaF() так и от прозрачности второго цвета clr2.
Если у обоих цветов альфа = 1, то результирующий цвет будет соответствовать clr2, поскольку он кладётся последним и полностью закрашивает первый clr1.


Название: Re: Объединение двух QColor
Отправлено: Igors от Июль 27, 2011, 17:06
Нет, clr1 - это цвет который кладут первым. Результирующий цвет зависит как от прозрачности этого первого clr1.alphaF() так и от прозрачности второго цвета clr2.
Если у обоих цветов альфа = 1, то результирующий цвет будет соответствовать clr2, поскольку он кладётся последним и полностью закрашивает первый clr1.
Если бы Вы назвали переменные как-то разумно (напр front, back) - ничего бы "дешифрировать" и пояснять не пришлось :) Непонятно причем тут "у обоих цветов" - если у переднего цвета полная alpha = 1.0f то какой цвет и alpha у "заднего" - до лампочки, сквозь совершенно непрозрачный передний все равно ничего не увидеть.

Код
C++ (Qt)
   qreal r = clr1.redF() + (clr2.redF() - clr1.redF()) * clr2.alphaF();
 
Это не пляшет потому что clr1.alpha никак не учтена. Ладно, подставим цифирки

// clr1 задний. 1% красного
clr1.red = 1.0
clr1.alpha = 0.01

// clr2 передний, красный отсутствует
clr2.red = 0.0;
clr2.alpha = 0.5;

r = 1.0 + (0.0 - 1.0) * 0.5 = 0.5
В итоге видимая красная компонента = 0.5 * 0.5 = 0.25  (25%). Как так если в обоих исходных красного было 1%? Опять ребенок накакал больше собственного веса   :)


Название: Re: Объединение двух QColor
Отправлено: m_ax от Июль 27, 2011, 20:07
Цитировать
В итоге видимая красная компонента = 0.5 * 0.5 = 0.25  (25%). Как так если в обоих исходных красного было 1%? Опять ребенок накакал больше собственного веса   Улыбающийся
Да, верно, здесь явный косяк)

А как Вы себе представляете процесс смешения цветов? В смысле, какой аналогией с реальностью руководствуетесь в этом методе весов?

Вот я сейчас прикинул такой вариант.
Итоговый цвет это результат следующего процесса.
Есть источник некоторых объектов (типа фотонов) которые характеризуются тремя значениями (r, g, b) Источник приготавлевает их в состоянии (r=1, g=1, b=1).

Есть приёмник, который их принимает и может распознать значение каждой компоненты.
В приёмник они попадают в результате отражения от "плёнки", причём плёнка обладает свойствами:
1) процентом отражённых "фотонов" (альфа-канал)
2) новыми компонентами r, g, b. (при условии что падающие ф. были в состоянии (1, 1, 1)).
3) прошедшие сквозь плёнку ф. не подвергаются изменению компонент.

Теперь наш случай с наложением двух цветов.
Пусть первый цвет - back, а накладываемый на него - front.
Тогда, в рамках рассматриваемой модели итоговый цвет есть результат следующего процесса:

Приёмник испускает N "фотонов" в состоянии (1, 1, 1) которые вначале падают на front - плёнку. Число отразившихся от неё ф.
R1 = N*front.alphaF()
которые попадают в приёмник и имеют компоненты соответствующего front - цвета.
Число прошедших ф.
D1 = N*(1 - front.alphaF())
которые имеют по-прежнему компоненты (1, 1, 1) и попадают на back - пластинку.
Число отразившихся от неё составит:
R2 = D1 * back.alphaF()
после чего они попадают в приёмник.

И так, в приёмнике у нас имеется R1 "фотонов" с компонентами front и R2 "фотонов" с компонентами back. Общие число испущенных ф. N.
1) Считаем итоговый альфа-канал:
alpha = (R1 + R2)/N =  front.alphaF() * (1 - back.alphaF()) + back.alphaF()

2) Считаем итоговый цвет:
R = a1 * front.redF() + a2 * back.redF();
G = ...
B = ...
где
a1 + a2 = 1
a1/a2 = R1/R2
т.е.
a1 = front.alphaF() / (front.alphaF() + back.alphaF() * (1 - front.alphaF()))
a2 = back.alphaF() * (1 - front.alphaF()) / (front.alphaF() + back.alphaF() * (1 - front.alphaF()))

Вобщем это всего лишь один из вариантов, просто как аналогия некого процесса, который можно себе представить.


Название: Re: Объединение двух QColor
Отправлено: Igors от Июль 27, 2011, 21:19
a1 = front.alphaF() / (front.alphaF() + back.alphaF() * (1 - front.alphaF()))
a2 = back.alphaF() * (1 - front.alphaF()) / (front.alphaF() + back.alphaF() * (1 - front.alphaF()))
Теперь все верно, но записывать надо аккуратно и не лениться создавать локальные переменные.

Если говорить о фотонах то лучше в терминах "фотонной карты". Да, действительно, изначально испускается N фотонов. Проходя через материал, цвет фотонов не изменяется, просто их разное число проходит. Если материал полностью прозрачен (alpha = 0) то все фотоны пройдут и наоборот ни один при (alpha = 1). То есть с альфой все прекрасно сходится, и мы быстро приходим к тому же численному выражению экспоненты. А вот с RGB такого нет. Если материал напр. зеленый - мы не можем сказать что, мол, он отражает зеленую компоненту а остальные пропускает - тогда бы зеленая бутылка бросала фиолетовую тень, но в жизни этого нет. Но мы всегда можем взвеситься по альфе - и в принятой модели это 100% корректно.

А вообще хотя это самая популярная модель прозрачности "subtractive" (вычитающая) - не надо считать что это "корректно и единственно правильно" - это всего лишь "удобно". Материал может напр поглощать какие-то цветовые компоненты (и совсем не в терминах RGB), "отражение" - то просто так сказано, их там вида 3 только популярных и.т.д. и.т.п


Название: Re: Объединение двух QColor
Отправлено: m_ax от Июль 27, 2011, 21:41
Цитировать
Если говорить о фотонах то лучше в терминах "фотонной карты". Да, действительно, изначально испускается N фотонов. Проходя через материал, цвет фотонов не изменяется, просто их разное число проходит. Если материал полностью прозрачен (alpha = 0) то все фотоны пройдут и наоборот ни один при (alpha = 1). То есть с альфой все прекрасно сходится, и мы быстро приходим к тому же численному выражению экспоненты. А вот с RGB такого нет. Если материал напр. зеленый - мы не можем сказать что, мол, он отражает зеленую компоненту а остальные пропускает - тогда бы зеленая бутылка бросала фиолетовую тень, но в жизни этого нет. Но мы всегда можем взвеситься по альфе - и в принятой модели это 100% корректно.
На все вопросы, касательно света, ответит КЭД  :)


Название: Re: Объединение двух QColor
Отправлено: Igors от Июль 28, 2011, 09:45
На все вопросы, касательно света, ответит КЭД  :)
Со своим провинциальным политехом полагаю что это "квантовая электродинамика" (поправьте если не так). Действительно, уравнение Максвелла первое что тыкается человеку в морду почти в любой статье. Вот только толку от него как от козла молока :) Есть конечно фанаты "смотрите какой прекрасный результат 640x480 и всего-навсего после 30 часов рендера!". Но в реальной жизни упрощенные CG модели рулят. 


Название: Re: Объединение двух QColor
Отправлено: Fregloin от Июль 29, 2011, 12:26
я делал проще (не учитывая альфа канал правда)

uint32_t raw1 = color1.red()+color1.green()<<8+color1.blue()<<16;
uint32_t raw2 = color2.red()+color2.green()<<8+color2.blue()<<16;
uint32_t raw = raw1|raw2;
result.setRed(raw & 0x000000FF);
result.setGreen(raw & 0x0000FF00);
result.setBlue(raw & 0x00FF0000);

и такой подход работает, но вот на счет прозрачности скорее всего не сработает..