JavaScript binary data handling 101

chikoski@

TL;DR; DataView is the universal tool for binary data handling

const res = await fetch("some.jpg");
const buffer = await res.arrayBuffer();
const view = new DataView(buffer);

/*
 Take 4bytes from the first byte of the buffer 
 and interprete as unsigned integer 
*/
const value =  view.getUint32(0); 
const value =  view.getUint32(0, true); // Little endian

52bit

const x = Number.MAX_SAFE_INTEGER;
console.log(`x + 0 = ${x + 0}`);
console.log(`x + 1 = ${x + 1}`);
console.log(`x + 2 = ${x + 2}`);

Before starting talk...

  • 1 byte = 8 bit
  • 0xFF (Hexadecimal; Hex) = 255 (decimal)
  • 2 digits in Hex consumes 1 byte
  • 4 digits in Hex consumes 2 bytes

Access via View objects

const buffer = new ArrayBuffer(4);
const u32array = new Uint32Array(buffer);
const u16array = new Uint16Array(buffer);
const u8array = new Uint8Array(buffer);

console.log(`u32array.length = ${u32array.length}`);
console.log(`u16array.length = ${u1yarray.length}`);
console.log(`u8array.length = ${u8array.length}`);

Endian / byte order

const buffer = new ArrayBuffer(4);
const u32array = new Uint32Array(buffer);
const u8array = new Uint8Array(buffer);
      
u8array[0] = 0x00;
u8array[1] = 0x00;
u8array[2] = 0xFF;
u8array[3] = 0xFF;
  
const value = u32array[0];
console.log(`value = ${value.toString(16)}`);

Bit operator: intersection

const READ = 0b100;
const WRITE = 0b010;
const EXECUTE = 0b001;
  
const flag = 5;
console.log(`Can read?: ${flag & READ != 0}`);
console.log(`Can write?: ${flag & WRITE != 0}`);
console.log(`Can execute?: ${flag & EXECUTE != 0}`);

Bit operator: union

const READ = 0b100;
const WRITE = 0b010;
const EXECUTE = 0b001;
      
const flag = READ | EXECUTE;
console.log(`Can read?: ${flag & READ != 0}`);
console.log(`Can write?: ${flag & WRITE != 0}`);
console.log(`Can execute?: ${flag & EXECUTE != 0}`);

Bit operator: shift

const dimension = {
  width: 240,
  height: 180        
};
const code = dimension.width & 8 | dimension.height;
const width = code &; 0xFF;
const height = code >> 8;

Signed integer

const buffer = new ArrayBuffer(4);
const u8array = new Uint8Array(buffer);
const i8array = new Int8Array(buffer);
const u32array = new Uint32Array(buffer);
const i32array = new Int32Array(buffer);
      
u8array[0] = 0x00;
u8array[1] = 0x00;
u8array[2] = 0xFF;
u8array[3] = 0xFF;
  
console.log(`u8array[3] = ${u8array[3]} / i8array[3] = ${i8array[3]}`);
console.log(`u32array[0] = ${u32array[0} / i32array[0] = ${i32array[0]}`);

Signed integer and bit operation

const rgba = [255, 192, 192, 255];
const code = rgba[0] << 24 | rgba[1] << 16 | rgba[2] << 8 | rgba[3];

const decoded = [code >> 24, code >> 16 & 0xFF, code >> 8 & 0xFF, code & 0xFF];
// [-1, 192, 192, 255]

const decodedv2 = [code >> 24 & 0xFF, code >> 16 & 0xFF, code >> 8 & 0xFF, code & 0xFF];
const decodedv3 = [code >>> 24, code >>> 16 & 0xFF, code >>> 8 & 0xFF, code & 0xFF];
        

Exchangable Image Format: EXIF

JPEG File Interchange Format (JFIF)

Segments

Name Marker Identifier Description
SOI 0xFFDB Sart of iamge
DQT 0xFFDB Quantization table
SOF0 0xFFc0 Start of frame
DHT 0xFFc4 Huffman table
SOS 0xFFDA Sart of scan
EOI 0xFFC0 End of iamge
APP0 0xFFE0 JFIF application segment
APP1 0xFFE1 EXIF

Segment

Offset(bytes) Length(bytes) Description
0 2 Marker
2 2 Length
4 N Segment data