Быстрый способ копирования памяти с перевод в формате ARGB БГР


обзор

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

Итак, метод грубой силы:

// Assume a 32 x 32 pixel image
#define IMAGESIZE (32*32)

typedef struct{ UInt8 Alpha; UInt8 Red; UInt8 Green; UInt8 Blue; } ARGB;
typedef struct{ UInt8 Blue; UInt8 Green; UInt8 Red; } BGR;

ARGB orig[IMAGESIZE];
BGR  dest[IMAGESIZE];

for(x = 0; x < IMAGESIZE; x++)
{
     dest[x].Red = orig[x].Red;
     dest[x].Green = orig[x].Green;
     dest[x].Blue = orig[x].Blue;
}

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

дополнительная информация

каждое изображение кратно не менее 4 пикселей. Таким образом, мы могли бы обратиться к 16 байтам ARGB и переместить их в 12 байтов RGB на цикл. Возможно, этот факт можно использовать для ускорения работы, тем более что он хорошо вписывается в 32-битные границы.

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

хотя я привел пример небольших буферов выше, я действительно перемещаю HD-видео (1920x1080), а иногда и большие, в основном меньшие, буферы вокруг, поэтому, хотя ситуация 32x32 может быть тривиальной, копирование 8,3 МБ данных изображения байт за байтом действительно, очень плохо.

работает на Intel процессоры (Core 2 и выше) и, следовательно, существуют команды потоковой передачи и обработки данных, о которых я знаю, но не знаю - возможно, указатели на то, где искать специализированные инструкции по обработке данных, были бы хороши.

это происходит в приложении OS X, и я использую XCode 4. Если сборка безболезненна и очевидный способ пойти, я прекрасно путешествую по этому пути, но не сделав этого на этой установке раньше, заставляет меня опасаться погружения слишком много времени в оно.

псевдокод в порядке-я не ищу полного решения, просто алгоритм и объяснение любого обмана, который может быть не сразу понятен.

11 65

11 ответов:

я написал 4 разных версии, которые работают путем замены байтов. Я скомпилировал их с помощью gcc 4.2.1 с -O3 -mssse3, пробежал их 10 раз по 32 МБ случайных данных и нашел средние значения.

первая версия использует цикл C для преобразования каждого пикселя отдельно, с помощью OSSwapInt32 функция (которая компилируется в bswap инструкция -O3).

void swap1(ARGB *orig, BGR *dest, unsigned imageSize) {
    unsigned x;
    for(x = 0; x < imageSize; x++) {
        *((uint32_t*)(((uint8_t*)dest)+x*3)) = OSSwapInt32(((uint32_t*)orig)[x]);
    }
}

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

void swap2(ARGB *orig, BGR *dest, unsigned imageSize) {
    asm (
        "0:\n\t"
        "movl   (%1),%%eax\n\t"
        "bswapl %%eax\n\t"
        "movl   %%eax,(%0)\n\t"
        "addl   ,%1\n\t"
        "addl   ,%0\n\t"
        "decl   %2\n\t"
        "jnz    0b"
        :: "D" (dest), "S" (orig), "c" (imageSize)
        : "flags", "eax"
    );
}

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

typedef uint8_t v16qi __attribute__ ((vector_size (16)));
void swap3(uint8_t *orig, uint8_t *dest, size_t imagesize) {
    v16qi mask = __builtin_ia32_lddqu((const char[]){3,2,1,7,6,5,11,10,9,15,14,13,0xFF,0xFF,0xFF,0XFF});
    uint8_t *end = orig + imagesize * 4;
    for (; orig != end; orig += 16, dest += 12) {
        __builtin_ia32_storedqu(dest,__builtin_ia32_pshufb128(__builtin_ia32_lddqu(orig),mask));
    }
}

наконец, четвертая версия является эквивалентом встроенной сборки третьей.

void swap2_2(uint8_t *orig, uint8_t *dest, size_t imagesize) {
    int8_t mask[16] = {3,2,1,7,6,5,11,10,9,15,14,13,0xFF,0xFF,0xFF,0XFF};//{0xFF, 0xFF, 0xFF, 0xFF, 13, 14, 15, 9, 10, 11, 5, 6, 7, 1, 2, 3};
    asm (
        "lddqu  (%3),%%xmm1\n\t"
        "0:\n\t"
        "lddqu  (%1),%%xmm0\n\t"
        "pshufb %%xmm1,%%xmm0\n\t"
        "movdqu %%xmm0,(%0)\n\t"
        "add    ,%1\n\t"
        "add    ,%0\n\t"
        "sub    ,%2\n\t"
        "jnz    0b"
        :: "r" (dest), "r" (orig), "r" (imagesize), "r" (mask)
        : "flags", "xmm0", "xmm1"
    );
}

