Подтвердить что ты не робот

Как альфа-смесь RGBA беззнакового байтового цвета быстро?

Я использую С++, я хочу сделать альфа-смесь, используя следующий код.

#define CLAMPTOBYTE(color) \
    if ((color) & (~255)) { \
        color = (BYTE)((-(color)) >> 31); \
    } else { \
        color = (BYTE)(color); \
    }
#define GET_BYTE(accessPixel, x, y, scanline, bpp) \
    ((BYTE*)((accessPixel) + (y) * (scanline) + (x) * (bpp))) 

    for (int y = top ; y < bottom; ++y)
    {
        BYTE* resultByte = GET_BYTE(resultBits, left, y, stride, bytepp);
        BYTE* srcByte = GET_BYTE(srcBits, left, y, stride, bytepp);
        BYTE* srcByteTop = GET_BYTE(srcBitsTop, left, y, stride, bytepp);
        BYTE* maskCurrent = GET_GREY(maskSrc, left, y, width);
        int alpha = 0;
        int red = 0;
        int green = 0;
        int blue = 0;
        for (int x = left; x < right; ++x)
        {
            alpha = *maskCurrent;
            red = (srcByteTop[R] * alpha + srcByte[R] * (255 - alpha)) / 255;
            green = (srcByteTop[G] * alpha + srcByte[G] * (255 - alpha)) / 255;
            blue = (srcByteTop[B] * alpha + srcByte[B] * (255 - alpha)) / 255;
            CLAMPTOBYTE(red);
            CLAMPTOBYTE(green);
            CLAMPTOBYTE(blue);
            resultByte[R] = red;
            resultByte[G] = green;
            resultByte[B] = blue;
            srcByte += bytepp;
            srcByteTop += bytepp;
            resultByte += bytepp;
            ++maskCurrent;
        }
    }

однако я считаю, что он все еще медленный, он занимает около 40-60 мс, когда составляет два 600 * 600 изображений. Есть ли способ повысить скорость до менее 16 мс?

Может ли любой орган помочь мне ускорить этот код? Большое спасибо!

4b9b3361

Ответ 1

Использовать SSE - начать со страницы 131.

Основной рабочий процесс

  • Загрузите 4 пикселя из src (16 байтовых номеров) RGBA RGBA RGBA RGBA (потоковая загрузка)

  • Загрузите еще 4, которые вы хотите смешать с помощью srcbytetop RGBx RGBx RGBx RGBx

  • Сделайте несколько swizzling, чтобы член A в 1 заполнял каждый слот I.e

    xxxA xxxB xxxC xxxD → AAAA BBBB CCCC DDDD

    В моем решении ниже я решил вместо этого использовать ваш существующий массив "масок", но если альфа, интегрированная в поле "А" 1, потребует меньше нагрузки из памяти и, следовательно, будет быстрее. Swizzling в этом случае, вероятно, будет: И с маской для выбора A, B, C, D. Сдвиг вправо 8, Или с оригиналом, сдвигом вправо 16 или снова.

  • Добавьте вышеперечисленное к вектору, который все -255 в каждом слоте

  • Умножьте 1 * 4 (источник с 255-альфа) и 2 * 3 (результат с альфой).

    Вы можете использовать для этого команду SSE2 "умножить и отбросить нижние 8 бит".

  • добавьте эти два (4 и 5) вместе

  • Храните их где-нибудь еще (если возможно) или поверх вашего адресата (если нужно)

