Loading a BMP file to show

If you have questions about any aspect of QBasic programming, or would like to help fellow programmers solve their problems, check out this board!

Moderators: Pete, Mods

Post Reply
Andrew Dance
Coder
Posts: 18
Joined: Mon Dec 17, 2007 8:41 pm
Contact:

Loading a BMP file to show

Post by Andrew Dance »

i know there are some good tuts for this such as http://www.petesqbsite.com/sections/tut ... _bmps.html, but my problem is i like to know every little detail and whats going on....it's a lot to ask, but I'd appreciate it if anyone could explain in the code with comments whats actually happening line by line. I just need help reaching the point where i go "Oh! Thats how it works!" and getting it :)
thank you to anyone in advance,

-Andrew
Nodtveidt
Veteran
Posts: 826
Joined: Sun Jul 25, 2004 4:24 am
Location: Quebradillas, PR
Contact:

Post by Nodtveidt »

I can give you some more detailed information. I did not copy the TYPE information because it really doesn't need explaining, and it wouldn't copy with linebreaks anyways. In fact, nothing did...I had to add those manually. Urg! Anyways...

Code: Select all

DIM BmpHeader AS BMPHeaderType
The TYPE you see earlier is just a collection of variables. This line essentially "collects" all of those variables and makes them "children" of another variable, this one called BmpHeader.

Code: Select all

OPEN "demo.bmp" FOR BINARY AS #1
I normally don't advocate the use of "#1" for file handles, but dynamic file handles are a subject for another day. Anyways, this just opens the BMP file in "Binary" file mode, which means no special formatting will be done, the file will simply be read byte-for-byte.

Code: Select all

GET #1, , BmpHeader
This fetches the header information from the BMP file. It will contain all of the information you need to draw the BMP on the screen. All of this information is kept in the variable BmpHeader and can be read by using this format:

PRINT BmpHeader.id
PRINT BmpHeader.hei
etc etc etc.

Code: Select all

SCREEN 13
You know what this does. :D

Code: Select all

a$ = BmpHeader.pal
BmpHeader.pal contains the image's palette data. This simply assigns that data to a temporary string. Why they do this is beyond me, but whatever. :D

Code: Select all

OUT &H3C8, 0
This is the way the palette registers are affected directly. Port &H3C8 is the index pointer. By setting it to 0, we're telling it to start at index 0, or the very beginning of the palette index.

Code: Select all

FOR I% = 1 TO 1024 STEP 4
The palette is stored in segments of 4 bytes. However, only three of those bytes are relevant. So, we STEP 4 to skip through the variable 4 bytes at a time.

Code: Select all

b% = ASC(MID$(a$, I%, 1)) \ 4
Now we begin reading the color data from the variable. We start with the blue. Since it's a string type, and we want numeric values, we use ASC which gives us the numeric value of any string. MID$ will "extract" only the amount of data we need; in this case, 1 byte. At the end of the line, the value has to be reduced to fit into the color rules of the VGA palette. Normally, a BMP will contain a color range of 0 to 255 for each color, but in VGA land, we only have 0 to 63. The \ 4 at the end reduces the color information so it will fit in the palette register. Don't worry, everything will still look fine. :D

Code: Select all

g% = ASC(MID$(a$, I% + 1, 1)) \ 4
The Green information comes next. Note that the MID$ keyword's parameters now include a + 1. That simply means we're looking at the next byte in the temporary variable we created earlier to hold the palette.

Code: Select all

r% = ASC(MID$(a$, I% + 2, 1)) \ 4
And finally, the red. Note that there is no +3; the fourth byte in the string is "worthlesss" so we don't need to care about it.

Code: Select all

OUT &H3C9, r%
OUT &H3C9, g%
OUT &H3C9, b%
Here, all of the values that we collected so far are written to the palette registers through this port, &H3C9. Notice that there isn't another call to &H3C8...conveniently, the palette index increments itself when you write three values to &H3C9, so you don't have to mess with it! :D

