Как правильно выполнять альфа-смешение? (С)

Я пишу очень графическую библиотеку и пытаюсь понять, как выполнять альфа-смешивание. Я пробовал это несколько раз, но мои результаты были менее чем удовлетворительными. Согласно Википедии, я должен делать:

Значение = (1-альфа) Значение0 + альфа значение1

Однако это совсем не работает. Может я что не так делаю?

Код, который я включил, рисует красочную картинку (это функция «приближения»), а затем пытается нарисовать частично прозрачный прямоугольник в точке (100,100). Однако вместо белого полупрозрачного поля я получаю странное искажение изображения (я постараюсь разместить их внизу своего поста). Какие-либо предложения? Вот мой код:

#include "hgl.h"

void proximity()
{
    int x = 0, y = 0, d1, d2, d3, dcenter;

    while(x < WIDTH){
        while(y < HEIGHT){
            d1 = distance(x, y, (WIDTH/2) - 200, (HEIGHT/2) + 200);
            d2 = distance(x, y, (WIDTH/2) + 200, (HEIGHT/2) + 200);
            d3 = distance(x, y, (WIDTH/2), (HEIGHT/2) - 150);
            dcenter = distance(x, y, WIDTH/2, HEIGHT/2);
            putpixel(x, y, d1, d2, d3);
            y++;
        }
        y = 0;
        x++;
    }
}

int alpha_transparency(float alpha, float value1, float value2)
{
    return (1-alpha) * value1 + alpha * value2;
}

void transparent_box(int pos_x, int pos_y, int width, int height, float alpha, char r, char g, char b)
{
    int x = 0, y = 0;
    while(x < width)
    {
        while(y < height)
        {
            int rr, rg, rb;
            rr = alpha_transparency(alpha, p.bitmap[x+pos_x][y+pos_y].r, r);
            rg = alpha_transparency(alpha, p.bitmap[x+pos_x][y+pos_y].g, g);
            rb = alpha_transparency(alpha, p.bitmap[x+pos_x][y+pos_y].b, b);
            putpixel(pos_x + x, pos_y + y, rr, rg, rb);
            y++;
        }
        x++;
        y = 0;
    }
}

int main()
{
    fp = fopen("out.bmp","wb");

    set_dimensions(1440, 900);
    insert_header();

    white_screen();

    proximity();
    transparent_box(100, 100, 500, 500, .9, 255, 255, 255);

    insert_image();
    fclose(fp);
    return 0;
}

Извините, я не смог включить вывод, потому что я новый пользователь. Однако вот ссылки:

Исходное изображение

Изображение с "прозрачным" полем


person Hassan    schedule 30.06.2010    source источник
comment
Для начала вы смешиваете числа с плавающей запятой и целые числа без явного приведения типов. В частности, ваша функция alpha_transparency объявлена ​​как возвращающая int, но возвращает float. Знает ли C об автоматическом преобразовании возвращаемого значения?   -  person phkahler    schedule 01.07.2010
comment
C знает, как автоматически преобразовать возвращаемое значение. Это может вызвать у вас проблемы, если он автоматически усекает дробь до 0.   -  person A. Levy    schedule 01.07.2010
comment
См. Предыдущий вопрос: stackoverflow.com/questions / 1944095 / how-to-mix-two-argb-pixels   -  person Mark Ransom    schedule 01.07.2010
comment
вы должны проверить мой ответ здесь: stackoverflow.com/questions/3153390/. Он точно объясняет, почему вы получили такой результат и как вы можете легко его исправить.   -  person MSN    schedule 02.07.2010


Ответы (3)


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

Однако не следует передавать компоненты цвета как char, который по умолчанию подписан. Их следует передавать как unsigned char или как более широкий целочисленный тип. Происходит то, что вместо передачи 255, как вы ожидаете, вы передаете -1.

Другими словами, сохраните свои цветовые компоненты как unsigned chars, чтобы убедиться, что у вас нет махинаций с подписью (см. EDIT2).

РЕДАКТИРОВАТЬ: Обратите внимание, что если ваша альфа находится в [0, 255], вы должны нормализовать ее до [0, 1], чтобы выполнить операцию альфа-смешивания.

РЕДАКТИРОВАТЬ2: Кроме того, если вы сохраняете свои пиксели как char вместо unsigned char, это объясняет странное зажимание, которое я видел:

alpha_transparency(0.9, (char)255, (char)255)
== alpha_transparency(0.9f, -1.0f, -1.0f)
== -1.0f
== 0xff (cast to char)

alpha_transparency(0.9, (char)128, (char)255)
== alpha_transparency(0.9f, -128.0f, -1.0f)
== -13.7f
== 0xf3

alpha_transparency(0.9, (char)127, (char)255)
== alpha_transparency(0.9f, 127.0f, -1.0f)
== -11.80f
== 0x0b

alpha_transparency(0.9, (char)0, (char)255)
== alpha_transparency(0.9f, 0.0f, -1.0f)
== -0.9f
== 0x00
person MSN    schedule 01.07.2010

Я думаю, проблема в том, как вы работаете с цветами. Метод, используемый в Википедии, предполагает, что 0 - черный, 1 - белый, а 0,5 - посередине. Однако ваш код использует целые числа, поэтому я предполагаю, что вы определяете 0 как черный и 255 как белый.

Итак, правильный код:

return (255-alpha)*value1+alpha*value2;

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

float result = (255.0f-alpha)*value1+alpha*value2;
return (int) result;

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

person Timothy Baldridge    schedule 30.06.2010
comment
Что ж, одно можно сказать наверняка: насчет округления вы были правы. Я даже не спрашивал об этом! Спасибо! Что касается прозрачности, то, похоже, это не сработало. Вы были правы насчет цветов, для меня они варьируются от 0 до 255. Удивительно, но изменение с (1-альфа) на (255-альфа) не имело большого значения. Значение цвета 256 точно такое же, как значение цвета ноль, поэтому я думаю, что в первый раз он просто зациклился и произвел тот же общий эффект. Я пока буду возиться с этим, и еще раз спасибо за помощь! Хасан - person Hassan; 01.07.2010
comment
Альфа-расчет правильный; в этом случае альфа - это дробное значение, которое следует интерполировать между значением1 и значением2. Тот факт, что диапазон цветов составляет [0, 255], не имеет значения. - person MSN; 02.07.2010

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

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

int alpha_transparency(int alpha, int value1, int value2) {
  int unscaled= (255-alpha)*value1 + alpha*value2;
  return unscaled / 255;  /* integer division */
}

Для all-float вам нужно не забыть нормализовать целочисленные входные данные от необработанного значения в [0..255] (или что-то еще) до float в [0.0 .. 1.0], выполнить всю обработку, а затем преобразовать обратно только в целое число. в конце.

float input_raw_value(unsigned char value)  { return value/255.0; }
...
float alpha_transparency(float alpha, float value1, float value2) {
  return (1.0-alpha)*value1 + alpha*value2;
}
...
unsigned char output_raw_value(float value) { return value*255.0; }

Обратите внимание, что я проигнорировал проблемы округления в каждом методе; Как только вы освоите основы математики, вам следует обратить на это внимание. Кроме того, существуют различные уловки для замены делений (которые могут быть относительно медленными) умножением или перестановкой битов.

person comingstorm    schedule 01.07.2010