• 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

Fixed Canvas.Readpixels doesn't read the given canvas

Wingnut

Well-known member
3rd Party Module Dev
Tutorial Author
Joined
Jan 2, 2020
Messages
1,414
I was trying to debug something so I had to move from Android studio to take individual screenshots of each canvases.
Not sure if that was a good idea to try because it brought new bugs to my attention I think? I'm not sure but the behaviour perplexes me.

I tried to build a simple test-case this whole morning so others can see it.
let's see if I can explain it, there's some notes in the code too.

I have a couple of canvases that I tried to screenshot separately. That's for sure.

The save could not save more than one of the canvses. I can't really figure out how it picks out WHICH ONE it can save, but I would call it the "main". But what is the main canvas? So I thought, no, it's the last one that is flushed that is possible to save. So I flush the one i want to sav before call the screenshot. Nope, not quiet.

But now the screen goes black as soon as I flush something else. Wonderful.
So I tried to change the order of the flushes for a few hours and trying loading and creating graphics and see if it makes any difference.

I can tell that if you load graphics there's a chance it save it upside down but not always and I can't find the pattern when it does it.
But i noted what I found in the actual code, and I'm kind of lost. Instead of getting screenshots of the individual parts of my game I now
feel that I understand absolutely nothing how Cerberus works.

The only conclusion other than that is that, maybe there's deep bugs in .. graphics? I don't think it's the save? The code is minimal and it saves whatever Cerberus gives it? Because it works sometimes?! (If I only have one canvas in my whole game it sure works but that's the only time)

Everything is a mess right now. e.g. the Flushes of other canvases makes the display go black?! The saves ahve correct canvas sizes but the content is never correct if it is correct it is always just one, the rest are black or garbage. I never experienced this but I can't get it to work?!


EDIT
The wanted effect is to save a hand, a -redbox-with-a-dot, and a screenshot of both of those in the upper corner (without any upsidedown or garbage effects) and for the display not to go black because of flushing canvases, so I can feel safe about using multiple canvases. I'm trying to understand what I've done wrong..


Cerberus:
Strict
Import mojo2
Import brl.requesters
Import brl.filepath
Import saveImage

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

Class myApp Extends App
    Field powerupRom : Image
    Field powerupImage : Image
    Field powerupCanvas : Canvas
    Field bgImg : Image
    Field bgCanvas : Canvas
    Field myCanvas : Canvas
    Field individualscreenshot : Bool = False

    Method OnCreate:Int()
        SetSwapInterval 1 ; SetUpdateRate 0
        myCanvas = New Canvas()
    
        powerupRom = Image.Load("powerup.png",0,0,0)
        '   
        powerupImage = New Image(powerupRom.Width(),powerupRom.Height(),0,0,Image.Managed)
        powerupCanvas = New Canvas(powerupImage)
        powerupCanvas.Clear
        powerupCanvas.DrawImage powerupRom,0,0
        powerupCanvas.Flush
    
        bgImg = New Image(64,64,0,0,Image.Managed)
        bgCanvas = New Canvas(bgImg)
        bgCanvas.Clear 1,0,0
        bgCanvas.SetColor 0,0,1
        bgCanvas.DrawOval 16,16,16,16
        bgCanvas.Flush
        Return 0
    End

    Method OnUpdate:Int()
        If MouseDown(0) Then individualscreenshot = True
        Return 0
    End

    Method OnRender:Int()
        myCanvas.Clear
        myCanvas.DrawImage powerupImage,0,0    ' draw powerup
        myCanvas.DrawImage bgImg,64,0        ' draw red square with blue dot
    
        ' ----------------------------------------------------
        ' powerupCanvas.Flush ' uncommenting this line will make the screen go black
        ' if its enabled alone or last the file gets saved correctly, BUT the image will be upside down
    
        ' bgCanvas.Flush ' uncommenting this line will cause the screen go black
        ' if its enabled alone or last the file gets saved correctly
    
        myCanvas.Flush ' as long as this line is activated and alone you will see a working screen
        ' and it will be saved correctly if its enabled alone (if others are activated it does not even matter if its last it still doesn't work)
        ' if it is activate alone then all other files will be saved as solid black
        
        ' Instructions : (uncomment one or two or three of the above lines before running)
        ' Click left mouse button to screeshoot all three canvases to individual files
        ' The saved files will always have the correct canvassizes but they will be black, empty, or filled with trash. Except when you follow the above comments.
        ' Some are upsidedown sometimes, as described above.
        ' ----------------------------------------------------
        
        If individualscreenshot = True
            individualscreenshot  = False
            Local file := RequestFile("Save file....", "Image Files:png,jpg,bmp;All Files:*", True)
            Local ext:=filepath.ExtractExt(file).ToLower()
            SavePNG(file, powerupCanvas)            ' Canvas 1 (powerup) this does not work
            SavePNG(file+"landscape.png", myCanvas)    ' Canvas 2 this one works
            SavePNG(file+"clouds.png", bgCanvas)    ' Canvas 3 (clouds) this does not work
        Endif
    
        Return 0
    End