Code: Select all

NEXT
Yep. :D

Code: Select all

DIM Pixel AS STRING * 1
Okay, on to the image data. In an 8-bit BMP, each pixel is stored 1 byte at a time. However, since QB doesn't have a BYTE type (huge mistake), the best we can do is STRING * 1 for reading bytes from a file.

Code: Select all

iHeight% = BmpHeader.hei - 1
This new temporary variable will be used by the loop later to keep track of the Y position, or scanline, of the image. It is given -1 because the PSET later will use screen values, and if you're doing a 320x200 image, you can't write to line 200 on the screen because it doesn't exist! So, reduce this by 1 so it will write from 0-199.

Code: Select all

iWidth% = BmpHeader.wid - 1
And this one for the X position. Same rules regarding the -1; trying to PSET at the 320th X pixel will break stuff.

Code: Select all

FOR y% = iHeight% TO 0 STEP -1
Since the image is stored line-by-line, the Y loop comes first. Also, curiously, BMP files are stored upside-down. No one except Microsoft knows what the motive was for this boneheaded move. :shock:

Code: Select all

FOR x% = 0 TO iWidth%
Now that Y is taken care of, handle X too.

Code: Select all

GET #1, , Pixel
Here's where we get the pixel information from the BMP. Just one byte needs to be read here, since we're dealing with an 8 bit image.

Code: Select all

PSET (x%, y%), ASC(Pixel)
Now it gets drawn to the screen. Notice the ASC; again, since we need numeric values and we only have a string, it has to be converted to numeric. The data in Pixel corresponds to the index in the palette that we set earlier.

Code: Select all

NEXT x%, y%
End the loops.

Code: Select all

CLOSE #1
Don't forget to close the file. :D

As you can see, this is one of the simplest image storage methods available. Rather wasteful, but simple. This code doesn't delve into a lot of things though, such as scanline optimization or even proper byte alignment issues, but it's a start.

Anyways, I hope that was helpful. If you have any other questions, feel free to ask, I'm here all day today. :D
Andrew Dance
Coder
Posts: 18
Joined: Mon Dec 17, 2007 8:41 pm
Contact:

Post by Andrew Dance »

oh, i love you lol. thanks a whole lot! this REALLY helped me.


Edit: You know what, i think I'm about to feel really stupid, but in the header under TYPE BMPHeaderType where all the AS INTEGER and AS LONGs are, do i replace it with the actual data or not :x?

because it's saying in the comments 'Should be BM' or 'size of the data'

'Nother Edit (since i don't like to double post):

I'm getting the "Overflow" error when i type in the line

Code: Select all

OUT &H3C8, 0
in the program and press return to go to the next line :/
Nodtveidt
Veteran
Posts: 826
Joined: Sun Jul 25, 2004 4:24 am
Location: Quebradillas, PR
Contact:

Post by Nodtveidt »

No. The TYPE is to be left alone. It's code after all. The comments simply explain to you what you should find after the header is read from the BMP file.

Code: Select all

OUT &H3C8, 0
is always going to fail. It's not valid syntax; it's a result of someone's web script monkeying up. The valid syntax is like this:

Code: Select all

OUT &H3C8, 0
Andrew Dance
Coder
Posts: 18
Joined: Mon Dec 17, 2007 8:41 pm
Contact:

Post by Andrew Dance »

Well i got it all in there, got my own bmp file in the same directory, and when i press F5 to run it, all i get is a dark green line across the top row of pixels. any ideas as to what happened?
User avatar
burger2227
Veteran
Posts: 2466
Joined: Mon Aug 21, 2006 12:40 am
Location: Pittsburgh, PA

Post by burger2227 »

Do you want a program that you cannot write? Do you want the code?

