Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.
|
Forum Index : Microcontroller and PC projects : Displaying Bitmap Images
Author | Message | ||||
crackerjack Senior Member Joined: 11/07/2011 Location: AustraliaPosts: 164 |
Hi All, I have a small program that allows me to display bitmap images loaded from the SD card. Of course monochrome images no larger than 480x432 pixels are the limit (and it is painfully slow), but it was a fun exercise learning about the BMP format and how to read it from a file & decode it using MMBasic. Attached is a photo of a bitmap loaded from the SD Card (Don, I hope you don't mind me using the Duinomite logo as a test case). It may be of some use if you want to display small images as splash logos, etc. If anybody is interested, I can post the code (I'll have to clean it up a bit first). Cheers, Lance. |
||||
Sumit Newbie Joined: 13/09/2011 Location: Posts: 23 |
Hi, Nice work. I did a similar thing but my code took up a good amount of RAM. I would like to see your code. Maybe yours is more efficient. Sumit |
||||
donmck Guru Joined: 09/06/2011 Location: AustraliaPosts: 1313 |
Actually it is an Olimex generated Logo, but I'll stick my neck out and say they are more than happy for you to use it. Nice work. Cheers Don... https://www.dontronics.com |
||||
sparkey Senior Member Joined: 15/06/2011 Location: AustraliaPosts: 819 |
heres a pic for you guy`s technicians do it with least resistance |
||||
crackerjack Senior Member Joined: 11/07/2011 Location: AustraliaPosts: 164 |
Below is the code to read a bitmap file in MMBasic. It could clearly be improved upon and made more efficient - in particular around the 3 subroutines that read double-words, words and bytes (a single routine given a number of bytes to read would do the trick). Also the "workhorse" section (lines 330 to 530) is a bit "ugly" for lack of a better term. I have tested it with various sizes of monochrome bitmap files, but there might be some sizes that may cause some display issues. The program uses about 6% of program space and 7% of variable memory - the images are loaded into the display space, so there are no issues with RAM really (just speed!) [code] 10 ' Display a monochrome bitmap (max size 480x432) 20 INPUT "Bitmap file: ", f$ 30 OPEN f$ FOR input AS #1 40 ADDRESS = 0 50 IF EOF(#1) GOTO 540 60 IF INPUT$(2,#1) <> "BM" THEN 70 ? "Not a Bitmap File" : GOTO 540 80 ENDIF 90 ADDRESS = ADDRESS + 2 100 GOSUB 570 ' Get the next DWORD - FILESIZE 110 ? "File Size:"; DWORD 120 GOSUB 570 ' Skip next two WORDs - RESERVED DWORD = 2 x WORD 130 GOSUB 570 ' Get OFFSET 140 OFFSET = DWORD : ? "OFFSET:"; OFFSET 150 GOSUB 570 ' Skip the next DWORD - DIB HEADER INFO 160 GOSUB 570 : WIDTH = DWORD : ? "Width:", WIDTH 170 GOSUB 570 : HEIGHT = DWORD : ? "Height:", HEIGHT 180 IF HEIGHT > MM.VRES OR WIDTH > MM.HRES THEN 190 ? "Image exceeds Maximite resolution" : GOTO 540 200 ENDIF 210 GOSUB 650 ' Skip a word 220 GOSUB 650 : ? "Bits Per Pixel:"; word 230 IF WORD <> 1 THEN 240 ? "Only monochrome (1bpp) bitmaps supported" : GOTO 540 250 ENDIF 260 'Advance to OFFSET 270 CURRENT = ADDRESS 280 FOR A = CURRENT TO OFFSET 290 GOSUB 730 300 NEXT A 310 'CLS 330 ROWSIZE = FIX((WIDTH/32)+SGN((WIDTH/32)-(WIDTH\32))) * 4 340 FOR Y = HEIGHT TO 0 STEP -1 350 FOR Z = 1 TO ROWSIZE 360 IF EOF(#1) THEN GOTO 540 370 FOR B = 7 TO 0 STEP -1 380 PIXEL(X,Y) = SGN(BYTE AND (2^B)) 390 X = X + 1 400 IF X = WIDTH THEN 410 X = 0 420 EXIT FOR 430 ENDIF 440 NEXT B 450 IF X = 0 THEN 460 PAD=ROWSIZE-FIX((WIDTH / 32) * 4) - 1 470 IF PAD < 0 THEN PAD = 0 480 FOR I=0 TO PAD:GOSUB 730: NEXT I 490 EXIT FOR 500 ENDIF 510 GOSUB 730 520 NEXT Z 530 NEXT Y 540 CLOSE #1 550 END 560 ' -------------------------------------------------------- 570 ' _GETDWORD_ 580 DWORD = 0 590 DWORD$ = INPUT$(4,#1) 600 FOR B = 4 TO 1 STEP -1 610 DWORD = DWORD + ASC(MID$(DWORD$,B,1)) * ((2^(B-1))^8) 620 NEXT B 630 ADDRESS = ADDRESS+4 640 RETURN 650 ' _GETWORD_ 660 WORD = 0 670 WORD$ = INPUT$(2,#1) 680 FOR B = 2 TO 1 STEP -1 690 WORD = WORD + ASC(MID$(WORD$,B,1)) * ((2^(B-1))^8) 700 NEXT B 710 ADDRESS = ADDRESS+2 720 RETURN 730 ' _GETBYTE_ 740 BYTE = ASC(INPUT$(1,1)) 750 ADDRESS = ADDRESS+1 760 RETURN [/code] |
||||
crackerjack Senior Member Joined: 11/07/2011 Location: AustraliaPosts: 164 |
Just for the sake of completeness - here is a final version - refactored somewhat now that I better understand the BMP format's padding spec. There are some diagnostic details still printed by the code which could be removed to make it more compact (since the CLS at line 280 wipes them out). 5% of program memory is used by this version of the code. I am thinking there may be a better, more quickly rendered format for raster graphics specifically for the Maximite... I may look into what this could be. Code follows along with another screenshot. [code] 10 ' BITMAP.BAS v1.0 Display a monochrome bitmap (max size 480x432) 20 INPUT "Bitmap file: ", f$ 30 OPEN f$ FOR input AS #1 40 ADDRESS = 0 50 IF EOF(#1) GOTO 570 60 IF INPUT$(2,#1) <> "BM" THEN 70 ? "Not a Bitmap File" : GOTO 570 80 ENDIF 90 ADDRESS = ADDRESS + 2 100 NO_BYTES = 4 : GOSUB 460 ' Get the next DWORD - FILESIZE 110 ? "File Size:"; BYTES 120 GOSUB 460 ' Skip next two WORDs - RESERVED DWORD = 2 x WORD 130 GOSUB 460 ' Get OFFSET 140 OFFSET = BYTES : ? "OFFSET:"; OFFSET 150 GOSUB 460 ' Skip the next DWORD - DIB HEADER INFO 160 GOSUB 460 : WIDTH = BYTES : ? "Width:", WIDTH 170 GOSUB 460 : HEIGHT = BYTES: ? "Height:", HEIGHT 180 IF HEIGHT > MM.VRES OR WIDTH > MM.HRES THEN 190 ? "Image exceeds Maximite resolution" : GOTO 570 200 ENDIF 210 NO_BYTES = 2 : GOSUB 460 ' Skip a word 220 GOSUB 460 : ? "Bits Per Pixel:"; BYTES 230 IF BYTES <> 1 THEN 240 ? "Only monochrome (1bpp) bitmaps supported" : GOTO 570 250 ENDIF 260 'Advance to OFFSET 270 NO_BYTES = OFFSET - ADDRESS : GOSUB 460 280 CLS 290 ROWSIZE = FIX((WIDTH/32)+SGN((WIDTH/32)-(WIDTH\32))) * 4 300 NO_BYTES = 1 310 GOSUB 460 320 FOR Y = HEIGHT TO 0 STEP -1 330 FOR Z = 1 TO ROWSIZE 340 FOR B = 7 TO 0 STEP -1 350 IF X <= WIDTH THEN 360 PIXEL(X,Y) = SGN(BYTES AND (2^B)) 370 ENDIF 380 X = X + 1 390 IF X >= (ROWSIZE * 8) THEN X = 0 400 NEXT B 410 GOSUB 460 420 NEXT Z 430 NEXT Y 440 GOTO 570 450 ' -------------------------------------------------------- 460 ' _GETBYTES_ 470 BYTES = 0 480 BYTES$ = INPUT$(NO_BYTES,#1) 490 IF EOF(#1) THEN GOTO 440 500 FOR B = NO_BYTES TO 1 STEP -1 510 BYTES = BYTES + ASC(MID$(BYTES$,B,1)) * ((2^(B-1))^8) 520 NEXT B 530 ADDRESS = ADDRESS+NO_BYTES 540 RETURN 550 ' -------------------------------------------------------- 560 ' _END_ 570 CLOSE #1 580 END [/code] |
||||
Olimex Senior Member Joined: 02/10/2011 Location: BulgariaPosts: 226 |
did you check JPEG format too? as we have nice VGA camera with UEXT connector now and it sends JPEG compressed pictures over RS232 and would connect directly to DuinoMite with UEXT connector |
||||
crackerjack Senior Member Joined: 11/07/2011 Location: AustraliaPosts: 164 |
Hi All, Just in response to the Olimex post from a week or so back - I did look at JPEGs and very quickly decided to look no further. The JPEG compression algorithm is complex - vastly complex for MMBasic and the Maximite's processing power. Apart from that, the Maximite has 1-bit, 480x432 graphics resolution which could not really offer much in the way of rendering images from a JPEG which I don't believe goes much lower than 8x8 grayscale. Since the Maximite can generate it's own Bitmaps to file with the SAVEBMP command, I thought I'd stick with that. My experiments at getting the Maximite to be used to display some basic graphics in the form or rendering monochrome Windows Bitmap (BMP) files showed that it was hopelessly slow rendering the bitmap pixel-by-pixel. I spent a little time thinking about it and decided there was possibly a way to render images in more reasonable time-frames. The following is what I have come up with. For those who feel less inclined to read about the details and just "have a play" - a zipped file with the MMBasic code and a README file can be downloaded here. Essentially, by converting the BMP files into what I have dubbed the "Maximite Picture Format" (MPF), the results are encouraging and I think nifty Splash Screens or even games using bitmapped graphics might be feasible. Some stats to illustrate the results: A single 400x328 pixel image (the "Face" image in my post above) previously rendered in over 50 seconds as a BMP, while as an MPF, the same image is rendered in slightly more than 1 second. The file size is reduced from around 18kB to just under 4kB. The boring details follow (some may care to know)... The 2-step process explained: 1 -> Convert the BMP files to a format more readily renderable in MMBasic. Maximite Picture Format (MPF) works as follows: A program (BMP2MPF.BAS) validates and reads an ordinary monochrome Windows Bitmap file (of dimensions no greater than 480x432 pixels). The data is encoded using a modified Run Length Encoding algorithm such that horizontal "runs" of alike pixel "colours" (black or white) are counted and written out as count/colour pairs. For example, 10 pixels of white followed by 27 pixels of black would be encoded as (10)1(27)0. The count values are written as single byte ASCII characters rather than decimal numbers. Of course a maximum count of only 255 is achievable using this method, hence a modified algorithm to take this and the image width into account. The result is a vastly smaller file (in most cases) which really contains information about the image in a "horizontal vector" type of format. Actual image details will cause variation in file size differences, but the results I have had are MPF files in the range of 80% - 90% smaller than the original BMP file. It is of course possible that an MPF file may be larger than the original BMP file if the image is say of a grey dithered pattern. There is really nothing new about RLE. 2 -> Decode the MPF file for rendering, as follows: The program MPFVIEW.BAS simply reads the Run Length Encoded data from an MPF file, and then using the MMBasic LINE(x1,y)-(x2,y),c statement, renders the vectors in a series of horizontal lines (bottom up) to create the image. MPFVIEW basically shows the decoding algorithm. It takes up less than 1KB of program space and is simple enough to use in any larger program to render previously converted images (either from SD card file of even from an in-memory array). It's quick and pretty easy to use. Converting an image from BMP to MPF can take a while, but this only needs to be done once for an image. I hope this is of some use to somebody and inspires some ideas as to what more this great little machine can do. Cheers, Lance. |
||||
brucepython Regular Member Joined: 19/06/2011 Location: AustraliaPosts: 64 |
Thanks Crackerjack. I've been working on the same challenge but had to press pause for a few weeks due to work and a trip away from home. I got to the stage where I could display full-width BMPs but hadn't conquered those of less than full horizontal width. You sure get some spectacular op art when the code wanders off into its own little dimension! The routine below as its stands draws a full screen in about 38 sec, which is painfully slow. The MPF approach sounds a good one. However, if it's going to become part of the Maximite system an encoding/decoding routine will probably need to be included in Maxi BASIC version umpteen at some stage. I suspect there will always be the need to display standard BMPs without going through the RLE utility first so I'll plug on regardless when time permits. For what it's worth, here's my effort. Some of the comments may clarify what's going on at different stages. 10 ' BMPSHOW: displays a 1-bit BMP image 20 ' from Bruce Mitchell <brucepython@gmail.com> 30 ' 40 ' BMP is a format with many variations and vagaries. 50 ' Don't be surprised if some images appear inverted, negative or 60 ' otherwise weird. 70 ' See en.wikipedia.ord/wiki/BMP_file_format for basic info and some 80 ' useful references. 90 ' Max image size displayed is 480 wide x 432 high. 100 'Larger images might load OK but only a corner will be shown. 110 'It's very slow! 120 ' 130 'Possible enhancements might include choices to display the image as: 140 ' * inverted 150 ' * negative 160 ' * flipped horizontally 170 ' 180 INPUT "BMP file to load: "; f$ 190 ext$=UCASE$(RIGHT$(f$,4)) 200 IF ext$<>".BMP" THEN f$=f$+".BMP" 210 OPEN f$ FOR input AS #1 220 ' 230 FOR ptr=0 TO 9:a$=INPUT$(1,#1):NEXT: ' Skip the signature field. 240 ' 250 'Get data offset. 260 GOSUB 650: offset = byte-1 270 FOR ptr=ptr TO ptr+5:a$=INPUT$(1,#1):NEXT 280 ' 290 GOSUB 650:width=byte 300 FOR ptr=ptr TO ptr+1:a$=INPUT$(1,#1):NEXT 310 GOSUB 650:height=byte 320 ptr=ptr+8 330 ' 340 ' Get colour depth in bits from &1C. 350 FOR ptr=ptr TO ptr+3:a$=INPUT$(1,#1) 360 NEXT 370 GOSUB 650: depth=byte 380 ' 390 IF depth<>1 THEN PRINT "Only 1-bit BMP images are supported.":END 400 ' 410 REM PRINT "Height=";height:' Remove REM if you need to check the image size. 420 REM PRINT "Width =";width:' See above. 430 ' 440 CLOSE #1: OPEN f$ FOR input AS #1:' Reset to start of file. 450 FOR ptr=0 TO offset:a$=INPUT$(1,#1):NEXT:' ptr points to start of bitmap. 460 ' 470 'Copy the bitmap to the screen. 480 CLS 490 FOR y=height-1 TO 0 STEP -1 500 FOR x=0 TO width-8 STEP 8 510 byte=ASC(INPUT$(1,#1)) 520 FOR bit=7 TO 0 STEP -1 530 pxl=NOT(byte AND 2^bit): ' Remove the NOT to get a negative image. 540 PIXEL(x+7-bit,y)=pxl 550 NEXT bit 560 NEXT x 570 NEXT y 580 ' 590 'Indicate job is done and wait for a keypress. 600 SOUND 400,100:PAUSE 150:SOUND 200,100 610 IF INKEY$="" THEN GOTO 610 620 END 630 ' 640 '============ SUBROUTINE =============== 650 ' Get next two bytes from ptr, return as 'byte', then inc file ptr. 660 lsb=ASC(INPUT$(1,#1)):msb=ASC(INPUT$(1,#1)) 670 byte=(msb * 256) + lsb 680 RETURN I may get back to working on the rest of the challenge, but don't hold your breath! I agree with the comments about JPEG images. They're a bit outside the Maximite's capabilities at present, and certainly not in BASIC. And, as you say, who's ever seen a 1-bit JPEG? Or needs one? Bruce |
||||
brucepython Regular Member Joined: 19/06/2011 Location: AustraliaPosts: 64 |
The above code can be speeded up about 10% by combining lines 530 and 540 as follows: Original: 530 pxl=NOT(byte AND 2^bit): ' Remove the NOT to get a negative image. 540 PIXEL(x+7-bit,y)=pxl Replace with: 530 ' 540 PIXEL(x+7-bit,y)=NOT(byte AND 2^bit): ' Remove the NOT to get a negative image. Sorry about that - it's been a few weeks since I looked at it. Bruce |
||||
brucepython Regular Member Joined: 19/06/2011 Location: AustraliaPosts: 64 |
Here's an improved version of the above code: download here (about 270k) . The ZIP file also includes some images to play with. This version displays 1-bit BMPs of any width and height, but if the images are bigger than the screen only the top left corner will appear, and only after the rest of the image has been "plotted" off-screen so you'll have a long wait before anything appears. Despite a considerable increase in plotting speed over the old version, this is still pretty slow. Bruce |
||||
brucepython Regular Member Joined: 19/06/2011 Location: AustraliaPosts: 64 |
A much faster version is now available at this address. Plotting time has been halved for most images, and anything below the display area of a standard MM screen is ignored. Bruce |
||||
Nick Guru Joined: 09/06/2011 Location: AustraliaPosts: 512 |
|
||||
Print this page |