Loading BMP files

Pierrou

Active Member
Joined
Jul 6, 2017
Hello and Merry Christmas
Is there a way to load bmp files in Mojo1 CX? LoadImage only loads PNG and JPG files!
 

Pierrou

Active Member
Joined
Jul 6, 2017
In fact in my current Mojo1 project the user can make screenshots using Frederick Raynal's BMP class, it saves the pics to BMP files. I'd like to be able to reuse those BMPs to make some kind of gallery. In fact the gallery is ready and while trying to run it I discovered CerberusX couln't read them.
The BMP class goes like this :
Cerberus X:
Class    BmpFile

    Field    Width:Int              ' bmp image width
    Field    Height:Int            ' bmp image height
   
    Field    Pixels:Int[1]        ' array for screen pixels

    Field    FileSize:Int        ' bmp file size
    Field    Buffer:DataBuffer    ' bmp file data (header + pixels)

    Field    PadLineWidth:Int    ' bmp real line size
   
    ' default header for 24bpp image
    Field    Header:Int[] = [ $42,$4D,$D6,$83,$00,$00,$00,$00,$00,$00,$36,$00,$00,$00,$28,$00,$00,$00,$6C,$00,$00,$00,$68,$00,$00,
                            $00,$01,$00,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00,$C3,$0E,$00,$00,$C3,$0E,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ]  
    Const    FILE_SIZE        :=    $2  
    Const    IMAGE_WIDTH        :=    $12
    Const    IMAGE_HEIGHT    :=    $16
    Const    RAW_DATA_SIZE    :=    $22

    Method Grab:Void( x:Int, y:Int, w:Int, h:Int )    ' Call within OnRender after drawing the image to grab
   
        Width = w
        Height = h
        PadLineWidth = (((Width*3) + 3) / 4) * 4
   
        Pixels = Pixels.Resize( Width * Height )      
        ReadPixels Pixels, x, y, Width, Height
           
        FileSize = Header.Length() + PadLineWidth * Height
        Buffer = New DataBuffer( FileSize )
       
        local ptr:Int
        for ptr = 0 until Header.Length
            Buffer.PokeByte ptr, Header[ptr]
        next
       
        Buffer.PokeInt FILE_SIZE, FileSize
        Buffer.PokeInt IMAGE_WIDTH, Width
        Buffer.PokeInt IMAGE_HEIGHT, Height
        Buffer.PokeInt RAW_DATA_SIZE, PadLineWidth * Height

        local i:Int = 0
        local pix:Int
        for local ys:Int = 0 until Height
            ptr = (Height - ys - 1) * PadLineWidth + Header.Length
            for local xs:Int = 0 until Width
                pix = Pixels[i]
                Buffer.PokeByte ptr+0, pix & $ff
                Buffer.PokeByte ptr+1, (pix shr 8) & $ff
                Buffer.PokeByte ptr+2, (pix shr 16) & $ff
                ptr += 3
                i += 1
            next
        next
       
    End
     
    Method    Save:Void( filename:String )    ' filename without .bmp

        Local file:=FileStream.Open( "monkey://internal/"+filename+".bmp","w" )
        If file
            file.Write Buffer, 0, FileSize
            file.Close
        Endif
       
    End
   
End
No Load Method and I don't think I have the skills to write one but I guess it's doable? Something using ReadPixels instead of WritePixels, one would have to figure out first how to get data from the BMP file to an array, it's definitely something a little bit too big for me but I will try since it's very frustrating to have to give up so close to the goal.

Thanks for your answer Mike!
 
Last edited:

Pierrou

Active Member
Joined
Jul 6, 2017
OK I can store and get the data from the file using PeekInt and so on, should be able to reverse Fred's code to write it to an image...
 

Pierrou

Active Member
Joined
Jul 6, 2017
OK this comes from Fred's Grab method shown above.

He stores the RGB pixel data in the Pixels:Int[] array
Cerberus X:
Pixels = Pixels.Resize( Width * Height )
        ReadPixels Pixels, x, y, Width, Height
and then stores each of the Pixel array's data into the Buffer,

line Buffer.PokeByte ptr+0, pix & $ff coding for Blue,
Buffer.PokeByte ptr + 1, (pix shr 8) & $ff for Green
and Buffer.PokeByte ptr+2, (pix shr 16) & $ff for Red

Cerberus X:
for local ys:Int = 0 until Height
            ptr = (Height - ys - 1) * PadLineWidth + Header.Length
            for local xs:Int = 0 until Width
                pix = Pixels[i]
                Buffer.PokeByte ptr+0, pix & $ff
                Buffer.PokeByte ptr + 1, (pix shr 8) & $ff
                Buffer.PokeByte ptr+2, (pix shr 16) & $ff
                ptr += 3
                i += 1
            next
        next
