News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Creating a bitmap programmatically: how?

Started by NoCforMe, January 17, 2024, 08:37:55 AM

Previous topic - Next topic

NoCforMe

Quote from: Greenhorn on January 18, 2024, 02:32:38 AM
Quote from: NoCforMe on January 17, 2024, 05:06:01 PMEven if someone came along 5 minutes from now and gave me code that works, that's not what I want. I want to understand how this all works.

Progamming Windows, 5th Edition

Read the section about Bitmaps, that should help.

No. No! Nyet! No no no!

Goddamnit, I'm starting to get pissed. NONE OF THIS HELPS!

I have Petzold's book (the physical one, AKA "the doorstop" or "the boat anchor"). I've read the stuff about bitmaps. None of it helps in this case.

So unless you can actually answer my questions without posting unexplained chunks of code, maybe just STFU, OK?
Assembly language programming should be fun. That's why I do it.

HSE

Equations in Assembly: SmplMath

NoCforMe

I'm working on it. Tomorrow I'll probably be in more of a programming mood. I have a glimmer of a clue what to do. Will report when I know for sure.
Assembly language programming should be fun. That's why I do it.

_japheth

QuoteNo. No! Nyet! No no no!

Goddamnit, I'm starting to get pissed. NONE OF THIS HELPS!

....

So unless you can actually answer my questions without posting unexplained chunks of code, maybe just STFU, OK?

This is getting a bit silly. A member with such a huge post count like yourself and unable or unwilling to look into 50-lines of Win32 code samples? Had you done that, you would have realized that there's more or less just ONE Windows API function ( GetDIBits() ) that you are to fully understand and all your "questions" are answered.



Dummheit, gepaart mit Dreistigkeit - eine furchtbare Macht.

NoCforMe

Quote from: _japheth on January 18, 2024, 05:21:31 PMHad you done that, you would have realized that there's more or less just ONE Windows API function ( GetDIBits() ) that you are to fully understand and all your "questions" are answered.

I am coming to the realization that GetDIBits() is what I've been looking for. If only you (or someone else) had mentioned this earlier. Anyhow, more later ...
Assembly language programming should be fun. That's why I do it.

HSE

You have to read the code people take time to post. If you can not download the code... you are alone.
Equations in Assembly: SmplMath

daydreamer

my none asm creations
https://masm32.com/board/index.php?topic=6937.msg74303#msg74303
I am an Invoker
"An Invoker is a mage who specializes in the manipulation of raw and elemental energies."
Like SIMD coding

Greenhorn

Quote from: NoCforMe on January 18, 2024, 12:45:14 PMGoddamnit, I'm starting to get pissed. NONE OF THIS HELPS!

...

So unless you can actually answer my questions without posting unexplained chunks of code, maybe just STFU, OK?

OK, got it. You established to get on my ignore list.
Kole Feut un Nordenwind gift en krusen Büdel un en lütten Pint.

NoCforMe

Quote from: HSE on January 18, 2024, 10:29:41 PMYou have to read the code people take time to post. If you can not download the code... you are alone.

That would be true, if it was code I was asking for. If you'll notice, I specifically asked for conceptual help with my problem, not code. Go back through my posts and you'll see I've been very consistent about that. I want to know how the process works--in words--without having to plow through pages of code (which may or may not explain what I'm trying to understand). Do you get that?

But nobody seemed to get that. So now I'm the bad guy because I won't read every line of code that everyone posts here, eh? Well, whatever.

I am starting to work on the code now, so we'll see what develops.
Assembly language programming should be fun. That's why I do it.

HSE

> Do you get that?

:biggrin:  You don't get that you must have Vortex's code at hand.

A good opportunity to remark that for new members  :thumbsup:
Equations in Assembly: SmplMath

NoCforMe

Quote from: HSE on January 19, 2024, 11:26:19 AM:biggrin:  You don't get that you must have Vortex's code at hand.
Why? Remember, I didn't ask for code, I asked for words.

Besides, I do have some of Vortex's code here (related to bitmaps), and I have to say that it has helped me immensely in the past, so I do appreciate that.

Coding stuff up, getting closer ...
Assembly language programming should be fun. That's why I do it.

NoCforMe

OK, I give up. I started coding this up, was able to create a bitmap file, but it came out blank (all black).

