¿Cómo se puede convertir RGB565 de 16 bits a RGB888 de 24 bits?

Tengo en mis manos una imagen rgb565 de 16 bits (específicamente, un volcado de framebuffer Android), y me gustaría convertirla a rgb888 de 24 bits para verla en un monitor normal.

La pregunta es, ¿cómo se puede convertir un canal de 5 o 6 bits a 8 bits? La respuesta obvia es cambiarlo. Empecé escribiendo esto:

puts("P6 320 480 255"); uint16_t buf; while (read(0, &buf, sizeof buf)) { unsigned char red = (buf & 0xf800) >> 11; unsigned char green = (buf & 0x07e0) >> 5; unsigned char blue = buf & 0x001f; putchar(red << 3); putchar(green << 2); putchar(blue << 3); } 

Sin embargo, esto no tiene una propiedad que me gustaría, que es 0xffff para asignar a 0xffffff , en lugar de 0xf8fcf8 . Necesito expandir el valor de alguna manera, pero no estoy seguro de cómo debería funcionar.

El Android SDK viene con una herramienta llamada ddms (Dalvik Debug Monitor) que toma capturas de pantalla. Por lo que puedo decir al leer el código , implementa la misma lógica; sin embargo, sus capturas de pantalla son diferentes, y el blanco está mapeando en blanco.

Aquí está el framebuffer sin formato , la conversión inteligente por ddms y la conversión tonta por el algoritmo anterior. Tenga en cuenta que este último es ligeramente más oscuro y más verde.

(Por cierto, esta conversión se implementa en ffmpeg , pero solo está realizando la conversión tonta enumerada anteriormente, dejando los LSB en cero).

Supongo que tengo dos preguntas:

  • ¿Cuál es la forma más sensata de convertir rgb565 a rgb888?
  • ¿Cómo está convirtiendo DDMS sus capturas de pantalla?

Podrías cambiar y luego o con los bits más significativos; es decir

 Red 10101 becomes 10101000 | 101 => 10101101 12345 12345--- 123 12345123 

Tiene la propiedad que busca, pero no es el mapeo más lineal de valores de un espacio a otro. Es rápido, sin embargo. 🙂

La respuesta de Cletus es más completa y probablemente sea mejor. 🙂

Desea asignar cada uno de estos desde un espacio de 5/6 bits a un espacio de 8 bits.

  • 5 bits = 32 valores
  • 6 bits = 64 valores
  • 8 bits = 256 valores

El código que estás usando toma el enfoque ingenuo que x5 * 256/32 = x8 donde 256/32 = 8 y multiplicar por 8 se deja shift 3 pero, como dices, esto no llena necesariamente el nuevo espacio numérico ” correctamente”. 5 a 8 para un valor máximo es de 31 a 255 y ahí está su pista de la solución.

 x8 = 255/31 * x5 x8 = 255/63 * x6 

donde x5 , x6 y x8 son valores de 5, 6 y 8 bits, respectivamente.

Ahora hay una pregunta sobre la mejor manera de implementar esto. Implica división y con división entera perderá cualquier resultado restante (redondee básicamente) así que la mejor solución es probablemente hacer aritmética de coma flotante y luego redondear la mitad hacia arriba a un número entero.

Esto se puede acelerar considerablemente simplemente usando esta fórmula para generar una tabla de búsqueda para cada una de las conversiones de 5 y 6 bits.

Mis pocos centavos:

Si te preocupa el mapeo preciso, pero el algoritmo rápido puedes considerar esto:

 R8 = ( R5 * 527 + 23 ) >> 6; G8 = ( G6 * 259 + 33 ) >> 6; B8 = ( B5 * 527 + 23 ) >> 6; 

Solo utiliza: MUL, ADD y SHR -> ¡así que es bastante rápido! Desde el otro lado es compatible en 100% con el mapeo de coma flotante con el redondeo apropiado:

 // R8 = (int) floor( R5 * 255.0 / 31.0 + 0.5); // G8 = (int) floor( G6 * 255.0 / 63.0 + 0.5); // B8 = (int) floor( R5 * 255.0 / 31.0 + 0.5); 

Algunos centavos extra: si está interesado en la conversión de 888 a 565, esto también funciona muy bien:

 R5 = ( R8 * 249 + 1014 ) >> 11; G6 = ( G8 * 253 + 505 ) >> 10; B5 = ( B8 * 249 + 1014 ) >> 11; 

Se encontraron constantes utilizando la búsqueda de fuerza bruta con algunos rechazos tempranos para acelerar un poco la situación.

Conversión de iOS vImage

IOS Accelerate Framework documenta el siguiente algoritmo para la función vImageConvert_RGB565toARGB8888 :

 Pixel8 alpha = alpha Pixel8 red = (5bitRedChannel * 255 + 15) / 31 Pixel8 green = (6bitGreenChannel * 255 + 31) / 63 Pixel8 blue = (5bitBlueChannel * 255 + 15) / 31 

Para una conversión única, esto será lo suficientemente rápido, pero si quiere procesar muchos fotogtwigs, quiere usar algo como la conversión de iOS vImage o impleméntelo usted mismo utilizando los intrínsecos de NEON .

