Comment convertir 16-bit RGB565 en 24-bit RGB888?
j'ai mis la main sur une image rgb565 16 bits (plus précisément, un dump de framebuffer Android), et j'aimerais la convertir en RGB888 24 bits pour la visualiser sur un moniteur normal.
la question Est, comment convertir un canal de 5 ou 6 bits en 8 bits? La réponse évidente est de la déplacer. J'ai commencé par écrire ceci:
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);
}
cependant, ceci n'a pas une propriété que je voudrais, qui est pour 0xffff
à la carte 0xffffff
au lieu de 0xf8fcf8
. J'ai besoin d'élargir la d'une certaine façon, ça a de la valeur, mais je ne sais pas comment ça devrait marcher.
le SDK Android est livré avec un outil appelé ddms (Dalvik Debug Monitor) qui prend des captures d'écran. Aussi loin que je peux dire à partir de lire le code, il implémente la même logique; pourtant ses screenshots sortent différents, et le blanc se mappe au blanc.
Voici le brut framebuffer, le conversion intelligente par ddms, et le muet conversion par l'algorithme ci-dessus. Notez que cette dernière est légèrement plus foncée et plus verte.
(En passant, cette conversion est implémenté dans ffmpeg, mais il ne fait que réaliser la conversion muette listée ci-dessus, laissant les LSBs à zéro.)
je suppose que j'ai deux questions:
- Quelle est la meilleure façon de convertir rgb565 en rgb888?
- comment le DDMS convertit-il ses screenshots?
8 réponses
Vous pouvez changer et puis ou avec les bits les plus significatifs; c'est à dire
Red 10101 becomes 10101000 | 101 => 10101101
12345 12345--- 123 12345123
ceci a la propriété que vous recherchez, mais ce n'est pas la cartographie la plus linéaire des valeurs d'un espace à l'autre. C'est rapide, cependant. :)
la réponse de Cletus est plus complète et probablement meilleure. :)
vous voulez cartographier chacun de ces derniers d'un espace de 5/6 bits à un espace de 8 bits.
- 5 bits = 32 valeurs
- 6 bits = 64 valeurs
- 8 bits = 256 valeurs
le code que vous utilisez prend l'approche naïve que x5 * 256/32 = x8 où 256/32 = 8 et multiplier par 8 est le décalage de gauche 3 mais, comme vous dites, cela ne remplit pas nécessairement le nouvel espace de nombre "correctement". 5 à 8 car la valeur max est de 31 à 255 et c'est là que se trouve votre indice solution.
x8 = 255/31 * x5
x8 = 255/63 * x6
où x5
,x6
et x8
sont des valeurs de 5, 6 et 8 bits respectivement.
maintenant, il y a une question sur la meilleure façon de mettre en oeuvre ceci. Il ne comporte division et avec la division entière vous perdrez n'importe quel résultat de reste (arrondir bas essentiellement) de sorte que la meilleure solution est probablement de faire l'arithmétique de virgule flottante et puis arrondir la moitié vers le haut de nouveau à un entier.
cela peut être considérablement accéléré en utilisant simplement cette formule pour générer une table de recherche pour chacune des conversions 5 et 6 bits.
Mes quelques cents:
si vous vous souciez de cartographie précise, mais algorithme rapide vous pouvez considérer ceci:
R8 = ( R5 * 527 + 23 ) >> 6;
G8 = ( G6 * 259 + 33 ) >> 6;
B8 = ( B5 * 527 + 23 ) >> 6;
il utilise seulement: MUL, ADD et SHR - > donc il est assez rapide! De l'autre côté, il est compatible à 100% à la cartographie en virgule flottante avec un arrondi correct:
// 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);
quelques cents supplémentaires: Si vous êtes intéressé par la conversion 888 à 565, cela fonctionne aussi très bien:
R5 = ( R8 * 249 + 1014 ) >> 11;
G6 = ( G8 * 253 + 505 ) >> 10;
B5 = ( B8 * 249 + 1014 ) >> 11;
les constantes ont été trouvées en utilisant la recherche de force brute avec somę des rejets précoces pour accélérer un peu les choses.
iOS Vimage Conversion
iOS Accélérer le Cadre documente l'algorithme suivant pour le vImageConvert_RGB565toARGB8888
fonction:
Pixel8 alpha = alpha
Pixel8 red = (5bitRedChannel * 255 + 15) / 31
Pixel8 green = (6bitGreenChannel * 255 + 31) / 63
Pixel8 blue = (5bitBlueChannel * 255 + 15) / 31
pour une conversion unique, ce sera assez rapide, mais si vous voulez traiter de nombreux cadres, vous voulez utiliser quelque chose comme la conversion iOS vImage ou l'implémenter vous-même en utilisant Neon intrinsics.
Dans Les Bras De La Communauté Du Forum Tutoriel
D'abord, nous allons regarder à la conversion RGB565 en RGB888. Nous supposons qu'il y a huit pixels 16 bits dans le registre q0, et nous aimerions séparer les rouges, les verts et les Bleus en Éléments 8 bits à travers trois registres d2 à 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.
Les effets de chaque instruction sont décrites dans les commentaires ci-dessus, mais en résumé, l'opération effectuée sur chaque canal: Supprimer les données de couleur pour les canaux adjacents en utilisant des décalages pour pousser les bits de chaque extrémité de l'élément. Utilisez un deuxième quart de travail pour positionnez les données de couleur dans les bits les plus significatifs de chaque élément, et rétrécissez pour réduire la taille de l'élément de 16 à huit bits.
notez l'utilisation de tailles d'éléments dans cette séquence pour traiter les éléments 8 et 16 bits, afin de réaliser certaines des opérations de masquage.
Un petit problème
vous pouvez remarquer que, si vous utilisez le code ci-dessus pour passer au format RGB888, vos blancs ne sont pas tout à fait blancs. C'est parce que, pour chaque canal, les deux ou trois bits les plus bas sont zéro, plutôt qu'un; un blanc représenté dans RGB565 comme (0x1F, 0x3F, 0x1F) devient (0xF8, 0xFC, 0xF8) dans RGB888. Ceci peut être corrigé en utilisant shift avec insert pour placer certains des bits les plus significatifs dans les bits inférieurs.
pour un exemple spécifique à Android j'ai trouvé un conversion YUV-to-RGB écrit en intrinsèques.
essaye ceci:
red5 = (buf & 0xF800) >> 11;
red8 = (red5 << 3) | (red5 >> 2);
carte tous les zéros dans tous les zéros, tous les 1 dans tous les 1, et tout ce qui entre dans tout entre les deux. Vous pouvez le rendre plus efficace en mettant les bits en place en une seule étape:
redmask = (buf & 0xF800);
rgb888 = (redmask << 8) | ((redmask<<3)&0x070000) | /* green, blue */
faire de même pour le vert et le bleu (pour 6 bits, décaler à gauche 2 et à droite 4 respectivement dans la méthode du haut).
il y a une erreur jleedev !!!
unsigned char green = (buf & 0x07c0) >> 5;
unsigned char blue = buf & 0x003f;
le bon code
unsigned char green = (buf & 0x07e0) >> 5;
unsigned char blue = buf & 0x001f;
Cheers, Andy
la solution générale est de traiter les nombres comme des fractions binaires - ainsi, le nombre de 6 bits 63/63 est le même que le nombre de 8 bits 255/255. Vous pouvez calculer cela en utilisant des maths flottantes au départ, puis calculer une table de recherche, comme d'autres affiches suggèrent. Cela présente également l'avantage d'être plus intuitif que les solutions de bit-bashing. :)
j'ai utilisé ce qui suit et j'ai obtenu de bons résultats. Il s'est avéré que ma caméra Logitek était 16bit RGB555 et l'utilisation de ce qui suit pour convertir en 24bit RGB888 m'a permis d'enregistrer comme un jpeg en utilisant les petits animaux ijg: Merci pour l'allusion trouvé ici sur 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;
}