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

I'm trying to create a bitmap (BMP) file programmatically by drawing into a device context with GDI functions and then saving the contents to a BMP file, but I don't know quite how to do this. I have some of the pieces of the puzzle figured out, but not how the whole thing fits together.

I could just try this and try that until I get it to work; indeed, I've done this lots of times before. But even if I do get it working, there would be an uneasy filling that I don't know why it works. So rather than trying randomly I'm asking the folks here for advice. Better to understand the problem before attacking it.

I'd rather not go into specific code at this point; code is OK, but what I really would like is a conceptual understanding of what I'm trying to do. And I'd prefer to use GDI, not GDI+, although I'll use that if that's the only option. (I don't think it is, though.) So maybe use pseudocode or better yet just a good explanation.

So I know how to create a DC and draw into it with GDI. I know how to create a bitmap file, how to deal with all those various structures (BITMAPINFO and friends). What I can't figure out is this: Once I have a DC with my content, and I have the structures to create the BMP file, how do I transfer the contents of the drawn-into DC to the bits of the BMP?

I understand a device context to be an object within Windows that contains a memory bitmap of the image I want. Of course that memory bitmap isn't accessible to the outside world. But somehow I need to transfer that bitmap to my in-memory bitmap. Once I have that I know how to create the BMP file correctly. I know I can use BitBlt() to transfer between two DCs, but that doesn't help me here.

I'd like to be able to create the bitmap as a 16- or 256-color paletted image or a 24-bit image.

The attached diagram shows how I think this should work, and it shows the areas that I don't really understand. It may turn out that my idea of the logic flow here is flawed; if so, feel free to correct it.

Couple things I've dealt with: I considered using CreateDIBSection(), which would give me access to the actual bitmap bits in memory. But I don't think I can use this, because by choosing the DIB_RGB_COLORS flag, I lose the DC parameter, so I don't see how I could connect with my DC. (The other option, DIB_PAL_COLORS, is for indexed colors and is pretty much obsolete, so I can't use that.)

Ditto for CreateCompatibleBitmap(). I can use this to create a bitmap attached to a DC, but then I have no control over the format of the BMP, since "the color format of the bitmap created by the CreateCompatibleBitmap function matches the color format of the device identified by the hdc parameter.".

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

jj2007

Quote from: NoCforMe on January 17, 2024, 08:37:55 AMthere would be an uneasy filling that I don't know why it works

Easy: fill yourself a glass of red wine and read what BitBlt & friends do. There's also M$ Learn :cool:

NoCforMe

Sorry to say this, JJ, but that wasn't very helpful. (Well, the red wine part was!) I'm sure you can do better than that.

The first link may have some clues, and more on that below. The 2nd link I already know all about (dealing with bitmap file data).

That Stack Overflow page is interesting, mainly this:
bool screenCapturePart(int x, int y, int w, int h, LPCSTR fname){
    HDC hdcSource = GetDC(NULL);
    HDC hdcMemory = CreateCompatibleDC(hdcSource);

    int capX = GetDeviceCaps(hdcSource, HORZRES);
    int capY = GetDeviceCaps(hdcSource, VERTRES);

    HBITMAP hBitmap = CreateCompatibleBitmap(hdcSource, w, h);
    HBITMAP hBitmapOld = (HBITMAP)SelectObject(hdcMemory, hBitmap);

    BitBlt(hdcMemory, 0, 0, w, h, hdcSource, x, y, SRCCOPY);
    hBitmap = (HBITMAP)SelectObject(hdcMemory, hBitmapOld);

    DeleteDC(hdcSource);
    DeleteDC(hdcMemory);

    HPALETTE hpal = NULL;
    if(saveBitmap(fname, hBitmap, hpal)) return true;
    return false;
}

Now, this is for capturing the screen, so I can just substitute my own DC for the first one (they used GetDC(NULL) to capture the screen, I can just create a DC and draw into it). And it will eventually give me a handle to a BMP which I can deal with.

But one problem (at least): how do I dictate the format (color depth) of the bitmap? Or can I even, or am I stuck with whatever format my DC is in?

These are the kinds of questions I need answers to.
Assembly language programming should be fun. That's why I do it.

NoCforMe

