• 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

[Solved ] Mojo1 works, Mojo2 has bug?

Wingnut

Well-known member
3rd Party Module Dev
Tutorial Author
Joined
Jan 2, 2020
Messages
1,414
I'm doing a water level and really need pixels for doing a few things.

I got everything working with Mojo1 but now I'm using Mojo2 so I built an example for you to show what I mean and I made it on purpose to be as close to Mojo1 as possible.

So when we use Mojo1 to draw to a image, we'll do this :
And it works perfectly in every way.

Code:
Import mojo

Class MyApp Extends App

    Field myImage:Image  
   
    Method OnCreate()
    End
   
    Method OnRender()
   
        If Not myImage
            ' ------------ CREATE MYIMAGE ---------------
            Local w=256,h=256
            myImage=CreateImage(w,h,1,Image.MidHandle)
            ' ------ DRAW INSIDE MYIMAGE, NORMALLY -------
            Cls 128,0,255
            SetColor 255,128,0
            DrawRect 16,16,w-32,h-32
            SetColor 255,255,255
            DrawText "Hello World",w/2,h/2,.5,.5
            ' - DRAW INSIDE MYIMAGE, INDIVIDUALLY PIXELS -
            '   The raw format is : rrggbb & aa, where 0=transparent, 255 = opaque)
            Local buf:=New Int[w*2*h]
            ReadPixels buf,0,0,w,h ,w,w*2 ' First read existing pixels in case we want to use it in next step
            For Local i=0 Until h Step 4
                For Local j=0 Until w*2
                    buf[i*w*2+j]=buf[i*w*2+j] + $ff00ff | $ff Shl 24 ' < Change ($rrggbb & $aa) here to change what to draw. We draw only on every forth row so we can see some of the original contents
                Next
            Next
            myImage.WritePixels buf,0,0,w,h ,w,w*2
        End
            ' -----------------------------------
        Cls
        SetColor 255,255,255
        DrawText "Testing...",0,0
        DrawImage myImage,MouseX/2.0,MouseY/2.0
    End
End

Function Main()
    New MyApp
End


Let's try to do the same thing with Mojo2.

It will stay opaque or have strange alpha levels. Try for instance to change the alpha so the white rectangle becomes more or less transparent by setting alpha.

Cerberus:
Strict

Import mojo2
Import brl.databuffer
Import cerberus.maths

Class MyApp Extends App

    Field myCanvas:Canvas
    Field myImage:Image
    Field myData:DataBuffer
    Field imageCanvas:Canvas
    Field patternSeed:Float = 1
   
    Method OnCreate:Int()
    ' ------------ CREATE MYIMAGE ---------------
        myCanvas = New Canvas()
        myImage = New Image(256, 256)
        imageCanvas = New Canvas(myImage)
        myData = New DataBuffer(16 * 16 * 4)
        Return 0
    End
   
    Method OnRender:Int()
        ' ------ DRAW INSIDE MYIMAGE, NORMALLY -------
        patternSeed += 0.1
        For Local x:Int = 0 Until 128
            For Local y:Int = 0 Until 128
                If (x & y & Int(patternSeed))
                    imageCanvas.SetColor(Sin(Millisecs() * 0.1 ) * 0.5 + 0.5, Cos(Millisecs() * 0.1) * 0.5 + 0.5, 0.5)
                Else
                    imageCanvas.SetColor(0, 0, 0)
                Endif
                imageCanvas.DrawRect(x * 2, y * 2, 2, 2)
            Next
        Next
                ' - DRAW INSIDE MYIMAGE, INDIVIDUALLY PIXELS -
                '   Paint the upper left 16 x 16 so we can see some of the original contents
                For Local i:Int = 0 Until 16 * 16 * 4 Step 4
               
               
                    Local r:Int = 255 ' Change red here (we use 0-255 in raw format, not 0.0-1.0)
                    Local g:Int = 255 ' Change green here
                    Local b:Int = 255 ' Change blue here
                    Local a:Int = 255 ' Change alpha here (0 = transparent, 255 = opaque, this does not work yet in Mojo2)
                    Local rgba:Int = a  Shl 24 | b Shl 16 | g Shl 8 | r
                   
                   
                    myData.PokeInt(i, rgba)
                Next

        imageCanvas.Flush()      
        myImage.WritePixels(0, 0, 16, 16, myData)
        ' -----------------------------------      
        ' now render to main canvas
        myCanvas.Clear()  
        myCanvas.DrawImage(myImage, MouseX(), MouseY())
        myCanvas.Flush()
        Return 0
    End
End