He then saves the databuffer data into the BMP file, beginning at the bottom left I think (which is how one writes BMP files apparently)
Cerberus X:
If file
            file.Write Buffer, 0, FileSize
            file.Close
        EndIf
When reading the Pixels Array data I get numbers looking like -8547088 Or -7560193, not being sure what they really mean

When the values of pix, pix shr8 and pix shr16 are pix = -827044 pix shr8 = -3231 and pix shr16 = -13 for example
I can see that
pix & $ff = 92
(pix shr 8) & $ff = 97
(pix shr 16) & $ff = 243
(the pixel being read's RGB values being 140,163,255 indeed)

So my question is how does that & ff suffix work and what does it mean exactly?


Then, in the Load method I'm trying to write I'm able to get data from the file and store it back to a DataBuffer, with the BMP File Header data and then the RGB data

Some code sample:
Cerberus X:
For Local ptr:Int = Header.Length To Header.Length + 1000
                        Print Buffer.PeekByte(ptr)
                    Next
If I get the data from the example above, The Print command will print
255
163
140
which are the right RGB values for the first pixel at the bottom.

So, so far so good I can read the file length, the image width and height from the file header, and then every pixel's RGB data
but what I can't do for now is storing back every RGB info into an image using CreateImage and then image.WritePixels(...)
What the doc says about the WritePixels Method:
Method WritePixels : Void ( pixels:Int[], x:Int, y:Int, width:Int, height:Int, arrayOffset:Int=0, arrayPitch:Int=0, frame:Int=0 )

Copies a rectangular section of pixels from an int array to the image.

The pixel data must be stored in int-per-pixel ARGB format, with the alpha component stored in bits 24-31, the red component in bits 16-23, the green component in bits 8-15 and the blue component in bits 0-7.
So my question do you know how to store my RGB data back into some Pixels array?
Thanks!!

EDIT : working on it, very badly and slowly, getting an image displayed at least but wrong colours and other problems... I'm about to figure out how it works..
 
Last edited:

Pierrou

Active Member
Joined
Jul 6, 2017
Here is a LoadBmp function to be used alongside with Fred's BmpFile Class above.

Cerberus X:
Function LoadBmp:Image(path:String)
   
    Local file:= FileStream.Open(path, "r")
    Local bmpimage:BmpFile
    Local image:Image
    bmpimage = New BmpFile
    bmpimage.Buffer = New DataBuffer(file.Length)
    If file
        file.ReadAll(bmpimage.Buffer, 0, file.Length)
        file.Close
        bmpimage.Width = bmpimage.Buffer.PeekInt($12)
        bmpimage.Height = bmpimage.Buffer.PeekInt($16)
                   
        bmpimage.Pixels = bmpimage.Pixels.Resize(bmpimage.Width * bmpimage.Height)
       
        Local ptr:Int = 0
        Local scanpixelsarray:Int = (bmpimage.Height - 1) * bmpimage.Width 'starting to write into the Pixels array where the data for the last line of the image begins
        For Local i:Int = bmpimage.Height - 1 To 0 Step - 1 ' pixel info in the BMP file is coded upside down for some reason
            For Local j:Int = 0 To bmpimage.Width - 1
                Local b:Int = bmpimage.Buffer.PeekInt(ptr + 0) ' blue component
                Local g:Int = bmpimage.Buffer.PeekInt(ptr + 1) ' green component
                Local r:Int = bmpimage.Buffer.PeekInt(ptr + 2) ' red component
                bmpimage.Pixels[scanpixelsarray + j] = (255 Shl 24) | (r Shl 16) | (g Shl 8) | b ' Data in the Pixels array must be stored in int-per-pixel ARGB format, with the alpha component stored in bits 24-31, the red component in bits 16-23, the green component in bits 8-15 and the blue component in bits 0-7
                ptr += 3
                If j = bmpimage.Width - 1 Then scanpixelsarray = (i - 1) * bmpimage.Width
            Next
        Next
       
       
        image = CreateImage(bmpimage.Width, bmpimage.Height)
        image.WritePixels(bmpimage.Pixels, 0, 0, bmpimage.Width, bmpimage.Height, 0, 0)
       
    EndIf
    Return image
End Function
Is it working? Nope.
bmpbug.png

The bmp images are supposed to be displayed in the frames. The far left one and the right one look OK. The two in the middle look rather weird... It's really too tricky for me :(
BlitzMax used to load BMP files out of the box (never had a use for that but it was cool anyway), weird that Monkey/CX doesn't.
 

Attachments

Last edited:

MikeHart

Administrator
Joined
Jun 19, 2017
Location
Germany
Try this one:
Cerberus X:
Function LoadBMP:Image(path:String)
    Local db:DataBuffer = DataBuffer.Load(path)
    If db
        'Print("length="+db.Length())
        'Print("type="+db.PeekString(0,2))
        'Print("size="+db.PeekInt(2))
        Local  offset:Int=db.PeekInt(10)
        'Print("offet="+offset)
        'Print("header size="+db.PeekInt(14))
        Local width:Int=db.PeekInt(18)
        'Print("image width="+width)
        Local height:Int=db.PeekInt(22)
        Local img:Image = CreateImage( width, height, 1 )
        'Print("image height="+height)
        'Print("image planes="+db.PeekShort(26))
        Local bitcount:Int= +db.PeekShort(28)
        'Print("image bitcount="+bitcount)
        'Print("image compression="+db.PeekInt(30))
        Local imagesize:Int=db.PeekInt(34)
        'Print("image Size="+imagesize)
        Local pixels:Int[width*height]
        For Local y:Int= 0 To (height-1)
            For Local x:Int=0 To (width-1)
                Local index:Int=y*width+x
                Local i:Int = offset+index*(bitcount/8)
                Local alpha:Int = 255
                Local red:Int = db.PeekByte(i)+256
                Local green:Int = db.PeekByte(i+1)+256
                Local blue:Int = db.PeekByte(i+2)+256
                Local rgba:Int = alpha Shl 24 | blue Shl 16 | green Shl 8 | red
                pixels[width*height-index-1]=rgba
            Next
        Next
        If img
            img.WritePixels(pixels,0,0,width,height)
            Return img
        Endif
    Else
        Print ("Error loading file "+path)
    Endif
    Return Null
End
 

Pierrou

Active Member
Joined
Jul 6, 2017
Thanks Mike!!
Same result unfortunately except that the images are horizontally reverted :)
I've only tried with BMPs made within my game using Fred's Class I'll try with BMPs made with GIMP.
 