I'm posting code below, the core of the program which creates the bitmap. First, here's what I'm doing, in words, not code:
  • Render text into a window
  • Get DCs: GetDC() on the window (srcDC), CreateCompatibleDC() from it (bmpDC)
  • Creating the bitmap using CreateBitmap(). (Here's where problems arise: see below)
  • Select the bitmap into the 2nd DC (bmpDC)
  • BitBlt() from srcDC--> bmpDC
  • Construct the header structure for the BMP file
  • Transfer the bits from the DC to my file buffer with GetDIBits()
  • Compute total file size, create the BMP file, write the file buffer to the file

Problems:
1. I've tried 3 ways of creating the bitmap (the internal structure, not the BMP file). The first one runs but creates a blank (black) bitmap; the other two crash the program with exceptions. Here they are:
; 1st call creates blank (black) bitmap; other 2 cause exceptions:
    INVOKE    CreateBitmap, BMPwidth, BMPheight, 1, BMPbpp, OutputBMPbuffer
;    INVOKE    CreateCompatibleBitmap, bmpDC, BMPwidth, BMPheight
;    INVOKE    CreateDIBSection, bmpDC, ADDR bmi, DIB_RGB_COLORS, ADDR bmpBitsPtr, NULL, 0
    MOV    bmpHandle, EAX
Part of the problem is that I really don't know whether I need a DIB or a DDB. And yes, I've read all about that in Petzold, the differences between device-independent and device-dependent bitmaps. I still do not really understand the difference. It's a big problem since Petzold's book is so out-of-date: most of the stuff regarding DIB/DDB is long since obsolete (no more 16-color displays). So I would really appreciate it if someone could help by explaining the difference, in words, not code!

2. Not sure if GetDIBits() is actually transferring anything to my buffer or not. Oh, and I have figured one thing out, something which is really poorly explained by all of the documentation I've seen on this subject: that function transfers both the actual bitmap bits AND the palette, if there is one. At least it's supposed to ...

I know the text is being rendered in the text-rendering window because I can see it.

So here's the code. Have at it; go nuts; tear the shit out of it. Oh, BTW, "$BMPHEADER" is my private structure that combines the BITMAPFILEHEADER and BITMAPINFOHEADER into one chunk for ease of use. It's been verified through previous usage.
;====================================
; MakeTheBMP()
;
; On entry, the following globals are set:
;  o BMPwidth, BMPheight: size of bitmap to create
;  o BMPbpp: bits/pixel (currently set to 8 for 256-color BMP)
;  o TextRenderHandle is a handle to text-rendering window
;  o OutputBMPbuffer is a pointer to heap-allocated memory (128 Kb)
;====================================

MakeTheBMP    PROC
    LOCAL    srcDC:HDC, bmpDC:HDC, bitsSize:DWORD, bmpHandle:HBITMAP, actualBMPsize:DWORD
    LOCAL    bmpBitsPtr:DWORD, bmpFileHandle:HFILE, dummy:DWORD, bm:BITMAP, bmi:BITMAPINFO
    local    buffer[256]:byte

; Set up BITMAPINFO struct for GetDIBits():
    MOV    bmi.bmiHeader.biSize, SIZEOF BITMAPINFOHEADER
    MOV    EAX, BMPwidth
    MOV    bmi.bmiHeader.biWidth, EAX
    MOV    EAX, BMPheight
    MOV    bmi.bmiHeader.biHeight, EAX
    MOV    bmi.bmiHeader.biPlanes, 1
    MOV    EAX, BMPbpp
    MOV    bmi.bmiHeader.biBitCount, AX
    MOV    bmi.bmiHeader.biCompression, BI_RGB
    MOV    bmi.bmiHeader.biSizeImage, 0
    MOV    bmi.bmiHeader.biXPelsPerMeter, 0
    MOV    bmi.bmiHeader.biYPelsPerMeter, 0
    MOV    bmi.bmiHeader.biClrUsed, 0
    MOV    bmi.bmiHeader.biClrImportant, 0

; Render the text into the rendering window:
    INVOKE    InvalidateRect, TextRenderHandle, NULL, FALSE

    INVOKE    GetDC, TextRenderHandle
    MOV    srcDC, EAX
    INVOKE    CreateCompatibleDC, EAX
    MOV    bmpDC, EAX

; 1st call creates blank (black) bitmap; other 2 cause exceptions:
    INVOKE    CreateBitmap, BMPwidth, BMPheight, 1, BMPbpp, OutputBMPbuffer
;    INVOKE    CreateCompatibleBitmap, bmpDC, BMPwidth, BMPheight
;    INVOKE    CreateDIBSection, bmpDC, ADDR bmi, DIB_RGB_COLORS, ADDR bmpBitsPtr, NULL, 0
    MOV    bmpHandle, EAX
    INVOKE    SelectObject, bmpDC, bmpHandle

    INVOKE    BitBlt, bmpDC, 0, 0, BMPwidth, BMPheight, srcDC, 0, 0, SRCCOPY

; Calculate actual bitmap size including any needed padding:
    MOV    EAX, BMPwidth
    MUL    BMPbpp
    ADD    EAX, 31
    AND    EAX, NOT 31
    SHR    EAX, 3
    MUL    BMPheight
    MOV    actualBMPsize, EAX

; Construct BMP file header:
    PUSH    EDI
    MOV    EDI, OutputBMPbuffer        ;Point to heap-allocated buffer.

    MOV    [EDI].$BMPHEADER.bfType, "MB"
    MOV    [EDI].$BMPHEADER.bfHdrSize, 40
    MOV    EAX, BMPwidth
    MOV    [EDI].$BMPHEADER.bfWidth, EAX
    MOV    EAX, BMPheight
    MOV    [EDI].$BMPHEADER.bfHeight, EAX
    MOV    [EDI].$BMPHEADER.bfPlanes, 1
    MOV    EAX, BMPbpp
    MOV    [EDI].$BMPHEADER.bfBitCount, AX
    MOV    [EDI].$BMPHEADER.bfCompression, BI_RGB
    MOV    EAX, actualBMPsize
;    MOV    [EDI].$BMPHEADER.bfSizeImage, EAX
    MOV    [EDI].$BMPHEADER.bfSizeImage, 0
    MOV    [EDI].$BMPHEADER.bfXPelsPerMeter, 0
    MOV    [EDI].$BMPHEADER.bfYPelsPerMeter, 0
    CMP    BMPbpp, 24
    JNE    @F
    XOR    EAX, EAX            ;24bpp = no palette.
    JMP    SHORT setCus

@@:    MOV    EAX, 1
    MOV    ECX, BMPbpp
    SHL    EAX, CL
setCus:    MOV    [EDI].$BMPHEADER.bfClrUsed, EAX
    MOV    [EDI].$BMPHEADER.bfClrImportant, 0

    invoke    wsprintf, addr buffer, offset actualsizefmt, actualBMPsize
    invoke    MessageBox, NULL, addr buffer, NULL, MB_OK

; Copy bitmap bits from DC to file buffer:
    MOV    EDX, EDI
    ADD    EDX, SIZEOF $BMPHEADER
    INVOKE    GetDIBits, bmpDC, bmpHandle, 0, BMPheight, EDX,
        ADDR bmi, DIB_RGB_COLORS

; Calculate total file size:
    MOV    EAX, BMPbpp
    CMP    EAX, 4
    JE    is4
    CMP    EAX, 8
    JE    is8

; Must be 24bpp, so no palette:
    XOR    EAX, EAX
    JMP    SHORT @F

is4:    MOV    EAX, 16 * 4
    JMP    SHORT @F

is8:    MOV    EAX, 256 * 4
@@:    ADD    EAX, SIZEOF $BMPHEADER
    MOV    [EDI].$BMPHEADER.bfOffBits, EAX
    ADD    EAX, actualBMPsize
    MOV    [EDI].$BMPHEADER.bfFileSize, EAX

    INVOKE    CreateFile, OFFSET BMPfilename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL, NULL
    MOV    bmpFileHandle, EAX
    INVOKE    WriteFile, bmpFileHandle, EDI, [EDI].$BMPHEADER.bfFileSize, ADDR dummy, NULL
    INVOKE    CloseHandle, bmpFileHandle

    INVOKE    ReleaseDC, TextRenderHandle, srcDC
    INVOKE    DeleteDC, bmpDC
    INVOKE    DeleteObject, bmpHandle

    POP    EDI

exit99:    RET

MakeTheBMP    ENDP





Assembly language programming should be fun. That's why I do it.

_japheth

Well, the first problem is: CreateCompatibleBitmap() must be used, not CreateBitmap().

Perhaps you should make yourself familar with the concept of Device Independent bitmaps?

hint: When calling CreateCompatibleBitmap(), you have to supply a "real" device context as parameter, not a memory context ( because the latter is rather "small" after creation, just 1 "pixel" ).
Dummheit, gepaart mit Dreistigkeit - eine furchtbare Macht.

Vortex

#28
Hi NoCforMe,

Your list of the steps to create a bitmap looks OK. Japheth is right, you should focus on device independent bitmaps :

QuoteAccording to Microsoft support:[5]

A device-independent bitmap (DIB) is a format used to define device-independent bitmaps in various color resolutions. The main purpose of DIBs is to allow bitmaps to be moved from one device to another (hence, the device-independent part of the name). A DIB is an external format, in contrast to a device-dependent bitmap, which appears in the system as a bitmap object (created by an application...). A DIB is normally transported in metafiles (usually using the StretchDIBits() function), BMP files, and the Clipboard (CF_DIB data format).

https://en.wikipedia.org/wiki/BMP_file_format

QuoteThe device-independent bitmap (DIB) is by far the more widely used for in-memory operations. It has become the common denominator format because it is so simple: it describes just the image, without reference to characteristics of the device(s) upon which it may later be displayed.

https://help.accusoft.com/ImageGear/v18.2/Windows/ActiveX/IGAX-08-08.html

I modified my SaveDesktop example to meet your first requirement : Render text into a window.

The attached application displays a window and expects keyboard input to save the client area to a bitmap file.

NoCforMe

OK, new day, some progress.

Warning: Like many of my posts, you may find this TL:DR. Fine, but I ask one thing of you: if you're going to comment on what I've written, then please read through the entire thing. I find it very annoying when folks make comments but they obviously haven't read what I've written!

So, I successfully wrote a BMP file. Here's what I've learned:

@japheth is absolutely correct: I needed to use CreateCompatibleBitmap() instead of CreateBitmap(), and I needed to use my original source DC (a "real" DC) as opposed to the memory DC I created.

In so doing, I had to change my logic. What I was trying to do, which failed miserably, was to force the bitmap to my preferred format (8 bits/pixel in this case). This didn't work, since the bitmap created had the same format as the source DC, which was 32 bits/pixel as would be expected of a display DC. So I used the actual format (returned by GetObject() on the bitmap in the source DC) for the subsequent operations, most importantly GetDIBits() which was failing and crashing the program.

So now I know how to do that, and I think I understand what's going on "behind the scenes" here, which as I've repeatedly stated is what I'm really after here, not just code that works.

But I'm not satisfied. I would still like to know if it's possible for me to create a bitmap here which is not in the same format as my source DC (32bpp). What if I want to create a 256-color bitmap? Is that possible? (With GDI, remember, not GDI+.) Or am I stuck with only being able to use the "native" format here?

I suppose it's not really a show-stopper, as it's super-easy to load the BMP into Paint Shop Pro and downsample it. But it'd be a nice feature.

Here's the code I ended up with that works:
; Render the text into the rendering window:
    INVOKE    InvalidateRect, TextRenderHandle, NULL, FALSE

    INVOKE    GetDC, TextRenderHandle
    MOV    srcDC, EAX
    INVOKE    CreateCompatibleDC, EAX
    MOV    bmpDC, EAX

; Measure the text we've drawn to get the size of the bitmap:
    INVOKE    SelectObject, srcDC, FontHandle
    MOV    EAX, OFFSET TextBuffer
    CALL    strlen
    MOV    EDX, EAX
    INVOKE    GetTextExtentPoint32, srcDC, OFFSET TextBuffer, EDX, ADDR sizer
    MOV    EAX, sizer.x

; Make sure text isn't wider than the rendering window:
    CMP    EAX, TextRenderWinWidth
    JBE    @F
    MOV    EAX, TextRenderWinWidth
@@:    MOV    BMPwidth, EAX
    MOV    EAX, sizer.y
    MOV    BMPheight, EAX

    INVOKE    CreateCompatibleBitmap, srcDC, BMPwidth, BMPheight
    MOV    bmpHandle, EAX

    invoke    GetObject, bmpHandle, SIZEOF BITMAP, addr bm
    movzx    eax, bm.bmBitsPixel
    mov    BMPbpp, eax

; Set up BITMAPINFO struct for GetDIBits():
    MOV    bmi.bmiHeader.biSize, SIZEOF BITMAPINFOHEADER
    MOV    EAX, BMPwidth
    MOV    bmi.bmiHeader.biWidth, EAX
    MOV    EAX, BMPheight
    MOV    bmi.bmiHeader.biHeight, EAX
    MOV    bmi.bmiHeader.biPlanes, 1
    MOV    EAX, BMPbpp
    MOV    bmi.bmiHeader.biBitCount, AX
    MOV    bmi.bmiHeader.biCompression, BI_RGB
    MOV    bmi.bmiHeader.biSizeImage, 0
    MOV    bmi.bmiHeader.biXPelsPerMeter, 0
    MOV    bmi.bmiHeader.biYPelsPerMeter, 0
    MOV    bmi.bmiHeader.biClrUsed, 0
    MOV    bmi.bmiHeader.biClrImportant, 0

    INVOKE    SelectObject, bmpDC, bmpHandle

    INVOKE    BitBlt, bmpDC, 0, 0, BMPwidth, BMPheight, srcDC, 0, 0, SRCCOPY

; Calculate actual bitmap size including any needed padding:
    MOV    EAX, BMPwidth
    MUL    BMPbpp
    ADD    EAX, 31
    AND    EAX, NOT 31
    SHR    EAX, 3
    MUL    BMPheight
    MOV    actualBMPsize, EAX

; Construct BMP file header:
    PUSH    EDI
    MOV    EDI, OutputBMPbuffer        ;Point to heap-allocated buffer.

    MOV    [EDI].$BMPHEADER.bfType, "MB"
    MOV    [EDI].$BMPHEADER.bfHdrSize, 40
    MOV    EAX, BMPwidth
    MOV    [EDI].$BMPHEADER.bfWidth, EAX
    MOV    EAX, BMPheight
    MOV    [EDI].$BMPHEADER.bfHeight, EAX
    MOV    [EDI].$BMPHEADER.bfPlanes, 1
    MOV    EAX, BMPbpp
    MOV    [EDI].$BMPHEADER.bfBitCount, AX
    MOV    [EDI].$BMPHEADER.bfCompression, BI_RGB
    MOV    EAX, actualBMPsize
;    MOV    [EDI].$BMPHEADER.bfSizeImage, EAX
    MOV    [EDI].$BMPHEADER.bfSizeImage, 0
    MOV    [EDI].$BMPHEADER.bfXPelsPerMeter, 0
    MOV    [EDI].$BMPHEADER.bfYPelsPerMeter, 0
    CMP    BMPbpp, 24
    JNE    @F
    XOR    EAX, EAX            ;24bpp = no palette.
    JMP    SHORT setCus

@@:    MOV    EAX, 1
    MOV    ECX, BMPbpp
    SHL    EAX, CL
setCus:    MOV    [EDI].$BMPHEADER.bfClrUsed, EAX
    MOV    [EDI].$BMPHEADER.bfClrImportant, 0

; Copy bitmap bits from DC to file buffer:
    MOV    EDX, EDI
    ADD    EDX, SIZEOF $BMPHEADER
    INVOKE    GetDIBits, bmpDC, bmpHandle, 0, BMPheight, EDX,
        ADDR bmi, DIB_RGB_COLORS

; Calculate total file size:
    MOV    EAX, BMPbpp
    CMP    EAX, 4
    JE    is4
    CMP    EAX, 8
    JE    is8

; Must be 24bpp or greater, so no palette:
    XOR    EAX, EAX
    JMP    SHORT @F

is4:    MOV    EAX, 16 * 4
    JMP    SHORT @F

is8:    MOV    EAX, 256 * 4
@@:    ADD    EAX, SIZEOF $BMPHEADER
    MOV    [EDI].$BMPHEADER.bfOffBits, EAX
    ADD    EAX, actualBMPsize
    MOV    [EDI].$BMPHEADER.bfFileSize, EAX

    INVOKE    CreateFile, OFFSET BMPfilename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL, NULL
    MOV    bmpFileHandle, EAX
    INVOKE    WriteFile, bmpFileHandle, EDI, [EDI].$BMPHEADER.bfFileSize, ADDR dummy, NULL
    INVOKE    CloseHandle, bmpFileHandle

    INVOKE    ReleaseDC, TextRenderHandle, srcDC
    INVOKE    DeleteDC, bmpDC
    INVOKE    DeleteObject, bmpHandle

    POP    EDI
Assembly language programming should be fun. That's why I do it.