Function Main:Int()
    New MyApp()
    Return 0
End
 
Another Mojo2 code that's closer in that it reads pixels to like the Mojo1
It also shows that If you read and write without touching the values, the pixels are good.

I'm still very confused.

Cerberus:
Strict

Import mojo2
Import brl.databuffer
Import cerberus.maths

Class MyApp Extends App

    Field myCanvas:Canvas
    Field myImage:Image
    Field myData:DataBuffer
    Field imageCanvas:Canvas
    Field patternSeed:Float = 1
    
    Method OnCreate:Int()
    ' ------------ CREATE MYIMAGE ---------------
        myCanvas = New Canvas()
        myImage = New Image(256, 256)
        imageCanvas = New Canvas(myImage)
        myData = New DataBuffer(128 * 128 * 4)
        Return 0
    End
    
    Method OnRender:Int()
        ' ------ DRAW INSIDE MYIMAGE, NORMALLY -------
        patternSeed += 0.1
        imageCanvas.Clear(0,0,0,0)
        imageCanvas.SetColor 255,255,255
        
            ' - DRAW INSIDE MYIMAGE, INDIVIDUALLY PIXEL
            
        For Local x:Int = 0 Until 128
            For Local y:Int = 0 Until 128
                If (x & y & Int(patternSeed))
                    imageCanvas.SetColor(Sin(Millisecs() * 0.1 ) * 0.5 + 0.5, Cos(Millisecs() * 0.1) * 0.5 + 0.5, 0.5)
                    imageCanvas.DrawRect(x * 2, y * 2, 2, 2)
                Endif
            Next
        Next
        
        imageCanvas.SetColor 1,1,1
        imageCanvas.DrawText "Hello World",256/2+32,256/2,.5,.5
            
        imageCanvas.ReadPixels(0, 0, 128, 128, myData) ' offset, pitch
        
        ' - DRAW INSIDE MYIMAGE, INDIVIDUALLY PIXELS -
        '   Paint the upper left 64 x 64 so we can see some of the original contents
        For Local i:Int = 0 Until 64 * 256 * 4 Step 4

            Local r:Int = 255 ' Change red here (we use 0-255 in raw format, not 0.0-1.0)
            Local g:Int = 255 ' Change green here
            Local b:Int = 255 ' Change blue here
            Local a:Int = 0   ' Change alpha here
            Local rgba:Int = a  Shl 24 | b Shl 16 | g Shl 8 | r
            myData.PokeInt(i, rgba)
        Next

        imageCanvas.Flush()       
        myImage.WritePixels(0, 0, 128, 128, myData)
        ' -----------------------------------       
        ' now render to main canvas
        myCanvas.Clear()   

        myCanvas.SetColor(1,1,1)
        myCanvas.DrawText "Testing...",0,0
        myCanvas.DrawImage(myImage, MouseX(), MouseY())
        myCanvas.Flush()
        Return 0
    End
End

Function Main:Int()
    New MyApp()
    Return 0
End
 
I get some kind of saturated brightness. Tthe white underneath the gray (128,128,128,128) box does not show through but looks as it's on top, while the secondary gray box underneath (a stamped copy of itself) looks as they should when the boxeds overlap.



2.png
1.png
 
I followed the suggestion and it seems to work but to me It does not make sense to me exactly why?
Why should Opaque2 be set? Is this a general solution that you should use Opaque for images?

Because I'm drawing in images that I want to draw using normal Alpha mode and also, using subtractive mode.

Cerberus:
Strict

Import mojo2
Import brl.databuffer
Import cerberus.maths