Pierrou

Active Member
Joined
Jul 6, 2017
Using 24bit BMPs made with Paint lead to the same result
1577313751515.png

was supposed to look more or less like
1577313789191.png
 

MikeHart

Administrator
Joined
Jun 19, 2017
Location
Germany
Cerberus X:
Strict

'Simple mojo(1) script
'It should behave the same on all targets
#IMAGE_FILES+="*.bmp"
Import mojo
Import brl.databuffer

'-----------------------------------------------------------------
Class myClass Extends App
    Field angle:Float = 0.0
    Field img:Image
    '-----------------------------------------------------------------
    Method OnCreate:Int()
        SetUpdateRate 60
        img = LoadBMP("cerberus://data/gold2.bmp")
        
        Return 0
    End
    '-----------------------------------------------------------------
    Method OnUpdate:Int()
        ' update your content here
        angle += 0.5
        If angle > 360.0 Then angle -= 360.0
        Return 0
    End
    '-----------------------------------------------------------------
    Method OnRender:Int()
        ' here you render your app
        Cls 0,0,255
        DrawImage(img,10,10)
        Return 0
    End
End
Function LoadBMP:Image(path:String)
    Local db:DataBuffer = DataBuffer.Load(path)
    If db
        'Print("length="+db.Length())
        'Print("type="+db.PeekString(0,2))
        'Print("size="+db.PeekInt(2))
        Local  offset:Int=db.PeekInt(10)
        'Print("offet="+offset)
        'Print("header size="+db.PeekInt(14))
        Local width:Int=db.PeekInt(18)
        'Print("image width="+width)
        Local height:Int=db.PeekInt(22)
        Local img:Image = CreateImage( width, height, 1 )
        'Print("image height="+height)
        'Print("image planes="+db.PeekShort(26))
        Local bitcount:Int= +db.PeekShort(28)
        'Print("image bitcount="+bitcount)
        'Print("image compression="+db.PeekInt(30))
        Local imagesize:Int=db.PeekInt(34)
        'Print("image Size="+imagesize)
        Local pixels:Int[width*height]
        For Local y:Int= 0 To (height-1)
            For Local x:Int=0 To (width-1)
                Local index:Int=y*width+x
                Local i:Int = offset+index*(bitcount/8)
                Local alpha:Int = 255
                Local red:Int = db.PeekByte(i)+256
                Local green:Int = db.PeekByte(i+1)+256
                Local blue:Int = db.PeekByte(i+2)+256
                Local rgba:Int = alpha Shl 24 | blue Shl 16 | green Shl 8 | red
                pixels[width*height-index-1]=rgba
            Next
        Next
        If img
            img.WritePixels(pixels,0,0,width,height)
            Return img
        Endif
    Else
        Print ("Error loading file "+path)
    Endif
    Return Null