Go to QbasicStation.com and look in Member files / Utilities for BSAVER.ZIP if you are using SCREEN 12 or 13. Nodvelt cannot sum it up better than that unless he posts the entire procedure!

Give me a break! He is gone, but if you have a film of Chainey shooting his friend, that would be real cool.

Ted
Please acknowledge and thank members who answer your questions!
QB64 is a FREE QBasic compiler for WIN, MAC(OSX) and LINUX : https://www.qb64.org/forum/index.php
Get my Q-Basics demonstrator: https://www.dropbox.com/s/fdmgp91d6h8ps ... s.zip?dl=0
Nodtveidt
Veteran
Posts: 826
Joined: Sun Jul 25, 2004 4:24 am
Location: Quebradillas, PR
Contact:

Post by Nodtveidt »

Andrew: post your whole program so I can help you debug it. And ignore burger2227, he's a well-known troublemaker around these parts.
Andrew Dance
Coder
Posts: 18
Joined: Mon Dec 17, 2007 8:41 pm
Contact:

Post by Andrew Dance »

here is the source

Code: Select all

TYPE BMPHeaderType
        id AS STRING * 2     'Should be "BM"
        size AS LONG         'Size of the data
        rr1 AS INTEGER       '
        rr2 AS INTEGER       '
        offset AS LONG       'Position of start of pixel data
        horz AS LONG         '
        wid AS LONG          'Image width
        hei AS LONG          'Image height
        planes AS INTEGER    '
        bpp AS INTEGER       'should read 8 for a 256 colour image
        pakbyte AS LONG      '
        imagebytes AS LONG   'Width*Height
        xres AS LONG         '
        yres AS LONG         '
        colch AS LONG        '
        ic AS LONG           '
        pal AS STRING * 1024 'Stored as Blue, Green, Red, 0
END TYPE

DIM BmpHeader AS BMPHeaderType

OPEN "1.bmp" FOR BINARY AS #1
        GET #1, , BmpHeader

' Don't close the file just yet - were not finished!

SCREEN 13                                          'Set graphics mode

a$ = BmpHeader.pal                                 'Pal is stored in a 1024 character string

OUT &H3C8, 0                                        'Start writing form colour 0
FOR I% = 1 TO 1024 STEP 4
        b% = ASC(MID$(a$, I%, 1)) \ 4              'blue
        g% = ASC(MID$(a$, I% + 1, 1)) \ 4          'green
        r% = ASC(MID$(a$, I% + 2, 1)) \ 4         'red
                                                   'I% + 3 is set to zero.
        OUT &H3C8, r%
        OUT &H3C8, g%
        OUT &H3C8, b%
NEXT

DIM Pixel AS STRING * 1                'Our pixel "byte"

iHeight% = BmpHeader.hei - 1           'Subtract 1 for actual screen position
iWidth% = BmpHeader.wid - 1            '

FOR y% = iHeight TO 0 STEP -1          'Coutdown for upsidedown image
FOR x% = 0 TO iWidth%

GET #1, , Pixel
PSET (x%, y%), ASC(Pixel)

NEXT x%, y%

CLOSE #1


Andrew Dance
Coder
Posts: 18
Joined: Mon Dec 17, 2007 8:41 pm
Contact:

Post by Andrew Dance »

can someone please help?
User avatar
BadMrBox
Veteran
Posts: 86
Joined: Tue Feb 28, 2006 12:19 pm

Post by BadMrBox »

Is your bmp 256 colors?
Andrew Dance
Coder
Posts: 18
Joined: Mon Dec 17, 2007 8:41 pm
Contact:

Post by Andrew Dance »

yes, I'm pretty sure it is. i did it in photoshop, set the color palate to 256 colors, and also opened it in paint, and saved it as a 256 color bmp.

i saved it as a 16 color bmp and same result, except instead of a green line at the top, its 3 white dots :/
User avatar
burger2227
Veteran
Posts: 2466
Joined: Mon Aug 21, 2006 12:40 am
Location: Pittsburgh, PA