на моем MacBook Pro 2010 года, 2,4 ГГц i5, 4 ГБ оперативной памяти, это были средние времена для каждый:

Version 1: 10.8630 milliseconds
Version 2: 11.3254 milliseconds
Version 3:  9.3163 milliseconds
Version 4:  9.3584 milliseconds

как вы можете видеть, компилятор достаточно хорош в оптимизации, вам не нужно писать ассамблеи. Кроме того, векторные функции были всего на 1,5 миллисекунды быстрее на 32 МБ данных, поэтому это не причинит большого вреда, если вы хотите поддерживать самые ранние компьютеры Intel Mac, которые не поддерживали SSSE3.

Edit: лиори запросил информацию о стандартном отклонении. К сожалению, я не сохранил точки данных, поэтому я провел еще один тест с 25 повторения.

              Average    | Standard Deviation
Brute force: 18.01956 ms | 1.22980 ms (6.8%)
Version 1:   11.13120 ms | 0.81076 ms (7.3%)
Version 2:   11.27092 ms | 0.66209 ms (5.9%)
Version 3:    9.29184 ms | 0.27851 ms (3.0%)
Version 4:    9.40948 ms | 0.32702 ms (3.5%)

кроме того, вот необработанные данные из новых тестов, если кто-то этого хочет. Для каждой итерации набор данных размером 32 МБ был сгенерирован случайным образом и выполнялся через четыре функции. Время выполнения каждой функции в микросекундах указано ниже.

Brute force: 22173 18344 17458 17277 17508 19844 17093 17116 19758 17395 18393 17075 17499 19023 19875 17203 16996 17442 17458 17073 17043 18567 17285 17746 17845
Version 1:   10508 11042 13432 11892 12577 10587 11281 11912 12500 10601 10551 10444 11655 10421 11285 10554 10334 10452 10490 10554 10419 11458 11682 11048 10601
Version 2:   10623 12797 13173 11130 11218 11433 11621 10793 11026 10635 11042 11328 12782 10943 10693 10755 11547 11028 10972 10811 11152 11143 11240 10952 10936
Version 3:    9036  9619  9341  8970  9453  9758  9043 10114  9243  9027  9163  9176  9168  9122  9514  9049  9161  9086  9064  9604  9178  9233  9301  9717  9156
Version 4:    9339 10119  9846  9217  9526  9182  9145 10286  9051  9614  9249  9653  9799  9270  9173  9103  9132  9550  9147  9157  9199  9113  9699  9354  9314

очевидное, используя pshufb.

#include <assert.h>
#include <inttypes.h>
#include <tmmintrin.h>

// needs:
// orig is 16-byte aligned
// imagesize is a multiple of 4
// dest has 4 trailing scratch bytes
void convert(uint8_t *orig, size_t imagesize, uint8_t *dest) {
    assert((uintptr_t)orig % 16 == 0);
    assert(imagesize % 4 == 0);
    __m128i mask = _mm_set_epi8(-128, -128, -128, -128, 13, 14, 15, 9, 10, 11, 5, 6, 7, 1, 2, 3);
    uint8_t *end = orig + imagesize * 4;
    for (; orig != end; orig += 16, dest += 12) {
        _mm_storeu_si128((__m128i *)dest, _mm_shuffle_epi8(_mm_load_si128((__m128i *)orig), mask));
    }
}

объединение только ответов poseur и Jitamaro, если вы предполагаете, что входы и выходы выровнены по 16 байтам, и если вы обрабатываете пиксели 4 одновременно, вы можете использовать комбинацию перетасовок, масок, ands и ors для хранения с использованием выровненных магазинов. Основная идея состоит в том, чтобы сгенерировать четыре промежуточных набора данных, затем или их вместе с масками выбрать соответствующие значения пикселей и записать 3 16-байтовых набора данных пикселей. Обратите внимание, что я не компилировал это или не пытался запустить его все.

EDIT2: более подробно о базовой структуре кода:

С SSE2 вы получаете лучшую производительность при 16-байтовом выравнивании чтения и записи 16 байт. Поскольку ваш 3-байтовый пиксель выравнивается только до 16 байт для каждых 16 пикселей, мы пакуем 16 пикселей за раз, используя комбинацию перетасовок и масок и ors из 16 входных пикселей за раз.

от LSB до MSB, входы выглядят так, игнорируя конкретные компоненты:

s[0]: 0000 0000 0000 0000
s[1]: 1111 1111 1111 1111
s[2]: 2222 2222 2222 2222
s[3]: 3333 3333 3333 3333

и выхода выходит выглядеть так:

d[0]: 000 000 000 000 111 1
d[1]:  11 111 111 222 222 22
d[2]:   2 222 333 333 333 333

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

d[0]= combine_0(f_0_low(s[0]), f_0_high(s[1]))
d[1]= combine_1(f_1_low(s[1]), f_1_high(s[2]))
d[2]= combine_2(f_1_low(s[2]), f_1_high(s[3]))

теперь, что должен combine_<x> выглядеть? Если мы предположим, что d просто s спрессованы вместе, мы можем объединить два s'S с маской и или:

combine_x(left, right)= (left & mask(x)) | (right & ~mask(x))

где (1 означает выбор левого пикселя, 0 означает выбор правого пиксель): маска(0)= 111 111 111 111 000 0 маска(1)= 11 111 111 000 000 00 маска(2)= 1 111 000 000 000 000

но фактические преобразования (f_<x>_low,f_<x>_high) на самом деле не так просто. Поскольку мы обращаем и удаляем байты из исходного пикселя, фактическое преобразование (для первого назначения для краткости):

d[0]= 
    s[0][0].Blue s[0][0].Green s[0][0].Red 
    s[0][1].Blue s[0][1].Green s[0][1].Red 
    s[0][2].Blue s[0][2].Green s[0][2].Red 
    s[0][3].Blue s[0][3].Green s[0][3].Red
    s[1][0].Blue s[1][0].Green s[1][0].Red
    s[1][1].Blue

если вы переведете вышеизложенное в байтовые смещения от источника к dest, вы получите: d[0]= &s[0]+3 &s[0]+2 &s[0]+1
&s[0]+7 &s[0]+6 &s[0]+5 &s[0]+11 &s[0]+10 &s[0]+9 &s[0]+15 &s[0]+14 &s[0]+13
&s[1]+3 &s[1]+2 &s[1]+1
&s[1]+7

(если вы посмотрите на все смещения s[0], они соответствуют только маске перетасовки позера в обратном порядке.)

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

f_0_low=  3 2 1  7 6 5  11 10 9  15 14 13  X X X  X
f_0_high= X X X  X X X   X  X X   X  X  X  3 2 1  7

f_1_low=    6 5  11 10 9  15 14 13  X X X   X X X  X  X
f_1_high=   X X   X  X X   X  X  X  3 2 1   7 6 5  11 10

f_2_low=      9  15 14 13  X  X  X  X X X   X  X  X  X  X  X
f_2_high=     X   X  X  X  3  2  1  7 6 5   11 10 9  15 14 13

мы можно дополнительно оптимизировать это, посмотрев маски, которые мы используем для каждого исходного пикселя. Если вы посмотрите на маски перемешивания, которые мы используем для s[1]:

f_0_high=  X  X  X  X  X  X  X  X  X  X  X  X  3  2  1  7
f_1_low=   6  5 11 10  9 15 14 13  X  X  X  X  X  X  X  X

поскольку две маски перемешивания не перекрываются, мы можем объединить их и просто замаскировать ненужные пиксели в combine_, что мы уже сделали! Следующий код выполняет все эти оптимизации (плюс он предполагает, что исходный и конечный адреса выровнены по 16 байтам). Кроме того, маски выписываются в коде в MSB - >LSB заказ, в случае, если вы запутались в заказе.

изменить: изменил магазин на _mm_stream_si128 поскольку вы, вероятно, делаете много записей, и мы не хотим обязательно очищать кэш. Плюс он должен быть выровнен в любом случае, так что вы получите бесплатный perf!

#include <assert.h>
#include <inttypes.h>
#include <tmmintrin.h>

