News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

How do I get access to GDI+ bitmaps?

Started by NoCforMe, August 31, 2023, 11:04:52 AM

Previous topic - Next topic

NoCforMe

I'm using GDI+ to try to design an icon editor. So far I'm able to display an image in a window, no problem. I'm using this in the paint handler for the display window:

INVOKE GdipDrawImageRectI, gdiHgraphics, GdiHbitmap,
20, 20, 40, 32

But now I need to get at the actual bits of the bitmap. Does anyone know how I do that?

Just to give some clues, I'm using the following sequence to get to displaying the image:

;********  Initialize GDI+:  ********
INVOKE GdiplusStartup, OFFSET GDItoken, OFFSET GDIinputStruct, NULL

; Get a DC:
INVOKE GetDlgItem, hWin, $IconDisplay
MOV IconDispHandle, EAX
INVOKE GetDC, EAX
MOV hDC, EAX

; Get graphics "object" from DC handle:
INVOKE GdipCreateFromHDC, hDC, ADDR gdiHgraphics

INVOKE ReleaseDC, hWin, hDC

;***** User selects image file here *****

; Open image file:
INVOKE GdipLoadImageFromFile, ADDR unicodeName, OFFSET GdiHbitmap

; Get width & height of bitmap:
INVOKE GdipGetImageWidth, GdiHbitmap, OFFSET BitmapImageW
INVOKE GdipGetImageHeight, GdiHbitmap, OFFSET BitmapImageH

; Get width & height of display window:
INVOKE GetClientRect, hWin, ADDR gpRect
MOV EAX, gpRect.right
SUB EAX, gpRect.left
; SUB EAX, $border * 2
MOV IconDispWindowW, EAX
MOV EAX, gpRect.bottom
SUB EAX, gpRect.top
; SUB EAX, $border * 2
MOV IconDispWindowH, EAX

;***** Inside WM_PAINT handler in display window
; (between BeginPaint() and EndPaint() ): *****

; Get graphics "object" from DC handle:
INVOKE GdipCreateFromHDC, hDC, ADDR gdiHgraphics

; Display image at (X,Y) with dimensions (W,H):
; Many thanks to "mabdelouahab" from the MASM32 forum for this.
INVOKE GdipDrawImageRectI, gdiHgraphics, GdiHbitmap,
20, 20, 40, 32

I wonder, is @mabdelouahab around? They were much help to me to get my first GDI+ attempts off the ground.

I've rooted around some on learn.microsoft.com, but it's hard to find stuff like this out, because they treat what we think of as functions, like GdipDrawImageRectI(), as "methods", with everything nicely obfuscated for C++ programmers.
Assembly language programming should be fun. That's why I do it.

NoCforMe

Well, whaddya know: in the "answering my own question" department, I found a helpful post on good old Stack Overflow. First order of business: determine whether the bitmap I'm trying to get is a DIB (device-independent bitmap) or a DDB (device-dependent). Apparently easy enough to tell by using GetObject() on the GDI+ bitmap and looking at the pointer to the bitmap bits (BITMAP.bmBits) which should be NULL if the bitmap is a DDB. That's the experiment I'm about to do. We'll see soon enough.

[A minute or two later]
Well, that pointer came back non-zero. Yay! Now I can get at them bits ...
Assembly language programming should be fun. That's why I do it.

Caché GB

Hi NoCforMe

Try this.

.const

IFNDEF POINTER
  POINTER TYPEDEF PTR
ENDIF


comment ó " Access modes used when calling Image::LockBits (GdipBitmapLockBits API) " ó

ImageLockModeRead          EQU 1
ImageLockModeWrite         EQU 2
ImageLockModeReadWrite     EQU 3
ImageLockModeUserInputBuf  EQU 4

comment ó " Some PixelFormat Counts " ó

PixelFormat24bppRGB        EQU 21808h
PixelFormat32bppRGB        EQU 22009h
PixelFormat32bppARGB       EQU 26200Ah
PixelFormat32bppPARGB      EQU 0E200Bh


GdiplusStartupInput STRUCT QWORD
  GdiplusVersion DWORD ?
  DebugEventCallback POINTER ?
  SuppressBackgroundThread DWORD ?
  SuppressExternalCodecs DWORD ?
