News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Saving a Memory DC to a bitmap file

Started by Brett Kuntz, June 25, 2005, 11:08:39 AM

Previous topic - Next topic

Brett Kuntz

Hello. I have code that I want to load up a JPG, draw some simple text to it, and then save it as a bitmap (or even better, as a JPG). I have the following code so far, completely at a loss of how to save the 'memdc' info/bitmap into a file. I want this all to be done invisible to the user, so there will be no drawing to the screen at any point.


    local hjpg:dword
    local hdc:dword
    local memdc:dword

    invoke BitmapFromFile, addr jpgcachename
    mov hjpg, eax
    invoke CreateDC, addr devicename, 0, 0, 0
    mov hdc, eax
    invoke CreateCompatibleDC, hdc
    mov memdc, eax
    invoke SelectObject, memdc, hjpg
    invoke TextOut, memdc, 10, 10, addr ostatus, sizeof ostatus

donkey

#1
I have a routine to save a DIB32 to a file in my graphics library, You require only a bitmap handle and a file handle, it does the rest. You will ofcourse have to draw to the DC as a DIB bitmap but you should be doing that anyway as it is much faster and allows you to directly draw to the bits instead of using the GDI. If necessary, there is a routine to convert a normal compatible bitmap (v3) to a DIB. I also have many other routines you can freely use, no license, no fees don't even have to say that I wrote them. There are a few in there that are from other authors so you should mention them if you use them. The library works with both MASM and GoAsm (though it is written in GoAsm) and is available from my website...

http://www.quickersoft.com/donkey/libs.htm
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

Brett Kuntz

Wow thanks a lot, I'll take a look at it now.

Brett Kuntz

I'm not to sure what I must do then. LoadBitmapFile returns an handle to a bitmap. How do I get that into a DIB DC, draw text to it, then save?

donkey

You would generally load the bitmap, select it into a DC (memory or actual) then draw to it, select it out of the DC then save the bitmap. Because there are some functions when examining a bitmap that require that it is not currently selected into a DC, you must select it out before saving it. The following is example code for you...

#Define COLOR_3DFACE 15
#Define DT_CENTER 1h
#Define DT_VCENTER 4h
#Define DT_SINGLELINE 20h
#Define NULL 0
#Define GENERIC_WRITE 40000000h
#Define CREATE_ALWAYS 2

RECT STRUCT
  left    dd
  top     dd
  right   dd
  bottom  dd
ENDS

GRAPHLIB = "C:\RadASM\GoAsm\Projects\Graphics\Graphics.lib"
CreateIndependantBitmap = GRAPHLIB:CreateIndependantBitmap
SaveDIB32 = GRAPHLIB:SaveDIB32

DATA SECTION
hBmp DD ?
hFile DD ?
rect RECT <>

CODE SECTION

Start:
invoke GetDC,NULL
mov ebx,eax
invoke CreateIndependantBitmap, ebx,200,200
mov [hBmp], eax
invoke CreateCompatibleDC, ebx
push eax
invoke ReleaseDC, NULL, ebx
pop ebx

invoke SelectObject, ebx, [hBmp]
mov [hBmp], eax

mov D[rect.left], 0
mov D[rect.top], 0
mov D[rect.right], 199
mov D[rect.bottom], 199

invoke FillRect, ebx, offset rect, COLOR_3DFACE+1

invoke DrawTextA, ebx, "Hello", -1, offset rect,DT_CENTER + DT_SINGLELINE + DT_VCENTER

invoke SelectObject, ebx, [hBmp]
mov [hBmp], eax

invoke DeleteDC, ebx

invoke CreateFileA, "C:\Test.bmp",GENERIC_WRITE,NULL,NULL,CREATE_ALWAYS,NULL,NULL
mov [hFile], eax

invoke SaveDIB32, [hBmp], [hFile]

invoke CloseHandle, [hFile]

invoke DeleteObject, [hBmp]

invoke ExitProcess, 0
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

donkey

Note that a DC is not specific to either compatible or independant bitmaps, the bitmap will automatically take on the characteristics of the DC it is selected into and revert to it's original format when selected out. Your only requirement with my library is that you use 32 bit DIB sections (bitmaps), the DC can be any valid type of device context (ie screen, printer etc...)
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

Brett Kuntz

