• Dear Cerberus X User!

    As we prepare to transition the forum ownership from Mike to Phil (TripleHead GmbH), we need your explicit consent to transfer your user data in accordance with our amended Terms and Rules in order to be compliant with data protection laws.

    Important: If you accept the amended Terms and Rules, you agree to the transfer of your user data to the future forum owner!

    Please read the new Terms and Rules below, check the box to agree, and click "Accept" to continue enjoying your Cerberus X Forum experience. The deadline for consent is April 5, 2024.

    Do not accept the amended Terms and Rules if you do not wish your personal data to be transferred to the future forum owner!

    Accepting ensures:

    - Continued access to your account with a short break for the actual transfer.

    - Retention of your data under the same terms.

    Without consent:

    - You don't have further access to your forum user account.

    - Your account and personal data will be deleted after April 5, 2024.

    - Public posts remain, but usernames indicating real identity will be anonymized. If you disagree with a fictitious name you have the option to contact us so we can find a name that is acceptable to you.

    We hope to keep you in our community and see you on the forum soon!

    All the best

    Your Cerberus X Team

Masking sprites

Wingnut

Well-known member
3rd Party Module Dev
Tutorial Author
Joined
Jan 2, 2020
Messages
1,414
What would the simplest mojo2 example be if you want to load an image and then replace all green pixels with purple pixels?
Speed is no issue, just the simplest way that makes sense?

I'm trying to use only DrawPoint for writing pixels right now and I try to avoid WritePixels. Of course I have to use Readpixels though.

Lemmings.png
 
Last edited:
I'm not quiet there yet but..

Is this a sensible sway and how to draw it into the maskimage instead of onto the canvas?

How do you make this into the original or into a new image?
How do you use Discard to throw the original image/canvas away?

Screenshot 2020-10-25 at 06.06.45.png

Cerberus:
' 1) Loads sprite
' 2) prepares an maskimage
' 3) draws sprite remapped el onto canvas
' How to draw
' pixel per pixel onto canvas

' 1) Loads sprite
' 2) prepares an maskimage
' 3) draws sprite remapped el onto canvas
' How to draw
' pixel per pixel onto canvas

Import mojo2
Import brl.databuffer

Function Main:Int()
    New Game
    Return 0
End

Class Game Extends App

     Field myImage:Image
     Field myCanvas:Canvas
     Field myImage2:Image
     Field myCanvas2:Canvas
     Field pixels:DataBuffer = New DataBuffer(512 * 512 * 4) 
    Field srcimage:Image
    Field srccanvas:Canvas
    Field screen2:Image
    Field screen2canvas:Canvas
    Field canvas:Canvas    
    Field spritemask:Image
    Field spritemaskcanvas:Canvas

    Method OnCreate:Int()
          myImage2 = New Image(320,200,0,0)
          myCanvas2=New Canvas(myImage2)
          myCanvas2.Clear
        srcimage=Image.Load("sprite.png",0,0,0) ' Load sprite
        canvas=New Canvas
        screen2=New Image(320,200,0,0,0)
        screen2canvas=New Canvas(screen2)
        screen2canvas.Clear
        screen2canvas.Flush    
        ' Preapre a sprite mask image
        spritemask = New Image(320,200,0,0)
          spritemaskcanvas = New Canvas(spritemask)
          spritemaskcanvas.Clear
        SetSwapInterval 1 ; SetUpdateRate 0
        
        Return 0
    End
    
    Method OnRender:Int()
    
        canvas.Clear
        screen2canvas.SetColor 1,1,1,1 
        screen2canvas.DrawImage srcimage,0,20 ' put sprite
         screen2canvas.Flush
        canvas.SetColor 1,1,1,1
        canvas.DrawImage screen2,320,200,0,1,1
         myCanvas2.SetBlendMode(BlendMode.Opaque2)
          canvas.DrawImage(myImage2, 0, 0)
         
          ' Draw zoomed masked sprite on canvas by readoing 1 pixell at ta time instead of the whole sprite
         For Local y:Int = 50 To 100
                For Local x:Int = 50 To 100
                 myCanvas2.ReadPixels(x,y,1,1, pixels)
                 Local pixel:Int[] = ColorToRgb(pixels.PeekInt(0))
                  Local r: Int = pixel[3] ; Local g: Int = pixel[2] ; Local b: Int = pixel[1] ; Local a: Int = pixel[0]    
                  ' Now got pixel rgb
                  If r = 0 And g = 255 And b = 0 Then r = 255  ; g = 0 ; b = 255 ' Green into purple
                  canvas.SetColor r/255.0,g/255.0,b/255.0,a/255.0
                  canvas.DrawRect x*4,y*4,4,4
            Next
        Next
     
         canvas.Flush
       Return 0
    End

   Function ColorToRgb:Int[](value:Int)
        Local resp:Int[] ;  resp = resp.Resize(4)
        Local v:Int = value ;  resp[3] = v & 255
        v = v Shr 8 ; resp[2] = v & 255
        v = v Shr 8 ;  resp[1] = v & 255
        v = v Shr 8 ;  resp[0] = v & 255
            Return resp
     End  