End
'-----------------------------------------------------------------
Function Main:Int()
    New myClass
    Return 0
End
 

Pierrou

Active Member
Joined
Jul 6, 2017
Well, I downloaded a few bmp pics from Google Images : some work and some don't, whether I use your Function or mine (same result except for the flipping)
 

Pierrou

Active Member
Joined
Jul 6, 2017
It might have something to do with row lengths being or not being multiples of 4?
Padding bytes (not necessarily 0) must be appended to the end of the rows in order to bring up the length of the rows to a multiple of four bytes. When the pixel array is loaded into memory, each row must begin at a memory address that is a multiple of 4. This address/offset restriction is mandatory only for Pixel Arrays loaded in memory. For file storage purposes, only the size of each row must be a multiple of 4 bytes while the file offset can be arbitrary.[4] A 24-bit bitmap with Width=1, would have 3 bytes of data per row (blue, green, red) and 1 byte of padding, while Width=2 would have 2 bytes of padding, Width=3 would have 3 bytes of padding, and Width=4 would not have any padding at all.

When resizing the pictures they look OK again...
[EDIT : definitely a matter of multiples of 4, when resizing the bull pic to 492x436 it looks good again. Let's try to manage padding]
[EDIT 2 : not quite sure how to do it :\ but it's getting close]
 
Last edited:

Pierrou

Active Member
Joined
Jul 6, 2017
This piece of code seems to work :

Cerberus X:
Function LoadBmp:Image(path:String)
    Local Width:Int              ' bmp image width
    Local Height:Int            ' bmp image height
    Local Pixels:Int[1]        ' array for screen pixels
    Local Buffer:DataBuffer    ' bmp file data (header + pixels)
    Local file:= FileStream.Open(path, "r")
    Local image:Image
    Buffer = New DataBuffer(file.Length)
    If file
        file.ReadAll(Buffer, 0, file.Length)
        file.Close
        Width = Buffer.PeekInt($12)
       
        Height = Buffer.PeekInt($16)
        Pixels = Pixels.Resize(Width * Height)
        Local PadLineWidth:Int = ( ( (Width * 3) + 3) / 4) * 4
        Local OriginalLineWidth:Int = Width * 3
        Local Padding:Int = PadLineWidth - OriginalLineWidth
        Local ptr:Int = 0
        Local scanpixelsarray:Int = (Height - 1) * Width 'starting to write into the Pixels array where the data for the last line of the image begins
        For Local i:Int = Height - 1 To 0 Step - 1 ' pixel info in the BMP file is coded upside down for some reason
            For Local j:Int = 0 To Width - 1
                Local b:Int = Buffer.PeekInt(ptr + 0) ' blue component
                Local g:Int = Buffer.PeekInt(ptr + 1) ' green component
                Local r:Int = Buffer.PeekInt(ptr + 2) ' red component
               
                       Pixels[scanpixelsarray + j] = (255 Shl 24) | (r Shl 16) | (g Shl 8) | b ' Data in the Pixels array must be stored in int-per-pixel ARGB format, with the alpha component stored in bits 24-31, the red component in bits 16-23, the green component in bits 8-15 and the blue component in bits 0-7
                ptr += 3
                If j = Width - 1
                scanpixelsarray = (i - 1) * Width
                If Padding<> 0 Then ptr += Padding
                EndIf
            Next
        Next
       
       
        image = CreateImage(Width, Height)
        image.WritePixels(Pixels, 0, 0, Width, Height, 0, 0)
       
    EndIf
    Return image
End Function
 
Last edited:

Pierrou

Active Member
Joined
Jul 6, 2017
No one seemed to be missing it so far but adding a few lines and make it possible won't hurt.
Thanks again for your help, glad it works, now I can finish my game!
 

MikeHart

Administrator
Joined
Jun 19, 2017
Location
Germany
Your function has a little problem (the picture is off by several pixels to the right):

1577437737310.png


And because the file is loaded with FileStream, you can't use it in HTML5.
Better load it into a databuffer directly?
 

Pierrou

Active Member
Joined
Jul 6, 2017
Hello
Yes I noticed the offset problem afterwards too. About FileStream, it was already needed in my game but yes I saw that you loaded the data directly into the databuffer, I just took some code here and there, mainly from Fred's BMP class and tried to make it work backwards without exactly knowing what was going on :oops::oops::oops:
"My" function works okayish for what I need but the one you wrote is probably a better and safer start. Don't have time to think about it for now, no emergency I unfortunately have to focus on my day(and sometimes night-)job for a few days...
 
Last edited:
Top Bottom