Hello, I tried to following code just to see if it'd load up the jpg and save it as a bmp right away, it does not work:


    local hbmp:dword
    local hfile:dword

    invoke BitmapFromFile, addr jpgcachename
    invoke ConvertToDIB32, eax
    mov hbmp, eax
    invoke CreateFile, addr filename, FILE_ALL_ACCESS, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0
    mov hfile, eax
    invoke SaveDIB32, hbmp, hfile
    invoke CloseHandle, hfile
    ret


It creates a 56 byte file that doesn't draw properly. However paint and explorer report the bmp is 500x100, which is the dimensions of the jpg. Any idea?

donkey

I will have to brush off my copy of MASM and try it out, the code I posted tested perfectly using GoAsm. I assume that BitmapFromFile is Ernie's imagelib routine, I wrote my own but will use the MASM32 version and try it out.
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

donkey

can't seem to get the MASM32 library to work on my system anymore, probably have to reinstall it or something, I haven't used MASM for quite a long time and rarely used the library even then. But I tried this...

.386
.model flat, stdcall  ;32 bit memory model
option casemap :none  ;case sensitive

; ##########################################
; PROTO's for Graphics.lib

ConvertToDIB32 PROTO :DWORD
SaveDIB32 PROTO :DWORD,:DWORD

includelib C:\RadASM\GoAsm\Projects\Graphics\Graphics.lib

include windows.inc
include kernel32.inc
include user32.inc
include shell32.inc
include gdi32.inc
include MASM32.INC

includelib kernel32.lib
includelib user32.lib
includelib shell32.lib
includelib gdi32.lib
includelib masm32.lib

.DATA
hInst DD ?
hBmp DD ?
hFile DD ?
BmpFile DB "Paullette.bmp",0
OutFile DB "TestMASM.bmp",0

.CODE
Start:
invoke GetModuleHandle, 0
mov [hInst], eax

invoke LoadImage, [hInst], offset BmpFile,IMAGE_BITMAP,0 , 0, LR_LOADFROMFILE
mov [hBmp],eax
invoke ConvertToDIB32, [hBmp]
mov [hBmp],eax

invoke CreateFile,offset OutFile,GENERIC_WRITE,NULL,NULL,CREATE_ALWAYS,NULL,NULL
mov [hFile], eax

invoke SaveDIB32, [hBmp], [hFile]

invoke CloseHandle, [hFile]
invoke DeleteObject, [hBmp]
xor eax,eax
ret

end Start


And it worked perfectly as expected using LoadImage, so I would suspect that there might be a problem with the BitmapFromFile function. I would check to make sure that the stream for IPicture actually contains data, this would give you the effect of showing a size but no image in the final file.
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

donkey

Arrgh, forgot that you need to actually use import libraries for everything, even functions in other libraries (not a problem in GoAsm). So I added Ole32 to the proggy and got BitmapFromFile to work. It seems to work perfectly as long as you use a fully qualified path, otherwise I get the same problem you had. The returned bitmap handle was 0 (which you should have been checking for) if a relative or incomplete path was used. Anyway, this works perfectly...

.386
.model flat, stdcall  ;32 bit memory model
option casemap :none  ;case sensitive

; ##########################################
; PROTO's for Graphics.lib

ConvertToDIB32 PROTO :DWORD
SaveDIB32 PROTO :DWORD,:DWORD

includelib C:\RadASM\GoAsm\Projects\Graphics\Graphics.lib

include windows.inc
include kernel32.inc
include user32.inc
include shell32.inc
include gdi32.inc
include oleaut32.inc
include ole32.inc
include MASM32.INC

includelib kernel32.lib
includelib user32.lib
includelib shell32.lib
includelib gdi32.lib
includelib oleaut32.lib
includelib ole32.lib
includelib masm32.lib

.DATA
hInst DD ?
hBmp DD ?
hFile DD ?
JpgFile DB "C:\RadASM\Masm\Projects\TestBitmap\Paullette.jpg",0
OutFile DB "TestMASM.bmp",0

.CODE
Start:
invoke CoInitialize, 0

invoke BitmapFromFile, offset JpgFile
test eax,eax
jz ErrorBmp

invoke ConvertToDIB32, eax
mov [hBmp],eax

invoke CreateFile,offset OutFile,GENERIC_WRITE,NULL,NULL,CREATE_ALWAYS,NULL,NULL
mov [hFile], eax

