News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

GDI+ revisited

Started by NoCforMe, January 28, 2022, 06:52:00 PM

Previous topic - Next topic

NoCforMe

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

jj2007


Greenhorn

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

NoCforMe

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

jj2007

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

NoCforMe

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

mabdelouahab

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

NoCforMe

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 NowTM. Until then I can use all the help I can get.
Assembly language programming should be fun. That's why I do it.

mabdelouahab

;============================================
; 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


NoCforMe

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

NoCforMe

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

NoCforMe

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

hutch--

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.

NoCforMe

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

hutch--

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.