De ARMs Community Forum Tutorial

Primero, veremos la conversión de RGB565 a RGB888. Suponemos que hay ocho píxeles de 16 bits en el registro q0, y nos gustaría separar los rojos, verdes y azules en elementos de 8 bits en tres registros d2 a d4.

  vshr.u8 q1, q0, #3 @ shift red elements right by three bits, @ discarding the green bits at the bottom of @ the red 8-bit elements. vshrn.i16 d2, q1, #5 @ shift red elements right and narrow, @ discarding the blue and green bits. vshrn.i16 d3, q0, #5 @ shift green elements right and narrow, @ discarding the blue bits and some red bits @ due to narrowing. vshl.i8 d3, d3, #2 @ shift green elements left, discarding the @ remaining red bits, and placing green bits @ in the correct place. vshl.i16 q0, q0, #3 @ shift blue elements left to most-significant @ bits of 8-bit color channel. vmovn.i16 d4, q0 @ remove remaining red and green bits by @ narrowing to 8 bits. 

Los efectos de cada instrucción se describen en los comentarios anteriores, pero en resumen, la operación realizada en cada canal es: Eliminar datos de color para canales adyacentes utilizando cambios para empujar los bits fuera de cada extremo del elemento. Use un segundo turno para ubicar los datos de color en los bits más significativos de cada elemento y estreche para reducir el tamaño del elemento de 16 a ocho bits.

Tenga en cuenta el uso de tamaños de elementos en esta secuencia para abordar elementos de 8 y 16 bits, para lograr algunas de las operaciones de enmascaramiento.

Un pequeño problema

Puede observar que, si utiliza el código anterior para convertir al formato RGB888, sus blancos no son del todo blancos. Esto se debe a que, para cada canal, los dos o tres bits más bajos son cero, en lugar de uno; un blanco representado en RGB565 como (0x1F, 0x3F, 0x1F) se convierte (0xF8, 0xFC, 0xF8) en RGB888. Esto se puede arreglar usando shift con insert para colocar algunos de los bits más significativos en los bits más bajos.

Para un ejemplo específico de Android, encontré una conversión de YUV a RGB escrita en intrínsecos.

Prueba esto:

 red5 = (buf & 0xF800) >> 11; red8 = (red5 < < 3) | (red5 >> 2); 

Esto mapeará todos los ceros en todos los ceros, todos los 1 en todos los 1, y todo lo intermedio en todo lo que esté entre ellos. Puede hacerlo más eficiente cambiando los bits en su lugar en un solo paso:

 redmask = (buf & 0xF800); rgb888 = (redmask < < 8) | ((redmask<<3)&0x070000) | /* green, blue */ 

Haga lo mismo para verde y azul (para 6 bits, cambie a la izquierda 2 y a la derecha 4 respectivamente en el método superior).

Hay un error jleedev !!!

 unsigned char green = (buf & 0x07c0) >> 5; unsigned char blue = buf & 0x003f; 

el buen código

 unsigned char green = (buf & 0x07e0) >> 5; unsigned char blue = buf & 0x001f; 

Saludos, Andy

La solución general es tratar los números como fracciones binarias, por lo tanto, el número de 6 bits 63/63 es el mismo que el número de 8 bits 255/255. Puede calcular esto usando matemática de coma flotante inicialmente, y luego calcular una tabla de búsqueda, como sugieren otros carteles. Esto también tiene la ventaja de ser más intuitivo que las soluciones de ataques de bits. 🙂

Usé lo siguiente y obtuve buenos resultados. Resultó que mi cámara Logitek era RGB555 de 16 bits y el uso de lo siguiente para convertir a RGB888 de 24 bits me permitió guardar como un jpeg usando el ijg de animales más pequeños: Gracias por la pista que se encuentra aquí en stackoverflow.

 // Convert a 16 bit inbuf array to a 24 bit outbuf array BOOL JpegFile::ByteConvert(BYTE* inbuf, BYTE* outbuf, UINT width, UINT height) { UINT row_cnt, pix_cnt; ULONG off1 = 0, off2 = 0; BYTE tbi1, tbi2, R5, G5, B5, R8, G8, B8; if (inbuf==NULL) return FALSE; for (row_cnt = 0; row_cnt < = height; row_cnt++) { off1 = row_cnt * width * 2; off2 = row_cnt * width * 3; for(pix_cnt=0; pix_cnt < width; pix_cnt++) { tbi1 = inbuf[off1 + (pix_cnt * 2)]; tbi2 = inbuf[off1 + (pix_cnt * 2) + 1]; B5 = tbi1 & 0x1F; G5 = (((tbi1 & 0xE0) >> 5) | ((tbi2 & 0x03) < < 3)) & 0x1F; R5 = (tbi2 >> 2) & 0x1F; R8 = ( R5 * 527 + 23 ) >> 6; G8 = ( G5 * 527 + 23 ) >> 6; B8 = ( B5 * 527 + 23 ) >> 6; outbuf[off2 + (pix_cnt * 3)] = R8; outbuf[off2 + (pix_cnt * 3) + 1] = G8; outbuf[off2 + (pix_cnt * 3) + 2] = B8; } } return TRUE; }