JavaScript оптимизированная версия преобразования массива ARGB в RGBA - PullRequest
0 голосов
/ 11 марта 2020

Я хочу преобразовать Uint8Array байтов, которые содержат изображение ARGB, в его представление RGBA, однако я хотел бы получить это с чем-то более оптимизированным, чем то, что я предлагаю здесь, используя, например, сдвиг байтов.

Что я сейчас делаю, так это просто поменяю порядок байтов в том смысле, что (например, длина может быть кратна 4):

let input = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 ];
let expected = [0x22, 0x33, 0x44, 0x11, 0x88, 0x55, 0x66, 0x77 ];

Таким образом, простой случай будет:

function argbToRgba(src) {
    let dest = new Uint8Array(src.length)

    for (let i = 0; i < src.length; i += 4) {
        let a = src[i]
        let r = src[i + 1]
        let g = src[i + 2]
        let b = src[i + 3]

        dest[i] = r;
        dest[i + 1] = g;
        dest[i + 2] = b;
        dest[i + 3] = a;
    }

    return dest;
}

Однако в идеале я хотел бы построить значение RGBA, используя сдвиг байтов (и это тривиальная часть), но моя проблема заключается в том, как поместить такое число uint32 в dest (которое должно быть Uint8Array и в идеале даже тот же самый из sr c, чтобы сделать изменения на месте).

Я имею в виду, в C, как только я определил uint32_t rgbaColor, я могу просто memwrite или присвойте int индексу массива, а переполнение сделает все остальное. Но как мне сделать это в JS?


Редактировать: После некоторых тестов кажется, что первоначальное предложение все еще самое быстрое, на самом деле

function argbToRgbaDataView(src, inline=false) {
    let dest = inline ? src : new Uint8Array(src.length);
    let srcView = new DataView(src.buffer);
    let destView = new DataView(dest.buffer);

    for (let i = 0; i < src.length; i += 4) {
        let argb = srcView.getUint32(i);
        let rgba = (argb & 0x00FFFFFF) << 8 |
                   (argb & 0xFF000000) >>> 24;
        destView.setUint32(i, rgba);
    }

    return dest;
}

function argbToRgbaDataViewInline(src) {
    return argbToRgbaDataView(src, true);
}

function argbToRgbaSwap(src, inline=false) {
    let dest = inline ? src : new Uint8Array(src.length);

    for (let i = 0; i < src.length; i += 4) {
        let a = src[i]
        let r = src[i + 1]
        let g = src[i + 2]
        let b = src[i + 3]

        dest[i] = r;
        dest[i + 1] = g;
        dest[i + 2] = b;
        dest[i + 3] = a;
    }

    return dest;
}

function argbToRgbaSwapInline(src) {
    return argbToRgbaSwap(src, true);
}

function argbToRgbaSwapNoVars(src, inline = false) {
    let dest = inline ? src : new Uint8Array(src);

    for (let i = 0; i < src.length; i += 4) {
        let a = src[i]

        dest[i] = src[i + 1];
        dest[i + 1] = src[i + 2];
        dest[i + 2] = src[i + 3];
        dest[i + 3] = a;
    }

    return dest;
}

function argbToRgbaSwapNoVarsInline(src) {
    return argbToRgbaSwapNoVars(src, true);
}

// From https://stackoverflow.com/a/60639510/210151
function argb2rgbaStackOverflow(inArr) {
    return inArr.reduce((a, c, i, t) => {
        if (i % 4 === 0) {
            let [A, R, G, B] = t.slice(i, i + 4)
            a.push(R, G, B, A)
        }
        return a
    }, [])
}

function measureFunction(func) {
    let preTime = new Date().getTime();
    let ret = func.call(...arguments);
    console.log(`Calling ${func.name} took ${new Date().getTime() - preTime}ms`);
    return ret;
}

function createRandomArray(size) {
    return new Uint8Array(size).fill().map((a, i) =>
        a = i).sort(() => Math.random() - 0.5);
}

function iconSizeToBytes(iconSize) {
    const bytesPerPixel = 4;
    return iconSize * iconSize * bytesPerPixel;
}

// This is to add support to console.log to gjs
try {
    console;
} catch(e) {
    window.console = {
        log: function() { print(...arguments) },
    };
}

let allSizes = [
    iconSizeToBytes(32),
    iconSizeToBytes(64),
    iconSizeToBytes(512),
    iconSizeToBytes(1024),
    iconSizeToBytes(2048),
];

