Author Topic: How to use GuiBitmapCtrl::getPixelColor  (Read 3715 times)

I'm trying to load a screenshot in a bitmap control to read the colors, then send it to a server.

However, getPixelColor only returns garbage. For testing I've created a bitmap control at offset 300 300 with size 1024 768 and loaded the default screenshots/acmcity.jpg.

I've tried these:
getPixelColor(0, 0) -> 1 14 38
getPixelColor(1, 1) -> 12 9 30
getPixelColor(300, 300) -> 255 255 255
getPixelColor(301, 301) -> 255 255 255

The actual color of the top left pixel is 29 13 16.

How does this work?
« Last Edit: June 17, 2015, 01:40:29 PM by Zeblote »

getPixelColor actually (incorrectly) uses absolute position on the screen. So getPixelColor(0, 0) returns the pixel color of the absolute top left pixel on your screen.

Just offset it by the object's position and it should work just fine.

EDIT: as to why it didn't work properly when you did that, I have no idea. I'll go test it on my own right now but in the past that's what I've done to get it to work.

I tried that, but it still doesn't work. My next idea was that maybe it's getting the color of the console window for some reason (white) so I used a schedule to let me close it first. Now it's returning a different color each time for the same pixel (wat)

Perhaps the main menu slideshow is causing that in some way? Assuming that you're testing it on the main menu, of course.

what if you just keep using 0, 0 each time?
maybe it's moving it's point of reference, which would be extremely weird

Perhaps the main menu slideshow is causing that in some way? Assuming that you're testing it on the main menu, of course.
I did test it on the menu, but offset 301 301 should definitely be inside the control...

Or is getpixelcolor completely broken and it just returns the colors behind the control? So I'd need to use a second one to get the image behind it.


Actually this function seems to be abnormally slow anyways, scanning a 640x640 image like I planned isn't going to work.
« Last Edit: June 18, 2015, 12:32:56 PM by Zeblote »

Actually this function seems to be abnormally slow anyways, scanning a 640x640 image like I planned isn't going to work.

Definitely not. The only reason I explored this possibility anyway was as an attempt to make an image uploader in Blockland. The only way I could get it to not freeze-lag was to make it 'take the picture' over like ten seconds.

Definitely not. The only reason I explored this possibility anyway was as an attempt to make an image uploader in Blockland. The only way I could get it to not freeze-lag was to make it 'take the picture' over like ten seconds.
I'll have to try this again later. I wanted to test what kind of stuff it's getting and it took over 15 seconds to get a single row of 640 pixels and add them together to a long string.


Edit: nope it's too slow.

%a = testctrl; for(%i = 0; %i < 640; %i++){ %a.getpixelcolor(0,0); }

takes almost 20 seconds to process.


Edit 2: It finishes almost instantly with vsync deactivated. The speed appears to be tied to the refresh rate .....?
« Last Edit: June 18, 2015, 02:18:37 PM by Zeblote »

Edit 2: It finishes almost instantly with vsync deactivated. The speed appears to be tied to the refresh rate .....?

I wouldn't be surprised if the function requests the engine to render the view to an offscreen buffer before sampling a pixel from that buffer. Perhaps the render function directly interacts with vsync in some way regarding how it flushes, which would mean that it would literally block for vsync inside of the function.

Well, even if I have vsync disabled, it still takes 21 minutes to scan all 640*640 pixels. That's not useful for now.

Time to resolve this mystery!

getPixelColor(x, y) is written very badly. Allow me to illustrate roughly what it's doing:

Code: [Select]
<wrapper for getPixelColor defined with ConsoleFunction>

...

void GuiBitmapCtrl::getPixelColor(int x, int y, ColorI* pOut)
{
     Canvas->renderFrame(false); //Render canvas children.
     Canvas->renderFrame(false); //It's called twice-- maybe for transparency, since glClear isn't called inbetween.

     //renderFrame renders to the back buffer and then swaps it to front.
     //Supposedly reading from the back buffer is undefined after a swap, but whatever. This is Blockland.
     glReadBuffer(GL_BACK);

     unsigned char pixel[3];
     glReadPixels(x + this->mBounds.point.x, y + this->mBounds.point.y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, pixel);

     pOut->set(pixel[0], pixel[1], pixel[2]); //Alpha = 255
}

Summary: The code renders all the canvas contents twice (this includes the scene plus any active GUI controls), then reads a pixel from the back buffer using (Ctrl.position.x + arg_x, Ctrl.position.y + arg_y).

This seems somewhat logical for the most part, but recall that by default OpenGL uses the bottom left corner for the (0, 0) position. Controls in Blockland are positioned with the (0, 0) corner at the top left. Calling getPixelColor(5, 5) on a simple bitmap control located at (100, 100) will make this code evaluate glReadPixels(105, 105, ...), which will return incorrect data, since the y coordinate is flipped.

In conclusion, getPixelColor() sucks. It should return pixels based on the bitmap's texture data, not actual pixels rendered on the canvas.

Correct usage is something like this ((0, 0) being top left corner of control):

Code: [Select]
%py = getWord(TestBitmapControl.position, 1);
%yRes = getWord(getRes(), 1);

...

%RealY = 10;
%RealX = 10;

TestBitmapControl.getPixelColor(%RealX, -2 * %py + %yRes - %RealY);

Make sure your control is in view, I guess. Obviously I don't recommend this because you will be re-rendering the entire canvas twice for every call, which would badly hurt performance when scanning an entire image, even if it were 64x64.

re-rendering the entire canvas twice
that's hilariously horrendous

So basically, we need to ask badspot to fix it