I may have figured out part of this. Regarding the format (color depth) of the bitmap file, since the format I want may be different from the DC format, it turns out that BitBlt() will take care of this for me, since according to Micro$oft
QuoteIf the color formats of the source and destination device contexts do not match, the BitBlt function converts the source color format to match the destination format.

So I might go ahead and try that code above and see how it works.
Assembly language programming should be fun. That's why I do it.

HSE

Hi NoC!

Quote from: NoCforMe on January 17, 2024, 08:37:55 AMtrying to create a bitmap (BMP) file programmatically by drawing into a device context with GDI

  MAthArt: 32 / 64 bit demo program ...

  MathArt's Interface and the BITMAP thing

Not exactly what you want, but perhaps some ideas.

Regards, HSE

Equations in Assembly: SmplMath

NoCforMe

Thanks, HSE, but all of that is waaaay too complicated.

I wish someone would approach my questions here from a conceptual standpoint, not with complex code examples. Surely someone here understands how GDI works better than I do.
Assembly language programming should be fun. That's why I do it.

sinsi

Quote from: NoCforMe on January 17, 2024, 08:37:55 AMSo I know how to create a DC and draw into it with GDI.
Can you post some code that shows how?

You may have to get all of the sections assembled, convert the palette and write everything.
🍺🍺🍺

NoCforMe

At this point I really, really would rather engage in a general discussion of GDI, device contexts, etc. I want to know how these things work. I think I have at this point a pretty good general idea of how to pull this project off, but there are nagging questions. The current one is this: assuming I can get those DC bits transferred to my BMP bits, what about the palette (assuming a 16- or 256-color image)? Do I need to construct the palette myself? (I know how to do that, but am not sure of how it fits in with the overall scheme.) Or can I somehow magically retrieve a palette that's been constructed for the image?

Do you see what I'm getting at here?

Yes, I will have to assemble all the sections (BMP file header, BMPINFO header, palette and bitmap bits) myself. I do know how to do that.
Assembly language programming should be fun. That's why I do it.

sinsi

So, as I see it, you just need to convert a palette?
🍺🍺🍺

NoCforMe

I need to get a palette. If, for instance, I know the image is grayscale, 256 colors, then I can easily create a palette. But I'm not sure I can know that.

I'll reveal a little bit more of what I'm trying to do: I plan on writing text into the DC using GDI. I want bitmaps of font characters. So I have no idea what colors I'm going to get when I render text into my DC. Will it be grayscale? or a whole rainbow of colors? (which is likely if the font quality is CLEARTYPE_QUALITY.)

So I need to know: how do I obtain the palette for the BMP image? Will it be generated by the OS and somehow come from the DC? or do I need to create it?

Of course I could sidestep this issue for the time being by creating a 24-bit bitmap, which has no palette. But I still won't know how to deal with paletted images, which I definitely want to be able to do.

Which is just one of the conceptual issues I'm dealing with here.

Even 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.
Assembly language programming should be fun. That's why I do it.

daydreamer

If you want rainbow colored text, creating 256 color rainbow palette and write to empty. Bmp file to start with
768 bytes palette included in the start of 8 bit. Bmp file
Invoke loadbitmap
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

_japheth

Quote from: NoCforMe on January 17, 2024, 05:06:01 PMI need to get a palette.
...
Even 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.

Well, even if it doesn't is what you want, here's a small sample with just 2 procs that writes the desktop screen to a .bmp file. It's very simple, using a generated filename in the TEMP directory. But it handles the "palette case".


;--- save graphics screen as a .BMP file

.386
.Model Flat, StdCall
Option Casemap :None   

Include \masm32\include\windows.inc
Include \masm32\include\kernel32.inc
INCLUDE \masm32\include\Gdi32.inc
Include \masm32\include\user32.inc
include macros.inc

IncludeLib \masm32\lib\kernel32.lib
INCLUDELIB \masm32\lib\Gdi32.lib
IncludeLib \masm32\lib\user32.lib

.code

SaveBitmapAsFile proc uses ebx hFile:dword, hdc:DWORD, hBitmap:DWORD

local dwSize:DWORD
local dwSizeClr:DWORD
local dwWritten:DWORD
local lpBits:DWORD
local rgbquad[255]:RGBQUAD
local bmi:BITMAPINFO
local bmfh:BITMAPFILEHEADER

