skip to Main Content

So I have a file which contains values of 12 bit unsigned integers. I have no problem reading the file directly into a UInt8Array or UInt16Array but the values are of course incorrect and the length of those arrays is incorrect. How can I read those 12 bit values and save them into a Uint16Array but the values must be correct?

So I read that I could read the values as 3 8bit integers and then split the midle one into two parts but I dont know how to really do this in JavaScript.

The code bellow makes the correct length of UInt16Array, so the splitting is right, but the values in array are not correct:

fileReader.onload = () => {
    const arrayBuffer = fileReader.result;

    const dataView = new DataView(arrayBuffer);
    const length = Math.floor((dataView.byteLength * 8) / 12);
    const result = new Uint16Array(length);

    let byteOffset = 0;
    let bitOffset = 0;
    let currentIndex = 0;

    for (let i = 0; i < length; i++) {
        const bitsRemaining = 12 - bitOffset;
        const bitsToRead = Math.min(bitsRemaining, 16);

        let value = (dataView.getUint8(byteOffset) << 8) | dataView.getUint8(byteOffset + 1);
        value >>= (16 - bitOffset - bitsToRead);
        value &= (0b1 << bitsToRead) - 1;

        result[currentIndex] = value;

        bitOffset += bitsToRead;
        if (bitOffset === 12) {
            byteOffset += 1;
            bitOffset = 0;
        }
    
        currentIndex++;
    }
}
                  

2

Answers


  1. Chosen as BEST ANSWER

    So i managed to get the result I wanted with the code below:

    fileReader.onload = () => {
        const arrayBuffer = fileReader.result;
        var dataView = new DataView(arrayBuffer)
    
        var numIntegers = Math.floor((dataView.byteLength * 8) / 12);
        uint16Array = new Uint16Array(numIntegers);
    
        var j = 0;
        for (var i = 0; i < dataView.byteLength; i += 3) {
            uint16Array[j] = dataView.getUint8(i) << 4 | dataView.getUint8(i + 1) >> 4;
            uint16Array[j + 1] = (dataView.getUint8(i + 1) & 0x0F) << 8 | dataView.getUint8(i + 2);
            j += 2;
        }
    }
    

  2. With consideration of nibble order (something similar to endianness for bytes), 4 most expected layouts can be implemented within a single generic function:

    BE0/BE1  |Hi0 Hi0|Lo0 Hi1|Lo1 Lo1|
    BE0/LE1  |Hi0 Hi0|Lo0 Lo1|Hi1 Hi1|
    LE0/BE1  |Lo0 Lo0|Hi0 Hi1|Lo1 Lo1|
    LE0/LE1  |Lo0 Lo0|Hi0 Lo1|Hi1 Hi1|
    

    The function loops through 3-byte chunks to run faster.

    function unpackUint12 (arrayBuffer, nibbleOrder) {
    
        let u8 = new Uint8Array(arrayBuffer);
        let length = Math.floor(arrayBuffer.byteLength * 8 / 12);
        let result = new Uint16Array(length);
    
        switch (nibbleOrder.toUpperCase()) {
        case 'BB':
            var read = () => {
                // |Hi0 Hi0|Lo0 Hi1|Lo1 Lo1|
                let chunk = (u8[j++] << 16) | (u8[j++] << 8) | u8[j++];
                result[i++] = chunk >> 12;
                result[i++] = chunk & 0x0fff;
            };
            break;
        case 'BL':
            var read = () => {
                // |Hi0 Hi0|Lo0 Lo1|Hi1 Hi1|
                result[i++] = (u8[j++] << 4) | (u8[j] >> 4);
                result[i++] = (u8[j++] & 15) | (u8[j++] << 4);
            };
            break;
        case 'LB':
            var read = () => {
                // |Lo0 Lo0|Hi0 Hi1|Lo1 Lo1|
                result[i++] = u8[j++] | ((u8[j] & 0xf0) << 4);
                result[i++] = ((u8[j++] & 15) << 8) | u8[j++];
            };
            break;
        case 'LL':
            var read = () => {
                // |Lo0 Lo0|Hi0 Lo1|Hi1 Hi1|
                result[i++] = u8[j++] | ((u8[j] & 0xf0) << 4);
                result[i++] = (u8[j++] & 15) | (u8[j++] << 4);
            };
            break;
        default:
            throw Error('Invalid nibble order');
        }
    
        for (var i = 0, j = 0; j <= u8.length - 3;) {
            read();
        }
        // read trailing value, if there's any
        if (u8.length % 3 == 2) {
            result[i] = (nibbleOrder[0].toUpperCase() == 'B')
                ? (u8[j] << 4) | (u8[j+1] >> 4)
                : u8[j] | ((u8[j+1] & 0xf0) << 4);
        }
        return result;
    }
    
    // test buffers with numbers from 1 to 2048, and trailing 3333
    let test_BB = new Uint8Array([
        0b00000000,0b00010000,0b00000010,
        0b00000000,0b01000000,0b00001000,
        0b00000001,0b00000000,0b00100000,
        0b00000100,0b00000000,0b10000000,
        0b00010000,0b00000010,0b00000000,
        0b01000000,0b00001000,0b00000000,
        0b11010000,0b01010000
    ]).buffer;
    let test_BL = new Uint8Array([
        0b00000000,0b00010010,0b00000000,
        0b00000000,0b01001000,0b00000000,
        0b00000001,0b00000000,0b00000010,
        0b00000100,0b00000000,0b00001000,
        0b00010000,0b00000000,0b00100000,
        0b01000000,0b00000000,0b10000000,
        0b11010000,0b01010000
    ]).buffer;
    let test_LB = new Uint8Array([
        0b00000001,0b00000000,0b00000010,
        0b00000100,0b00000000,0b00001000,
        0b00010000,0b00000000,0b00100000,
        0b01000000,0b00000000,0b10000000,
        0b00000000,0b00010010,0b00000000,
        0b00000000,0b01001000,0b00000000,
        0b00000101,0b11010000
    ]).buffer;
    let test_LL = new Uint8Array([
        0b00000001,0b00000010,0b00000000,
        0b00000100,0b00001000,0b00000000,
        0b00010000,0b00000000,0b00000010,
        0b01000000,0b00000000,0b00001000,
        0b00000000,0b00010000,0b00100000,
        0b00000000,0b01000000,0b10000000,
        0b00000101,0b11010000
    ]).buffer;
    
    console.log(unpackUint12(test_BB, 'BB'));
    console.log(unpackUint12(test_BL, 'BL'));
    console.log(unpackUint12(test_LB, 'LB'));
    console.log(unpackUint12(test_LL, 'LL'));

    However if nibbles are mixed in a more complicated way, it would be better to write some dedicated method for extraction.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search