Вот вам стартовая точка:

    //Define your image with __declspec(align(16)) i.e char __declspec(align(16)) image[640*480]
    // so the first byte is aligned correctly for SIMD.
    // Stride must be a multiple of 16.

    for (int y = top ; y < bottom; ++y)
    {
        BYTE* resultByte = GET_BYTE(resultBits, left, y, stride, bytepp);
        BYTE* srcByte = GET_BYTE(srcBits, left, y, stride, bytepp);
        BYTE* srcByteTop = GET_BYTE(srcBitsTop, left, y, stride, bytepp);
        BYTE* maskCurrent = GET_GREY(maskSrc, left, y, width);
        for (int x = left; x < right; x += 4)
        {
            //If you can't align, use _mm_loadu_si128()
            // Step 1
            __mm128i src = _mm_load_si128(reinterpret_cast<__mm128i*>(srcByte)) 
            // Step 2
            __mm128i srcTop = _mm_load_si128(reinterpret_cast<__mm128i*>(srcByteTop)) 

            // Step 3
            // Fill the 4 positions for the first pixel with maskCurrent[0], etc
            // Could do better with shifts and so on, but this is clear
            __mm128i mask = _mm_set_epi8(maskCurrent[0],maskCurrent[0],maskCurrent[0],maskCurrent[0],
                                        maskCurrent[1],maskCurrent[1],maskCurrent[1],maskCurrent[1],
                                        maskCurrent[2],maskCurrent[2],maskCurrent[2],maskCurrent[2],
                                        maskCurrent[3],maskCurrent[3],maskCurrent[3],maskCurrent[3],
                                        ) 

            // step 4
            __mm128i maskInv = _mm_subs_epu8(_mm_set1_epu8(255), mask) 

            //Todo : Multiply, with saturate - find correct instructions for 4..6
            //note you can use Multiply and add _mm_madd_epi16

            alpha = *maskCurrent;
            red = (srcByteTop[R] * alpha + srcByte[R] * (255 - alpha)) / 255;
            green = (srcByteTop[G] * alpha + srcByte[G] * (255 - alpha)) / 255;
            blue = (srcByteTop[B] * alpha + srcByte[B] * (255 - alpha)) / 255;
            CLAMPTOBYTE(red);
            CLAMPTOBYTE(green);
            CLAMPTOBYTE(blue);
            resultByte[R] = red;
            resultByte[G] = green;
            resultByte[B] = blue;
            //----

            // Step 7 - store result.
            //Store aligned if output is aligned on 16 byte boundrary
            _mm_store_si128(reinterpret_cast<__mm128i*>(resultByte), result)
            //Slow version if you can't guarantee alignment
            //_mm_storeu_si128(reinterpret_cast<__mm128i*>(resultByte), result)

            //Move pointers forward 4 places
            srcByte += bytepp * 4;
            srcByteTop += bytepp * 4;
            resultByte += bytepp * 4;
            maskCurrent += 4;
        }
    }

Чтобы узнать, какие процессоры AMD будут запускать этот код (в настоящее время он использует инструкции SSE2), см. Википедия Список микропроцессоров AMD Turion. Вы также можете посмотреть другие списки процессоров в Википедии, но мои исследования показывают, что AMD cpus примерно с 4 лет назад поддерживает как минимум SSE2.

Вы должны ожидать, что хорошая импликация SSE2 будет работать примерно в 8-16 раз быстрее, чем ваш текущий код. Это связано с тем, что мы устраняем ветки в цикле, обрабатываем 4 пикселя (или 12 каналов) сразу и улучшаем производительность кеша с помощью инструкций по потоку. В качестве альтернативы SSE вы, вероятно, могли бы сделать свой существующий код намного быстрее, исключив те проверки, которые вы используете для насыщения. Кроме того, мне нужно будет запустить профилировщик с вашей рабочей нагрузкой.

Конечно, лучшим решением является использование аппаратной поддержки (например, ваша проблема в DirectX) и сделайте это на видеокарте.

Ответ 2

Вы всегда можете рассчитать альфа красного и синего одновременно. Вы также можете использовать этот трюк с упомянутой ранее реализацией SIMD.

unsigned int blendPreMulAlpha(unsigned int colora, unsigned int colorb, unsigned int alpha)
{
    unsigned int rb = (colora & 0xFF00FF) + (alpha * (colorb & 0xFF00FF)) >> 8;
    unsigned int g = (colora & 0x00FF00) + (alpha * (colorb & 0x00FF00)) >> 8;
    return (rb & 0xFF00FF) + (g & 0x00FF00);
}


unsigned int blendAlpha(unsigned int colora, unsigned int colorb, unsigned int alpha)
{
    unsigned int rb1 = ((0x100 - alpha) * (colora & 0xFF00FF)) >> 8;
    unsigned int rb2 = (alpha * (colorb & 0xFF00FF)) >> 8;
    unsigned int g1  = ((0x100 - alpha) * (colora & 0x00FF00)) >> 8;
    unsigned int g2  = (alpha * (colorb & 0x00FF00)) >> 8;
    return ((rb1 | rb2) & 0xFF00FF) + ((g1 | g2) & 0x00FF00);
}

0 <= альфа = 0x100

Ответ 3

Для людей, которые хотят разделить на 255, я нашел идеальную формулу:

pt->r = (r+1 + (r >> 8)) >> 8; // fast way to divide by 255

Ответ 4

Вот некоторые указатели.

Рассмотрите возможность использования предварительно умноженных изображений переднего плана, как описано Porter и Duff. Как и потенциально быстрее, вы избегаете множества возможных эффектов цветокоррекции.

Уравнение состава изменяется от

r =  kA + (1-k)B