End
 
Last edited:
How do you use Discard to throw the original image/canvas away?
  1. Use Image.Discard().
  2. Any object that you want to get collected by the garbadge collector has to be nulled out and make sure that no variable stores it somewhere else.
In any case, on Android the GC collects when it feels like to. You don't have any control over it from CX.

In general and if all your post are related to create something similar to the YT video of the game Noita you posted....

I would't not store the dynamic content of your art inside images, reread it and then change it. I would simply work with databuffers and copy these into the images that are rendering, when the data is changed or at resume. That is what I take from the talks from these developers.
 
Am I doing it wrong? I know about shaders but I really don't want to use a shaders biw becuase it seems overly difficult for me (I'm already learning quiet abit right now so It better wait) Of course the speed would not hurt but right now I just need
to learn how to do this normally. It's hard even manually

I will try come up with some other solution or improve this today but I'm really lost.
 
It seems you are trying to do something that is on the edge of what is possible performance wise. Things that are done per pixel/fragment without the need of taking other pixels into account is something shaders are best for.
If you don't mind performance and want to do it the "normal way" you could just store all your pixels in an array and draw them with DrawPoint to the screen or to an image. I guess some thousand pixels could be handled this way with a bit of optimization in terms of which pixels have to be alive at any moment.
 
No i do it on the web using Cerberus-X 60 fps, I have to take some time and ponder about Android. Maybe I'll learn something while doing it :)
 
I never write pixels, you can use Blendmode opaque2 and DrawRect to do that and write absolute values to rgba!

Of course I need to read, but I read very small quantities. On web you can read about 320x200 at least @ 60 fps. Maybe more but I want headroom and do not need more.
 
