PlanetSquires Forums

Support Forums => General Board => Topic started by: José Roca on August 01, 2016, 01:44:33 AM

Title: GdiPlus classes
Post by: José Roca on August 01, 2016, 01:44:33 AM
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.
Title: Re: GdiPlus classes
Post by: José Roca on August 01, 2016, 02:30:35 AM
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
' ========================================================================================

Title: Re: GdiPlus classes
Post by: José Roca on August 03, 2016, 05:16:04 AM
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.


Title: Re: GdiPlus classes
Post by: José Roca on August 04, 2016, 04:40:41 AM
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

Title: Re: GdiPlus classes
Post by: José Roca on August 04, 2016, 04:44:10 AM
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
' =====================================================================================

Title: Re: GdiPlus classes
Post by: José Roca on August 04, 2016, 04:47:43 AM
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
' ========================================================================================

Title: Re: GdiPlus classes
Post by: José Roca on August 04, 2016, 05:18:21 AM
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.