I'm writing classes for GDI+ as close as I can to the C++ classes. GDI+ has a flat API that can be used with FB, but the problem is that almost all the code that you can find in the web uses the C++ or . NET classes. I have done about 2 thirds of the work. The problem is testing the more than 600 functions.
One of my main interests was to find the way to make it High DPI aware, without using the tedious way of multiply the values by the scale ratio.
Three of the more important classes, Graphics, Brushes and Pens have an ScaleTransform function that allows to do it by passing the scaling ratios, and the AdjustableArrowCap allows to do it calling the SetWidthScale function.
For example:
DIM graphics AS CGpGraphics = hdc
DIM rxRatio AS SINGLE = graphics.GetDpiX / 96
graphics.ScaleTransform(rxRatio, rxRatio)
DIM AS CGpAdjustableArrowCap myArrow = CGpAdjustableArrowCap(10, 10, true)
myArrow.SetWidthScale(rxRatio)
GDI+ is an important technology to work with graphics and images.
As you can see in the captured image, making the application High DPI aware and using scaling we get the same size that running it virtualized by Windows, but it is sharper.
This is how it looks the code that produces the above capture, using the GDI+ classes and my graphic control.
' ########################################################################################
' Microsoft Windows
' File: AdjustableArrowCapGetHeight.bas
' Contents: GDI+ - AdjustableArrowCapGetHeight example
' Compiler: FreeBasic 32 & 64 bit
' Copyright (c) 2016 Jose Roca. Freeware. Use at your own risk.
' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
' EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
' MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
' ########################################################################################
#define UNICODE
#INCLUDE ONCE "Afx/CWindow.inc"
#INCLUDE ONCE "Afx/CGdiPlus/CGdiPlus.inc"
#INCLUDE ONCE "Afx/CGraphCtx.inc"
USING Afx
CONST IDC_GRCTX = 1001
DECLARE FUNCTION WinMain (BYVAL hInstance AS HINSTANCE, _
BYVAL hPrevInstance AS HINSTANCE, _
BYVAL szCmdLine AS ZSTRING PTR, _
BYVAL nCmdShow AS LONG) AS LONG
END WinMain(GetModuleHandleW(NULL), NULL, COMMAND(), SW_NORMAL)
DECLARE FUNCTION WndProc (BYVAL hwnd AS HWND, BYVAL uMsg AS UINT, BYVAL wParam AS WPARAM, BYVAL lParam AS LPARAM) AS LRESULT
' ========================================================================================
' The following example creates an AdjustableArrowCap, myArrow, and sets the height of the
' cap. The code then creates a Pen, assigns myArrow as the ending line cap for the Pen,
' and draws a capped line. Next, the code gets the height of the arrow cap, creates a new
' arrow cap with height equal to the height of myArrow, assigns the new arrow cap as the
' ending line cap for the Pen, and draws another capped line.
' ========================================================================================
SUB Example_GetHeight (BYVAL hdc AS HDC)
' // Create a graphics object from the window device context
DIM graphics AS CGpGraphics = hdc
' // Get the DPI scaling ratio
DIM rxRatio AS SINGLE = graphics.GetDpiX / 96
' // Set the scale transform
graphics.ScaleTransform(rxRatio, rxRatio)
' // Create an AdjustableArrowCap with a height of 10 pixels.
DIM myArrow AS CGpAdjustableArrowCap = CGpAdjustableArrowCap(10, 10, TRUE)
' // Adjust to DPI by setting the scale width
myArrow.SetWidthScale(rxRatio)
' // Create a Pen, and assign myArrow as the end cap.
DIM arrowPen AS CGpPen = GDIP_ARGB(255, 0, 0, 0)
arrowPen.SetCustomEndCap(@myArrow)
' // Draw a line using arrowPen.
graphics.DrawLine(@arrowPen, 0, 20, 100, 20)
' // Create a second arrow cap using the height of the first one.
DIM AS CGpAdjustableArrowCap otherArrow = CGpAdjustableArrowCap(myArrow.GetHeight, 20, TRUE)
otherArrow.SetWidthScale(rxRatio)
' // Assign the new arrow cap as the end cap for arrowPen.
arrowPen.SetCustomEndCap(@otherArrow)
' // Draw a line using arrowPen.
graphics.DrawLine(@arrowPen, 0, 55, 100, 55)
END SUB
' ========================================================================================
' ========================================================================================
' Main
' ========================================================================================
FUNCTION WinMain (BYVAL hInstance AS HINSTANCE, _
BYVAL hPrevInstance AS HINSTANCE, _
BYVAL szCmdLine AS ZSTRING PTR, _
BYVAL nCmdShow AS LONG) AS LONG
' // Set process DPI aware
' // The recommended way is to use a manifest file
AfxSetProcessDPIAware
' // Initialize GDI+
DIM token AS ULONG_PTR = AfxGdipInit
' // Creates the main window
DIM pWindow AS CWindow
' -or- DIM pWindow AS CWindow = "MyClassName" (use the name that you wish)
pWindow.Create(NULL, "GDI+ AdjustableArrowCapGetHeight", @WndProc)
pWindow.WindowStyle = WS_OVERLAPPED OR WS_CAPTION OR WS_SYSMENU
' // Sizes it by setting the wanted width and height of its client area
pWindow.SetClientSize(400, 250)
' // Centers the window
pWindow.Center
' // Add a graphic control
DIM pGraphCtx AS CGraphCtx = CGraphCtx(@pWindow, IDC_GRCTX, "", 0, 0, pWindow.ClientWidth, pWindow.ClientHeight)
pGraphCtx.Clear BGR(255, 255, 255)
' // Get the memory device context of the graphic control
DIM hdc AS HDC = pGraphCtx.GetMemDc
' // Draw the graphics
Example_GetHeight(hdc)
' // Displays the window and dispatches the Windows messages
FUNCTION = pWindow.DoEvents(nCmdShow)
' // Shutdown GDI+
AfxGdipShutdown(token)
END FUNCTION
' ========================================================================================
' ========================================================================================
' Main window procedure
' ========================================================================================
FUNCTION WndProc (BYVAL hwnd AS HWND, BYVAL uMsg AS UINT, BYVAL wParam AS WPARAM, BYVAL lParam AS LPARAM) AS LRESULT
SELECT CASE uMsg
CASE WM_COMMAND
SELECT CASE LOWORD(wParam)
CASE IDCANCEL
' // If ESC key pressed, close the application by sending an WM_CLOSE message
IF HIWORD(wParam) = BN_CLICKED THEN
SendMessageW hwnd, WM_CLOSE, 0, 0
EXIT FUNCTION
END IF
END SELECT
CASE WM_DESTROY
' // Ends the application by sending a WM_QUIT message
PostQuitMessage(0)
EXIT FUNCTION
END SELECT
' // Default processing of Windows messages
FUNCTION = DefWindowProcW(hwnd, uMsg, wParam, lParam)
END FUNCTION
' ========================================================================================
The FB headers for GDI+ are a mess, with several incompatibilities with the 32 and 64-bit versions. I will try to solve them adding some unions to AfxGdiPlus.inc.
For example, the 32-bit version has not defined a ColorMap structure, and the 32-bit application tries to use the one for GDI. So, if you intend to write a GDI+ application that works with 32 and 64-bit, you have to do:
#ifdef __FB_64BIT__
DIM redToBlue AS ColorMap_
redToBlue.oldColor.value = GDIP_ARGB(255, 255, 0, 0)
redToBlue.newColor.value = GDIP_ARGB(255, 0, 0, 255)
#else
DIM redToBlue AS ColorMap
redToBlue.from = GDIP_ARGB(255, 255, 0, 0)
redToBlue.to = GDIP_ARGB(255, 0, 0, 255)
#endif
GDIP_ARGB is a function to solve another incompatibility between 32 and 64-bit.
I have defined the following union:
UNION GDIP_COLORMAP
' // For compatibility with GDI+
TYPE
oldColor AS COLORREF
newColor AS COLORREF
END TYPE
' // For compatibility with GDI
TYPE
from AS COLORREF
to AS COLORREF
END TYPE
END UNION
So, instead of the above mess, I can use:
DIM redToBlue AS GDIP_COLORMAP = (GDIP_ARGB(255, 255, 0, 0), GDIP_ARGB(255, 0, 0, 255))
with both compilers.
The following macros aren't defined in the headers for FB 32bit, so I have added them conditionally to CGdiPlus.inc.
' // Not defined in the FB headers for 32-bit
#ifndef __FB_64BIT__
#define GetImageDecoders(numDecoders, size, decoders) cast(GpStatus, GdipGetImageDecoders((numDecoders), (size), (decoders)))
#define GetImageDecodersSize(numDecoders, size) cast(GpStatus, GdipGetImageDecodersSize((numDecoders), (size)))
#define GetImageEncoders(numEncoders, size, encoders) cast(GpStatus, GdipGetImageEncoders((numEncoders), (size), (encoders)))
#define GetImageEncodersSize(numEncoders, size) cast(GpStatus, GdipGetImageEncodersSize((numEncoders), (size)))
#endif
Another mess is that GpPoint is defined as Point in 64 bit and as Point_ in 32 bit.
DIM pts(0 TO 2) AS GpPoint = {GDIP_POINT(-15, -15), GDIP_POINT(0, 0), GDIP_POINT(15, -15)}
'#ifdef __FB_64BIT__
' DIM pts(0 TO 2) AS GpPoint = {(-15, -15), (0, 0), (15, -15)}
'#else
' ' // With the 32-bit compiler, the above syntax can't be used because a mess in the
' ' // FB headers for GdiPlus: GpPoint is defined as Point in 64 bit and as Point_ in 32 bit.
' ' // Using the helper function GDIP_POINT, the same syntax can be used with both compilers.
' DIM pts(0 TO 2) AS GpPoint
' pts(0).x = -15 : pts(0).y = -15 : pts(2).x = 15: pts(2).y = -15
'#endif
There are similar problems with GpPointF, GpRectF and GpRect, so I have added the following helper functions to AfxGdiPlus.inc.
' ========================================================================================
' Fills a RECTF structure
' ========================================================================================
PRIVATE FUNCTION GDIP_RECTF (BYVAL x AS SINGLE, BYVAL y AS SINGLE, BYVAL nWidth AS SINGLE, BYVAL nHeight AS SINGLE) AS GpRectF
DIM rcf AS GpRectF
rcf.x = x : rcf.y = y : rcf.Width = nWidth : rcf.Height = nHeight
RETURN rcf
END FUNCTION
' ========================================================================================
' ========================================================================================
' Fills a RECT structure
' ========================================================================================
PRIVATE FUNCTION GDIP_RECT (BYVAL x AS LONG, BYVAL y AS LONG, BYVAL nWidth AS LONG, BYVAL nHeight AS LONG) AS GpRect
DIM rc AS GpRect
rc.x = x : rc.y = y : rc.Width = nWidth : rc.Height = nHeight
RETURN rc
END FUNCTION
' ========================================================================================
' =====================================================================================
' Fills a POINTF structure
' =====================================================================================
PRIVATE FUNCTION GDIP_POINTF (BYVAL x AS SINGLE, BYVAL y AS SINGLE) AS GpPointF
DIM pt AS GpPointF
pt.x = x : pt.y = y
RETURN pt
END FUNCTION
' =====================================================================================
' =====================================================================================
' Fills a POINTF structure
' =====================================================================================
PRIVATE FUNCTION GDIP_POINT (BYVAL x AS SINGLE, BYVAL y AS SINGLE) AS GpPoint
DIM pt AS GpPoint
pt.x = x : pt.y = y
RETURN pt
END FUNCTION
' =====================================================================================
Also problems with GetAlpha, GetRed, GetGreen, GetBlue and Color, not defined in 32 bit and defined as member of a class in 64 bit.
So I have added the following helper functions to AfxGdiPlus.inc.
UNION GDIP_BGRA
color AS COLORREF
TYPE
blue AS UBYTE
green AS UBYTE
red AS UBYTE
alpha AS UBYTE
END TYPE
END UNION
' ========================================================================================
' Returns an ARGB color value initialized with the specified values for the alpha, red,
' green, and blue components.
' ========================================================================================
PRIVATE FUNCTION GDIP_ARGB (BYVAL a AS UBYTE, BYVAL r AS UBYTE, BYVAL g AS UBYTE, BYVAL b AS UBYTE) AS COLORREF
DIM clr AS GDIP_BGRA
clr.alpha = a : clr.red = r : clr.green = g : clr.blue = b
FUNCTION = clr.color
END FUNCTION
' ========================================================================================
' ========================================================================================
PRIVATE FUNCTION GDIP_COLOR (BYVAL a AS UBYTE, BYVAL r AS UBYTE, BYVAL g AS UBYTE, BYVAL b AS UBYTE) AS COLORREF
DIM clr AS GDIP_BGRA
clr.alpha = a : clr.red = r : clr.green = g : clr.blue = b
FUNCTION = clr.color
END FUNCTION
' ========================================================================================
' ========================================================================================
' Returns an XRGB color value initialized with the specified values for the red, green,
' and blue components.
' ========================================================================================
PRIVATE FUNCTION GDIP_XRGB (BYVAL r AS UBYTE, BYVAL g AS UBYTE, BYVAL b AS UBYTE) AS COLORREF
FUNCTION = GDIP_ARGB(&HFF, r, g, b)
END FUNCTION
' ========================================================================================
' ========================================================================================
' Returns a BGRA color value initialized with the specified values for the blue, green,
' red and alpha components.
' ========================================================================================
PRIVATE FUNCTION GDIP_BGRA (BYVAL b AS UBYTE, BYVAL g AS UBYTE, BYVAL r AS UBYTE, BYVAL a AS UBYTE) AS COLORREF
DIM clr AS GDIP_BGRA
clr.blue = b : clr.green = g : clr.red = r : clr.alpha = a
FUNCTION = clr.color
END FUNCTION
' ========================================================================================
' ========================================================================================
' Returns an RGBA color value initialized with the specified values for the red, green,
' blue and alpha components.
' ========================================================================================
PRIVATE FUNCTION GDIP_RGBA (BYVAL r AS UBYTE, BYVAL g AS UBYTE, BYVAL b AS UBYTE, BYVAL a AS UBYTE) AS COLORREF
FUNCTION = GDIP_ARGB(a, r, g, b)
END FUNCTION
' ========================================================================================
' ========================================================================================
' Gets the alpha component of an ARGB color.
' ========================================================================================
PRIVATE FUNCTION GDIP_GetAlpha (BYVAL argbcolor AS COLORREF) AS BYTE
DIM clr AS GDIP_BGRA
clr.color = argbcolor
RETURN clr.alpha
END FUNCTION
' ========================================================================================
' ========================================================================================
' Gets the red component of an ARGB color.
' ========================================================================================
PRIVATE FUNCTION GDIP_GetRed (BYVAL argbcolor AS COLORREF) AS BYTE
DIM clr AS GDIP_BGRA
clr.color = argbcolor
RETURN clr.red
END FUNCTION
' ========================================================================================
' ========================================================================================
' Gets the green component of an ARGB color.
' ========================================================================================
PRIVATE FUNCTION GDIP_GetGreen (BYVAL argbcolor AS COLORREF) AS BYTE
DIM clr AS GDIP_BGRA
clr.color = argbcolor
RETURN clr.green
END FUNCTION
' ========================================================================================
' ========================================================================================
' The GetBlue method gets the blue component of an ARGB color.
' ========================================================================================
PRIVATE FUNCTION GDIP_GetBlue (BYVAL argbcolor AS COLORREF) AS BYTE
DIM clr AS GDIP_BGRA
clr.color = argbcolor
RETURN clr.blue
END FUNCTION
' ========================================================================================
The classes are close to completion. It remains a couple of classes for metafiles and effects.
I also have more than 100 examples testing Caps, Brushes, Graphics, Matrices, Pens and Regions.
Many more examples are needed to test the other classes.
I also want to write a help file documenting the GDI+ flat API functions and the CGdiPlus classes.
I will write some examples using the flat API for comparison purposes, but using the classes makes GDI+ programming much easier.