GdiplusStartupInput ENDS


BitmapData STRUCT                         
  dWidth DWORD ?   
  dHeight DWORD ?   
  Stride DWORD ?   
  _PixelFormat DWORD ?   
  Scan0 DWORD ?   
  Reserved DWORD ?
BitmapData ENDS


GdipBitmapLockBits PROTO STDCALL \          ; GpStatus WINGDIPAPI
   bitmap :DWORD, \                         ; GpBitmap*
   rect :DWORD, \                           ; GDIPCONST GpRect*
   flags :DWORD, \                          ; UINT
   format :DWORD, \                         ; PixelFormat
   lockedBitmapData :DWORD                  ; BitmapData*


GdipBitmapUnlockBits PROTO STDCALL \        ; GpStatus WINGDIPAPI
   bitmap :DWORD, \                         ; GpBitmap*
   lockedBitmapData :DWORD                  ; BitmapData*


GdipCreateBitmapFromFile PROTO STDCALL \    ; GpStatus WINGDIPAPI
   filename :PTR WORD, \                    ; GDIPCONST WCHAR*
   bitmap :DWORD                            ; GpBitmap **


GdiplusShutdown PROTO STDCALL \             ; Void
   token :DWORD                             ; In ULONG_PTR


GdiplusStartup PROTO STDCALL \              ; Status
   token :DWORD, \                          ; Out ULONG_PTR token *
   input :DWORD, \                          ; In const GdiplusStartupInput *
   output :DWORD                            ; Out GdiplusStartupOutput *


.data

      GdiplusSUI             GdiplusStartupInput   <1, null, false, false>

.code

    local  GdipBitmapData:BitmapData
    local  GdiplusToken:ptr
    local  pImage:ptr

    local  pImageFile:ptr word ; POINTER to unicode path and filename


         invoke  GdiplusStartup, addr GdiplusToken, addr GdiplusSUI, null

         invoke  GdipCreateBitmapFromFile, pImageFile, addr pImage
           test  eax, eax
            jnz  CloseGDIplus

         invoke  GdipBitmapLockBits, pImage, null, ImageLockModeRead, PixelFormat32bppRGB, addr GdipBitmapData
           test  eax, eax
            jnz  CloseGDIplus


            mov  eax, GdipBitmapData.Scan0 ; start of the "actual" bits of the bitmap


          ; your code here


         invoke  GdipBitmapUnlockBits, pImage, addr GdipBitmapData

CloseGDIplus:

          invoke  GdiplusShutdown, GdiplusToken

Cool
Caché GB's 1 and 0-nly language:MASM

NoCforMe

Wow; did you just pull that out of your back pocket? Impressive. Dunno if it'll work but I'll try it.

Got to be better than this, which is obviously wrong:
Assembly language programming should be fun. That's why I do it.

NoCforMe

I'm adapting the code you posted to my program, but before I try it, a question:
You have:

    invoke  GdipBitmapLockBits, pImage, null, ImageLockModeRead, PixelFormat32bppRGB, addr GdipBitmapData

but what if the image I'm loading from a file isn't in 32 bpp format? (In fact, none of my test files are in that format.) Does it matter? do I need to know the image format before locking the bits? will this step work no matter what the format is?

Do I actually need to lock the bits before I can examine them?

The mysteries of GDI+ ...
Assembly language programming should be fun. That's why I do it.

Caché GB

#5
I have not used GDI+ for a long time. Useing WIC now.

What format do YOU want your bitmap data in?

From what I can remeber you choose the PixelFormat you would like with the PixelFormat flag.
D2D requires PixelFormat32bppPARGB so you will use this flag.

If you want a pointer to the start of the bitmap data you will need to call GdipBitmapLockBits,
so that the Scan0 member of the BitmapData struct (the GdipBitmapData variable) will hold a pointer
to the start of this data.

Also I forgot the :-

invoke  GdipDisposeImage, pImage

after the GdipBitmapUnlockBits call.
Caché GB's 1 and 0-nly language:MASM

NoCforMe

Quote from: Caché GB on August 31, 2023, 03:27:44 PMWhat format do YOU want your bitmap data in?