One more thing is that if you want to deal with pixels on the CPU side, never EVER use databuffers.
instead use arrays, which are 30x faster (this actually limits enough to the point that you simply can't just blame the band limited bus and memory speed to get informaition over to the GPU and back.

Databuffers are slooooow compared to arrayys.

The good thing is, if you paint using drawrect, you have a choice.
 
I can show some test code I've been coding the last nights to get me to this point.

I've done heavy testing. Cerberus-X got it going! It just have a few minor bugs that need to get sorted between Cerberus-x and me, and that would allow even more games possible. This looks crazy but it test combination of very useful techniques and how they affect eachother in performance and stability. And It has both.

Not on Android. But anything else. Try HTML5.

DISCLAIMER! Does not work with Android. At all.

All other platforms will give you 60 fps pleasure of nonsense. Highly useful, practical nonsense.

Screenshot 2020-10-26 at 23.19.53.png


Cerberus:
Import mojo2
Import brl.databuffer

#GLFW_WINDOW_WIDTH = 800
#GLFW_WINDOW_HEIGHT = 600

Function Main()
    New MyApp
    Return 0
End

Class MyApp Extends App
    
    Field layers:Image[16]
    Field ccanvas:Canvas
    Field canvas:Canvas
    Field layername:DrawList[16]
    Field x:Int = 0
    Field pixels:DataBuffer  = New DataBuffer(4 * 320 * 200)
    Method OnCreate()
        canvas = New Canvas()
        For Local i:Int = 0 To 15
            layers[i] = New Image(800,600,.0,.0)
        Next
        ccanvas = New Canvas(layers[0])

    ' Clear all 16 layers & create 16 Drawlists
    For Local i:Int = 0 To 15
                  layername[i] = New DrawList
        ccanvas.SetRenderTarget(layers[i])
        ccanvas.Clear(0,0,0,0)
        Next
              Return 0
    End

    Method OnUpdate()
        If KeyHit( KEY_ESCAPE ) EndApp
    End

    Method OnRender()
      
         ccanvas.SetRenderTarget(layers[1])
         ccanvas.SetAlpha(0.1) ' 10% OPAQUE
         ccanvas.DrawCircle(30,30,200) ' LAYER  1 VISIBLE

         ccanvas.SetRenderTarget(layers[3])
         ccanvas.SetAlpha(0.5) ' 50% OPAQUE
'       ccanvas.DrawCircle(80,80,100) ' LAYER 3 IS  CIRCLE MOVING TO THE RIGHT

         ccanvas.SetRenderTarget(layers[10])
         ccanvas.SetAlpha(1.0) ' 100% OPAQUE
  '     ccanvas.SetColor 255,255,0
  '     ccanvas.DrawCircle(200,100,100) ' VISIBLE LAYER

         x = x + 2
         x = x Mod 800

         ccanvas.SetRenderTarget(layers[0])
         ccanvas.SetColor 1,1,1
         ccanvas.SetAlpha(0.1)
         ccanvas.DrawImage(layers[3],x,0)

     ccanvas.SetAlpha(1.0) ' 100% OPAQUE
           ccanvas.SetBlendMode(BlendMode.Opaque2)

' -------------------------------------------------------
' LAYER 15 = RANDOM RECTANGLES
ccanvas.SetRenderTarget(layers[15])
                    
                        For Local temp:=1 To 16
            If Int(Rnd(1)>0.5) Then ccanvas.SetColor 0,0,0 Else ccanvas.SetColor 1,1,1
            Local xx:= Int(Rnd(7)) ; Local yy:= Int(Rnd(7))
            ccanvas.DrawRect xx*4,yy*4,2*4,2*4
            Next

' LAYER 10 = CIRCLE
ccanvas.SetRenderTarget(layers[0])
ccanvas.SetColor(1,0,0)
ccanvas.SetAlpha(1.0)               
ccanvas.SetColor(1,1,1)

' -------------------------------------------------------------------------------------------------------------------

     ccanvas.SetAlpha(1.0)
         ccanvas.DrawImage(layers[1],x/2,150) ' DRAW LAYER 1

       ' READ PIXELS
    ccanvas.ReadPixels(0,0,320,200, pixels)
    Local pos:Int = 4*320
        For Local y:Int = 0 Until (100-1)
                For Local x:Int = 0 Until 320
                If pixels.PeekInt(pos) <> $ffffffff Then pixels.PokeInt(pos,$800000ff);
                pos = pos + 4
                Next
        Next

    ' CHANGE PIXELS
       pixels.PokeInt(150*320*4+Rnd(10,50)*4,$000000ff)
    pixels.PokeInt(10*320*4+10*4,pixels.PeekInt(10*320*4+10*4) | $ff000000)

     ' WRITEPIXELS
        layers[0].WritePixels(0, 0, 320, 200, pixels)

      ccanvas.Flush()
          canvas.Clear(0,0,0,0)
          canvas.SetBlendMode(BlendMode.Opaque2)

       canvas.DrawImage(layers[10],0,0)
          canvas.DrawImage(layers[0],0,0)

          canvas.SetAlpha(1.0)
          canvas.SetBlendMode(BlendMode.Alpha)
          canvas.DrawEllipse ( 150,150,50,20)' add material for non-default material (needed also for shaders)

          canvas.SetAlpha(1.0)
          canvas.SetBlendMode(BlendMode.Alpha)
          canvas.SetColor(1,0,1)
          canvas.DrawImage(layers[1],100,100)
  
          canvas.SetAlpha(0.4)
          canvas.DrawImage(layers[0],100,200+Sin( Millisecs*.1 )*200+20)
          canvas.SetAlpha(1.0)
          canvas.SetColor(1,1,1)

' DRAWLISTS
' --------------
' Prepare a few drawlists (batches of instructions) These are commands that hasn't become pixels yet (they are primitives, vectors, parametric graphics)
layername[0].Reset() ' Same as Clear() for pixels and all-already-drawn-graphics of a canvas (by which I mean when written pixels written by pixelwrite and these batches of commands has become pixels)
layername[0].SetColor(1,0,0) ' Red
layername[0].SetColor(1,1,1) ' White
layername[0].DrawRect(x,100,200,120)
layername[0].SetColor(0,0,0) ' Black
layername[0].SetColor(0,0,1) ' Blue
layername[0].SetColor(0,1,0) ' Green
layername[0].DrawText("This is an imagelist" ,x,50)
layername[0].DrawText("This is an imagelist" ,x+1,50)

' BATCHING GRAPHICS USING DRAWLISTS
' --------------------------------------
' Create another drawlist (batch)
layername[1].Reset()
layername[1].SetColor(0,0,1)
layername[1].DrawRect((x+x/3)*(400.0/64.0),50,400,90)

ccanvas.RenderDrawList(layername[0]) '  Draw the batch to ccanvas

canvas.PushMatrix()
canvas.Scale(64.0/400.0,64.0/90.0)
ccanvas.RenderDrawList(layername[1])
canvas.Scale(1.0,1.0)
canvas.PopMatrix()

    ccanvas.ReadPixels(0,0,320,200, pixels)

Local pixel:Int[]  = ColorToRgb(pixels.PeekInt(Int(Int(MouseX())*4+Int(MouseY())*320*4)))
        Local r: Int = pixel[3] ; Local g: Int = pixel[2] ; Local b: Int = pixel[1] ; Local a: Int = pixel[0]
                  
          canvas.SetColor 1,0,0
           canvas.DrawText( String(r).Split("~n")),DeviceWidth()/2,DeviceHeight()/2,0.5,0.5
           canvas.DrawText( String(g).Split("~n")),DeviceWidth()/2,DeviceHeight()/2+20,0.5,0.5
            canvas.DrawText( String(b).Split("~n")),DeviceWidth()/2,DeviceHeight()/2+40,0.5,0.5
           canvas.DrawText( String(a).Split("~n")),DeviceWidth()/2,DeviceHeight()/2+60,0.5,0.5
          canvas.SetColor 1,1,1 ' one then hex (3 or 4 bytes 0-255 if 3 or 4 values then 0-1

          canvas.DrawImage(layers[10],0,0)
          canvas.DrawImage(layers[0],0,0)
      canvas.Flush()
End

End Class

Function ColorToRgb:Int[](value:Int)
    Local resp:Int[] ;  resp = resp.Resize(4)
    Local v:Int = value ;  resp[3] = v & 255
    v = v Shr 8 ; resp[2] = v & 255
    v = v Shr 8 ;  resp[1] = v & 255
    v = v Shr 8 ;  resp[0] = v & 255
    Return resp
End
 
Last edited:
I would agree on that this should be cut into pieces and presented with some good exaplaining texts..

Will get there eventually ;)
 
Back
Top Bottom