I had a thread on GDI+ going here a (couple? few?) years ago which got me started. Now I'm back on that project and have more questions.
I'm going to have a lot more questions, and will post some code I'm working on, but for now, one general question:
When using GDI+, can I also use regular GDI functions? Or should I not use them? Are the two worlds completely separate? or can I mix and match?
For instance, in trying to set the origin of my viewport, can I use SetWindowOrgEx()? or do I need to rely only on the GDI+ functions? (I'm using the GDI+ "flat" API here, which has regular functions as opposed to those designed for C++.)
Thanks for anyone who can answer this without going too far off topic.
Iirc you can mix them
> When using GDI+, can I also use regular GDI functions? Or should I not use them? Are the two worlds completely separate? or can I mix and match?
Of course you can do. There are no restrictions. Consider the "+" on GDI+.
Quote from: Greenhorn on January 29, 2022, 02:07:04 AM
> When using GDI+, can I also use regular GDI functions? Or should I not use them? Are the two worlds completely separate? or can I mix and match?
Of course you can do. There are no restrictions. Consider the "+" on GDI+.
Yes, that makes sense.
OK, here's a little testbed I'm using to learn this stuff. If you run it, you can use the trackbar to change the size of the image (in % scale). Notice how the image jumps around as you resize it. Can anyone tell me what's going on here?
;============================================
; GDI+ Testbed #2
;
;============================================
include \masm32\include\masm32rt.inc
include \masm32\include\gdiplus.inc
includelib \masm32\lib\gdiplus.lib
;============================================
; Defines, macros, prototypes, etc.
;============================================
WinMain PROTO :DWORD
$mainWinWidth EQU 800
$mainWinHeight EQU 600
;===== Window styles: =====
$mainWinStyles EQU WS_OVERLAPPEDWINDOW OR WS_CLIPCHILDREN OR WS_VISIBLE
$trackbarStyles EQU WS_CHILD or WS_VISIBLE or TBS_AUTOTICKS or TBS_BOTH or TBS_TOOLTIPS
;===== Window background colors: =====
$bkRED EQU 254
$bkGRN EQU 243
$bkBLUE EQU 199
$BkColor EQU $bkRED OR ($bkGRN SHL 8) OR ($bkBLUE SHL 16)
Ok EQU 0 ;GDI+ OK status value.
$TbarLow EQU 25
$TbarHigh EQU 400
$TbarID EQU 1200
$$Unicode MACRO name, string
&name LABEL BYTE
FORC char, string
DB '&char', 0
ENDM
DB 0, 0
ENDM
;============================================
; HERE BE DATA
;============================================
.data
WC WNDCLASSEX < SIZEOF WNDCLASSEX, \
CS_HREDRAW or CS_VREDRAW or CS_BYTEALIGNWINDOW, \
NULL, \ ;lpfnWndProc
NULL, \ ;cbClsExtra
NULL, \ ;cbWndExtra
NULL, \ ;hInstance
NULL, \ ;hIcon
NULL, \ ;hCursor
NULL, \ ;hbrBackground
NULL, \ ;lpszMenuName
NULL, \ ;lpszClassName
NULL > ;hIconSm
GDIinputStruct GdiplusStartupInput <1, NULL, FALSE, 0>
Xscale REAL4 1.0
Yscale REAL4 1.0
MainClassName DB "GDIplusTest2", 0
MainTitleText DB "GDI+ Testbed II", 0
TrackbarClassName DB "msctls_trackbar32", 0
;***** Names in Unicode: *****
$$Unicode JPEGfilenameW, test.jpg
;============================================
; UNINITIALIZED DATA
;============================================
.data?
MainWinHandle HWND ?
GDItoken DD ?
GdiHbitmap DD ?
TbarHandle HWND ?
Xform XFORM<>
;============================================
; CODE LIVES HERE
;============================================
.code
start: INVOKE GetModuleHandle, NULL
MOV WC.hInstance, EAX
INVOKE WinMain, EAX
INVOKE ExitProcess, EAX
;====================================================================
; Mainline proc
;====================================================================
WinMain PROC hInst:DWORD
LOCAL msg:MSG, brush:HBRUSH, wX:DWORD, wY:DWORD, gpRect:RECT
LOCAL hDC:HDC, gdiGraphics:DWORD, gdiHgraphics:DWORD
LOCAL bmWidth:DWORD, bmHeight:DWORD
; Create brush to set background color:
INVOKE CreateSolidBrush, $BkColor
MOV brush, EAX
; Register class for parent window:
MOV WC.lpfnWndProc, OFFSET MainWindowProc
MOV EAX, brush
MOV WC.hbrBackground, EAX
MOV WC.lpszClassName, OFFSET MainClassName
MOV WC.hIcon, NULL
INVOKE LoadCursor, NULL, IDC_ARROW
MOV WC.hCursor, EAX
INVOKE RegisterClassEx, OFFSET WC
INVOKE GetSystemMetrics, SM_CXSCREEN
MOV EDX, $mainWinWidth
CALL CenterDim
MOV wX, EAX
INVOKE GetSystemMetrics, SM_CYSCREEN
MOV EDX, $mainWinHeight
CALL CenterDim
MOV wY, EAX
; Create our main window:
INVOKE CreateWindowEx, WS_EX_OVERLAPPEDWINDOW, OFFSET MainClassName,
OFFSET MainTitleText, $mainWinStyles, wX, wY, $mainWinWidth,
$mainWinHeight, NULL, NULL, hInst, NULL
MOV MainWinHandle, EAX
; Create trackbar control:
INVOKE CreateWindowEx, 0, OFFSET TrackbarClassName,
NULL, $trackbarStyles, 200, 510, 300,
40, MainWinHandle, $TbarID, hInst, NULL
MOV TbarHandle, EAX
INVOKE SendMessage, EAX, TBM_SETRANGE, TRUE, $TbarLow OR ($TbarHigh SHL 16)
; Set up transform matrix:
mov eax, FP4(1.0) ; scale horizontal
mov Xform.eM11, eax
; mov eax, FP4(0.259) ; sin 15º
mov eax, FP4(0.0) ; sin 0º
mov Xform.eM12, eax
; mov eax, FP4(-0.259) ; -sin 15º
mov eax, FP4(0.0) ; -sin 0º
mov Xform.eM21, eax
mov eax, FP4(1.0) ; scale vertical
mov Xform.eM22, eax
mov eax, FP4(0.0) ; Translation X axis
mov Xform.ex, eax
mov eax, FP4(0.0) ; Translation Y axis
mov Xform.ey, eax
;******** Initialize GDI+: ********
INVOKE GdiplusStartup, OFFSET GDItoken, OFFSET GDIinputStruct, NULL
; Open JPEG image file:
INVOKE GdipLoadImageFromFile, OFFSET JPEGfilenameW, OFFSET GdiHbitmap
; Get width & height of bitmap:
INVOKE GdipGetImageWidth, GdiHbitmap, ADDR bmWidth
INVOKE GdipGetImageHeight, GdiHbitmap, ADDR bmHeight
; Get a DC:
INVOKE GetDC, MainWinHandle
MOV hDC, EAX
; Get graphics "object" from DC handle:
INVOKE GdipCreateFromHDC, hDC, ADDR gdiHgraphics
; Scale graphics:
INVOKE GdipScaleWorldTransform, gdiHgraphics, Xscale, Yscale, 0
;============= Message loop ===================
msgloop:
INVOKE GetMessage, ADDR msg, NULL, 0, 0
OR EAX, EAX ;EAX = 0 = exit
JZ exit99
INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
JMP msgloop
exit99: INVOKE GdiplusShutdown, GDItoken
MOV EAX, msg.wParam
RET
WinMain ENDP
;====================================================================
; Main Window Proc
;====================================================================
MainWindowProc PROC hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
LOCAL hDC:HDC, gdiHgraphics:DWORD
LOCAL ps:PAINTSTRUCT
LOCAL temp:DWORD, quotient:REAL4
MOV EAX, uMsg
CMP EAX, WM_PAINT
JE do_paint
CMP EAX, WM_HSCROLL
JE do_tbar
CMP EAX, WM_CLOSE
JE do_close
dodefault:
; use DefWindowProc for all other messages:
INVOKE DefWindowProc, hWin, uMsg, wParam, lParam
RET
do_paint:
INVOKE BeginPaint, hWin, ADDR ps
MOV hDC, EAX
INVOKE SetGraphicsMode, hDC, GM_ADVANCED
; INVOKE SetWorldTransform, hDC, addr Xform
; Get graphics "object" from DC handle:
INVOKE GdipCreateFromHDC, hDC, ADDR gdiHgraphics
; Set scale:
INVOKE GdipScaleWorldTransform, gdiHgraphics, Xscale, Yscale, 0
; Display image:
INVOKE GdipDrawImageI, gdiHgraphics, GdiHbitmap, $mainWinWidth / 2, $mainWinHeight / 2
INVOKE EndPaint, hWin, ADDR ps
commonexit:
XOR EAX, EAX
RET
do_tbar:
MOV EAX, wParam
CMP AX, TB_THUMBPOSITION
JNE commonexit
; Get new thumb pos.:
SHR EAX, 16
; EAX now in range $TbarLow - $TbarHigh (percentages):
MOV temp, EAX
FILD temp
MOV temp, 100
FILD temp
FDIV
FSTP quotient
MOV EAX, quotient
mov Xform.eM11, eax
mov Xform.eM22, eax
MOV Xscale, EAX
MOV Yscale, EAX
INVOKE InvalidateRect, hWin, NULL, TRUE ;Force paint.
JMP commonexit
do_close:
INVOKE PostQuitMessage, NULL
MOV EAX, TRUE
RET
MainWindowProc ENDP
;====================================================================
; CenterDim
;
; Returns half screen dimension (X or Y) minus half window dimension
;
; On entry,
; EAX = screen dimension
; EDX = window dimension
;
; Returns:
; sdim/2 - wdim/2
;====================================================================
CenterDim PROC
SHR EAX, 1 ;divide screen dimension by 2
SHR EDX, 1 ;divide window dimension by 2
SUB EAX, EDX
RET ;Return w/# in EAX
CenterDim ENDP
END start
Obviously my understanding of how this stuff works is far from perfect. I'm either not doing some things I should be doing, or doing something wrong. There's the transform matrix, for one thing: I was using it to change the scale (in the WM_PAINT handler), which did the same thing as this. So I tried using
GdipScaleWorldTransform(), in the message handler for the trackbar; same result. And you'll notice some things commented out in the code.
What I want to do is to pin down the origin of this image so it stays at the same place in the window. That would
probably be at the upper-left corner of the image.
I also understand that GDI+ reckons the Cartesian coordinates differently from the GUI; the GUI Y-position increases from top to bottom, whereas GDI+ increases from bottom to top (as in a true Cartesian system); one more point of confusion to deal with here.
Quote from: NoCforMe on January 29, 2022, 09:45:18 AMWhat I want to do is to pin down the origin of this image so it stays at the same place in the window.
Try this in line 241:
INVOKE GdipDrawImageI, gdiHgraphics, GdiHbitmap, $mainWinWidth / 20, $mainWinHeight / 20
That's definitely better, but it doesn't solve the problem; the image stays pretty much within the window, but its position still changes (X increases, Y decreases). Seems like a kluge rather than a solution.
I wish someone could explain to me how, exactly, positioning works here. And of course there's all that stuff about world--> page--> device mapping, which I kinda sorta understand ...
If I had enough time, I would make an example for you
But I think you are missing some steps
I'm not sure but try :
Invoke GdipCreateFromHDC, hdc, Addr pGraphics
Invoke GdipCreateBitmapFromGraphics, ImageWidth, ImageHeight, pGraphics, Addr pBitmap
Invoke GdipGetImageGraphicsContext, pBitmap, Addr gdiHgraphics
......
Invoke GdipDrawImageI, gdiHgraphics, GdiHbitmap, .....
Invoke GdipDrawImageRectI, pGraphics, pBitmap, x, y, ImageWidth, ImageHeight
Quote from: mabdelouahab on January 29, 2022, 07:35:27 PM
If I had enough time, I would make an example for you
But I think you are missing some steps
Oh, that's for sure! So could you possibly explain in general terms the minimum steps I need to take to display an image? Just in conceptual terms, not actual code.
I'm going to read all the MSDN documentation on the subject, Very Soon Now
TM. Until then I can use all the help I can get.
;============================================
; GDI+ Testbed #2
;
;============================================
include \masm32\include\masm32rt.inc
include \masm32\include\gdiplus.inc
includelib \masm32\lib\gdiplus.lib
;============================================
; Defines, macros, prototypes, etc.
;============================================
WinMain PROTO :DWORD
$mainWinWidth EQU 800
$mainWinHeight EQU 600
;===== Window styles: =====
$mainWinStyles EQU WS_OVERLAPPEDWINDOW OR WS_CLIPCHILDREN OR WS_VISIBLE
$trackbarStyles EQU WS_CHILD or WS_VISIBLE or TBS_AUTOTICKS or TBS_BOTH or TBS_TOOLTIPS
;===== Window background colors: =====
$bkRED EQU 254
$bkGRN EQU 243
$bkBLUE EQU 199
$BkColor EQU $bkRED OR ($bkGRN SHL 8) OR ($bkBLUE SHL 16)
Ok EQU 0 ;GDI+ OK status value.
$TbarLow EQU 25
$TbarHigh EQU 100
$TbarID EQU 1200
$$Unicode MACRO name, string
&name LABEL BYTE
FORC char, string
DB '&char', 0
ENDM
DB 0, 0
ENDM
;============================================
; HERE BE DATA
;============================================
.data
Img_Width = $mainWinWidth
Img_Height = $mainWinHeight
Img_Width_scale = (($mainWinWidth * $TbarLow)/100)
Img_Height_scale = (($mainWinHeight * $TbarLow)/100)
Img_Init_X = ($mainWinWidth - Img_Width_scale) /2
Img_Init_Y = ($mainWinHeight - Img_Height_scale) /2
X DD Img_Init_X
Y DD Img_Init_Y
W DD Img_Width_scale
H DD Img_Height_scale
WC WNDCLASSEX < SIZEOF WNDCLASSEX, \
CS_HREDRAW or CS_VREDRAW or CS_BYTEALIGNWINDOW, \
NULL, \ ;lpfnWndProc
NULL, \ ;cbClsExtra
NULL, \ ;cbWndExtra
NULL, \ ;hInstance
NULL, \ ;hIcon
NULL, \ ;hCursor
NULL, \ ;hbrBackground
NULL, \ ;lpszMenuName
NULL, \ ;lpszClassName
NULL > ;hIconSm
GDIinputStruct GdiplusStartupInput <1, NULL, FALSE, 0>
Xscale REAL4 1.0
Yscale REAL4 1.0
MainClassName DB "GDIplusTest2", 0
MainTitleText DB "GDI+ Testbed II", 0
TrackbarClassName DB "msctls_trackbar32", 0
;***** Names in Unicode: *****
$$Unicode JPEGfilenameW, test.jpg
;============================================
; UNINITIALIZED DATA
;============================================
.data?
MainWinHandle HWND ?
GDItoken DD ?
GdiHbitmap DD ?
TbarHandle HWND ?
Xform XFORM<>
;============================================
; CODE LIVES HERE
;============================================
.code
start: INVOKE GetModuleHandle, NULL
MOV WC.hInstance, EAX
INVOKE WinMain, EAX
INVOKE ExitProcess, EAX
;====================================================================
; Mainline proc
;====================================================================
WinMain PROC hInst:DWORD
LOCAL msg:MSG, brush:HBRUSH, wX:DWORD, wY:DWORD, gpRect:RECT
LOCAL hDC:HDC, gdiGraphics:DWORD, gdiHgraphics:DWORD
LOCAL bmWidth:DWORD, bmHeight:DWORD
; Create brush to set background color:
INVOKE CreateSolidBrush, $BkColor
MOV brush, EAX
; Register class for parent window:
MOV WC.lpfnWndProc, OFFSET MainWindowProc
MOV EAX, brush
MOV WC.hbrBackground, EAX
MOV WC.lpszClassName, OFFSET MainClassName
MOV WC.hIcon, NULL
INVOKE LoadCursor, NULL, IDC_ARROW
MOV WC.hCursor, EAX
INVOKE RegisterClassEx, OFFSET WC
INVOKE GetSystemMetrics, SM_CXSCREEN
MOV EDX, $mainWinWidth
CALL CenterDim
MOV wX, EAX
INVOKE GetSystemMetrics, SM_CYSCREEN
MOV EDX, $mainWinHeight
CALL CenterDim
MOV wY, EAX
; Create our main window:
INVOKE CreateWindowEx, WS_EX_OVERLAPPEDWINDOW, OFFSET MainClassName,
OFFSET MainTitleText, $mainWinStyles, wX, wY, $mainWinWidth,
$mainWinHeight, NULL, NULL, hInst, NULL
MOV MainWinHandle, EAX
; Create trackbar control:
INVOKE CreateWindowEx, 0, OFFSET TrackbarClassName,
NULL, $trackbarStyles, 200, 510, 300,
40, MainWinHandle, $TbarID, hInst, NULL
MOV TbarHandle, EAX
INVOKE SendMessage, EAX, TBM_SETRANGE, TRUE, $TbarLow OR ($TbarHigh SHL 16)
; Set up transform matrix:
; mov eax, FP4(1.0) ; scale horizontal
; mov Xform.eM11, eax
; mov eax, FP4(0.259) ; sin 15?
; mov eax, FP4(0.0) ; sin 0?
; mov Xform.eM12, eax
; mov eax, FP4(-0.259) ; -sin 15?
; mov eax, FP4(0.0) ; -sin 0?
; mov Xform.eM21, eax
; mov eax, FP4(1.0) ; scale vertical
; mov Xform.eM22, eax
; mov eax, FP4(0.0) ; Translation X axis
; mov Xform.ex, eax
; mov eax, FP4(0.0) ; Translation Y axis
; mov Xform.ey, eax
;******** Initialize GDI+: ********
INVOKE GdiplusStartup, OFFSET GDItoken, OFFSET GDIinputStruct, NULL
; Open JPEG image file:
INVOKE GdipLoadImageFromFile, OFFSET JPEGfilenameW, OFFSET GdiHbitmap
; Get width & height of bitmap:
; INVOKE GdipGetImageWidth, GdiHbitmap, ADDR bmWidth
; INVOKE GdipGetImageHeight, GdiHbitmap, ADDR bmHeight
; Get a DC:
INVOKE GetDC, MainWinHandle
MOV hDC, EAX
; Get graphics "object" from DC handle:
INVOKE GdipCreateFromHDC, hDC, ADDR gdiHgraphics
; Scale graphics:
; INVOKE GdipScaleWorldTransform, gdiHgraphics, Xscale, Yscale, 0
;============= Message loop ===================
msgloop:
INVOKE GetMessage, ADDR msg, NULL, 0, 0
OR EAX, EAX ;EAX = 0 = exit
JZ exit99
INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
JMP msgloop
exit99: INVOKE GdiplusShutdown, GDItoken
MOV EAX, msg.wParam
RET
WinMain ENDP
;====================================================================
; Main Window Proc
;====================================================================
MainWindowProc PROC hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
LOCAL hDC:HDC, gdiHgraphics:DWORD
LOCAL ps:PAINTSTRUCT
LOCAL temp:DWORD, quotient:REAL4
MOV EAX, uMsg
CMP EAX, WM_PAINT
JE do_paint
CMP EAX, WM_HSCROLL
JE do_tbar
CMP EAX, WM_CLOSE
JE do_close
dodefault:
; use DefWindowProc for all other messages:
INVOKE DefWindowProc, hWin, uMsg, wParam, lParam
RET
do_paint:
INVOKE BeginPaint, hWin, ADDR ps
MOV hDC, EAX
INVOKE SetGraphicsMode, hDC, GM_ADVANCED
; INVOKE SetWorldTransform, hDC, addr Xform
; Get graphics "object" from DC handle:
INVOKE GdipCreateFromHDC, hDC, ADDR gdiHgraphics
; Set scale:
; INVOKE GdipScaleWorldTransform, gdiHgraphics, Xscale, Yscale, 0
; Display image:
; INVOKE GdipDrawImageI, gdiHgraphics, GdiHbitmap, $mainWinWidth / 2, $mainWinHeight / 2
Invoke GdipDrawImageRectI, gdiHgraphics, GdiHbitmap, X,Y,W,H
INVOKE EndPaint, hWin, ADDR ps
commonexit:
XOR EAX, EAX
RET
do_tbar:
MOV EAX, wParam
CMP AX, TB_THUMBPOSITION
JNE commonexit
; Get new thumb pos.:
MOV ECX,EAX
SHR ECX, 16
; EAX now in range $TbarLow - $TbarHigh (percentages):
; MOV temp, EAX
; FILD temp
; MOV temp, 100
; FILD temp
; FDIV
; FSTP quotient
; MOV EAX, quotient
; mov Xform.eM11, eax
; mov Xform.eM22, eax
;
; MOV Xscale, EAX
; MOV Yscale, EAX
MOV EBX, 100
mov eax,$mainWinWidth
mul ecx
div ebx
mov W,eax
mov eax,$mainWinHeight
mul ecx
div ebx
mov H,eax
mov eax,$mainWinWidth
sub eax,W
shr eax,1
mov X,eax
mov eax,$mainWinHeight
sub eax,H
shr eax,1
mov Y,eax
INVOKE InvalidateRect, hWin, NULL, TRUE ;Force paint.
JMP commonexit
do_close:
INVOKE PostQuitMessage, NULL
MOV EAX, TRUE
RET
MainWindowProc ENDP
;====================================================================
; CenterDim
;
; Returns half screen dimension (X or Y) minus half window dimension
;
; On entry,
; EAX = screen dimension
; EDX = window dimension
;
; Returns:
; sdim/2 - wdim/2
;====================================================================
CenterDim PROC
SHR EAX, 1 ;divide screen dimension by 2
SHR EDX, 1 ;divide window dimension by 2
SUB EAX, EDX
RET ;Return w/# in EAX
CenterDim ENDP
END start
@mabdelouahab: You the man! Yeah!
Just downloaded it and saw that it worked. Now I've got to study what you did there.
Thanks!
So I can see that the magic is simply using
Invoke GdipDrawImageRectI, gdiHgraphics, GdiHbitmap, X,Y,W,H
which lets me choose where the image draws, as well as the scaled size.
If I set X and Y to fixed values then the upper-left hand corner stays put. Great!
Here's a finished app (well, testbed) that displays any JPEG image in its window. The image is sized to the window (which can be resized) within a 10-pixel border. Pretty much what I was aiming for.
;============================================
; ShowPic - GDI+ testbed for displaying JPEGs
;
;============================================
.nolist
include \masm32\include\masm32rt.inc
include \masm32\include\gdiplus.inc
includelib \masm32\lib\gdiplus.lib
.list
;============================================
; Defines, macros, prototypes, etc.
;============================================
WinMain PROTO :DWORD
ScaleImg2Win PROTO scaleImgStructPtr:DWORD
MakeUnicodeName PROTO asciiName:DWORD, unicodeName:DWORD
OFDialog PROTO hWin:HWND, fileOpenNamePtr:DWORD, fileOpenStartPath:DWORD, dialogTitle:DWORD, \
fileFilterPtr:DWORD
strcpy PROTO dest:DWORD, source:DWORD
$mainWinWidth EQU 800
$mainWinHeight EQU 600
;===== Window styles: =====
$mainWinStyles EQU WS_OVERLAPPEDWINDOW or WS_CLIPCHILDREN or WS_VISIBLE or WS_SIZEBOX
;===== Window background colors: =====
$bkRED EQU 254
$bkGRN EQU 243
$bkBLUE EQU 199
$BkColor EQU $bkRED OR ($bkGRN SHL 8) OR ($bkBLUE SHL 16)
$menuOpen EQU 2001
$menuExit EQU 2002
$imgStruct STRUCT
imgW DD ?
imgH DD ?
imgX DD ?
imgY DD ?
winW DD ?
winH DD ?
$imgStruct ENDS
$scaleC EQU 1000 ;Scaling constant
$border EQU 10
;============================================
; HERE BE DATA
;============================================
.data
ImgStruct $imgStruct <>
ScaleC DD $scaleC
WC WNDCLASSEX < SIZEOF WNDCLASSEX, \
CS_HREDRAW or CS_VREDRAW or CS_BYTEALIGNWINDOW, \
NULL, \ ;lpfnWndProc
NULL, \ ;cbClsExtra
NULL, \ ;cbWndExtra
NULL, \ ;hInstance
NULL, \ ;hIcon
NULL, \ ;hCursor
NULL, \ ;hbrBackground
NULL, \ ;lpszMenuName
NULL, \ ;lpszClassName
NULL > ;hIconSm
GDIinputStruct GdiplusStartupInput <1, NULL, FALSE, 0>
MainClassName DB "ShowPic", 0
MainTitleText DB "GDI+ Testbed for Displaying JPEGs", 0
MenuFileStr DB "File", 0
MenuOpenStr DB "Open...", 0
MenuExitStr DB "Exit", 0
FileOpenDlgTitle DB "Select JPEG File to View", 0
FileOpenStartPathStr DB ".\*."
DefaultExtension DB "jpg", 0
JPGfilters LABEL BYTE
DB "JPEG files (*.jpg, *.jpeg)", 0, "*.jpg;*.jpeg", 0
DB "All files", 0, "*.*", 0, 0
;============================================
; UNINITIALIZED DATA
;============================================
.data?
MainWinHandle HWND ?
GDItoken DD ?
GdiHbitmap DD ?
;============================================
; CODE LIVES HERE
;============================================
.code
start: INVOKE GetModuleHandle, NULL
MOV WC.hInstance, EAX
INVOKE WinMain, EAX
INVOKE ExitProcess, EAX
;====================================================================
; Mainline proc
;====================================================================
WinMain PROC hInst:DWORD
LOCAL msg:MSG, brush:HBRUSH, wX:DWORD, wY:DWORD, gpRect:RECT
LOCAL hDC:HDC, gdiGraphics:DWORD, gdiHgraphics:DWORD
LOCAL bmWidth:DWORD, bmHeight:DWORD
; Create brush to set background color:
INVOKE CreateSolidBrush, $BkColor
MOV brush, EAX
; Register class for parent window:
MOV WC.lpfnWndProc, OFFSET MainWindowProc
MOV EAX, brush
MOV WC.hbrBackground, EAX
MOV WC.lpszClassName, OFFSET MainClassName
MOV WC.hIcon, NULL
INVOKE LoadCursor, NULL, IDC_ARROW
MOV WC.hCursor, EAX
INVOKE RegisterClassEx, OFFSET WC
INVOKE GetSystemMetrics, SM_CXSCREEN
MOV EDX, $mainWinWidth
CALL CenterDim
MOV wX, EAX
INVOKE GetSystemMetrics, SM_CYSCREEN
MOV EDX, $mainWinHeight
CALL CenterDim
MOV wY, EAX
; Create our main window:
INVOKE CreateWindowEx, WS_EX_OVERLAPPEDWINDOW, OFFSET MainClassName,
OFFSET MainTitleText, $mainWinStyles, wX, wY, $mainWinWidth,
$mainWinHeight, NULL, NULL, hInst, NULL
MOV MainWinHandle, EAX
;******** Initialize GDI+: ********
INVOKE GdiplusStartup, OFFSET GDItoken, OFFSET GDIinputStruct, NULL
; Get a DC:
INVOKE GetDC, MainWinHandle
MOV hDC, EAX
; Get graphics "object" from DC handle:
INVOKE GdipCreateFromHDC, hDC, ADDR gdiHgraphics
;============= Message loop ===================
msgloop:
INVOKE GetMessage, ADDR msg, NULL, 0, 0
OR EAX, EAX ;EAX = 0 = exit
JZ exit99
INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
JMP msgloop
exit99: INVOKE GdiplusShutdown, GDItoken
MOV EAX, msg.wParam
RET
WinMain ENDP
;====================================================================
; Main Window Proc
;====================================================================
MainWindowProc PROC hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
LOCAL hDC:HDC, gdiHgraphics:DWORD, ps:PAINTSTRUCT
LOCAL hMenu:HMENU, subMenu1:HMENU, fileOpenName[MAX_PATH]:BYTE
LOCAL unicodeName[MAX_PATH * 2]:BYTE, gpRect:RECT
MOV EAX, uMsg
CMP EAX, WM_COMMAND
JE do_command
CMP EAX, WM_PAINT
JE do_paint
CMP EAX, WM_CLOSE
JE do_close
CMP EAX, WM_CREATE
JE do_create
dodefault:
; use DefWindowProc for all other messages:
INVOKE DefWindowProc, hWin, uMsg, wParam, lParam
RET
;==============================
; WM_COMMAND handler
;==============================
do_command:
MOV AX, WORD PTR wParam
CMP AX, $menuOpen
JE do_openfile
CMP AX, $menuExit
JE do_close
JMP dodefault
do_openfile:
INVOKE OFDialog, hWin, ADDR fileOpenName, OFFSET FileOpenStartPathStr, OFFSET FileOpenDlgTitle,
OFFSET JPGfilters
OR EAX, EAX ;Did user select a file?
JZ dodefault ; Nope.
; File selected--must make filename Unicode:
INVOKE MakeUnicodeName, ADDR fileOpenName, ADDR unicodeName
; Open JPEG image file:
INVOKE GdipLoadImageFromFile, ADDR unicodeName, OFFSET GdiHbitmap
; Get width & height of bitmap:
INVOKE GdipGetImageWidth, GdiHbitmap, OFFSET ImgStruct.imgW
INVOKE GdipGetImageHeight, GdiHbitmap, OFFSET ImgStruct.imgH
; Get width & height of display window:
INVOKE GetClientRect, hWin, ADDR gpRect
MOV EAX, gpRect.right
SUB EAX, gpRect.left
SUB EAX, $border * 2
MOV ImgStruct.winW, EAX
MOV EAX, gpRect.bottom
SUB EAX, gpRect.top
SUB EAX, $border * 2
MOV ImgStruct.winH, EAX
; Scale image to our window:
INVOKE ScaleImg2Win, OFFSET ImgStruct
INVOKE InvalidateRect, hWin, NULL, TRUE ;Force paint.
XOR EAX, EAX
RET
;==============================
; WM_CREATE handler
;==============================
do_create:
; Creating the menu here "on the fly" avoids the need for a resource file:
CALL CreateMenu
MOV hMenu, EAX
CALL CreatePopupMenu
MOV subMenu1, EAX
INVOKE AppendMenu, hMenu, MF_POPUP or MF_STRING, subMenu1, OFFSET MenuFileStr
INVOKE AppendMenu, subMenu1, MF_STRING, $menuOpen, OFFSET MenuOpenStr
INVOKE AppendMenu, subMenu1, MF_STRING, $menuExit, OFFSET MenuExitStr
INVOKE SetMenu, hWin, hMenu
XOR EAX, EAX
RET
;==============================
; WM_PAINT handler
;==============================
do_paint:
INVOKE BeginPaint, hWin, ADDR ps
MOV hDC, EAX
; Apparently this isn't needed:
; INVOKE SetGraphicsMode, hDC, GM_ADVANCED
; 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,
ImgStruct.imgX, ImgStruct.imgY, ImgStruct.imgW, ImgStruct.imgH
INVOKE EndPaint, hWin, ADDR ps
commonexit:
XOR EAX, EAX
RET
do_close:
INVOKE PostQuitMessage, NULL
MOV EAX, TRUE
RET
MainWindowProc ENDP
;=====================================================
; ScaleImg2Win (imgStructPtr)
;
; Given the dimensions of an image and a window,
; scales the image up or down as needed to fit
; within the window and sets the origin (X,Y).
;
; All arguments are passed via the $imgStruct.
;=====================================================
ScaleImg2Win PROC imgStructPtr:DWORD
LOCAL wScale:DWORD, hScale:DWORD
PUSH EBX
MOV EBX, imgStructPtr
; Calculate width & height conversion factors:
MOV EAX, [EBX].$imgStruct.winW
MUL ScaleC
XOR EDX, EDX
DIV [EBX].$imgStruct.imgW
MOV wScale, EAX
MOV EAX, [EBX].$imgStruct.winH
MUL ScaleC
XOR EDX, EDX
DIV [EBX].$imgStruct.imgH
MOV hScale, EAX
; Which factor is smaller?
CMP EAX, wScale
JB useH ;Height, so scale to it.
MOV EAX, wScale
useH: MOV ECX, EAX ;ECX = scale factor.
; Scale to selected factor:
MOV EAX, [EBX].$imgStruct.imgW
MUL ECX
XOR EDX, EDX
DIV ScaleC
MOV [EBX].$imgStruct.imgW, EAX
MOV EAX, [EBX].$imgStruct.imgH
MUL ECX
XOR EDX, EDX
DIV ScaleC
MOV [EBX].$imgStruct.imgH, EAX
; Now determine upper-left corner of image:
MOV EAX, [EBX].$imgStruct.winW
SUB EAX, [EBX].$imgStruct.imgW
JNC @F ;Check for result <0.
XOR EAX, EAX
@@: SHR EAX, 1 ; / 2.
ADD EAX, $border
MOV [EBX].$imgStruct.imgX, EAX
MOV EAX, [EBX].$imgStruct.winH
SUB EAX, [EBX].$imgStruct.imgH
JNC @F
XOR EAX, EAX
@@: SHR EAX, 1
ADD EAX, $border
MOV [EBX].$imgStruct.imgY, EAX
POP EBX
RET
ScaleImg2Win ENDP
;=====================================================
; OFDialog (hWin, fileOpenNamePtr, fileOpenStartPath, dialogTitle, fileFilterPtr)
;
; Basically a wrapper for GetOpenFileName()
; Sets up needed structure, invokes file-selection dialog.
;
; On exit,
; EAX = return value (zero if user cancelled or
; selected no file)
; EDX = offset of filename from pathname (returned
; in fileOpenNamePtr)
;=====================================================
OFDialog PROC hWin:HWND, fileOpenNamePtr:DWORD, fileOpenStartPath:DWORD, dialogTitle:DWORD,
fileFilterPtr:DWORD
LOCAL ofn:OPENFILENAME
; First, zero out entire structure:
PUSH EDI
LEA EDI, ofn
MOV ECX, SIZEOF OPENFILENAME
XOR EAX, EAX
REP STOSB ;Zero it out.
POP EDI
MOV ofn.lStructSize, SIZEOF OPENFILENAME
MOV EAX, hWin
MOV ofn.hWndOwner, EAX
MOV EAX, WC.hInstance
MOV ofn.hInstance, EAX
MOV EAX, fileOpenNamePtr
MOV ofn.lpstrFile, EAX
INVOKE strcpy, EAX, fileOpenStartPath
MOV ofn.nMaxFile, MAX_PATH
MOV EAX, dialogTitle
MOV ofn.lpstrTitle, EAX
MOV EAX, fileFilterPtr
MOV ofn.lpstrFilter, EAX
MOV ofn.Flags, OFN_EXPLORER OR OFN_FILEMUSTEXIST OR OFN_LONGNAMES OR OFN_HIDEREADONLY
INVOKE GetOpenFileName, ADDR ofn
MOVZX EDX, ofn.nFileOffset
RET
OFDialog ENDP
;=====================================================
; MakeUnicodeName (asciiName, unicodeName)
;
; Magically turns a plain ASCII string into a Unicode one.
; Arguments are pointers to strings.
;=====================================================
MakeUnicodeName PROC asciiName:DWORD, unicodeName:DWORD
PUSH ESI
PUSH EDI
MOV ESI, asciiName
MOV EDI, unicodeName
XOR AH, AH
@@: LODSB
STOSW
OR AX, AX
JNZ @B
STOSW ;Terminator.
POP EDI
POP ESI
RET
MakeUnicodeName ENDP
;====================================================================
; strcpy (dest, source)
;====================================================================
strcpy PROC dest:DWORD, source:DWORD
PUSH ESI
PUSH EDI
MOV ESI, source
MOV EDI, dest
scp10: LODSB
STOSB
OR AL, AL ;Stop on NULL.
JNZ scp10
scp99: POP EDI
POP ESI
RET
strcpy ENDP
;====================================================================
; CenterDim
;
; Returns half screen dimension (X or Y) minus half window dimension
;
; On entry,
; EAX = screen dimension
; EDX = window dimension
;
; Returns:
; sdim/2 - wdim/2
;====================================================================
CenterDim PROC
SHR EAX, 1 ;divide screen dimension by 2
SHR EDX, 1 ;divide window dimension by 2
SUB EAX, EDX
RET ;Return w/# in EAX
CenterDim ENDP
END start
Something I just noticed is that if you display a small image, the details become very soft when it gets blown up. Wonder if there are any parameters that can be tweaked to improve this behavior? The displayed images look pretty crappy. (Try it with a small JPG, say 50x50 or so.)
Not really, a small image has far less data in it that a big one that is properly in focus. You are better off to use a larger image and tweak the JPG compression level to trade clarity for size.
No. Yes, a small image will have less data, obviously, but when I view it in my graphics program (I use an old version of Paint Shop Pro), then I can see the pixels clearly when I blow it up, which is what I would expect to see. But the GDI+ display function smears the pixels into mush.
When you blow up any small image you should be able to see the individual pixels clearly. I've attached both images here, one from this testbed, the other from Paint Shop Pro. The difference in quality is obvious.
The smearing of pixels may be intentional on the part of the GDI+ designers, the thinking being that if you blow up an image you should not be able to see individual pixels. But I do want to see them. That's why I wonder if there are any variables that can be tweaked to change the rendering behavior. (Also, since Paint Shop Pro only scales its images in integral steps, 1:1, 1:2, 1:3, etc., it may be easier for it to render the image exactly as-is, as opposed to the GDI+ renderer which can scale to any fractional value. It looks like they're doing some kind of interpolation on the image.
Try enlarging you small image with StretchBlt and see if there is any difference. Many people botch GDI+ so you would not be alone here.
Thanks, but I don't know how to do that here. Let me think about it. Not sure how I mix GDI and GDI+ stuff.
The attached zip file has 4 images scaled up from 32x32 to 128x128. All done with GDI.
The scaling is done with the following API.
HANDLE CopyImage(
HANDLE hImage, // handle to the image to copy
UINT uType, // type of image to copy
int cxDesired, // desired width of new image
int cyDesired, // desired height of new image
UINT fuFlags // copy flags
);
You can thank Vortex for decyphering this one.