invoke SaveDIB32, [hBmp], [hFile]

invoke CloseHandle, [hFile]
invoke DeleteObject, [hBmp]

invoke CoUninitialize
invoke ExitProcess, 0

ErrorBmp:
invoke CoUninitialize
invoke ExitProcess, -1

end Start
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

Brett Kuntz

Hello, it seems it still does not work. My debugger tells me your ConvertToDIB32 doesn't actually do anything. It puts the number 20 into eax, checks if eax is equal to 20, then just returns. It doesn't actually do anything else other then this it seems. The following code of course does not work, I also tried calling CoInit but that function fails, so I removed it.


    local hbmp:dword
    local hfile:dword

    invoke BitmapFromFile, addr jpgcachename
    invoke ConvertToDIB32, eax
    mov hbmp, eax
    invoke CreateFile, addr filen, FILE_ALL_ACCESS, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0
    mov hfile, eax
    invoke SaveDIB32, hbmp, hfile
    invoke CloseHandle, hfile
    ret


BitmapFromFile returns a valid handle to my jpg image, I know this because I can blit it to the screen and see it. Next up, your function executes the following:

PUSH EBP
MOV EBP,ESP
SUB ESP,58
PUSH EBP
ADD DWORD PTR SS:[ESP],-28
PUSH 18
PUSH DWORD PTR SS:[EBP+8]
CALL <JMP.&gdi32.GetObjectA>  ; Always returns the number 18h
MOV EAX,DWORD PTR SS:[EBP-16]  ; Always the number 20h
CMP EAX,20
JNZ SHORT gstats.00404026
MOV EAX,DWORD PTR SS:[EBP+8]
MOV ESP,EBP
POP EBP
RETN 4

Any ideas?

Edit-

I forgot I could check your source code. So the problem seems; either the JPG isn't 32 bits, or BitmapFromFile doesn't load it into a 32 bit thingy. Is there a solution to this problem?

donkey

Well, the jump short jumps past a return that exits if it is not passed a valid bitmap handle. The source for the library is available with the download, and the disassebled code you are showing is definitely not my ConvertToDIB32 function. It comes preassembled in the lib so MASM has nothing to do but insert the code at a page boundary and fix up the addresses. The following is my MASM test code using the MASM32 library for BitmapFromFile and my lib routines to convert and save it.

When I assemble using MASM I get the following, it is static in that it should not change as it is precompiled in a lib...

00402000   55               PUSH EBP
00402001   89E5             MOV EBP,ESP
00402003   81EC 94000000    SUB ESP,94
00402009   8D7D AC          LEA EDI,DWORD PTR SS:[EBP-54]
0040200C   31C0             XOR EAX,EAX
0040200E   B9 54000000      MOV ECX,54
00402013   F3:AA            REP STOS BYTE PTR ES:[EDI]
00402015   55               PUSH EBP
00402016   830424 AC        ADD DWORD PTR SS:[ESP],-54
0040201A   6A 54            PUSH 54
0040201C   FF75 08          PUSH DWORD PTR SS:[EBP+8]
0040201F   E8 84F0FFFF      CALL <JMP.&gdi32.GetObjectA>
00402024   837D B0 00       CMP DWORD PTR SS:[EBP-50],0
00402028   0F84 A6010000    JE TestBitm.004021D4
0040202E   837D B4 00       CMP DWORD PTR SS:[EBP-4C],0
00402032   0F84 9C010000    JE TestBitm.004021D4
00402038   C785 78FFFFFF 28>MOV DWORD PTR SS:[EBP-88],28

[attachment deleted by admin]
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

Brett Kuntz

Hello, I got it to work. The following file in your library "ConvertToDIB32.asm" has a bug:


invoke GetObjectA,[hBitmap],SIZEOF BITMAP,offset dibs
mov eax,[dibs.bmBitsPixel]
cmp eax,32
jne >
    mov eax,[hBitmap]
    ret
:


Needs to be changed to:


invoke GetObjectA,[hBitmap],SIZEOF BITMAP,offset dibs
mov eax,[dibs.bmBitsPixel]
cmp eax,32
je >
    mov eax,[hBitmap]
    ret
:


