Pour PHPOffice, j’ai dû apprendre à travailler les octets d’un fichier en binaire. Voici un résumé de ce que j’ai appris et des astuces que j’ai soutiré.
Un fichier est juste un tableau d’octets
Tout d’abord, il faut imaginer un fichier comme une chaîne de caractères.
1
| $data = file_get_contents('data.jpg');
|
Mais une chaîne de caractères est stocké au niveau mémoire comme un tableau (Source : PHP).
A ce niveau, on peut récupérer le code ASCII d’un élément du tableau :
Mais surtout son code hexadécimal :
1
| echo dechex(ord($data[0]));
|
Ainsi pour un fichier JPEG, les deux premiers octets d’un fichier sont 0xFFD8.
Pour le récupérer, utilisons nos snippets précédents :
1
2
| echo dechex(ord($data[0])).PHP_EOL;
echo dechex(ord($data[1])).PHP_EOL;
|
Ce qui donne :
Un octet, mais pour deux, trois ou quatre…
Je voudrais récupérer directement les deux octets.
Au lieu de faire cela malproprement, c’est à dire via une simple concaténation, on va le faire du bitshifting ou décalage de bits.
Ainsi pour récupérer deux octets, on fait comme cela :
1
2
3
4
5
| function getInt2d($data, $pos) {
return ord($data[$pos + 1]) | (ord($data[$pos]) >> 8);
}
echo dechex(getInt2d($data, 0)); // Retourne ffd8
|
Donc après deux octets, il est très simple de faire avec trois ou quatre octets :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| function getInt2d($data, $pos) {
return ord($data[$pos + 1]) | (ord($data[$pos]) << 8);
}
echo dechex(getInt2d($data, 0)).PHP_EOL; // ffd8
function getInt3d($data, $pos) {
return ord($data[$pos + 2]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos]) << 16);
}
echo dechex(getInt3d($data, 0)).PHP_EOL; // ffd8ff
function getInt4d($data, $pos) {
return ord($data[$pos + 3]) | (ord($data[$pos + 2]) << 8) | (ord($data[$pos + 1]) << 16) | (ord($data[$pos]) << 24);
}
echo dechex(getInt4d($data, 0)).PHP_EOL; // ffd8ffe0
|
Byte order : Little endian & Big Endian
Word et les formats binaires de Microsoft m’ont apporté une difficulté : l’ordre des octets (Lien : MSDN).
Ainsi lorsque l’on lit 0xFFD8FFE0
par défaut, l’ordre des octets est du big-endian. En little endian, cela donne 0xE0FFD8FF
.
Modifions nos fonctions pour que cela fonctionne. On a juste à choisir les octets par ordre inverse.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| function getInt2d($data, $pos) {
return ord($data[$pos]) | (ord($data[$pos + 1]) << 8);
}
echo dechex(getInt2d($data, 0)).PHP_EOL; // d8ff
function getInt3d($data, $pos) {
return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16);
}
echo dechex(getInt3d($data, 0)).PHP_EOL; // ffd8ff
function getInt4d($data, $pos) {
return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | (ord($data[$pos + 3]) << 24);
}
echo dechex(getInt4d($data, 0)).PHP_EOL; // e0ffd8ff
|
Après les octets, les bits
Il faut savoir qu’un octet est composé de 8 bits (1 ou 0).
On va récupérer via du bitshifting ces bits :
1
2
3
4
5
6
7
8
9
10
| $dataBit = ord($data[1]); // 0xD8
echo ($dataBit >> 7 & bindec('1')); // 1
echo ($dataBit >> 6 & bindec('1')); // 1
echo ($dataBit >> 5 & bindec('1')); // 0
echo ($dataBit >> 4 & bindec('1')); // 1
echo ($dataBit >> 3 & bindec('1')); // 1
echo ($dataBit >> 2 & bindec('1')); // 0
echo ($dataBit >> 1 & bindec('1')); // 0
echo ($dataBit >> 0 & bindec('1')); // 1
echo decbin($dataBit); // 11011000
|
Mais on peut récupérer deux d’un coup :
1
2
3
4
| echo decbin($dataBit >> 6 & bindec('11')); // 11
echo decbin($dataBit >> 4 & bindec('11')); // 01
echo decbin($dataBit >> 2 & bindec('11')); // 10
echo decbin($dataBit >> 0 & bindec('11')); // 00
|
Ou par quatre :
1
2
| echo decbin($dataBit >> 4 & bindec('1111')); // 1101
echo decbin($dataBit >> 0 & bindec('11')); // 1000
|