No, you don't understand: I don't know what format the data is in, since I'm trying to read a file. It's like a Catch-22: I need to know the format of the file in order to set the format of the file whose bits I want to lock?

How do I determine the "pixel format" before I call GdipBitmapLockBits()?
Assembly language programming should be fun. That's why I do it.

fearless

José Roca has a chm help file on gdi+ that could be useful: https://forum.it-berater.org/index.php/topic,4427.0.html

The GDIPLUS_HELP.rar is attached at the bottom of the post.


Caché GB

You ask "But now I need to get at the actual bits of the bitmap. Does anyone know how I do that?"

GdipCreateBitmapFromFile does not care what format your *.bmp is in. This function "GdipCreateBitmapFromFile" will
load a *.bmp or a *.png or a *.jpg file. It creates a temporary image. You choose the format that this temporary
image will be converted to by specifying it with the 4th parm of the GdipBitmapLockBits function. The 5th parm is
a pointer to a BitmapData STRUCT. After the call to GdipBitmapLockBits the Scan0 member of this BitmapData STRUCT
will hold the ADDRESS of the start of ACTUAL BITS of the image which is now in memory (RAM).

Do you now understand?
Caché GB's 1 and 0-nly language:MASM

NoCforMe

Quote from: Caché GB on August 31, 2023, 08:15:17 PMYou choose the format that this temporary image will be converted to by specifying it with the 4th parm of the GdipBitmapLockBits function.

Aha. That's the part I wasn't getting. Will try again on this tomorrow.
Assembly language programming should be fun. That's why I do it.

NoCforMe

Well, @Caché GB, it is as you say. I'm making progress. Thanks again.

I even found a GDI+ function (GdipCreateHBITMAPFromBitmap () )that will take me back into the regular GDI realm, which I'm much more comfortable operating in. That's the next experiment.
Assembly language programming should be fun. That's why I do it.

NoCforMe

@Caché GB, another question: I think I know the answer to this, but just checking. When I get the "actual bits" of the bitmap, this includes the RGBQUADs that precede the actual bitmap data IF the image is one of the "indexed" types, amiright? In which case, I guess you need to use that indexed type to figure out how many RGBQUADs there are.

(Pixel formats: PixelFormat1bppIndexed (monochrome, no RGBQUADs???), PixelFormat4bppIndexed (16 colors), PixelFormat8bppIndexed (256 colors) ).
Assembly language programming should be fun. That's why I do it.

NoCforMe

Well, I see nobody answered that last question.

I have an issue, and I'm hoping you, Caché GB, might be able to shed some light on this.

OK, so I'm able to read a bitmap (in this case an actual .BMP file) and display it, using GDI+ functions. Ackshooly, it turns out that locking the bits (using GdipBitmapLockBits() as you suggested) isn't necessary at all. What I'm doing is using GdipCreateHBITMAPFromBitmap() to turn the GDI+ bitmap into a regular GDI bitmap. (What is that, GDI-? Just joking.)

But even if I don't work with this using GDI instead of GDI+, if I use GDI+ functions to interrogate and display the bitmap (using GdipDrawImageRectI() to display it), I don't get all the colors in the original image.

The bitmap image I'm testing with is a 16-million color image, as shown below (opened in Paint Shop Pro). But when I display it in my program and get the "pixel format", it always comes out as 8-bit indexed (that's the value "198659" shown there), and the image is obviously down-sampled (2nd image attached). Any idea why this is?

When I was using the lock function, it made absolutely no difference what "pixel format" parameter I passed it, from the lowest to the highest value; I always get that same value back ("PixelFormat8bppIndexed").

The thing seems stuck on stupid. What's going on here?
Assembly language programming should be fun. That's why I do it.

Caché GB

#13
Hi NoCForMe

I've not worked with GDI+ since I don't no, 2006/7 maybe. Even then it was limited (my knowledge of GDI+).
If no one here can help, I don't think there is any shame in posting your query on stackoverflow.

My I suggest using WIC. It may be a steep learning curve but once waxed you will not look back.

qWord has translated the wincodec file already

https://masm32.com/board/index.php?msg=31568
Caché GB's 1 and 0-nly language:MASM