mov lpBits, 0
invoke RtlZeroMemory, addr bmfh, sizeof BITMAPFILEHEADER
invoke RtlZeroMemory, addr bmi.bmiHeader, sizeof BITMAPINFOHEADER
mov bmi.bmiHeader.biSize,sizeof BITMAPINFOHEADER
;--- set biBitCount to 0 to get the current bitmap format
mov bmi.bmiHeader.biBitCount,0
mov bmi.bmiHeader.biCompression,BI_RGB
invoke GetDIBits, hdc, hBitmap, 0, 0, 0, addr bmi, DIB_RGB_COLORS
.if (!eax)
invoke MessageBox, 0, CStr("1. call of GetDIBits() failed"), 0, MB_OK
            jmp exit
        .endif
mov eax, bmi.bmiHeader.biHeight
mul bmi.bmiHeader.biWidth
movzx ecx, bmi.bmiHeader.biBitCount
mul ecx
shr eax, 3
mov dwSize, eax

invoke LocalAlloc, LMEM_FIXED, dwSize
.if (!eax)
invoke MessageBox, 0, CStr("insufficient memory"), 0, MB_OK
            jmp exit
.endif
mov lpBits, eax

invoke GetDIBits, hdc, hBitmap, 0, bmi.bmiHeader.biHeight, lpBits, addr bmi, DIB_RGB_COLORS
        .if (!eax)
invoke MessageBox, 0, CStr("2. call of GetDIBits() failed"), 0, MB_OK
            jmp exit
        .endif

.if (bmi.bmiHeader.biBitCount == 8)
mov eax, sizeof RGBQUAD * 256
.elseif (bmi.bmiHeader.biCompression == BI_BITFIELDS)
mov eax, sizeof DWORD * 3
.else
xor eax, eax
.endif
mov dwSizeClr, eax

mov bmfh.bfType, "MB"
        mov eax, dwSize
mov bmfh.bfSize, eax
        mov eax, dwSizeClr
lea eax, [eax + sizeof BITMAPFILEHEADER + sizeof BITMAPINFOHEADER]
mov bmfh.bfOffBits, eax
invoke WriteFile, hFile, addr bmfh, sizeof BITMAPFILEHEADER, addr dwWritten, 0

invoke WriteFile, hFile, addr bmi.bmiHeader, sizeof BITMAPINFOHEADER, addr dwWritten, 0
.if (dwSizeClr)
invoke WriteFile, hFile, addr bmi.bmiColors, dwSizeClr, addr dwWritten, 0
.endif
invoke WriteFile, hFile, lpBits, dwSize, addr dwWritten, 0
exit:
.if (lpBits)
invoke LocalFree, lpBits
        .endif
ret
SaveBitmapAsFile endp
       

CreateScreenBitmap proc

    local hDCSrc ,hDCMemory, hBitmap
    local dwWidth, dwHeight
    local hFile, dwFileIdx
local szTempPath[MAX_PATH]:byte
local szTempName[MAX_PATH]:byte

    invoke GetSystemMetrics,SM_CXSCREEN
    mov dwWidth,eax
    invoke GetSystemMetrics,SM_CYSCREEN
    mov dwHeight,eax
    invoke GetDC,0
    mov hDCSrc,eax   
    invoke CreateCompatibleDC,eax
    mov hDCMemory,eax
    invoke CreateCompatibleBitmap,hDCSrc,dwWidth,dwHeight
    mov hBitmap,eax
    invoke SelectObject,hDCMemory,eax
    invoke BitBlt,hDCMemory,0,0,dwWidth,dwHeight,hDCSrc,0,0,SRCCOPY

invoke GetTempPath, MAX_PATH, addr szTempPath
    mov dwFileIdx, 0
.while (1)
inc dwFileIdx
        invoke wsprintf, addr szTempName, CStr("%s\~hx%u.BMP"), addr szTempPath, dwFileIdx
invoke CreateFile, addr szTempName, GENERIC_WRITE, 0, 0, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0
.break .if (eax != -1)
.endw
mov hFile, eax
    invoke SaveBitmapAsFile, hFile, hDCMemory, hBitmap