Class MyApp Extends App

    Field myCanvas:Canvas
    Field myImage:Image
    Field myData:DataBuffer
    Field imageCanvas:Canvas
    Field patternSeed:Float = 1
    
    Method OnCreate:Int()
    ' ------------ CREATE MYIMAGE ---------------
        myCanvas = New Canvas()
        myImage = New Image(256, 256)
        imageCanvas = New Canvas(myImage)
        myData = New DataBuffer(256 * 256 * 4)
        Return 0
    End
    
    Method OnRender:Int()
        myCanvas.SetBlendMode(BlendMode.Opaque2)
        ' ------ DRAW INSIDE MYIMAGE, NORMALLY -------
        patternSeed += 0.1
        imageCanvas.Clear(0,0,0,0)
        imageCanvas.SetColor 255,255,255
        
            ' - DRAW INSIDE MYIMAGE, INDIVIDUALLY PIXEL
            
        For Local x:Int = 0 Until 128
            For Local y:Int = 0 Until 128
                If (x & y & Int(patternSeed))
                    imageCanvas.SetColor(Sin(Millisecs() * 0.1 ) * 0.5 + 0.5, Cos(Millisecs() * 0.1) * 0.5 + 0.5, 0.5)
                    imageCanvas.DrawRect(x * 2, y * 2, 2, 2)
                Endif
            Next
        Next
        
        imageCanvas.SetColor 1,1,1
        imageCanvas.DrawText "Hello World",256/2+32,256/2,.5,.5
        'imageCanvas.SetAlpha (0.5)
        'imageCanvas.SetColor 1,1,1
        'imageCanvas.DrawRect(0,0,80,80)
        imageCanvas.SetAlpha (1.0)
        imageCanvas.SetColor 1,1,1
        
        imageCanvas.ReadPixels(0, 0, 128, 128, myData)
    
        ' - DRAW INSIDE MYIMAGE, INDIVIDUALLY PIXELS -
        '   Paint the upper left 64 x 64 so we can see some of the original contents
        For Local j:Int = 0 To 63   
            For Local i:Int = 0 To 63
                Local r:Int = 128 ' Change red here
                Local g:Int = 128 ' Change green here
                Local b:Int = 128 ' Change blue here
                Local a:Int = 128 ' Change alpha here
                Local rgba:Int = a  Shl 24 | b Shl 16 | g Shl 8 | r
                myData.PokeInt((i*128+j)*4, rgba)
            Next
        Next
        
        imageCanvas.Flush()       
        myImage.WritePixels(0, 0, 128, 128, myData)
        ' -----------------------------------       
        ' now render to main canvas
        myCanvas.Clear()   
        myCanvas.SetColor(1,1,1)
        myCanvas.DrawImage(myImage, 200,200)
        myCanvas.DrawText "Testing...",0,0
        myCanvas.DrawImage(myImage, MouseX(), MouseY())
        myCanvas.Flush()
        Return 0
    End
End

Function Main:Int()
    New MyApp()
    Return 0
End
 
As you read in the link I posted, mojo2 uses pre-multiplied ABGR values. Why? Because Mark Sibly designed it that way. I guess this way mojo2 doesn't need to do this in the shader and so is faster. But that is just my guess.
If you don't want to use Opague2, then you need to set the color pre-multiplied.
 
How come that Mojo1 works so fine using a regular alpha mode instead of opaque2 blendmode?

Opaque2 sounds arbitrary For being such an important thing. It makes you curious about the other modes.
Does alpha2 And opaque have special roles in Cerberus?
 
I hand't seen any ABGR yet in Cerberus OpenGl source code. I guess you are right about the shader part, as that's the big thing that differs Mojo1 and Mojo2
 
How come that Mojo1 works so fine using a regular alpha mode instead of opaque2 blendmode?
mojo2 works fine too using a regular alpha mode - when using premultiplied ABGR ;) Because the transformation is done automatically when loading an image, users only stumble upon it when manually writing image data.

Opaque2 sounds arbitrary For being such an important thing. It makes you curious about the other modes.
Does alpha2 And opaque have special roles in Cerberus?
Opaque2 is not "such an important thing" when using mojo2 the way it was intended - with premultiplied component values. It was designed that way by the initial author. That's the way it is. Alpha2 has a special role when it comes to writing image data directly to image buffers. Opaque does not have a "special" role, it's probably the least special blend mode at all. Again, this applies when using mojo2 with premultiplied color components.
And yes, the "2" in Opaque2 might sound arbitrary - but there was even a thread here about its naming when it was introduced. We went for Opaque2 because lazily numbering identifiers is the way to go in mojo :p

I hand't seen any ABGR yet in Cerberus OpenGl source code. I guess you are right about the shader part, as that's the big thing that differs Mojo1 and Mojo2
It's hidden somewhere Texture.Load() I guess. I don't know what the idea behind using premultiplied ABGR was. Well, there are two parts to that - first ABGR vs ARGB and second premultiplied. Maybe some targets were bound to ABGR, that's why M.S. went for ABGR everywhere. Using premultiplied alpha seems to be a quality thing: https://en.wikipedia.org/wiki/Alpha_compositing#Straight_versus_premultiplied

Lastly: For backwards compatibility, we're stuck now with premultiplied ABGR. That's why we introduced these helper functions for people who like to manipulate image data (who doesn't?): https://www.cerberus-x.com/cxDocs/Modules_mojo2.graphics_DrawList.html#PremultiplyArgb2Abgr(Int)
 
I didn't understand much yet how it all works together but will try to look into this, but thank you for your answer.
 
Back
Top Bottom