Post by burger2227 »

Did you save it as an 8 bit bitmap? Paint is not the greatest kind of image editor. You should be able to set the palette's BPP to 8 in Photoshop. Your code is from here I gather and it should work for screen 13.

Does it load at all? If it is skewed, the bitmap needs a padder.

Ted

Nodvelt is a well known programmer who rarely finishes what he starts!
Please acknowledge and thank members who answer your questions!
QB64 is a FREE QBasic compiler for WIN, MAC(OSX) and LINUX : https://www.qb64.org/forum/index.php
Get my Q-Basics demonstrator: https://www.dropbox.com/s/fdmgp91d6h8ps ... s.zip?dl=0
Andrew Dance
Coder
Posts: 18
Joined: Mon Dec 17, 2007 8:41 pm
Contact:

Post by Andrew Dance »

burger2227 wrote:Did you save it as an 8 bit bitmap? Paint is not the greatest kind of image editor. You should be able to set the palette's BPP to 8 in Photoshop. Your code is from here I gather and it should work for screen 13.

Does it load at all? If it is skewed, the bitmap needs a padder.

Ted

Nodvelt is a well known programmer who rarely finishes what he starts!
i got it in 8 bit (it was still in 16 somehow) but its the same thing, but nnow the line is a bunch of random colors...

what is this padding you speak of?
User avatar
burger2227
Veteran
Posts: 2466
Joined: Mon Aug 21, 2006 12:40 am
Location: Pittsburgh, PA

Post by burger2227 »

If you can get more than a single line, then we can worry about the padder.

How large is the bitmap? I cannot be over 320 X 200.

Program problems:

1) Width and Height are Long variables so use & instead of % no matter what the bitmap size is.

2) FOR y% = iHeight TO 0 STEP -1 has no type suffix for iHeight.
Use: FOR y& = iHeight& TO 0 STEP - 1

3) OUT &H3C8, r% is wrong. It should be OUT &H3C9 for all three color settings. OUT &H3C8, 0 is used to set the attribute number to start the process. Each 3 OUT &H3C9 statements will increment to a new attribute without any help. 1024 / 4 = 256 colors. The 4th byte is a color padder byte skipped by STEP 4.

Ted
Please acknowledge and thank members who answer your questions!
QB64 is a FREE QBasic compiler for WIN, MAC(OSX) and LINUX : https://www.qb64.org/forum/index.php
Get my Q-Basics demonstrator: https://www.dropbox.com/s/fdmgp91d6h8ps ... s.zip?dl=0
Nodtveidt
Veteran
Posts: 826
Joined: Sun Jul 25, 2004 4:24 am
Location: Quebradillas, PR
Contact:

Post by Nodtveidt »

Andrew, I ran into the same exact problem while testing that source code. It appears to be a little broken. Unfortunately, I did not test that code before explaining it to you, so I'm partially at fault for that. In any event, if all you need is a working BMP viewer, you can use this:

http://www.nodtveidt.net/bmpload.zip

It could use some optimization but it loads all valid compressed and uncompressed 8-bit BMP files without error and can also handle uncompressed 4-bit images. I would explain how the code works but unfortunately that code in particular is relatively complex and would take quite awhile to completely document. The basic concepts of it are unchanged though, so feel free to look it over. Again, it is rather unoptimized so it's a little slow (I ported it from an FB library I wrote a couple of years back, so it had to be downgraded to work with QB), but it has been tested in QBASIC 1.1 to work just fine.

EDIT: By the way, I found your problem. Your code looks like this:

Code: Select all

FOR y% = iHeight TO 0 STEP -1          'Coutdown for upsidedown image
when it should look like this:

Code: Select all

FOR y% = iHeight% TO 0 STEP -1          'Coutdown for upsidedown image
This is one of the unseen dangers of not initializing your variables ahead of time. TYPE SUFFIXES ARE EVIL. I made that one-character change and the code worked almost as expected. Of course, the palette didn't work correctly...I'll see what's the deal there too.

