Pushing a 7712x960 Binary image to p5.js is Super slow. Is there a better way?

Hi, first of all this is my first post, so go easy on me if i am posting in the wrong place :slight_smile:

So what i am trying to do is render an image that is stored as a raw byte array. I am able to do that but its SUUUPER slow.In the code below i am even scaling the image by 1/4.

The image is 7712x920 about 7.1 MB

The reason i am quite positive there must be a better way is because if a store the pictures as bmp and use img = loadImage(“assets/my.bmp”); it works fine and the performance is good.

And no i can’t store all the images as bmp because the camera is running on an embedded device and storing the pixel data raw

There must be a better way!
looking forward to hearing you suggestions :smiley:

//load image from server (works fine)
loadBytes(data.nextFrame,function(data){
      updateImage(data);
    });


//create and render image
function updateImage(data){
  var counter = 0;
  var itr = 0;
  console.log("image W: "+ img.width + " \n image H "+img.height);
  if(data.bytes.length>1){
    img.loadPixels();
    for(var h = 0; h < img.height;h++){
      for(var w= 0; w< img.width;w++){
        img.set(w, h, color(data.bytes[counter], data.bytes[counter], data.bytes[counter]));
        counter += scaleFactor;
        itr++;
      }
    }
    console.log("PixelValue " + counter);
    console.log("itr " + itr);
    img.updatePixels();
    image(img,0 , 0);
  }
}
1 Like

Hi, take a look at the loadPixels example in the reference:

https://p5js.org/reference/#/p5/loadPixels

Your program currently uses .set() to set the pixels which is slow.
Using the pixels[...] array should be much faster.
Do you know how to do that?

2 Likes

Thank you for replying!

I have never used the pixels function before, but let med give it a shot.

I gave it a try. For a 1000x1000 image, it takes my laptop 25 milliseconds to randomize all the pixel colors using the pixels[] array, and the same operation takes 650 milliseconds when using set(). That’s about 26 times slower.

One thing to note is that set() sets R, G and B at the same time, while the pixels array has R, G, B and A stored as four consecutive values, so my example images contains 4.000.000 elements (not 1.000.000).

In your program you are setting the same value three times to build a color. Is that on purpose? That will make a gray scale color…

Update: actually, if you really want to use gray scale values it would be even faster, 13 milliseconds in my test program, because I would not need to call random() three times per pixel but just once, so 50 times faster than with set.

1 Like

Hi Then you again :slight_smile:

That sounds like exactly what i need!
I am struggling to update my function to use pixels[] instead of .set
could you maybe share your test code?

The image is gray scale (forgot to say that) nice catch!

Again thank you! :smiley:

function setup() {
  createCanvas(400, 400);
	background(20,180, 10);
	
	var img = createImage(200, 200);
	img.loadPixels();
	for(var i=0; i<img.pixels.length; i+=4) {
		var c = random(255);
		img.pixels[i] = c;
		img.pixels[i+1] = c;
		img.pixels[i+2] = c;
		img.pixels[i+3] = 255;
	}
	img.updatePixels();
	image(img, 0, 0);
}

Run it here: http://alpha.editor.p5js.org/abe/sketches/rkPTGyvyX

This example doesn’t take into account pixelDensity. But the third one here does:
https://p5js.org/reference/#/p5/createImage

I think that example has an error though (width and height should be img.width and img.height instead. I sent a pull request)

2 Likes

Thank you very much Hamoid!
Its working now! :smiley:

Have a nice weekend!

1 Like

Made my own version using a DataView + its method setInt32() as a wrapper to write to pixels[] 4 bytes (RGBa) at once: :grinning:


Dunno whether it’s faster or not, but you can check it out online here: :clown_face:

/**
 * Fill Image with Random Gray (v1.0.3)
 * GoToLoop (2018-May-26)
 *
 * https://Discourse.Processing.org/t/
 * pushing-a-7712x960-binary-image-to-p5-js-
 * is-super-slow-is-there-a-better-way/371/8
 *
 * Bl.ocks.org/GoSubRoutine/dd290c28663ef13aaf2f1b5e8d0adffa
 */

"use strict";

let img;

function setup() {
  createCanvas(400, 300).mousePressed(redraw);
  noLoop();
  img = createImage(width >> 1, height >> 1);
}

function draw() {
  background(top.document.title = '#' + hex(~~random(0x1000), 3));
  fillImgRndGray(img);
  set(width - img.width >> 1, height - img.height >> 1, img);
}

function keyPressed() {
  return !!redraw();
}

function fillImgRndGray(img) {
  if (!img)  img = createImage(width, height);
  img.pixels.buffer || img.loadPixels();

  const pix = new DataView(img.pixels.buffer),
        len = pix.byteLength,
        bytes = Int32Array.BYTES_PER_ELEMENT,
        range = 0x100;

  for (let i = 0; i < len; i += bytes) {
    const gray = ~~random(range),
          RGBa = gray<<0o30 | gray<<0o20 | gray<<0o10 | 0xff;

    pix.setInt32(i, RGBa);
  }

  img.updatePixels();
  return img;
}
1 Like

It’s cool that you know how to do this @GoToLoop but I think the syntax is unnecessarily hard to understand considering the audience of this forum.

Maybe if you share such an example it would be nice to explain why you choose to use octal, hexadecimal, bitwise operations, double negatives, let, strict, const etc, is there any advantage of using such and explaining how they work so others can learn from that.

2 Likes

Yup, a lil’ harder indeed. However, I’ve made this example as a bonus over your already accepted & thanked example! So it’s on purpose it’s more “technical” than yours. :yum:

Whoa! What a big list to explain each item of it! :dizzy_face:

I believe if some1 really wants an explanation about some part of my code, they can ask about it here, pinpointing exactly what they hadn’t understood. :face_with_thermometer:

I don’t feel like pre-explaining how my code works line-by-line before some1 had even asked for it 1st! :astonished:

Actually, ~80% (or even 100%) you had listed can be easily found here: :innocent:

By strict, I guess you’ve meant "use strict"; ,right? :thinking:

This is a very old ES5 standard btW. :older_man:
And IMO, every single “.js” file should have that as its 1st executable statement: :file_cabinet:

Ditto for the ES6 let & const keywords. They’ve replaced the semi-deprecated var for ordinary usage: :muscle:

These are the hexadecimal & octal values I’ve used on my sketch: :unamused:
0x1000, 0x100, 0xff, 0o30, 0o20, 0o10

Frankly, if n1 wishes to know those corresponding base10 values can open their browser’s console by hitting CTRL + SHIFT + J (or just F12) and copy & paste this: :keyboard:
console.log(0x1000, 0x100, 0xff, 0o30, 0o20, 0o10);
Add get this following output:
4096 256 255 24 16 8

Now I ask you: Is '#' + hex(~~random(4096), 3) actually more understandable than '#' + hex(~~random(0x1000), 3)? :thinking:

Or would RGBa = gray<<24 | gray<<16 | gray<<8 | 255; enlighten us more than RGBa = gray<<0o30 | gray<<0o20 | gray<<0o10 | 0xff;? :flashlight:

Would n1 who still doesn’t understand how computers store data in RAM comprehend how the value 4096 (0x1000) could possibly be used to pick all main colors? :computer:

Why not just 4000 or 4100? Or for picking a random gray, why not 250 or 260?

The >> 1 is just a faster way to get the half of a value w/ its result truncated (no fractional part). :money_mouth_face:

The RGBa = gray<<24 | gray<<16 | gray<<8 | 255; is for composite-placing the randomly picked gray color at the Red, Green & Blue byte-octet-position within the 32-bit (4-byte) color value. Of course, the tail 255 is for a 100% opaque alpha. :wink:

For example, gray<<24 bit-shifts the gray value 24 bits (3 bytes) to the left.
The octal form gray<<0o30 implies those 3 bytes for those in the know. :stuck_out_tongue:

In order to facilitate general comprehension, I’ve turned all those “mysterious” values into constants.
Therefore that statement is now: :sunglasses:
RGBa = gray<<THREE_BYTES | gray<<TWO_BYTES | gray<<ONE_BYTE | MAX_BYTE;

So now you folks can focus more on what the code is doing rather than how it is doing it if u choose so! :smile_cat:

/**
 * Fill Image with Random Gray (v1.1.1)
 * GoToLoop (2018-May-26)
 *
 * https://Discourse.Processing.org/t/
 * pushing-a-7712x960-binary-image-to-p5-js-
 * is-super-slow-is-there-a-better-way/371/11
 *
 * Bl.ocks.org/GoSubRoutine/dd290c28663ef13aaf2f1b5e8d0adffa
 */

"use strict";

const WEBCOLOR_RANGE = 0x1000, GRAY_RANGE = 0x100,
      NUM_COLOR_BYTES = Int32Array.BYTES_PER_ELEMENT,
      ONE_BYTE = 0o10, TWO_BYTES = 0o20, THREE_BYTES = 0o30, MAX_BYTE = 0xff;

let img;

function setup() {
  createCanvas(400, 300).mousePressed(redraw);
  noLoop();
  img = createImage(width >> 1, height >> 1);
}

function draw() {
  background(top.document.title = randomColor());
  fillImgRndGray(img);
  set(width - img.width >> 1, height - img.height >> 1, img);
}

function keyPressed() {
  return !!redraw(); // returns false as preventDefault()
}

function randomColor() {
  return '#' + hex(~~random(WEBCOLOR_RANGE), 3);
}

function fillImgRndGray(img) {
  if (!img)  img = createImage(width, height);
  img.pixels.buffer || img.loadPixels();

  const pix = new DataView(img.pixels.buffer), len = pix.byteLength;

  for (let i = 0; i < len; i += NUM_COLOR_BYTES) {
    const gray = ~~random(GRAY_RANGE),
          RGBa = gray<<THREE_BYTES | gray<<TWO_BYTES | gray<<ONE_BYTE | MAX_BYTE;

    pix.setInt32(i, RGBa);
  }

  img.updatePixels();
  return img;
}
1 Like

There are reasons to use bitwise operations but speed is no longer one of them. Modern compilers choose the fastest way to achieve what you ask for. Browserscope shows that the normal division is actually faster in Chrome, Firefox and Opera.

Both are quite unreadable. Maybe this shows more the intention:

// Set the page title to a random color-code.
// Color R, G and B values can be between 0 and 255,
// so 256 different levels. 256 * 256 * 256 equals
// 16.777.216, which is the total number of different colors
// most current computers can display.
top.document.title = '#' + hex(random(256*256*256), 6);
background(top.document.title);
1 Like

For me they were about the same for 32-bit SlimJet (Chromium 64.0.3282.119) & 32-bit SRWare Iron (Chromium 66.0.3450.0) under my Win8.1 AMD laptop. :roll_eyes:

But surprisingly bitshifting was slower on 64-bit Waterfox (Firefox 56.2.0)! :dizzy_face:

However, if I go w/ latest 64-bit Firefox Dev Edition 61.0b8, bitshifting is back at the top again! :muscle:

Given that future Firefox bitshifting is faster again; and in Chromium they’re mostly about the same; plus width >> 1 is clearly much shorter than Math.floor(width >> 1) or even floor(width >> 1), I’ll keep on doing bitshifting all the way! :joy_cat:

BtW, I’ve setup another JsPerf.com w/ 5 tests now: :checkered_flag:
https://JsPerf.com/bitshift-vs-divide-by-2-vs-multiply-by-half-ii

1 Like