// needs:
// orig is 16-byte aligned
// imagesize is a multiple of 4
// dest has 4 trailing scratch bytes
void convert(uint8_t *orig, size_t imagesize, uint8_t *dest) {
    assert((uintptr_t)orig % 16 == 0);
    assert(imagesize % 16 == 0);

    __m128i shuf0 = _mm_set_epi8(
        -128, -128, -128, -128, // top 4 bytes are not used
        13, 14, 15, 9, 10, 11, 5, 6, 7, 1, 2, 3); // bottom 12 go to the first pixel

    __m128i shuf1 = _mm_set_epi8(
        7, 1, 2, 3, // top 4 bytes go to the first pixel
    -128, -128, -128, -128, // unused
        13, 14, 15, 9, 10, 11, 5, 6); // bottom 8 go to second pixel

    __m128i shuf2 = _mm_set_epi8(
        10, 11, 5, 6, 7, 1, 2, 3, // top 8 go to second pixel
    -128, -128, -128, -128, // unused
        13, 14, 15, 9); // bottom 4 go to third pixel

    __m128i shuf3 = _mm_set_epi8(
        13, 14, 15, 9, 10, 11, 5, 6, 7, 1, 2, 3, // top 12 go to third pixel
        -128, -128, -128, -128); // unused

    __m128i mask0 = _mm_set_epi32(0, -1, -1, -1);
    __m128i mask1 = _mm_set_epi32(0,  0, -1, -1);
    __m128i mask2 = _mm_set_epi32(0,  0,  0, -1);

    uint8_t *end = orig + imagesize * 4;
    for (; orig != end; orig += 64, dest += 48) {
        __m128i a= _mm_shuffle_epi8(_mm_load_si128((__m128i *)orig), shuf0);
        __m128i b= _mm_shuffle_epi8(_mm_load_si128((__m128i *)orig + 1), shuf1);
        __m128i c= _mm_shuffle_epi8(_mm_load_si128((__m128i *)orig + 2), shuf2);
        __m128i d= _mm_shuffle_epi8(_mm_load_si128((__m128i *)orig + 3), shuf3);

        _mm_stream_si128((__m128i *)dest, _mm_or_si128(_mm_and_si128(a, mask0), _mm_andnot_si128(b, mask0));
        _mm_stream_si128((__m128i *)dest + 1, _mm_or_si128(_mm_and_si128(b, mask1), _mm_andnot_si128(c, mask1));
        _mm_stream_si128((__m128i *)dest + 2, _mm_or_si128(_mm_and_si128(c, mask2), _mm_andnot_si128(d, mask2));
    }
}

Я немного опаздываю на вечеринку, похоже, что сообщество уже решило для pshufb-ответа poseur, но распространяя репутацию 2000,которая настолько чрезвычайно щедра, что я должен попробовать.

вот моя версия без специфических для платформы встроенных или машинных asm, я включил некоторый кросс-платформенный код синхронизации, показывающий 4х ускорение если вы оба немного крутите, как я и активировать компилятора-оптимизация (оптимизация регистра, развертывание цикла):

#include "stdlib.h"
#include "stdio.h"
#include "time.h"

#define UInt8 unsigned char

#define IMAGESIZE (1920*1080) 
int main() {
    time_t  t0, t1;
    int frames;
    int frame; 
    typedef struct{ UInt8 Alpha; UInt8 Red; UInt8 Green; UInt8 Blue; } ARGB;
    typedef struct{ UInt8 Blue; UInt8 Green; UInt8 Red; } BGR;

    ARGB* orig = malloc(IMAGESIZE*sizeof(ARGB));
    if(!orig) {printf("nomem1");}
    BGR* dest = malloc(IMAGESIZE*sizeof(BGR));
    if(!dest) {printf("nomem2");}

    printf("to start original hit a key\n");
    getch();
    t0 = time(0);
    frames = 1200;
    for(frame = 0; frame<frames; frame++) {
        int x; for(x = 0; x < IMAGESIZE; x++) {
            dest[x].Red = orig[x].Red;
            dest[x].Green = orig[x].Green;
            dest[x].Blue = orig[x].Blue;
            x++;
        }
    }
    t1 = time(0);
    printf("finished original of %u frames in %u seconds\n", frames, t1-t0);

    // on my core 2 subnotebook the original took 16 sec 
    // (8 sec with compiler optimization -O3) so at 60 FPS 
    // (instead of the 1200) this would be faster than realtime 
    // (if you disregard any other rendering you have to do). 
    // However if you either want to do other/more processing 
    // OR want faster than realtime processing for e.g. a video-conversion 
    // program then this would have to be a lot faster still.

    printf("to start alternative hit a key\n");
    getch();
    t0 = time(0);
    frames = 1200;
    unsigned int* reader;
    unsigned int* end = reader+IMAGESIZE;
    unsigned int cur; // your question guarantees 32 bit cpu
    unsigned int next;
    unsigned int temp;
    unsigned int* writer;
    for(frame = 0; frame<frames; frame++) {
        reader = (void*)orig;
        writer = (void*)dest;
        next = *reader;
        reader++;
        while(reader<end) {
            cur = next;
            next = *reader;         
            // in the following the numbers are of course the bitmasks for 
            // 0-7 bits, 8-15 bits and 16-23 bits out of the 32
            temp = (cur&255)<<24 | (cur&65280)<<16|(cur&16711680)<<8|(next&255); 
            *writer = temp;
            reader++;
            writer++;
            cur = next;
            next = *reader;
            temp = (cur&65280)<<24|(cur&16711680)<<16|(next&255)<<8|(next&65280);
            *writer = temp;
            reader++;
            writer++;
            cur = next;
            next = *reader;
            temp = (cur&16711680)<<24|(next&255)<<16|(next&65280)<<8|(next&16711680);
            *writer = temp;
            reader++;
            writer++;
        }
    }
    t1 = time(0);
    printf("finished alternative of %u frames in %u seconds\n", frames, t1-t0);

    // on my core 2 subnotebook this alternative took 10 sec 
    // (4 sec with compiler optimization -O3)

}

результаты таковы (на моем ядре 2 subnotebook):

F:\>gcc b.c -o b.exe

F:\>b
to start original hit a key
finished original of 1200 frames in 16 seconds
to start alternative hit a key
finished alternative of 1200 frames in 10 seconds

F:\>gcc b.c -O3 -o b.exe

F:\>b
to start original hit a key
finished original of 1200 frames in 8 seconds
to start alternative hit a key
finished alternative of 1200 frames in 4 seconds

вы хотите использовать устройство Даффа:http://en.wikipedia.org/wiki/Duff%27s_device. Он также работает в JavaScript. Этот пост, однако, немного смешно читать http://lkml.indiana.edu/hypermail/linux/kernel/0008.2/0171.html. Представьте себе устройство Duff с 512 Кбайт ходов.

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

void bulk_bgrFromArgb(byte[] dest, byte[] src, int n)
{
       thread threads[] = {
           create_thread(bgrFromArgb, dest, src, n/4),
           create_thread(bgrFromArgb, dest+n/4, src+n/4, n/4),
           create_thread(bgrFromArgb, dest+n/2, src+n/2, n/4),
           create_thread(bgrFromArgb, dest+3*n/4, src+3*n/4, n/4),
       }
       join_threads(threads);
}

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

код предназначен для MinGW GCC с Intel assembly flavour, вам придется изменить его в соответствии с вашим компилятором / ассемблером.

extern "C" {
    int convertARGBtoBGR(uint buffer, uint size);
    __asm(
        ".globl _convertARGBtoBGR\n"
        "_convertARGBtoBGR:\n"
        "  push ebp\n"
        "  mov ebp, esp\n"
        "  sub esp, 4\n"
        "  mov esi, [ebp + 8]\n"
        "  mov edi, esi\n"
        "  mov ecx, [ebp + 12]\n"
        "  cld\n"
        "  convertARGBtoBGR_loop:\n"
        "    lodsd          ; load value from [esi] (4byte) to eax, increment esi by 4\n"
        "    bswap eax ; swap eax ( A R G B ) to ( B G R A )\n"
        "    stosd          ; store 4 bytes to [edi], increment  edi by 4\n"
        "    sub edi, 1; move edi 1 back down, next time we will write over A byte\n"
        "    loop convertARGBtoBGR_loop\n"
        "  leave\n"
        "  ret\n"
    );
}

вы должны назвать это так:

convertARGBtoBGR( &buffer, IMAGESIZE );

эта функция обращается к памяти только дважды на пиксель / пакет (1 чтение, 1 запись) по сравнению с вашим грубой силы метод который имел (по крайней мере / предполагая, что он был скомпилирован для регистрации) 3 операции чтения и 3 операции записи. Метод тот же, но реализация делает его более эффективным.

вы можете сделать это кусками по 4 пикселя, перемещая 32 бита с беззнаковыми длинными указателями. Просто подумайте, что с 4 32 битными пикселями вы можете построить, сдвинув и или / и, 3 слова, представляющие 4 24 битных пикселя, например:

//col0 col1 col2 col3
//ARGB ARGB ARGB ARGB 32bits reading (4 pixels)
//BGRB GRBG RBGR  32 bits writing (4 pixels)

операции сдвига всегда выполняются по 1 циклу команд во всех современных 32/64-битных процессорах (метод сдвига ствола), поэтому его самый быстрый способ построения этих 3 слов для записи, побитового и или также пылают быстрый.

такой:

//assuming we have 4 ARGB1 ... ARGB4 pixels and  3 32 bits words,  W1, W2 and W3 to write
// and *dest  its an unsigned long pointer for destination
W1 = ((ARGB1 & 0x000f) << 24) | ((ARGB1 & 0x00f0) << 8) | ((ARGB1 & 0x0f00) >> 8) | (ARGB2 & 0x000f);
*dest++ = W1;

и так далее.... со следующими пикселями в цикле.

вам понадобится некоторая настройка с изображениями, которые не кратны 4, но я уверен, что это самый быстрый подход из всех, без использования ассемблера.

и кстати, забудьте об использовании структур и индексированного доступа, это медленнее способы всех для перемещения данных, просто взгляните на список разборки скомпилированной программы C++, и вы будете согласиться со мной.

typedef struct{ UInt8 Alpha; UInt8 Red; UInt8 Green; UInt8 Blue; } ARGB;
typedef struct{ UInt8 Blue; UInt8 Green; UInt8 Red; } BGR;

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

union uARGB
{
   struct ARGB argb;
   UInt32 x;
};
union uBGRA
{
   struct 
   {
     BGR bgr;
     UInt8 Alpha;
   } bgra;
   UInt32 x;
};

а затем для вашего ядра кода, с любым циклом развертывания подходит:

inline void argb2bgr(BGR* pbgr, ARGB* pargb)
{
    uARGB* puargb = (uARGB*)pargb;
    uBGRA ubgra;
    ubgra.x = __byte_reverse_32(pargb->x);
    *pbgr = ubgra.bgra.bgr;
}

здесь __byte_reverse_32() предполагает наличие встроенного компилятора, который переворачивает байты 32-разрядного слова.

чтобы суммировать базовый подход:

  • просмотр структуры ARGB как 32-разрядное целое число
  • обратный 32-разрядное целое число
  • просмотрите обратное 32-разрядное целое число как (BGR)a structure
  • пусть компилятор скопирует (BGR) часть (BGR)структуры

хотя вы можете использовать некоторые трюки, основанные на использовании процессора,

This kind of operations can be done fasted with GPU.

Кажется, что вы используете C / C++... Так что ваши Альтернативы для программирование на GPU может быть ( на платформе Windows )

в ближайшее время использовать GPU для такого рода операций с массивами для более быстрых вычислений. Они предназначены для этого.

Я не видел, чтобы кто-нибудь показывал пример того, как это сделать на GPU.

некоторое время назад я писал что-то похожее на вашу проблему. Я получил данные с камеры video4linux2 в формате YUV и хотел нарисовать его как серые уровни на экране (только компонент Y). Я также хотел нарисовать области, которые слишком темные в синем и перенасыщенные области в красном.

Я начал с smooth_opengl3.с примером из freeglut распределение.

данные копируются как YUV в текстуру, а затем применяются следующие программы шейдеров GLSL. Я уверен, что GLSL-код работает на всех компьютерах Mac в настоящее время, и это будет значительно быстрее, чем все подходы к процессору.

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

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

(defparameter *vertex-shader*
"void main(){
    gl_Position    = gl_ModelViewProjectionMatrix * gl_Vertex;
    gl_FrontColor  = gl_Color;
    gl_TexCoord[0] = gl_MultiTexCoord0;
}
")

(progn
 (defparameter *fragment-shader*
   "uniform sampler2D textureImage;
void main()
{
  vec4 q=texture2D( textureImage, gl_TexCoord[0].st);
  float v=q.z;
  if(int(gl_FragCoord.x)%2 == 0)
     v=q.x; 
  float x=0; // 1./255.;
  v-=.278431;
  v*=1.7;
  if(v>=(1.0-x))
    gl_FragColor = vec4(255,0,0,255);
  else if (v<=x)
    gl_FragColor = vec4(0,0,255,255);
  else
    gl_FragColor = vec4(v,v,v,255); 
}
")

enter image description here