... to...

r =  A + (1-k)B

В качестве альтернативы вы можете переработать стандартное уравнение, чтобы удалить один раз.

r =  kA + (1-k)B
==  kA + B - kB
== k(A-B) + B

Возможно, я ошибаюсь, но думаю, вам не нужно зажимать...

Ответ 5

Нет точно ответа на вопрос, но...

Одно дело - сделать это быстро, другое - сделать все правильно. Альфа-композитинг - опасный зверь, он выглядит прямолинейно и интуитивно, но распространенные ошибки широко распространены в течение десятилетий, и никто его не замечает (почти)!

Самая известная и распространенная ошибка - это не использование премультиплексной альфы. Я настоятельно рекомендую: Alpha Blending for Leaves

Ответ 6

Вы можете использовать 4 байта на пиксель в обоих изображениях (для выравнивания по памяти), а затем использовать инструкции SSE для обработки всех каналов вместе. Поиск "visual studio sse intrinsics".

Ответ 7

Прежде всего, используйте правильную формулу для каждого цветового компонента

Вы начинаете с этого:

  v = ( 1-t ) * v0 + t * v1

где t = параметр интерполяции [0..1] v0 = значение исходного цвета v1 = значение передачи цвета v = выходное значение

Перетасовывая термины, мы можем уменьшить количество операций:

  v = v0 + t * (v1 - v0)

Вам нужно будет выполнить это вычисление один раз на цветной канал (3 раза для RGB).

Для 8-битных неподписанных цветовых компонентов вам необходимо использовать правильную математику с фиксированной точкой:

  i = i0 + t * ( ( i1 - i0 ) + 127 ) / 255

где t = параметр интерполяции [0..255] i0 = исходное значение цвета [0..255] i1 = значение цвета передачи [0..255] я = цвет вывода

Если вы оставите +127, тогда ваши цвета будут смещены в сторону более темного конца. Очень часто люди используют /256 или → 8 для скорости. Это неверно! Если вы разделите на 256, вы никогда не сможете достичь чистого белого (255,255,255), потому что 255/256 немного меньше единицы.

Надеюсь, это поможет.

Ответ 8

Я сделал аналогичный код в небезопасном С#. Есть ли причина, по которой вы не зацикливаете каждый пиксель напрямую? Зачем использовать все вызовы BYTE * и GET_BYTE()? Вероятно, это часть проблемы скорости.

Как выглядит GET_GRAY?

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

  • Mac OS X
  • Windows 2000, XP, Server 2003, Windows CE, Vista и Windows 7
  • Расширение XRender для системы X Window (включая современные Linux-системы).
  • Настройка RISC OS
  • QNX Neutrino
  • План 9
  • Inferno
  • AmigaOS 4.1
  • BeOS, Zeta и Haiku
  • слог
  • MorphOS

Ответ 9

Я думаю, что аппаратная поддержка вам поможет. попробуйте перевести логику с программного обеспечения на аппаратное обеспечение, если это возможно.

Ответ 10

Переместите его на GPU.

Ответ 11

Основная проблема будет заключаться в конструкции плохой петли, возможно, усугубленной компилятором, неспособным исключить CSE. Переместите реальные общие биты вне петель. int red не является обычным, то есть - то, что должно быть внутри внутреннего цикла.

Кроме того, красный, зеленый и синий независимы. Если вы вычисляете их по очереди, вам не нужно сохранять промежуточные красные результаты в реестрах, когда вы вычисляете зеленые результаты. Это особенно важно для процессоров с ограниченными регистрами, такими как x86.

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

Как уже отмечалось, зажим не требуется. В alphablending вы создаете линейную комбинацию из двух изображений: a [x] [y] и b [x] [y]. Так как 0 <= альфа = 255, вы знаете, что каждый вывод привязан max (255 * a [x] [y], 255 * b [x] [y]). И так как ваш выходной диапазон совпадает с обоими диапазонами ввода (0-255), это нормально.

С небольшой потерей точности вы можете рассчитать (a[x][y]*alpha * b[x][y]*(256-alpha))>>8. Битчшифты часто бывают быстрее, чем деление.

Ответ 12

Я не могу комментировать, потому что у меня недостаточно репутации, но я хочу сказать, что версия Jasper не будет переполняться для действительного ввода. Маскирование результата умножения необходимо, поскольку в противном случае красное + синее умножение оставило бы биты в зеленом канале (это также было бы верно, если бы вы умножали красный и синий отдельно, вам все равно нужно было замаскировать биты в синем канале), а зеленое умножение оставит биты в синем канале. Это биты, которые теряются вправо, если вы отделяете компоненты, как это часто бывает при альфа-смешении. Таким образом, они не переполняются или не переполняются. Это просто бесполезные биты, которые необходимо замаскировать для достижения ожидаемых результатов.