invoke CloseHandle, hFile
    invoke ReleaseDC,0,hDCSrc
    invoke DeleteDC, hDCMemory
    invoke DeleteObject, hBitmap
    ret
   
CreateScreenBitmap endp

start:
   invoke CreateScreenBitmap
   invoke ExitProcess,NULL

end start

Dummheit, gepaart mit Dreistigkeit - eine furchtbare Macht.

Greenhorn

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.
Kole Feut un Nordenwind gift en krusen Büdel un en lütten Pint.

Vortex

Hi NoCforMe,

Maybe this code can be helpful for you, this command line tools captures the desktop and saves it to a file :

; References :
; https://msdn.microsoft.com/en-us/library/windows/desktop/dd145119%28v=vs.85%29.aspx
; https://msdn.microsoft.com/en-us/library/windows/desktop/dd183402%28v=vs.85%29.aspx

include     SaveDesktop.inc

.data
message     db 'SaveDesktop.exe filename.bmp [optional-32bits]',0

.data?
buffer      db 512 dup(?)

.code

start PROC

    call    main
    invoke  ExitProcess,0

start ENDP

main PROC uses rsi rdi

LOCAL argc       :QWORD
LOCAL hwnd       :QWORD
LOCAL pMem       :QWORD
LOCAL hBmp       :QWORD
LOCAL hDC        :QWORD
LOCAL tempDC     :QWORD
LOCAL x          :QWORD
LOCAL y          :QWORD
LOCAL hOldBmp    :QWORD
LOCAL hFile      :QWORD
LOCAL BmpSize    :QWORD
LOCAL BWritten   :QWORD
LOCAL SizeofDIB  :QWORD
LOCAL bm         :BITMAP
LOCAL Bits       :QWORD
LOCAL BmpFileHdr :BITMAPFILEHEADER
LOCAL BmpInfoHdr :BITMAPINFOHEADER

    lea     rsi,buffer
    invoke  ParseCmdLine,rsi
    cmp     rax,2
    jnb     @f

    invoke  StdOut,ADDR message
    ret
@@:
    mov     argc,rax

    invoke  GetConsoleWindow
    mov     hwnd,rax
    invoke  ShowWindow,rax,SW_HIDE
    invoke  Sleep,300

    lea     rdi,bm
    invoke  GetDC,0                                 ; get the DC of desktop
    mov     hDC,rax
    invoke  CreateCompatibleDC,rax                  ; create a DC compatible with hDC
    mov     tempDC,rax
   
    invoke  GetSystemMetrics,SM_CXSCREEN            ; get the width and height of the desktop
    mov     x,rax
    invoke  GetSystemMetrics,SM_CYSCREEN
    mov     y,rax
   
    invoke  CreateCompatibleBitmap,hDC,x,rax        ; create a compatible bitmap
    mov     hBmp,rax
    invoke  SelectObject,tempDC,rax
    mov     hOldBmp,rax

    invoke  BitBlt,tempDC,0,0,x,y,hDC,\
            0,0,SRCCOPY                             ; copy the screen to the target DC

    invoke  GetObject,hBmp,sizeof(BITMAP),rdi       ; the BITMAP structure of the copied image

    cmp     argc,3
    je      @f
    mov     BITMAP.mBitsPixel[rdi],24               ; Fill the BITMAP structure
@@:
    xor     rdx,rdx
    movzx   rax,BITMAP.mPlanes[rdi]
    mul     BITMAP.mBitsPixel[rdi]
    mov     Bits,rax

    lea     rcx,BmpInfoHdr

    mov     BITMAPINFOHEADER.biSize[rcx],\
            sizeof(BITMAPINFOHEADER)

    mov     rax,x
    mov     BITMAPINFOHEADER.biWidth[rcx],eax

    mov     rax,y
    mov     BITMAPINFOHEADER.biHeight[rcx],eax
   
    mov     ax,BITMAP.mPlanes[rdi]
    mov     BITMAPINFOHEADER.biPlanes[rcx],ax

    mov     ax,BITMAP.mBitsPixel[rdi]
    mov     BITMAPINFOHEADER.biBitCount[rcx],ax

    xor     rax,rax                                 ; Fill the BITMAPINFOHEADER structure
    mov     BITMAPINFOHEADER.biCompression[rcx],BI_RGB   
    mov     BITMAPINFOHEADER.biSizeImage[rcx],eax
    mov     BITMAPINFOHEADER.biXPelsPerMeter[rcx],eax
    mov     BITMAPINFOHEADER.biYPelsPerMeter[rcx],eax 
    mov     BITMAPINFOHEADER.biClrUsed[rcx],eax
    mov     BITMAPINFOHEADER.biClrImportant[rcx],eax

    xor     rdx,rdx
    xor     rax,rax
    mov     eax,BITMAP.bmWidth[rcx]

