GetPixel(x, y)

Monkey Forums/Monkey Programming/GetPixel(x, y)

therevills(Posted 2011) [#1]
Heres some extern code for HTML5, Flash, GLFW and Android to get RGB pixel at a coordinate:

Monkey Code:
Extern
	#If LANG="cpp" Then
		Function GetColorPixel:Int(x:Int, y:Int)="diddy::getPixel"
	#Else
		Function GetColorPixel:Int(x:Int, y:Int)="diddy.getPixel"
	#End

Public

Function GetPixel:Int[](x:Int, y:Int)
	Local colorArr:Int[4]
	Local color:Int = GetColorPixel(x, y)
	'Print color
	colorArr[0] = (color Shr 16) & $ff
	colorArr[1] = (color Shr 8) & $ff
	colorArr[2] = color & $ff
	colorArr[3] = (color Shr 24) & $ff

	Return colorArr
End


HTM5:
var diddy = new Object();

diddy.getPixel=function(x, y){
	var tcanvas=document.getElementById("GameCanvas").getContext("2d")
	if (tcanvas==null)
		return 0;
	var img = tcanvas.getImageData(x, y, 1, 1); 
	var pix = img.data;
	return (pix[3]<<24) | (pix[0]<<16) | (pix[1]<<8) | pix[2];
};

Android:
class diddy
{
	static int getPixel(int x, int y)
	{
		ByteBuffer pixelBuffer = ByteBuffer.allocateDirect(4);
		pixelBuffer.order(ByteOrder.LITTLE_ENDIAN); 
		GL11 gl = MonkeyGame.app.graphics.gl;
		if (gl!=null) {
			gl.glReadPixels((int)x, (int)MonkeyGame.app.graphics.height - y, 1, 1, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixelBuffer);
			
			int red = pixelBuffer.get(0) & 0xff;
			int green = pixelBuffer.get(1) & 0xff;
			int blue = pixelBuffer.get(2) & 0xff;
			int alpha = pixelBuffer.get(3) & 0xff;
			// returning ARGB
			return (alpha<<24) | (red<<16) | (green<<8) |  blue;
		}
		return 0;
	}
}

GLFW:
class diddy
{
	public:

	static int getPixel(int x, int y)
	{
		unsigned char pix[4];
		glReadPixels(x, app->graphics->height-y ,1 ,1 ,GL_RGBA ,GL_UNSIGNED_BYTE ,pix);
		return (pix[3]<<24) | (pix[0]<<16) | (pix[1]<<8) |  pix[2];
	}
}

Flash:
class diddy
{
	static public function getPixel(x:int, y:int):int{
		var alpha:Number = 0;
		var red:Number = 0;
		var green:Number = 0;
		var blue:Number = 0;
		
		var bmd:BitmapData = new BitmapData(1, 1);
		var matrix:Matrix = new Matrix();
		matrix.translate(-x, -y);
		bmd.draw(game.stage, matrix);
		var pixel:uint = bmd.getPixel(0, 0);

		alpha = pixel >> 24 & 0xFF;		
		red = pixel >> 16 & 0xFF;
		green = pixel >> 8 & 0xFF;
		blue = pixel & 0xFF;

		return pixel;
	}
}


For some reason the Flash version returns a slightly darker colour?!

Best using this within OnRender... in Android it will only work with OnRender!

Usage:
Field pixel:Int[3]

Method OnRender:Int()
	If MouseDown()
		pixel = GetPixel(MouseX(), MouseY())
		Print "Red   = " + pixel[0]
		Print "Green = " + pixel[1]
		Print "Blue  = " + pixel[2]
	End
End



dave.h(Posted 2011) [#2]
This is great i was just thinking about how i could achieve this thanks.


therevills(Posted 2011) [#3]
And if you didn't guess its been added to Diddy ;)


Beaker(Posted 2011) [#4]
Can I ask why the flash version has all that extra code that never gets used?


therevills(Posted 2011) [#5]
I was trying to work out why it returns a darker shade...


Beaker(Posted 2011) [#6]
I see. Good stuff tho.


therevills(Posted 2011) [#7]
I can't test this right now... but I wonder if getPixel32 might fix the Flash issue:

http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/BitmapData.html?filter_flash=cs5&filter_flashplayer=10.2&filter_air=2.6#getPixel32%28%29


therevills(Posted 2011) [#8]
getPixel32 didnt fix the issue... its either a Monkey issue or a Flash issue:

http://www.monkeycoder.co.nz/Community/posts.php?topic=1861

And I give up on XNA for GetPixel... no idea how to do it for this platform... :(


therevills(Posted 2011) [#9]
For iOS this might work:

	static int getPixel(int x, int y)
	{
		unsigned char pix[4];
		glReadPixels(x, app->graphics->height-y ,1 ,1 ,GL_RGBA ,GL_UNSIGNED_BYTE ,pix);
		return (pix[3]<<24) | (pix[0]<<16) | (pix[1]<<8) |  pix[2];
	}


(its the same as the GLFW c++ code)... Untested!


therevills(Posted 2011) [#10]
Samah pointed me to some MSDN code for XNA:

http://msdn.microsoft.com/en-us/library/bb197089.aspx

Which I've tried but it returns {R:0 G:0 B:0 A:0} :(

	public static int getPixel(int x, int y)
	{
		if ((x > 0 && y > 0 && x < gxtkApp.game.app.graphics.Width()) && (y < gxtkApp.game.app.graphics.Height()))
		{
			Texture2D backBufferData = new Texture2D(
				gxtkApp.game.app.graphics.device,
				gxtkApp.game.app.graphics.Width(),
				gxtkApp.game.app.graphics.Height());

			Rectangle sourceRectangle =	new Rectangle(x, y, 1, 1);

			Color[] retrievedColor = new Color[1];

			backBufferData.GetData<Color>(
				0,
				sourceRectangle,
				retrievedColor,
				0,
				1);
				
			bb_std_lang.Print("x="+x+",y="+y+" col="+retrievedColor[0].ToString());
		}
		return 0;
	}	


Any XNA experts in the house?


Tibit(Posted 2011) [#11]
This is more of a guess. It might be the last two parameters?

> Texture2D.GetData (Int32, Nullable<Rectangle>, T[], Int32, Int32) Gets > a copy of 2D texture data, specifying a mipmap level, source rectangle, > start index, and number of elements.

So maybe use this overload instead?

Color[] colors1DArray = new Color[backBufferData.Width * backBufferData .Height];
Color[,] color2DArray = backBufferData.GetData(colors1DArray);

Like I said, not sure, only a guess :)


therevills(Posted 2011) [#12]
Thanks Tibit, just tried it but it didn't work - worth a go :)

It seems if Monkey was using XNA 3 it would be pretty straight forward to do...

I've been messing around with RenderTarget2D and I can actually get it to return a colour! Shame it clears the screen to do it and it returns that colour (the default xna purple) ;)

GraphicsDevice device = gxtkApp.game.app.graphics.device;
int w = device.PresentationParameters.BackBufferWidth;
int h = device.PresentationParameters.BackBufferHeight;

RenderTarget2D screenshot = new RenderTarget2D(device, w, h, false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8); 


device.SetRenderTarget(screenshot);
gxtkApp.game.app.graphics.Flush();
device.SetRenderTarget(null);
Color[] data = new Color[w * h];
screenshot.GetData<Color>(data);
bb_std_lang.Print("x="+x+",y="+y+" col="+data[x*y].ToString());



Cheese(Posted 2011) [#13]
Very cool.

I can't help thinking about how amazing it would be if this was a method of mojo.graphics.Image.

While working on quick projects using frameworks other than Monkey, sometimes I like to store static array data (like 2D maps, where each pixel represents a tile and different colors represent different classifications of tiles) using bitmaps -- just to make editing the data simple and fun.
I believe Markus Persson is known for doing this while participating in 48-hour game jams. ;)


Yoda(Posted 2011) [#14]
That's exactly what I'd like to do: Store level-data in an image. For that, I'd need a "getpixel" that can actually return an image's pixel rgb value, not the canvas.

Any idea?


hardcoal(Posted 2011) [#15]
can you get a pixel from an image.

say i want to scan a png image and determan its borders.
so i can create a poly frame for the image. thats the goal..

for box2d purposes..

cheers


therevills(Posted 2011) [#16]
For getting a pixel from an image without drawing it you need to alter mojo's graphics.monkey so that the Surface class is public (remove Private from line 35) and the surface field from Image (remove Private line 118).

Then you can do something like this for HTML5:


Extern

Function GetImageColorPixel:Int(image:Surface, x:Int, y:Int)="diddy.getImagePixel"

Public

Function GetImagePixel:Int[](image:Image, x:Int, y:Int)
	Local colorArr:Int[4]

	Local color:Int = GetImageColorPixel(image.surface, x, y)

	colorArr[0] = (color Shr 16) & $ff
	colorArr[1] = (color Shr 8) & $ff
	colorArr[2] = color & $ff
	colorArr[3] = (color Shr 24) & $ff

	Return colorArr
End


And the javascript code:
diddy.getImagePixel=function(surface, x, y) {
	if (!bb_app_device.graphics.tmpCanvas) {
		bb_app_device.graphics.tmpCanvas=document.createElement( "canvas" );
	}
	var tgc=bb_app_device.graphics.tmpCanvas.getContext( "2d" );

	tgc.drawImage( surface.image , x, y, 1, 1, x, y, 1, 1);
	var imgData=tgc.getImageData( x, y , 1, 1);

	var pix = imgData.data;

	return (pix[3]<<24) | (pix[0]<<16) | (pix[1]<<8) | pix[2];
}


This is very slow... I looped thru a 16x16 image getting all the pixels and it took 477ms.

So it is doable but you need to hack mojo.


hardcoal(Posted 2011) [#17]
thanks therevills ill give it a shot.


frank(Posted 2011) [#18]
A fast putPixel(x,y, r,g,b) would be very nice as well...


hardcoal(Posted 2011) [#19]
look at this therevills



After I capture one Pixle is there a way to know if its empty or not.
I mean is there a way to know the Image weight.
if so.. problem solved for scanning.


therevills(Posted 2011) [#20]
Weight? Do you mean height and width? I really don't know what you are trying to do with that code sample...

In Monkey you can do image.Width() and image.Height()...


hardcoal(Posted 2011) [#21]
what i did in this code is just taking a pixel from the image.

i thought maybe with one pixel there is a way to know
if its an empty pixel or not somehow without sampeling its color.


therevills(Posted 2011) [#22]
Ahhh gotcha!

Did you see that the Monkey code GetImagePixel returns an array of 4 values, one for red, green, blue and alpha.

So using this you can change your GrabImagePix to be this:



Then you can test pixel[3], if it is 0 (zero) it is an empty pixel.


frank(Posted 2011) [#23]
Did the iOS version work? I guess iOS doesn't want you to work like this, but it still would have great benefit for users.


therevills(Posted 2011) [#24]
Not sure... I havent had chance to test it, it should work though...


hardcoal(Posted 2011) [#25]
theevills

this is my code file


Extern

Function GetImageColorPixel:Int(image:Surface, x:Int, y:Int)="diddy.getImagePixel"

Public

Function GetImagePixel:Int[](image:Image, x:Int, y:Int)
	Local colorArr:Int[4]

	Local color:Int = GetImageColorPixel(image.surface, x, y)

	colorArr[0] = (color Shr 16) & $ff
	colorArr[1] = (color Shr 8) & $ff
	colorArr[2] = color & $ff
	colorArr[3] = (color Shr 24) & $ff

	Return colorArr
End

Function GrabImagePix:Void(anImage:Image)
	Local w:Int = anImage.Width()
	Local h:Int = anImage.Height()

	Local pix:Image
	Local pixel:Int[4]
	Local start% =  Millisecs()

	If w>0 And h>0 Then
		For Local i:Int = 0 Until w
			For Local j:Int = 0 Until h
				pix = anImage.GrabImage(i, j, 1, 1)
				pixel = GetImagePixel(pix, i, j)
				Print(i+","+j+" = "+ pixel[0]+","+pixel[1]+","+pixel[2]+" a="+pixel[3])
			Next
		Next
	End
	Local fin% =  Millisecs()			
	Print "Time = "+(fin-start)
End


why do i get this error?

Error : Field Image.surface:Surface is private

i did import diddy and mojo whats missing...


slenkar(Posted 2011) [#26]
you could go into the source code and remove the private keyword,
its probably just there so people dont access it who dont know what they are doing

the source file is modules/mojo/graphics.monkey i think


hardcoal(Posted 2011) [#27]
hehe like me :p tnx


Snader(Posted 2012) [#28]
How can I get Getpixel to work in mojo without using Diddy?


therevills(Posted 2012) [#29]
Create your own extern file and just copy the code...


Snader(Posted 2012) [#30]
Thanks therevills, I've got my own minified version of 'diddy' working now!