Plotting Pixels in an Image

Monkey Forums/Monkey Beginners/Plotting Pixels in an Image

Boulderdash(Posted 2014) [#1]
Is there anyway I can plot pixels inside an image ? like an old school plot x,y routine, there is no "lockimage" or anything like that as in BlitzMax?

I need to be able to plot pixels inside a 320 x 200 pixel image and cant do it at speed using DrawRect

Thanks.

[edit] found Createimage and WritePixels but no examples and instructions very sparse, any one have any examples for a newbie like me ?


GuShh(Posted 2014) [#2]
Do you need to save the image later on, or have any reason to actually change it's contents? can't you draw on top instead?, what's the target anyway?


Goodlookinguy(Posted 2014) [#3]
Example of CreateImage and WritePixels

Import mojo

Function Main:Int()
	New Example()
	Return 0
End

Class Example Extends App
	
	Method OnCreate:Int()
		SetUpdateRate(15)
		
		image = CreateImage(320, 200)
		Local pixels:Int[320 * 200]
		
		For Local i:Int = 0 Until 320 * 200
			Local x:Int = i Mod 320 ' <-- unnecessary, but this is how you find x
			Local y:Int = i / 320 '<-- unnecessary, but this is how you find y
			' if you need to plot points from x and y
			' y * 320 + x = i
			
			pixels[i] = GenerateColor(i)
		Next
		image.WritePixels(pixels, 0, 0, 320, 200, 0)
		
		Return 0
	End
	
	Method OnRender:Int()
		Cls()
		
		DrawImage(image, 10, 10)
		
		Return 0
	End
	
	Method GenerateColor:Int( i:Int )
		Local rgb:Int = 255 * (Abs((i / 255.0) - 0.5) * 2.0)
		Return rgb | (rgb Shl 8) | (rgb Shl 16)
	End
	
	Field image:Image
End



Boulderdash(Posted 2014) [#4]
I also found an example last night for : LoadImageData() then computer shutdown and cant find it now ?

I was wondering if LoadImageData() is fastest or your example (I need high performance)

Tried your example on a Droid, 2 FPS

Tried in on a PC, 60 FPS but near 100% CPU load, so nothing left for rest of code.

I have done this in X86 assembler and CPU load is 1%

I suspect there is a bottleneck around plotting one pixel at a time, I need to move all the pixels to the image at one time, something to do with LoadImageData() etc, cant find examples


Midimaster(Posted 2014) [#5]
Switch off the function GenerateColor() to check the speed of the WritePixels() function. The sample above sets all 640.000 pixels of the image. In reallity you will have less calls when f.e. drawing a function. If you plot a linear graph you will normally have to draw not more than 320 pixels. The speed will be 200 times faster!

Your result of 2FPS may be a mistake... Because the first chance to check FPSs is after OnCreate(). But the test is already finished niside OnCreate()!

A second way (in cases of a lot pixels) is to draw the current image onto the screen, then plot the pixels on the screen too, then grab the screen with ReadPixels() into an array then (re-)create the image from the array with WritePixels()


Boulderdash(Posted 2014) [#6]
.


Boulderdash(Posted 2014) [#7]
.


Danilo(Posted 2014) [#8]
grab the screen with ReadPixels() into an array then (re-)create the image from the array with WritePixels()

Unfortunately WritePixels() is impossible slow on Android (Nexus 7 2013) with 1920x1200 screen.
960x600 is still very slow, 480x300 becomes useable.


Danilo(Posted 2014) [#9]
Test code to show the problem with Android.

Code runs fine with targets: iOS, HTML5, Flash, GLFW

On Android, "time between OnRender" when trying to draw something:

test_mode = 1 (WritePixels() with whole image data)
 18 ms with  128x128  image (55 FPS)
 50 ms with  256x256  image (20 FPS)
100 ms with  512x512  image (10 FPS)
400 ms with 1024x1024 image ( 2.5 FPS)

test_mode = 2 (WritePixels() with 3x3 size only)
 18 ms with  128x128  image (55 FPS)
 50 ms with  256x256  image (20 FPS)
100 ms with  512x512  image (10 FPS)
320 ms with 1024x1024 image  (3 FPS)


Frame rate goes down rapidly with WritePixels() and images bigger than 128x128. Only with Android!

All other targets are fine, 1024x1024 image and always under 20ms (50+ FPS) with iOS, HTML5, Flash, GLFW.

Looks like something is wrong with the Android stuff.

'#ANDROID_NATIVE_GL_ENABLED = True
#MOJO_IMAGE_FILTERING_ENABLED=False

Import mojo

Function Main:Int()
    New Example()
    Return 0
End

Class Example Extends App

    Global test_mode:Int = 2 ' 1 = copy whole image
                             ' 2 = copy only 3x3 rect
    
    Const ScreenW = 512      ' 128  256  512  1024
    Const ScreenH = 512      ' 128  256  512  1024
    
    Method OnCreate:Int()
        SetUpdateRate(60)
        
        image = CreateImage(ScreenW, ScreenH)
        
        
        For Local i:Int = 0 Until ScreenW * ScreenH
            pixels[i] = $FF404040
        Next
        image.WritePixels(pixels, 0, 0, ScreenW, ScreenH, 0)

        If test_mode = 2
            For Local i:Int = 0 Until ScreenW * ScreenH
                pixels[i] = $FFFFFFFF
            Next
        Endif
        
        Return 0
    End
    
    Method OnUpdate:Int()
        counter += 1
        Local time := Millisecs()
        timeBetweenOnUpdate = time - oldUpdateTime
        oldUpdateTime = time

        If TouchDown(0)
            Local x:Float = TouchX(0)
            Local y:Float = TouchY(0)
            'Print "DOWN " + x + ":" + y
            If x < ScreenW-4 And y < ScreenH-4 Then
                If test_mode = 1
                    For Local i:=0 To 2
                        pixels[Int(y)    *ScreenH + Int(x)+i] = $FFFFFFFF
                        pixels[(Int(y)+1)*ScreenH + Int(x)+i] = $FFFFFFFF
                        pixels[(Int(y)+2)*ScreenH + Int(x)+i] = $FFFFFFFF
                    next
                    image.WritePixels(pixels, 0, 0, ScreenW, ScreenH, 0)
                Elseif test_mode = 2
                    image.WritePixels(pixels, Int(x), Int(y), 3, 3, 0)
                Endif
            Endif
        Endif

        updateEnd = Millisecs()
        onUpdateTime = updateEnd-time
        Return 0
    End
    
    Method OnRender:Int()
        Local time := Millisecs()
        timeBetweenOnRender = time - timeBetweenOnRender
        Cls()
        DrawImage(image, 0, 0)
        DrawText("time required for OnUpdate: "+onUpdateTime,10,10)
        DrawText("time between OnUpdate and OnRender: "+(time-updateEnd),10,30)
        DrawText("time between OnRender: "+timeBetweenOnRender,10,50)
        DrawText("Counter: "+counter,10,70)
        timeBetweenOnRender = time
        Return 0
    End


    Field timeBetweenOnUpdate:Int, oldUpdateTime:int
    Field timeBetweenOnRender:Int
    Field onUpdateTime:Int, updateEnd:Int, counter:Int
    Field image:Image
    Field pixels:Int[ScreenW * ScreenH]
End



Goodlookinguy(Posted 2014) [#10]
A 1024x1024 sized integer array is about 4 million bytes (4 MB). That's quite a lot for a device with limited memory and to pack all into one space. It'd probably be faster to use a 1024 sized integer array and do each line separately 1024 times. Or whatever optimal size is optimal without causing slow downs.


Danilo(Posted 2014) [#11]
4MB is nothing. I tested with Google Nexus 7 2013 and the device has 2GB RAM.
The GPU is an Adreno 320, which seems to be a quite powerful thing.
The allocation of the 4MB array does not cause any slowdown, as it runs at 55 FPS
when not touching the screen. As soon as I touch the screen, and WritePixels is called,
the FPS goes down rapidly with images at sizes 256x256, 512x512, 1024x1024.


Goodlookinguy(Posted 2014) [#12]
I've seen an array that size cause slowdowns before.

Aside from that though, the issue could be mitigated by not trying to write all of the pixels on one update.


Danilo(Posted 2014) [#13]
Aside from that though, the issue could be mitigated by not trying to write all of the pixels on one update.

It is what test_mode 2 is for. It copies only a 3x3 rect (9 pixels), but looking at the resulting output numbers... they are quite the same.
I have images at 256x256, 512x512, 1024x1024. Using WritePixels() on those images with 3x3 rect or with full size does not
make a difference.

What's the results on your Android device(s)?

Several guys here have written that Android WritePixels() is very slow for them. Midimaster is the only exception,
he says everything is fine for him. That's the reason I would like to resolve the issue and discuss what may be the problem
with Android WritePixels().
It would be nice to use GrabImage(), manipulate that small image only, and draw the manipulated image back onto the
original. Unfortunately I can't find a command to draw an image onto another image. Using WritePixels() does not help,
because it slows down to 3 FPS, even if i write only a 3x3 rect onto a 1024x1024 image.

Here is the code with only an 3x3 array and WritePixels() only using the 3x3 array,
so we can see the 4MB array size thing is not the issue:
'#ANDROID_NATIVE_GL_ENABLED = True
#MOJO_IMAGE_FILTERING_ENABLED=False

Import mojo

Function Main:Int()
    New Example()
    Return 0
End

Class Example Extends App
    
    Const ScreenW = 1024      ' 128  256  512  1024
    Const ScreenH = 1024      ' 128  256  512  1024
    
    Method OnCreate:Int()
        SetUpdateRate(60)
        
        image = CreateImage(ScreenW, ScreenH)
        
        pixelbuffer = New Int[ ScreenW * ScreenH ]
        
        For Local i:Int = 0 Until ScreenW * ScreenH
            pixelbuffer[i] = $FF404040
        Next
        image.WritePixels(pixelbuffer, 0, 0, ScreenW, ScreenH, 0)
        
        pixelbuffer = pixelbuffer.Resize(1)
        Print( pixelbuffer.Length() )

        For Local i:Int = 0 To 8
            pixels[i] = $FFFFFFFF
        Next
        
        Return 0
    End
    
    Method OnUpdate:Int()
        counter += 1
        Local time := Millisecs()
        timeBetweenOnUpdate = time - oldUpdateTime
        oldUpdateTime = time

        If TouchDown(0)
            Local x:Float = TouchX(0)
            Local y:Float = TouchY(0)
            'Print "DOWN " + x + ":" + y
            If x < ScreenW-4 And y < ScreenH-4 Then
                image.WritePixels(pixels, Int(x), Int(y), 3, 3, 0)
            Endif
        Endif

        updateEnd = Millisecs()
        onUpdateTime = updateEnd-time
        Return 0
    End
    
    Method OnRender:Int()
        Local time := Millisecs()
        timeBetweenOnRender = time - timeBetweenOnRender
        Cls()
        DrawImage(image, 0, 0)
        DrawText("time required for OnUpdate: "+onUpdateTime,10,10)
        DrawText("time between OnUpdate and OnRender: "+(time-updateEnd),10,30)
        DrawText("time between OnRender: "+timeBetweenOnRender,10,50)
        DrawText("Counter: "+counter,10,70)
        timeBetweenOnRender = time
        Return 0
    End


    Field timeBetweenOnUpdate:Int, oldUpdateTime:int
    Field timeBetweenOnRender:Int
    Field onUpdateTime:Int, updateEnd:Int, counter:Int
    Field image:Image

    Field pixels:Int[3 * 3]
    Field pixelbuffer:Int[]
End



Goodlookinguy(Posted 2014) [#14]
I'm not at my workstation, but I'll look more into the slowdowns on Android tomorrow. Maybe I can resolve it. It wouldn't be the first thing I've resolved on the targets.

Edit: I just grabbed the source code on my computer here to take a look. WritePixels2 (which is WritePixels in this case), is doing some pretty standard bitmap modifications. From a first glance everything seems like it's Android's fault. Hmm...


Danilo(Posted 2014) [#15]
Looking at mojo.android.java, I see many operations like converting RGBA to ARGB, changing buffer order to BIG_ENDIAN and LITTLE_ENDIAN, etc.

Re-arranging 4 million bytes for sure takes some time. And all this on byte basis, using many shifts, binary And's, and multiplications - for every single pixel.


Goodlookinguy(Posted 2014) [#16]
I didn't see that in WritePixels. Maybe I was looking in the wrong area. But yeah, re-arranging 4 million bytes would almost certainly cause slowdowns.

Edit: Read pixels does that. Write Pixels clears the GL buffer context, uses what appears to be a built-in pixel setting function and then invalidates the surface to force it to redraw. Nothing out of the ordinary really.

Hmm: https://stackoverflow.com/questions/4715840/improving-speed-of-getpixel-and-setpixel-on-android-bitmap


Gerry Quinn(Posted 2014) [#17]
Indeed, you should probably do it every frame. On some platforms, doing stuff in OnCreate() may give bad results simply because there is some startup stuff going on around that time which slows every program.

But if the target is Android, see the thread on a 'finger painting app' in the Android forum. There are faster ways to manipulate images on Android compared to what Monkey does.


Gerry Quinn(Posted 2014) [#18]
Indeed, you should probably do it every frame. On some platforms, doing stuff in OnCreate() may give bad results simply because there is some startup stuff going on around that time which slows every program.

But if the target is Android, see the thread on a 'finger painting app' in the Android forum. There are faster ways to manipulate images on Android compared to what Monkey does.


Goodlookinguy(Posted 2014) [#19]
I became all jazzed up to fix this problem. Testing a 1024x1024 image it took an astonishing 1176 ms from my test. Ouch. I looked at the actual source code for the Android SDK. Here's the function for setPixels.

public void setPixels(int[] pixels, int offset, int stride,
		int x, int y, int width, int height) {
	checkRecycled("Can't call setPixels() on a recycled bitmap");
	if (!isMutable()) {
		throw new IllegalStateException();
	}
	if (width == 0 || height == 0) {
		return; // nothing to do
	}
	checkPixelsAccess(x, y, width, height, offset, stride, pixels);
	nativeSetPixels(mNativeBitmap, pixels, offset, stride,
					x, y, width, height, mIsPremultiplied);
}


It uses whatever the device's native function call is for setting pixels. From what I was reading, the only real way to speed it up is the use the NDK. How lame, I was hoping for something more easily fixable.

Edit: Well, I'm going to sleep. Meant to go to sleep hours ago but got distracted by this.


Boulderdash(Posted 2014) [#20]
On my new Droid. it takes around half a second per frame @320 x 240 - wow


Danilo(Posted 2014) [#21]
Just commented out the line "surface.Invalidate();" in WritePixels2() and tested again.
1024x1024 image:
mode 1 (full image): 80 ms = 12.5 FPS (was 400ms = 2.5 FPS)
mode 2 (3x3 rect): 18 ms = 55.5 FPS (was 320ms = 3 FPS)

Android's setPixels() may not be the fastest. But even more time is spend in Monkey X functions, because
surface.Invalidate() adds the texture to discarded textures. This leads to glDeleteTextures() in function FlushDiscarded().
The texture gets deleted and later re-created with a call to Bind(), and Bind() contains all that slow buffer re-arranging stuff
I was talking about.

OK so far.
I re-enabled "surface.Invalidate();" in WritePixels2(), and now I changed line 118
in function gxtkGraphics.Flush() from "surf.Bind();" to "surf.Bind2();" to test the
the new ***experimental*** Bind2().

With 1024x1024 image and the 3x3 rect mode I get now 58 FPS.
Yes, changing the magic line 118 in mojo.android.java from Bind() to Bind2() made the FPS jump from 3 to 58 FPS! :D


Goodlookinguy(Posted 2014) [#22]
Yes, changing the magic line 118 in mojo.android.java from Bind() to Bind2() made the FPS jump from 3 to 58 FPS!


***** v79c *****

Removed 'Flush' calls from mojo native WritePixels2 functions - shouldn't be necessary and causes issues outside OnRender.


Well, the problem was resolved officially in another manner. I never even tried to disable the call to flush.


Danilo(Posted 2014) [#23]
Well, the problem was resolved officially in another manner. I never even tried to disable the call to flush.

That's a different thing. I tried latest experimental v79c this morning and it did not speed-up the Android WritePixels() issue.

v79c contains a bug fix for http://www.monkey-x.com/Community/posts.php?topic=8512

Bind() and ***experimental*** Bind2() are another thing. After changing the Android bitmap pixels, the OpenGL texture
must get re-created/updated. Without surface.Invalidate() the OpenGL texture gets not updated.

The experimental Bind2() function shows they are aware of the issue and they are working on it.


Goodlookinguy(Posted 2014) [#24]
Flush calls Bind, which is what I assumed to be causing the slowdown. I misunderstood that it's for something else.