saldo - Método rápido para copiar memoria con traducción-ARGB a BGR
promociones visa bgr (11)
Visión de conjunto
Tengo un buffer de imagen que necesito convertir a otro formato. El buffer de imagen de origen es de cuatro canales, 8 bits por canal, Alpha, Red, Green y Blue. El búfer de destino es de tres canales, 8 bits por canal, azul, verde y rojo.
Entonces el método de la fuerza bruta es:
// 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;
}
Sin embargo, necesito más velocidad que la proporcionada por un bucle y tres copias de bytes. Espero que haya algunos trucos que pueda utilizar para reducir el número de lecturas y escrituras de memoria, dado que estoy trabajando en una máquina de 32 bits.
información adicional
Cada imagen es un múltiplo de al menos 4 píxeles. Entonces podríamos abordar 16 bytes ARGB y moverlos a 12 bytes RGB por ciclo. Tal vez este hecho se pueda usar para acelerar las cosas, especialmente porque cae muy bien en los límites de 32 bits.
Tengo acceso a OpenCL, y aunque eso requiere mover todo el búfer a la memoria de la GPU, mover el resultado nuevamente, el hecho de que OpenCL puede funcionar en muchas partes de la imagen simultáneamente, y el hecho de que los bloqueos grandes de memoria se mueven realmente bastante eficiente puede hacer de esto una exploración que vale la pena.
Si bien he dado el ejemplo de pequeños buffers anteriores, realmente estoy moviendo videos HD (1920x1080) y algunas veces buffers más grandes, más pequeños, así que mientras que una situación 32x32 puede ser trivial, copiar 8.3MB de datos de imágenes byte por byte es Realmente, realmente malo.
Ejecutar en procesadores Intel (Core 2 y superiores) y, por lo tanto, hay comandos de transmisión y procesamiento de datos que sé que existen, pero que no conozco, quizás sean buenos los indicadores sobre dónde buscar instrucciones especializadas de manejo de datos.
Esto va a una aplicación OS X, y estoy usando XCode 4. Si el montaje es sencillo y la forma obvia de hacerlo, estoy bien viajando por ese camino, pero no haberlo hecho en esta configuración me hace precavido de hundir demasiado tiempo en ello.
El seudocódigo está bien; no estoy buscando una solución completa, solo el algoritmo y una explicación de cualquier truco que pueda no estar inmediatamente claro.
Aunque puede usar algunos trucos basados en el uso de la CPU,
This kind of operations can be done fasted with GPU.
Parece que usa C / C ++ ... Entonces, sus alternativas para la programación de GPU pueden ser (en la plataforma de Windows)
- DirectCompute (DirectX 11) Ver este video
- Microsoft Research Project Accelerator Compruebe este enlace
- Cuda
- programación de GPU "google" ...
Use brevemente la GPU para este tipo de operaciones de matriz para hacer cálculos más rápidos. Ellos están diseñados para eso.
Combinando solo las respuestas de un poseur y Jitamaro, si supone que las entradas y salidas están alineadas a 16 bytes y procesa los píxeles 4 a la vez, puede usar una combinación de mezclas, máscaras, ands y ors para almacenar usando alineado víveres. La idea principal es generar cuatro conjuntos de datos intermedios, y luego o junto con máscaras para seleccionar los valores de píxeles relevantes y escribir 3 conjuntos de 16 bytes de datos de píxeles. Tenga en cuenta que no compilé esto o intenté ejecutarlo en absoluto.
EDIT2: Más detalles sobre la estructura de código subyacente:
Con SSE2, obtiene un mejor rendimiento con lecturas y escrituras alineadas de 16 bytes de 16 bytes. Como el píxel de 3 bytes solo se puede alinear a 16 bytes por cada 16 píxeles, combinamos 16 píxeles a la vez mediante una combinación de combinaciones y máscaras y orcos de 16 píxeles de entrada a la vez.
De LSB a MSB, las entradas se ven así, ignorando los componentes específicos:
s[0]: 0000 0000 0000 0000
s[1]: 1111 1111 1111 1111
s[2]: 2222 2222 2222 2222
s[3]: 3333 3333 3333 3333
y los ouptuts se ven así:
d[0]: 000 000 000 000 111 1
d[1]: 11 111 111 222 222 22
d[2]: 2 222 333 333 333 333
Entonces, para generar esos resultados, debe hacer lo siguiente (especificaré las transformaciones reales más adelante):
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]))
Ahora, ¿cómo debería combine_<x>
? Si suponemos que d
es simplemente compactado, podemos concatenar dos s con una máscara y una o:
combine_x(left, right)= (left & mask(x)) | (right & ~mask(x))
donde (1 significa seleccionar el píxel izquierdo, 0 significa seleccionar el píxel correcto): máscara (0) = 111 111 111 111 000 0 máscara (1) = 11 111 111 000 000 00 máscara (2) = 1 111 000 000 000 000
Pero las transformaciones reales ( f_<x>_low
, f_<x>_high
) en realidad no son tan simples. Dado que estamos invirtiendo y eliminando bytes del píxel fuente, la transformación real es (para el primer destino de brevedad):
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
Si traduces lo anterior en compensaciones de bytes desde el origen al destino, obtienes: 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
(Si echas un vistazo a todas las compensaciones s [0], solo coinciden con la máscara aleatoria de un poseur en orden inverso).
Ahora, podemos generar una máscara aleatoria para asignar cada byte de origen a un byte de destino ( X
significa que no nos importa qué es ese valor):
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
Podemos optimizar aún más esto mirando las máscaras que usamos para cada píxel fuente. Si echas un vistazo a las máscaras de mezcla que usamos para 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
Dado que las dos máscaras shuffle no se superponen, podemos combinarlas y simplemente enmascarar los píxeles irrelevantes en combine_, ¡lo cual ya hicimos! El siguiente código realiza todas estas optimizaciones (además, asume que las direcciones de origen y de destino están alineadas en 16 bytes). Además, las máscaras están escritas en código en orden MSB-> LSB, en caso de que se confunda sobre el orden.
EDITAR: cambió la tienda a _mm_stream_si128
ya que es probable que realice muchas escrituras y no deseamos necesariamente vaciar el caché. ¡Además, debe alinearse de todos modos para que obtengas resultados gratis!
#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));
}
}
Desea utilizar un dispositivo de Duff: http://en.wikipedia.org/wiki/Duff%27s_device . También está funcionando en JavaScript. Sin embargo, es un poco gracioso leer http://lkml.indiana.edu/hypermail/linux/kernel/0008.2/0171.html . Imagine un dispositivo Duff con 512 Kbytes de movimientos.
En combinación con una de las funciones de conversión rápida aquí, dado el acceso a Core 2s, sería conveniente dividir la traducción en hilos, que funcionan en su, por ejemplo, la cuarta parte de los datos, como en este 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);
}
Escribí 4 versiones diferentes que funcionan intercambiando bytes. Los compilé usando gcc 4.2.1 con -O3 -mssse3
, los ejecuté 10 veces sobre 32MB de datos aleatorios y encontré los promedios.
La primera versión usa un bucle C para convertir cada píxel por separado, utilizando la función OSSwapInt32
(que compila a una instrucción bswap
con -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]);
}
}
El segundo método realiza la misma operación, pero utiliza un bucle de ensamblaje en línea en lugar de un bucle C.
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 $4,%1/n/t"
"addl $3,%0/n/t"
"decl %2/n/t"
"jnz 0b"
:: "D" (dest), "S" (orig), "c" (imageSize)
: "flags", "eax"
);
}
La tercera versión es una versión modificada de solo la respuesta de un poseur . lddqu
las lddqu
incorporadas a los equivalentes de GCC y usé la lddqu
incorporada lddqu
para que el argumento de entrada no necesite alinearse.
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));
}
}
Finalmente, la cuarta versión es el ensamblaje en línea equivalente del tercero.
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 $16,%1/n/t"
"add $12,%0/n/t"
"sub $4,%2/n/t"
"jnz 0b"
:: "r" (dest), "r" (orig), "r" (imagesize), "r" (mask)
: "flags", "xmm0", "xmm1"
);
}
En mi MacBook Pro 2010, 2.4 Ghz i5, 4GB de RAM, estos fueron los tiempos promedio para cada uno:
Version 1: 10.8630 milliseconds Version 2: 11.3254 milliseconds Version 3: 9.3163 milliseconds Version 4: 9.3584 milliseconds
Como puede ver, el compilador es lo suficientemente bueno para la optimización que no necesita escribir el ensamblado. Además, las funciones vectoriales fueron solo 1.5 milisegundos más rápidas en 32MB de datos, por lo que no causará mucho daño si desea admitir las primeras Macs Intel, que no admitían SSSE3.
Editar: liori pidió información de desviación estándar. Desafortunadamente, no había guardado los puntos de datos, así que ejecuté otra prueba con 25 iteraciones.
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%)
Además, aquí están los datos brutos de las nuevas pruebas, en caso de que alguien lo quiera. Para cada iteración, se generó aleatoriamente un conjunto de datos de 32MB y se ejecutó a través de las cuatro funciones. El tiempo de ejecución de cada función en microsegundos se enumera a continuación.
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
Esta función de ensamblaje debería funcionar, sin embargo, no sé si desea conservar los datos antiguos o no, esta función lo anula.
El código es para MinGW GCC con intel assembly , tendrás que modificarlo para adaptarlo a tu compilador / ensamblador.
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"
);
}
Deberías llamarlo así:
convertARGBtoBGR( &buffer, IMAGESIZE );
Esta función accede a la memoria solo dos veces por píxel / paquete (1 lectura, 1 escritura) en comparación con el método de fuerza bruta que tenía (al menos / suponiendo que se compiló para registrar) 3 operaciones de lectura y 3 de escritura. El método es el mismo pero la implementación lo hace más eficiente.
Llego un poco tarde a la fiesta, pareciendo que la comunidad ya ha decidido responder al pshufb de poseur, pero distribuyendo la reputación de 2000, que es tan extremadamente generosa que tengo que intentarlo.
Aquí está mi versión sin intrínsecos específicos de la plataforma o asm específicos de la máquina, he incluido algún código de sincronización multiplataforma que muestra una aceleración de 4x si haces tanto el intercambio de bits como yo Y activas la optimización del compilador (optimización de registros, desenrollado de bucles) :
#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)
}
Los resultados son estos (en mi core 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
Lo obvio, usando 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));
}
}
No he visto a nadie mostrando un ejemplo de cómo hacerlo en la GPU.
Hace un tiempo escribí algo similar a tu problema. Recibí datos de una cámara video4linux2 en formato YUV y quería dibujarlos como niveles grises en la pantalla (solo el componente Y). También quería dibujar áreas que son demasiado oscuras en azul y regiones sobresaturadas en rojo.
Comencé con el ejemplo smooth_opengl3.c de la distribución de freeglut .
Los datos se copian como YUV en la textura y luego se aplican los siguientes programas de sombreado GLSL. Estoy seguro de que el código GLSL se ejecuta en todos los Mac hoy en día y será significativamente más rápido que todos los enfoques de CPU.
Tenga en cuenta que no tengo experiencia sobre cómo recuperar los datos. En teoría, glReadPixels debería leer los datos pero nunca medí su rendimiento.
OpenCL podría ser el enfoque más fácil, pero luego solo comenzaré a desarrollarlo cuando tenga un portátil que lo admita.
(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);
}
")
Puedes hacerlo en trozos de 4 píxeles, moviendo 32 bits con punteros largos sin signo. Simplemente piense que con 4 32 bits de píxeles puede construir cambiando y O / Y, 3 palabras que representan 4 24 bits de píxeles, como esta:
//col0 col1 col2 col3
//ARGB ARGB ARGB ARGB 32bits reading (4 pixels)
//BGRB GRBG RBGR 32 bits writing (4 pixels)
Las operaciones de desplazamiento siempre se realizan en 1 ciclo de instrucciones en todos los procesadores modernos de 32/64 bits (técnica de cambio de barril), por lo que es la manera más rápida de construir esas 3 palabras para escritura, Y a través de un bit y O también son extremadamente rápidas.
Me gusta esto:
//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;
y así sucesivamente ... con los próximos píxeles en un bucle.
Necesitarás algunos ajustes con imágenes que no son múltiplos de 4, pero apuesto a que este es el enfoque más rápido de todos, sin usar ensamblador.
Y, por cierto, olvídense de usar estructuras y acceso indexado, esas son las formas más LEVES de mover datos, solo echen un vistazo a una lista de desmontaje de un programa compilado de C ++ y estarán de acuerdo conmigo.
typedef struct{ UInt8 Alpha; UInt8 Red; UInt8 Green; UInt8 Blue; } ARGB;
typedef struct{ UInt8 Blue; UInt8 Green; UInt8 Red; } BGR;
Además de los intrínsecos de ensamblador o compilador, podría intentar hacer lo siguiente, mientras verificaba cuidadosamente el comportamiento final , ya que parte de él (en lo que respecta a los sindicatos) probablemente dependa de la implementación del compilador:
union uARGB
{
struct ARGB argb;
UInt32 x;
};
union uBGRA
{
struct
{
BGR bgr;
UInt8 Alpha;
} bgra;
UInt32 x;
};
y luego para su kernel de código, con cualquier desenrollado de bucle apropiado:
inline void argb2bgr(BGR* pbgr, ARGB* pargb)
{
uARGB* puargb = (uARGB*)pargb;
uBGRA ubgra;
ubgra.x = __byte_reverse_32(pargb->x);
*pbgr = ubgra.bgra.bgr;
}
donde __byte_reverse_32()
asume la existencia de un compilador intrínseco que invierte los bytes de una palabra de 32 bits.
Para resumir el enfoque subyacente:
- ver la estructura ARGB como un entero de 32 bits
- revertir el entero de 32 bits
- ver el entero de 32 bits invertido como una estructura (BGR) A
- deje que el compilador copie la parte (BGR) de la estructura (BGR) A