So that the proc continues if the dibs.bmBitsPixel equals 32, and fails if it does not. With this change my code works great. Your old code, if the ConvertToDIB32 proc was passed a valid 32-bit handle, it would fail. If it was passed a valid 8/16/24-bit handle I'm assuming it would succeed.

donkey

Not exactly sure what you are looking at there, this is my ConvertToDIB32 function. The proc exits and returns the handle if the DIB is already 32 bits, no need to blt it if it's already at the right color depth, only if it is not equal to 32 bits do you need to change it.

ConvertToDIB32 FRAME hBitmap
LOCAL bmp :DIBSECTION
LOCAL hDC :D
LOCAL hDIB :D
LOCAL bmi :BITMAPINFO
LOCAL ppvBits :D
LOCAL memdc1 :D
LOCAL memdc2 :D

lea edi,bmp
xor eax,eax
mov ecx,SIZEOF DIBSECTION
rep stosb

invoke GetObjectA,[hBitmap],SIZEOF DIBSECTION,offset bmp
cmp D[bmp.dsBm.bmWidth],0
je >>E2

cmp D[bmp.dsBm.bmHeight],0
je >>E2

mov D[bmi.bmiHeader.biSize],SIZEOF BITMAPINFOHEADER
mov eax,[bmp.dsBm.bmWidth]
mov D[bmi.bmiHeader.biWidth],eax
mov eax,[bmp.dsBm.bmHeight]
mov D[bmi.bmiHeader.biHeight],eax
mov W[bmi.bmiHeader.biPlanes],1
mov W[bmi.bmiHeader.biBitCount],32
mov D[bmi.bmiHeader.biCompression],BI_RGB
mov D[bmi.bmiHeader.biXPelsPerMeter],0
mov D[bmi.bmiHeader.biYPelsPerMeter],0
mov D[bmi.bmiHeader.biClrUsed],0
mov D[bmi.bmiHeader.biClrImportant],0

invoke GetDC,NULL
mov [hDC],eax

; Create a DIB section with the right attributes
invoke CreateDIBSection,[hDC],offset bmi,DIB_RGB_COLORS,offset ppvBits,0,0
mov [hDIB],eax
or eax,eax
jz >>E1
cmp D[ppvBits],0
je >>E1

; check if the original bitmap is a DIB Section
cmp D[bmp.dsBmih.biBitCount],0
jne >

; The Bitmap is a compatible bitmap so get the bits
invoke GetDIBits,[hDC],[hBitmap],0,[bmp.dsBm.bmHeight],[ppvBits],offset bmi,DIB_RGB_COLORS
invoke ReleaseDC,NULL,[hDC]
invoke DeleteObject,[hBitmap]
; 2 bool GDI operations should be flushed to preserve GDI memory on 9x systems
invoke GdiFlush
mov eax,[hDIB]
RET

:
; The Bitmap is a DIB section so BLT it to the new one
cmp D[bmp.dsBmih.biBitCount],32
jne >
mov eax,[hBitmap]
ret
:

invoke CreateCompatibleDC,[hDC]
mov [memdc1],eax
invoke CreateCompatibleDC,[hDC]
mov [memdc2],eax

invoke ReleaseDC,NULL,[hDC]

invoke SelectObject,[memdc1],[hBitmap]
mov [hBitmap],eax
invoke SelectObject,[memdc2],[hDIB]
mov [hDIB],eax
; BitBlt is faster in most cases than a DWORD copy because of HW accel.
invoke BitBlt,[memdc2],0,0,[bmp.dsBm.bmWidth],[bmp.dsBm.bmHeight],[memdc1],0,0,SRCCOPY

invoke SelectObject,[memdc1],[hBitmap]
invoke DeleteObject,eax
invoke SelectObject,[memdc2],[hDIB]
mov [hDIB],eax
invoke DeleteDC,[memdc1]
invoke DeleteDC,[memdc2]
; 2 bool GDI operations should be flushed to preserve GDI memory on 9x systems
invoke GdiFlush
mov eax,[hDIB]

RET

; If an error occurs clean up and return -1, original is preserved
E1:
invoke ReleaseDC,NULL,[hDC]
E2:
xor eax,eax
dec eax
RET
ENDF
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

Brett Kuntz

That's strange, the Graphics.zip I downloaded last night is completely different then the one availble now. The one now has the code you pasted, the old one has the code I pasted. Did you upload a new version of the lib today?