End


powerup.png
 
Last edited:
If you don't have savepng module you need it to test code above.

saveImage.cxs
Cerberus:
' install module instructions
' -----------------------------
' create a folder named saveImage inside modules_ext of the Cerberus folder
' put the cxs inside saveImage and create another folder named native inside that one, and put the .cpp there
' done

Import brl.databuffer
Import mojo2.graphics
Import os

Import "native\saveImage.cpp"

Extern
Function _savepng:Void(file:String, w:Int, h:Int, db:DataBuffer)
Function _savebmp:Void(file:String, w:Int, h:Int, db:DataBuffer)
Function _savejpg:Void(file:String, w:Int, h:Int, db:DataBuffer, q:Int)

Public

Function SavePNG:Void(file:String,cnvs:Canvas)
    'Print("width="+cnvs.Width()+"   height="+cnvs.Height())
    Local db:DataBuffer = New DataBuffer(cnvs.Width()*cnvs.Height()*4)
    cnvs.ReadPixels(0,0,cnvs.Width(),cnvs.Height(),db)
    'Print("length db="+db.Length())

    _savepng(file,cnvs.Width(),cnvs.Height(),db)
End

Function SaveBMP:Void(file:String,cnvs:Canvas)
    'Print("width="+cnvs.Width()+"   height="+cnvs.Height())
    Local db:DataBuffer = New DataBuffer(cnvs.Width()*cnvs.Height()*4)
    cnvs.ReadPixels(0,0,cnvs.Width(),cnvs.Height(),db)

    _savebmp(file,cnvs.Width(),cnvs.Height(),db)
End

Function SaveJPG:Void(file:String,cnvs:Canvas,quality:Int)
    'Print("width="+cnvs.Width()+"   height="+cnvs.Height())
    Local db:DataBuffer = New DataBuffer(cnvs.Width()*cnvs.Height()*4)
    cnvs.ReadPixels(0,0,cnvs.Width(),cnvs.Height(),db)

    _savejpg(file,cnvs.Width(),cnvs.Height(),db, quality)
End

saveImage.cpp
[CODE lang="cpp" title="saveImage.cpp"]//static String::CString<char> C_STR( const String &t ){ return t.ToCString<char>(); };

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

void _savepng(String filename, int w, int h, BBDataBuffer *db) {
std::vector<unsigned char> colorBuffer(w * h * 4, 255);
int i;
int i2;
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
i = x + y*w;
i2 = x + ((h-1)*w)-y*w;
colorBuffer[i*4] = db->PeekByte(i2*4); //Red
colorBuffer[i*4+1] = db->PeekByte(i2*4+1); //Green
colorBuffer[i*4+2] = db->PeekByte(i2*4+2); //Blue
colorBuffer[i*4+3] = db->PeekByte(i2*4+3); // alpha
}
}

stbi_write_png(C_STR(filename), w, h, 4, &colorBuffer[0], 0);
}

void _savebmp(String filename, int w, int h, BBDataBuffer *db) {
std::vector<unsigned char> colorBuffer(w * h * 4, 255);
int i;
int i2;
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
i = x + y*w;
i2 = x + ((h-1)*w)-y*w;
colorBuffer[i*4] = db->PeekByte(i2*4); //Red
colorBuffer[i*4+1] = db->PeekByte(i2*4+1); //Green
colorBuffer[i*4+2] = db->PeekByte(i2*4+2); //Blue
colorBuffer[i*4+3] = db->PeekByte(i2*4+3); // alpha
}
}