;   Bitmap size = (( BITMAPINFO.bmiHeader.biWidth * BITMAP.bmPlanes * BITMAP.bmBitsPixel + 31 ) & ~31 ) / 8 *
;                    BITMAPINFO.bmiHeader.biHeight

;   ~31 = NOT 31 = NOT 00011111 = 11100000

;   BITMAP.bmWidthBytes : The number of bytes in each scan line.
;   This value must be divisible by 2,because the system assumes
;   that the bit values of a bitmap form an array that is word aligned.

;   shl     rax,2
 
    mul     Bits
    add     rax,31
    and     rax,-32                                  ; align the width to the
    shr     rax,3                                    ; next DWORD boundry
    mul     BITMAP.bmHeight[rcx]
    mov     BmpSize,rax

    invoke  VirtualAlloc,0,rax,MEM_COMMIT,\
            PAGE_READWRITE

    mov     pMem,rax

    invoke  GetDIBits,tempDC,hBmp,0,\
            QWORD PTR BITMAP.bmHeight[rdi],\
            pMem,ADDR BmpInfoHdr,DIB_RGB_COLORS

    xor     rax,rax
    invoke  CreateFile,QWORD PTR [rsi+8],\          ; write the bitmap structures and the color info
            GENERIC_WRITE,rax,rax,CREATE_ALWAYS,\   ; to the disc
            rax,rax

    mov     hFile,rax

    mov     rax,BmpSize
   
    add     rax,\
            sizeof(BITMAPFILEHEADER) + \
            sizeof(BITMAPINFOHEADER)

    mov     SizeofDIB,rax

    lea     rdx,BmpFileHdr
    mov     BITMAPFILEHEADER.bfOffBits[rdx],\
            sizeof(BITMAPFILEHEADER) + \
            sizeof(BITMAPINFOHEADER)

    mov     rax,SizeofDIB
    mov     BITMAPFILEHEADER.bfSize[rdx],eax

    mov     BITMAPFILEHEADER.bfReserved1[rdx],0
    mov     BITMAPFILEHEADER.bfReserved2[rdx],0

    mov     BITMAPFILEHEADER.bfType[rdx],'MB'

    lea     rsi,BWritten
    invoke  WriteFile,hFile,ADDR BmpFileHdr,\
            sizeof(BITMAPFILEHEADER),rsi,0
           
    invoke  WriteFile,hFile,ADDR BmpInfoHdr,\
            sizeof(BITMAPINFOHEADER),rsi,0
           
    invoke  WriteFile,hFile,pMem,BmpSize,rsi,0

    invoke  CloseHandle,hFile

    invoke  VirtualFree,pMem,0,MEM_RELEASE          ; return back resources to the OS
    invoke  SelectObject,tempDC,hOldBmp             ; return back the old handle
    invoke  DeleteObject,hBmp
    invoke  DeleteDC,tempDC
    invoke  ReleaseDC,0,hDC

    invoke  ShowWindow,hwnd,SW_SHOW
    ret
   
main ENDP

END

jj2007

Quote from: NoCforMe on January 17, 2024, 08:58:58 AMSorry to say this, JJ, but that wasn't very helpful. (Well, the red wine part was!) I'm sure you can do better than that.

Sure:
- press Alt PrintSc
- launch attached SaveImage.exe
- size the image according to your taste
- right-click and save as test.bmp (or whatever.png or image.jpg or my.gif)


include \masm32\MasmBasic\Res\MbGui.asm
GuiControl MyCanvas, "canvas"
Event CanvasPaint
  GuiImage clipboard, fit
GuiEnd