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!
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 (https://stackoverflow.com/questions/9524393/how-to-capture-part-of-the-screen-and-save-it-to-a-bmp). There's also M$ Learn (https://learn.microsoft.com/en-gb/windows/win32/gdi/bitmap-storage?redirectedfrom=MSDN) :cool:
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.
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.
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 ... (https://masm32.com/board/index.php?topic=4216.0)
MathArt's Interface and the BITMAP thing (https://masm32.com/board/index.php?topic=6339.0)
Not exactly what you want, but perhaps some ideas.
Regards, HSE
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.
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.
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.
So, as I see it, you just need to convert a palette?
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.
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
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
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 (https://vulms.vu.edu.pk/Courses/CS410/Downloads/Charles%20Petzold%20-%20Programming%20Windows%20-%205th%20Ed.pdf)
Read the section about Bitmaps, that should help.
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
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
(https://i.postimg.cc/rdb89m89/Save-Image.png) (https://postimg.cc/rdb89m89)
Quote from: Greenhorn on January 18, 2024, 02:32:38 AMQuote 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 (https://vulms.vu.edu.pk/Courses/CS410/Downloads/Charles%20Petzold%20-%20Programming%20Windows%20-%205th%20Ed.pdf)
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?
Quote from: NoCforMe on January 18, 2024, 12:45:14 PMNo. No! Nyet! No no no!
:biggrin:
You have some code to talk about?
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.
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.
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 ...
You have to read the code people take time to post. If you can not download the code... you are alone.
Thanks all for example code :thumbsup:
Magnus
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.
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.
> 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:
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 ...
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
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" ).
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 (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 (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.
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
BTW, if you ever wondered what letters look like up close, here they are. (Bodoni Book BT, size 48**, ClearType quality.) Interesting to see how the characters are anti-aliased.
(https://i.postimg.cc/fJF6BzYX/ABCD-up-close.jpg) (https://postimg.cc/fJF6BzYX)
** "Logical" size, not point size. 36 points.
Quote from: NoCforMe on January 20, 2024, 07:16:37 AMI find it very annoying when folks make comments but they obviously haven't read what I've written!
Yeah, I know that feeling. There are people who don't even open attachments carefully prepared by members of this forum :cool:
QuoteWith GDI, remember, not GDI+.
You would see much better anti-aliasing if you could overcome this limitation.
Re converting bitmaps to 256 colours: Probably there are some API calls around, probably you'll find something on StackOverflow or CodeProject. The main stumbling block here is that 32-bit bitmaps don't need a palette, while 256 colour bitmaps do require one. And you need to transform a 32-bit pixels (unused alpha, R, G, B) into a one byte palette index. That means you have to squeeze 4 Gigabytes of colours into one 256 bit palette value. PaintShop, for example, offers several algorithms, see below. It's not trivial.
(https://i.postimg.cc/yDBTcV2g/Dec-Col-Depth16.png) (https://postimg.cc/yDBTcV2g)
P.S.: As you might have noticed, I am using words, not code, to communicate with you.
Quote from: jj2007 on January 20, 2024, 10:04:49 AMQuote from: NoCforMe on January 20, 2024, 07:16:37 AMI find it very annoying when folks make comments but they obviously haven't read what I've written!
Yeah, I know that feeling. There are people who don't even open attachments carefully prepared by members of this forum :cool:
Yeah, except that the code was unasked for. I asked for apples and got oranges.
QuoteQuoteWith GDI, remember, not GDI+.
You would see much better anti-aliasing if you could overcome this limitation.
Well, that would be an interesting experiment. Maybe after I get this thing put together I'll try GDI+ and see what differences there are.
QuoteRe converting bitmaps to 256 colours: Probably there are some API calls around, probably you'll find something on StackOverflow or CodeProject. The main stumbling block here is that 32-bit bitmaps don't need a palette, while 256 colour bitmaps do require one. And you need to transform a 32-bit pixels (unused alpha, R, G, B) into a one byte palette index. That means you have to squeeze 4 Gigabytes of colours into one 256 bit palette value. PaintShop, for example, offers several algorithms, see below. It's not trivial.
(https://i.postimg.cc/yDBTcV2g/Dec-Col-Depth16.png) (https://postimg.cc/yDBTcV2g)
As I said, not being able to use an alternative format isn't a show-stopper, but it's disappointing. I might root around some after getting this working. Some of what I've read implies that Windows might be able to synthesize a palette in some cases. Though as you say it isn't trivial and I wouldn't be so sure about the quality of the down-conversion.
Pretty sure 32-bit images are 16+ million colors, no? Because as you say the alpha channel is unused, so isn't it 256 x 256 x 256?
Yep, I'm very familiar with that reduce-#-of-colors dialog. Have used it hundreds of times.
QuoteP.S.: As you might have noticed, I am using words, not code, to communicate with you.
That, my friend, is appreciated.
Quote from: NoCforMe on January 20, 2024, 12:42:03 PMAs I said, not being able to use an alternative format isn't a show-stopper, but it's disappointing. I might root around some after getting this working.
There might exist multiple ways to achieve this. One way that I know of is using CreateDIBSection() - because I once created a monochrome bitmap of the screen with it. You have to carefully setup the BITMAPINFO structure that this function needs as input. For my monochrome picture I just supplied black and white colors, of course.
QuoteThe best shoot is probably to convert to the old Netscape palette, which is a color palette with 216 colors, which has 6 levels (00H, 33H, 66H, 99H, CCH and FFH) for each color (R, G, B). There are many links like this one: http://www.webmaster.crevier.org/tags/palette.html , which describes this palette.
Fixed color palette (https://www.webmaster.crevier.org/tags/palette.html)
OK, here it is, the finished product, let's call it v1.0. Creates a 32 BPP BMP. Please play around with it, let me know whatcha think, if anything. (Hopefully I didn't forget to include any files like I usually do!)
Try the font quality combobox: most dramatic difference is selecting "non-antialiased".
Quote from: TimoVJL on January 20, 2024, 08:28:14 PMQuoteThe best shoot is probably to convert to the old Netscape palette, which is a color palette with 216 colors, which has 6 levels (00H, 33H, 66H, 99H, CCH and FFH) for each color (R, G, B). There are many links like this one: http://www.webmaster.crevier.org/tags/palette.html , which describes this palette.
Fixed color palette (https://www.webmaster.crevier.org/tags/palette.html)
I like my "universal palette" better. Totally user-defined. A little bit of this, a little bit of that.
(https://i.postimg.cc/XpvNJ95b/Universal-Palette.jpg) (https://postimg.cc/XpvNJ95b)