for (let size of allSizes) {
    console.log(`Creating random array of ${size/(1024 * 1024)}Mbyte...`);
    let randomArray = measureFunction(createRandomArray, size);

    measureFunction(argbToRgbaDataView, randomArray);
    measureFunction(argbToRgbaDataViewInline, randomArray);
    measureFunction(argbToRgbaSwap, randomArray);
    measureFunction(argbToRgbaSwapInline, randomArray);
    measureFunction(argbToRgbaSwapNoVars, randomArray);
    measureFunction(argbToRgbaSwapNoVarsInline, randomArray);
    measureFunction(argb2rgbaStackOverflow, randomArray);

    console.log('------------------------------------------------------');
}
<script src="https://rawgit.com/eu81273/jsfiddle-console/master/console.js"></script>

Ответы [ 3 ]

1 голос
/ 11 марта 2020

Похоже, что при написании этого я обнаружил, что создал DataView, и это действительно упрощает мою жизнь за это!

Так что проблему можно решить с помощью:

function argbToRgba(src) {
    let dest = new Uint8Array(src.length);
    let srcView = new DataView(src.buffer);
    let destView = new DataView(dest.buffer);

    for (let i = 0; i < src.length; i += 4) {
        let argb = srcView.getUint32(i);
        let rgba = (argb & 0x00FFFFFF) << 8 |
                   (argb & 0xFF000000) >>> 24;
        destView.setUint32(i, rgba);
    }

    return dest;
}

Используя let dest = src;, изменение также можно сделать встроенным.

0 голосов
/ 12 марта 2020

Я думаю, что нашел самое быстрое решение.

Он в значительной степени вдохновлен вашим, но вместо воссоздания нового массива Uint8 с каждым вызовом
смещение A ( A rgb -> rgb A ) выполняется непосредственно на самом объекте.

function fRGB_x(xInOut)
  {
  for (let i = 0; i < xInOut.length; i += 4)
    {
    let x0 = xInOut[i]
    xInOut[i]    = xInOut[i +1];  // R
    xInOut[i +1] = xInOut[i +2];  // G
    xInOut[i +2] = xInOut[i +3];  // B
    xInOut[i +3] = x0;            // A
    }
  }

let bob = new Uint8Array(8).map((_,i)=>i)

AnswerProof.textContent = `before -> [ ${bob.join(', ')  } ]`  

fRGB_x(bob)

AnswerProof.textContent += `\n\nafter --> [ ${bob.join(', ')  } ]` 
0 голосов
/ 12 марта 2020

Я сделал свои собственные тесты скорости по этому поводу. На моем компьютере Linux скорость зависит от Firefox и Chromium!

Вы можете проверить самостоятельно:

function fRGB_0(src)  // optimized
  {
  let dest = new Uint8Array(src.length)
  for (let i = 0; i < src.length; i += 4)
    {
    dest[i]    = src[i +1];  // R
    dest[i +1] = src[i +2];  // G
    dest[i +2] = src[i +3];  // B
    dest[i +3] = src[i];     // A
    }
  return dest;
  }
function fRGB_x(xInOut)
  {
  for (let i = 0; i < xInOut.length; i += 4)
    {
    let x0 = xInOut[i]
    xInOut[i]    = xInOut[i +1];  // R
    xInOut[i +1] = xInOut[i +2];  // G
    xInOut[i +2] = xInOut[i +3];  // B
    xInOut[i +3] = x0;            // A
    }
  }
const twoMb = 2 * 1024 * 1024
  ,   Uint8  =_=>new Uint8Array(8).map(e=>Math.floor(Math.random()*256))
  ,   fRGB_1 =([A1,R1,G1,B1,A2,R2,G2,B2])=>[R1,G1,B1,A1,R2,G2,B2,A2]
  ,   fRGB_2 =inArr=>inArr.reduce((a,c,i,t)=>{if(i%4===0){let [A,R,G,B]=t.slice(i,i+4);a.push(R,G,B,A)}return a},[])
  ;

console.log('generate 2Mb Array for testing...')
let ArrayTest = []
for(let i=twoMb;i--;)  ArrayTest.push( Uint8() );

console.log('start test RGB_0')
console.time('Test RGB_0')
for(let i=twoMb;i--;)  { let bob = fRGB_0(ArrayTest[i]) }
console.timeEnd('Test RGB_0')

console.log('start test RGB_x')
console.time('Test RGB_x')
for(let i=twoMb;i--;)  { fRGB_x(ArrayTest[i]) }
console.timeEnd('Test RGB_x')

console.log('start test RGB_1')
console.time('Test RGB_1')
for(let i=twoMb;i--;)  { let bob = fRGB_1(ArrayTest[i]) }
console.timeEnd('Test RGB_1')

console.log('start test RGB_2')
console.time('Test RGB_2')
for(let i=twoMb;i--;)  { let bob = fRGB_2(ArrayTest[i]) }
console.timeEnd('Test RGB_2')
.as-console-wrapper { max-height: 100% !important; top: 0; }
...