EDIT2: It's all as burger2227 stated; the loop problem and the palette problem. Change the port number and the palette will load fine also.

EDIT3: However, you can disregard the suggestion to use LONG variables. LONGs are slower than INTEGERs and you will take a performance hit. The current method you're using is better. The only reason they are LONGs in the header is because of a format design decision; by making the value 32 bits, you can have an image of virtually unlimited size (do you really need a bitmap over 4 billion pixels wide and tall?). For practical use and speed considerations, stick with INTEGERs whenever possible, use LONG only when you have to.

EDIT4: There are also plenty of ways to speed up this code, and make it more reliable. For example, you can POKE to the screen memory instead of using PSET, and you will gain a tremendous speed increase. Also, the padding issue he was talking about...if you have an images whose width is NOT a multiple of 4, it needs to be "padded"...basically what this means in this case is that you have to read "extra pixels" from the file regardless if those pixels exist or not. This is by design; it's so the BMP format loads and saves as fast as possible (32 bit alignment). However, if all you're doing is 320 pixel wide images, you don't need to worry about padding. If you get into strange widths (like 265 pixels, for example), you will need to use padding or your image will skew on the screen.
Last edited by Nodtveidt on Wed Dec 19, 2007 10:13 pm, edited 1 time in total.
Andrew Dance
Coder
Posts: 18
Joined: Mon Dec 17, 2007 8:41 pm
Contact:

Post by Andrew Dance »

Awesome. i got it to work just fine. Thank you both a million :D
User avatar
burger2227
Veteran
Posts: 2466
Joined: Mon Aug 21, 2006 12:40 am
Location: Pittsburgh, PA

Post by burger2227 »

Now the image should look stretched. To fix that edit your bitmap by changing the height of it in Photoshop. Set the height to 83% and width to 100%. This will squish the bitmap image, but the image will look perfect in SCREEN 13.

If you want to learn how to BSAVE the image and be able to center your image there are BSAVERs on the web. I made one also if you need it.

I have never seen any speed problems, but I agree that you should never really need a long value in this application.

It is a shame that some of the programs on this site have never been edited correctly for years. I gather that is not possible.

Ted
Please acknowledge and thank members who answer your questions!
QB64 is a FREE QBasic compiler for WIN, MAC(OSX) and LINUX : https://www.qb64.org/forum/index.php
Get my Q-Basics demonstrator: https://www.dropbox.com/s/fdmgp91d6h8ps ... s.zip?dl=0
Nodtveidt
Veteran
Posts: 826
Joined: Sun Jul 25, 2004 4:24 am
Location: Quebradillas, PR
Contact:

Post by Nodtveidt »

That code would work if not for the & broken parts. All one has to do is recognize that minor issue and make the change in the source when they copy it.
Andrew Dance
Coder
Posts: 18
Joined: Mon Dec 17, 2007 8:41 pm
Contact:

Post by Andrew Dance »

its working fine now, and to Burger, its not stretched or warped at all...mabey my monitor is screwed up :D
User avatar
burger2227
Veteran
Posts: 2466
Joined: Mon Aug 21, 2006 12:40 am
Location: Pittsburgh, PA

Post by burger2227 »

Remember the squish idea. You may need it someday, especially for pictures of people.

There are other tutorials and code that does not work at all here. That was only one small example of a minor few mistakes. Try the FAST bitmap loader in one of the Interrupt tutorials. It does not work at all!

Ted
Please acknowledge and thank members who answer your questions!
QB64 is a FREE QBasic compiler for WIN, MAC(OSX) and LINUX : https://www.qb64.org/forum/index.php
Get my Q-Basics demonstrator: https://www.dropbox.com/s/fdmgp91d6h8ps ... s.zip?dl=0
Post Reply