How are images faster than buffers?!?

Ok so, basically, I’m trying to make a black and white image using a buffer, because I’m trying to get the pixels of an image faster and I only need to know On or Off and not the color information.
But when I use a buffer, my code is still slower than if I just use image.getPixel(x, y) = 15 (15 is black)
So I’m just wondering how using that is faster??
I’m pretty sure that my buffer method is only slower because I have to math.trunc the values before I get the data from the buffer and I don’t have to do that when getting from images, which, uh, how does that work??
@richard please explain:
When I look into the files, getPixel takes in integers, not floats, but I can give it floats and it just works?? I really want to know how that works in typescript!

1 Like

@WoofWoof as always, please share your code! also, are you talking about on hardware or in the browser?

you are correct that Image.getPixel doesn’t use Math.trunc on float values; Image.getPixel is implemented as a C++ function and it automatically gets converted to an integer on hardware. in the browser, it gets converted using a bitwise operation instead of Math.trunc.

in javascript, using any bitwise operation on a number will automatically convert it into an integer. for example, we can use the bitwise OR operator (|) like so to convert something into an integer:

let myFloat = 1.5;
let myInt = myFloat | 0;

console.log(myFloat)  // prints 1.5
console.log(myInt)    // prints 1

this is very, very slightly faster than using Math.trunc, but in practice it doesn’t make that much of a difference. we mostly use it because it takes less time to write!

here’s a sample project demonstrating the relative performance:

take a look at the console to see the difference in time. on my computer it takes about 2 seconds for both to complete. if it’s taking forever on yours, you might need to remove an extra 0 from the numberOfOperations variable.

after 100 million operations, the bitwise operations saves about 100 milliseconds on my computer. imo that’s small enough to not worry about :laughing:

2 Likes

Thanks! This clears up a lot and opens up some more questions. I didn’t share my code because it’s a game I’m working on, but I will if needed. I’ll just say I’m doing something that, kinda like 2.5D, “casts” out many rays in all directions, so I’m probably doing the check on the data about… 150,720 times per game update maximum… ok yeah, that’s a lot, but who cares. Using getPixel for this, I’m getting around 300 - 400 fps, which is just fine. Now, I want to make it faster because, well, I think I can of course!
So I made a buffer with the map data by going through the map image and putting 1s in the buffer when there are black pixels in the image. And yes, I already tried arrays, it’s also not faster.
I have to check if the pixel corresponding with the ray location is On or Off, and the ray locations are floats, which is fine with images but not with buffers.
The reason I thought it must be the math.trunc was because, well, I don’t think I’m doing anything different from images, which must be where I’m wrong.
I’m basically using the code map_buffer_data[ray_y * map_width + ray_x] to get the data from the buffer, which I imagine can’t possibly be too far off from what the image code is doing…?
I think where I’m wrong in that I’m using .setUint8 to set the 1s and 0s, which uses 8bits for each one.
I’m going to guess that images, only needing to store integers from 0 - 16 for all the colors, probably use a smaller, and thus faster format. What I am really wondering is:

  1. am I correct about the image format?
  2. I see that the function getCore is using a pointer to something returned from a function (?) called “pix” and I am wondering where the definition for that function is because I want to steal that code.
  3. since the getPixel function is being sneaky and unfair and using C++ code, does that mean it will always be faster than my buffer method?
  4. is there a way to make a Boolean buffer/is there a way to write to and read the individual bits? I mean, as easy one, one where I don’t have to set a byte to 30 just to get 00011110 or whatever. I’m pretty sure you can only deal with memory in 8 bit blocks, but a coder can hope :sweat_smile:.

P.S. I would also love an in depth explanation of whatever the heck IMAGE_HEADER_MAGIC is doing, but that can wait :wink:

answer #1

images do indeed use a smaller format (4 bits per pixel rather than 8), but that is actually slower than using 8 bits. the reason is because as you noted, you can only deal with 8 bits at a time, so images have to go through the extra step of unpacking the pixel from the byte after accessing it. the only reason we do 4 bits per pixel is because we optimize for memory on hardware devices rather than speed.

note that the above is only true for hardware! in the browser, where memory is not an issue at all, we actually represent pixels as 1 byte per pixel to speed things up.

answer #2

here is a link to the browser implementation of the pix function:

and here is the C++ implementation for hardware (which I don’t think you want):

answer #3

unless you share your code, i’m not sure i can answer that properly. if you don’t want to share the game, how about editing the project in my earlier post to demonstrate the different timings for your buffer/image code

answer #4

sure, but as mentioned above it’s going to end up being slower (though way more memory efficient). in fact, here’s one i did in typescript a while back:

note that this code isn’t makecode typescript, it’s normal typescript. if you swap the uint8array out for a buffer, it should work though.

ps answer

IMAGE_HEADER_MAGIC isn’t doing anything! it’s actually just an indicator that something is encoded in our image format. more info from wikipedia

1 Like