stbi_write_bmp(C_STR(filename), w, h, 4, &colorBuffer[0]);
}

void _savejpg(String filename, int w, int h, BBDataBuffer *db, int q) {
std::vector<unsigned char> colorBuffer(w * h * 4, 255);
int i;
int i2;
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
i = x + y*w;
i2 = x + ((h-1)*w)-y*w;
colorBuffer[i*4] = db->PeekByte(i2*4); //Red
colorBuffer[i*4+1] = db->PeekByte(i2*4+1); //Green
colorBuffer[i*4+2] = db->PeekByte(i2*4+2); //Blue
colorBuffer[i*4+3] = db->PeekByte(i2*4+3); // alpha
}
}

stbi_write_jpg(C_STR(filename), w, h, 4, &colorBuffer[0],q);
}[/CODE]
 
Oh how verbose your topic titles are...
Next time please attach a zip file with the projects content.
Anyway... I smell a bug in ReadPixel (always using the last canvas drawn) or a limitation in OpenGL/Mojo2. My guess is the first one so I am looking into this.
 
Yes If flushing managed textures is supposed to be illegal and have the effect of clearing the screen then that's good I guess because that means that Mojo2 is not broken, and readpixels has the bug.

I am terribly sorry about our use of violating material I was unaware of the copyright. Ya the verbosity is the insomnia speaking, I've been up the whole week trying to solve this myself but I guess I'm off the deep end.
 
The copyright problem was not this topic but your posting of a video.

Anyway, it is a bug. Please replace the Canvas.ReadPixels method with this version.

Cerberus:
    Method ReadPixels:Void( x:Int,y:Int,width:Int,height:Int,data:DataBuffer,dataOffset:Int=0,dataPitch:Int=0 )
   
        FlushPrims
        If _texture
            glPushFramebuffer( _texture.GLFramebuffer() )
        Else
            glPushFramebuffer( defaultFbo )
        Endif
                   
        If Not dataPitch Or dataPitch=width*4
            glReadPixels x,y,width,height,GL_RGBA,GL_UNSIGNED_BYTE,data,dataOffset
        Else
            For Local iy:=0 Until height
                glReadPixels x,y+iy,width,1,GL_RGBA,GL_UNSIGNED_BYTE,data,dataOffset+dataPitch*iy
            Next
        Endif
        glPopFramebuffer()

    End

Regarding the upside down saving, this happens with image canvases. This will fix it....

Add this method into the Canvas class:

Cerberus:
    Method GetTexture:Texture() Property
        Return _texture
    End


Then replace the SavePng Function with this version:

Cerberus:
Function SavePNG:Void(file:String,cnvs:Canvas)
    'Print("width="+cnvs.Width()+"   height="+cnvs.Height())
    Local db:DataBuffer = New DataBuffer(cnvs.Width()*cnvs.Height()*4)
    Local dataPitch=cnvs.Width()*4
    If cnvs.GetTexture()
        cnvs.ReadPixels(0,0,cnvs.Width(),cnvs.Height(),db,cnvs.Width()*cnvs.Height()*4-dataPitch,-dataPitch)
    Else
        cnvs.ReadPixels(0,0,cnvs.Width(),cnvs.Height(),db)
    Endif
    'Print("length db="+db.Length())

    _savepng(file,cnvs.Width(),cnvs.Height(),db)
End

I will add the save functionality to the canvas class in the next release.
 
@MikeHart : Up side down image issues. If I remember this is not an uncommon issue with dealing with OpenGL texture coordinate (0,0 bottom left) and loaded images (0,0 top left).
 
Don't forget that the managed flushes makes the display go black (order of instructions matter but I can't make any sens out of it). So it might be something more deeply involved?
 
I tried on the new version on github and sadly the display going black when flushing managed images are still a thing.
The screen goes only black when you uncomment one or both of the top flushes in onRender?

EDIT The saves are of course perfect now, that part works perfect.

Screenshot 2020-12-31 at 17.43.13.png
 
Last edited:
I think it's becuase in Flush there's a similar opengl readpixels when the managedflag is set, and that need the same treatment that you did inside readpixels.

EDIT On the other hand how could that affect the main display, but I was thinking it affects the canvas id somehow so it has a lot of power.
 
