Je travaille avec JNI et j'ai un tableau de type jbyte, où jbyte est représenté comme un caractère signé, c'est-à-dire allant de -128 à 127. Les jbytes représentent les pixels de l'image. Pour le traitement d'image, nous souhaitons généralement que les composants en pixels varient de 0 à 255. Je souhaite donc convertir la valeur de jbyte en plage de 0 à 255 (c'est-à-dire la même plage que le caractère non signé), faire quelques calculs sur la valeur, puis stocker le résultat comme un octet à nouveau.
Comment puis-je effectuer ces conversions en toute sécurité?
J'ai réussi à faire fonctionner ce code, où une valeur de pixel est incrémentée de 30 mais fixée à la valeur 255, mais je ne comprends pas si c'est sûr ou portable:
#define CLAMP255(v) (v > 255 ? 255 : (v < 0 ? 0 : v))
jbyte pixel = ...
pixel = CLAMP_255((unsigned char)pixel + 30);
Je suis intéressé de savoir comment faire cela en C et C++.
C'est l'une des raisons pour lesquelles C++ a introduit le nouveau style de distribution, qui inclut static_cast
et reinterpret_cast
Il y a deux choses que vous pouvez dire en disant la conversion de signé à non signé, vous pourriez vouloir dire que vous souhaitez que la variable non signée contienne la valeur de la variable signée modulo la valeur maximale de votre type non signé + 1. C'est-à-dire si votre caractère signé a un valeur de -128 puis CHAR_MAX+1
est ajouté pour une valeur de 128 et s'il a une valeur de -1, alors CHAR_MAX+1
est ajouté pour une valeur de 255, c'est ce que fait static_cast. D'un autre côté, vous pourriez vouloir interpréter la valeur binaire de la mémoire référencée par une variable à interpréter comme un octet non signé, quelle que soit la représentation entière signée utilisée sur le système, c'est-à-dire si elle a une valeur binaire 0b10000000
il doit être évalué à 128 et 255 pour la valeur de bit 0b11111111
, ceci est accompli avec reinterpret_cast.
Maintenant, pour la représentation du complément à deux, il se trouve que c'est exactement la même chose, puisque -128 est représenté par 0b10000000
et -1 est représenté par 0b11111111
et de même pour tous les intermédiaires. Cependant, d'autres ordinateurs (généralement des architectures plus anciennes) peuvent utiliser des représentations signées différentes telles que le signe et l'ampleur ou le complément de ceux-ci. En complément, les 0b10000000
bitvalue ne serait pas -128, mais -127, donc un transtypage statique en caractère non signé ferait cela 129, tandis qu'un reinterpret_cast ferait cela 128. De plus, dans les compléments à un, le 0b11111111
la valeur de bit ne serait pas -1, mais -0, (oui, cette valeur existe dans le complément de uns), et serait convertie en une valeur de 0 avec un static_cast, mais une valeur de 255 avec un reinterpret_cast. Notez que dans le cas de compléments, la valeur non signée de 128 ne peut en fait pas être représentée dans un caractère signé, car elle varie de -127 à 127, en raison de la valeur -0.
Je dois dire que la grande majorité des ordinateurs utiliseront le complément à deux, ce qui rendra tout le problème théorique à peu près partout où votre code s'exécutera. Vous ne verrez probablement que des systèmes avec autre chose que deux dans des architectures très anciennes, pensez au calendrier des années 60.
La syntaxe se résume à ce qui suit:
signed char x = -100;
unsigned char y;
y = (unsigned char)x; // C static
y = *(unsigned char*)(&x); // C reinterpret
y = static_cast<unsigned char>(x); // C++ static
y = reinterpret_cast<unsigned char&>(x); // C++ reinterpret
Pour ce faire d'une manière agréable en C++ avec des tableaux:
jbyte memory_buffer[nr_pixels];
unsigned char* pixels = reinterpret_cast<unsigned char*>(memory_buffer);
ou la voie C:
unsigned char* pixels = (unsigned char*)memory_buffer;
Oui, c'est sûr.
Le langage c utilise une fonctionnalité appelée promotion d'entiers pour augmenter le nombre de bits dans une valeur avant d'effectuer des calculs. Par conséquent, votre macro CLAMP255 fonctionnera avec une précision entière (probablement 32 bits). Le résultat est attribué à un octet, ce qui réduit la précision entière à 8 bits ajustés au octet.
Vous rendez-vous compte que CLAMP255 renvoie 0 pour v <0 et 255 pour v> = 0?
À mon humble avis, CLAMP255 doit être défini comme suit:
#define CLAMP255(v) (v > 255 ? 255 : (v < 0 ? 0 : v))
Différence: si v n'est pas supérieur à 255 et pas inférieur à 0: renvoie v au lieu de 255
Je ne suis pas sûr à 100% de comprendre votre question, alors dites-moi si je me trompe.
Si je comprends bien, vous lisez des octets qui sont techniquement caractères signés, mais vraiment valeurs de pixels allant de 0 à 255, et vous vous demandez comment vous devez les gérer sans corrompre les valeurs dans le processus.
Ensuite, vous devez procéder comme suit:
convertir les octets en caractères non signés avant de faire quoi que ce soit d'autre, cela restaurera définitivement les valeurs de pixels que vous essayez de manipuler
utiliser un type entier signé plus grand, tel que int lors des calculs intermédiaires, afin de garantir que les débordements et les débordements peuvent être détectés et traités (en particulier, pas la conversion en un type signé pourrait forcer à compilateur pour promouvoir chaque type en un type non signé, auquel cas vous ne pourrez pas détecter les débordements plus tard)
lors de la réaffectation à un octet, vous voudrez restreindre votre valeur à la plage 0-255, convertir en caractère non signé, puis reconvertir en caractère signé: je ne suis pas certain que la première conversion est strictement nécessaire, mais vous pouvez simplement ne te trompe pas si tu fais les deux
Par exemple:
inline int fromJByte(jbyte pixel) {
// cast to unsigned char re-interprets values as 0-255
// cast to int will make intermediate calculations safer
return static_cast<int>(static_cast<unsigned char>(pixel));
}
inline jbyte fromInt(int pixel) {
if(pixel < 0)
pixel = 0;
if(pixel > 255)
pixel = 255;
return static_cast<jbyte>(static_cast<unsigned char>(pixel));
}
jbyte in = ...
int intermediate = fromJByte(in) + 30;
jbyte out = fromInt(intermediate);
Il existe deux façons d'interpréter les données d'entrée; soit -128 est la valeur la plus basse, et 127 est la plus élevée (c'est-à-dire les vraies données signées), soit 0 est la valeur la plus basse, 127 est quelque part au milieu, et le nombre "supérieur" suivant est -128, -1 étant le la valeur "la plus élevée" (c'est-à-dire que le bit le plus significatif a déjà été mal interprété comme un bit de signe dans une notation de complément à deux.
En supposant que vous voulez dire ce dernier, la manière formellement correcte est
signed char in = ...
unsigned char out = (in < 0)?(in + 256):in;
qui au moins gcc reconnaît correctement comme un no-op.