Тем не менее, версия Джаспера неверна. Он должен быть 0xFF-альфа (255-альфа), а не 0x100-альфа (256-альфа). Вероятно, это не приведет к видимой ошибке. Что приведет к видимой ошибке, так это его использование | вместо + при объединении результатов умножения.

Я обнаружил, что адаптация кода Jasper будет быстрее, чем мой старый альфа-код смешивания, который был уже приличным, и в настоящее время я использую его в своем проекте рендеринга программного обеспечения. Я работаю с 32-разрядными пикселями ARGB:

Pixel AlphaBlendPixels(Pixel p1, Pixel p2)
{
    static const int AMASK = 0xFF000000;
    static const int RBMASK = 0x00FF00FF;
    static const int GMASK = 0x0000FF00;
    static const int AGMASK = AMASK | GMASK;
    static const int ONEALPHA = 0x01000000;
    unsigned int a = (p2 & AMASK) >> 24;
    unsigned int na = 255 - a;
    unsigned int rb = ((na * (p1 & RBMASK)) + (a * (p2 & RBMASK))) >> 8;
    unsigned int ag = (na * ((p1 & AGMASK) >> 8)) + (a * (ONEALPHA | ((p2 & GMASK) >> 8)));
    return ((rb & RBMASK) | (ag & AGMASK));
}

Ответ 13

В зависимости от целевой архитектуры вы можете попробовать либо векторизовать, либо парализовать функцию.

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

Ответ 14

Я предполагаю, что вы хотите сделать это совершенно переносимым образом, без использования графического процессора, использование проприетарной библиотеки Intel SIMD (которая может работать не так эффективно на процессорах AMD).

Поместите следующее место для вашего расчета для RGB

R = TopR + (SourceR * alpha) >> 8;
G = TopG + (SourceG * alpha) >> 8;
B = TopB + (SourceB * alpha) >> 8; 

Это более эффективный расчет.

Также используйте инструкцию shift left на вашем макросе пикселя получения вместо умножения на BPP.

Ответ 15

Здесь моя адаптация альфа-программы, которая хорошо работает для 2 целых чисел без знака.

Мой код немного отличается от того, что код выше в основном всегда предполагает, что целевая альфа - 255.

При наличии достойного оптимизирующего компилятора большинство вычислений должно быть в регистре, поскольку область большинства переменных очень короткая. Я также решил постепенно сдвигать результат < 8, чтобы избежать < 24, 16 при объединении ARGB. Я знаю это давным-давно... но я помню, что на 286 циклах сдвиг был (1 + 1 * каждый бит сдвинут), поэтому предположим, что для больших сдвигов есть еще какой-то штраф.

Также... вместо "/255" я выбрал " → 8", который можно изменить по желанию.

/*
    alpha blend source and destination, either may have an alpha!!!!

    Src  AAAAAAAA RRRRRRRR GGGGGGGG BBBBBBBB
    Dest AAAAAAAA RRRRRRRR GGGGGGGG BBBBBBBB

    res  AAAAAAAA RRRRRRRR GGGGGGGG BBBBBBBB

    NOTE - α = αsrc + αdest(1.0-αsrc)  where α = 0.0 - 1.0

    ALSO - DWORD is unsigned int so (F8000000 >> 24) = F8 not FFFFFFF8 as it would with int (signed)
    */

    inline DWORD raw_blend(const DWORD src, const DWORD dest)
    {       
        // setup and calculate α

        DWORD src_a = src >> 24;       
        DWORD src_a_neg = 255 - src_a;
        DWORD dest_a = dest >> 24;

        DWORD res = src_a + ((dest_a * src_a_neg) >> 8);

        // setup and calculate R

        DWORD src_r = (src >> 16) & 255;
        DWORD dest_r = (dest >> 16) & 255;

        res = (res << 8) | (((src_r * src_a) + (dest_r * src_a_neg)) >> 8);

        // setup and calculate G

        DWORD src_g = (src >> 8) & 255;
        DWORD dest_g = (dest >> 8) & 255;

        res = (res << 8) | (((src_g * src_a) + (dest_g * src_a_neg)) >> 8);

        // setup and calculate B

        DWORD src_b = src & 255;
        DWORD dest_b = dest & 255;

        return (res << 8) | (((src_b * src_a) + (dest_b * src_a_neg)) >> 8);
    }