I was too fast, the two managed files are saved upside down (but they are saved even if the display is black by leaving the the two flushes on them in there). Tough bug I feel this is something that Mark left us with.
 
Well, it runs and saves fine here. I used YOUR code you display above.

Will try on my Mac in the next year. If it works there too, then I can't help.
 
I tried on the new version on github and sadly the display going black when flushing managed images are still a thing.
The screen goes only black when you uncomment one or both of the top flushes in onRender?

EDIT The saves are of course perfect now, that part works perfect.

View attachment 1146
Just saw this, new version I guess. Why the hell are you flushing canvases that you don't draw too? That is beyond me. There is no need for this.
 
You need to flush if you were updating the images! (I don't do that in this test)
 
Additional testcode

On macos (Catalina, 10.15.6) it goes black while in HTML5 there's no problem.

I narrowed it down to this code section. read a few references for general black opengl/Shader problems that was introduced in macos that might be related or not.

graphics.cxs

If _texture._flags & Texture.Managed
Validate ' < this line is associated with what makes macos go blank

I put some efforts to look into it. It seems like a managed image flush forces a clearing mechanism only in macOS but it works everywhere if you just move the Flush up a bit :

myCanvas.Clear
bgCanvas.Flush
myCanvas.DrawImage bgImg,64,0
myCanvas.Flush


Cerberus:
#GLFW_WINDOW_FULLSCREEN=True

Strict
Import mojo2
'Import brl.requesters
'Import brl.filepath
'Import saveImage

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

Class myApp Extends App
    Field powerupRom : Image
    Field powerupImage : Image
    Field powerupCanvas : Canvas
    Field bgImg : Image
    Field bgCanvas : Canvas
    Field myCanvas : Canvas
    Field individualscreenshot : Bool = False

    Method OnCreate:Int()
'    SetDeviceWindow( 640, 480, Decorated_window|Resizable_window)
'    Local www=DeviceWidth, hhh=DeviceHeight   
'    canvas.SetViewport 0,0,www,hhh ; canvas.SetScissor 0,0,www,hhh ; canvas.SetProjection2d 0,www,0,hhh
'    screen2canvas.SetViewport 0,0,www,hhh ; screen2canvas.SetScissor 0,0,www,hhh ; screen2canvas.SetProjection2d 0,www,0,hhh

        SetSwapInterval 1 ; SetUpdateRate 0
        myCanvas = New Canvas()

   '     powerupRom = Image.Load("powerup.png",0,0,0)
   '   
   '     powerupImage = New Image(powerupRom.Width(),powerupRom.Height(),0,0,Image.Managed)
   '     powerupCanvas = New Canvas(powerupImage)
   '     powerupCanvas.Clear
    '    powerupCanvas.DrawImage powerupRom,0,0
    '    powerupCanvas.Flush

        bgImg = New Image(64,64,0,0,Image.Managed)
        bgCanvas = New Canvas(bgImg)
        bgCanvas.Clear 1,0,0
        bgCanvas.SetColor 0,0,1
        bgCanvas.DrawOval 16,16,16,16
        bgCanvas.Flush
        Return 0
    End

    Method OnUpdate:Int()
        If MouseDown(1) Then EndApp
        If MouseDown(0) Then individualscreenshot = True
        Return 0
    End

    Method OnRender:Int()
        myCanvas.Clear
       ' myCanvas.DrawImage powerupImage,0,0
        myCanvas.DrawImage bgImg,64,0

        ' ----------------------------------------------------
   '     powerupCanvas.Flush ' < -- Enable this one..
  
        bgCanvas.Flush ' < -- enabled the display will go black on macos Catalina 10.15.6   (Not in html5)

        myCanvas.Flush
        ' ----------------------------------------------------
    
   '     If individualscreenshot = True
   '         individualscreenshot  = False
   '         Local file := RequestFile("Save file....", "Image Files:png,jpg,bmp;All Files:*", True)
   '         Local ext:=filepath.ExtractExt(file).ToLower()
   '         SavePNG(file, powerupCanvas)
   '         SavePNG(file+"landscape.png", myCanvas)
   '         SavePNG(file+"clouds.png", bgCanvas)
   '     Endif

        Return 0
    End
End
 
Last edited:
Back
Top Bottom