• Welcome to Jose's Read Only Forum 2023.
 

GDI+ - Getting Image Object from Graphic Control

Started by Gary Beene, March 10, 2011, 03:55:11 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Gary Beene

In code that I've seen posted here, the image object created by loading a file, like this:
   sFileName = UCode$(sFileName)
   hStatus = GdipLoadImageFromFile(StrPTR(sFileName), pImage)   ' Create the Image object (pImage)
   hStatus = GdipCreateFromHDC(hDC, pGraphics)                  ' Create the Graphic object (pGraphics)
   hStatus = GdipDrawImage(pGraphics, pImage, 0, 0)             ' Draw the image


Can I create the image/graphic objects from an existing DDT graphic control? 

For example, if I create an image by drawing on a graphic control, then want to save the results as a BMP/GIF/JPG using GDI+, I'll need the image object to use as an argument for GdipSaveImageToFile.

My perusal of the samples here hasn't found the answer - but then, there are a lot of examples and I may well have missed it.  I'll keep looking.



José Roca

Quote
Can I create the image/graphic objects from an existing DDT graphic control?

Yes, if you have used GDI+ to create it. If it has been created using DDT graphic statements, it depends if you can get the Windows bitmap handle (never have used the DDT graphic control because can't be used with SDK windows). If you can, there is a function called GdipCreateBitmapFromHBITMAP for that purpose.

Patrice Terrier

#2
Gary,

Indeed you don't need a graphic control at all.
You can do everything from memory, using the double buffer technic, and BitBlt everything in a blink of and eye during the WM_PAINT message.

Also DDT graphic and GDI32 are very limited and they can't work with DWM.

I would encourage you to use only 32-bit with full ARGB colors.

...
Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com

Patrice Terrier

#3
Also much better to use GdipCreateBitmapFromScan0 than GdipCreateBitmapFromHBITMAP

Here is how i do it in GDImage:


'//2.11 To convert a GDImage bitmap into a GDI+ image,
'       based on a provided DC matching the GDImage bitmap
FUNCTION zBitmapToImage ALIAS "zBitmapToImage" (BYVAL hDC AS LONG) EXPORT AS LONG
   LOCAL bi AS MYBITMAPINFO
   LOCAL bm AS BITMAP, hBitmap AS LONG, img AS LONG

   hBitmap = ZI_GetBitmapFromDC(hDC)
   IF hBitmap THEN
      CALL GetObject(hBitmap, SIZEOF(bm), bm)

      bi.bmiHeader.biSize        = SIZEOF(bi.bmiHeader)
      bi.bmiHeader.biWidth       = bm.bmWidth
      bi.bmiHeader.biHeight      = -bm.bmHeight ' Put top in TOP instead on bottom!
      bi.bmiHeader.biPlanes      = 1
      bi.bmiHeader.biBitCount    = 32
      bi.bmiHeader.biCompression = %BI_RGB

      MaxBound& = bm.bmWidth * bm.bmHeight * 4
      IF Ubound(gByteArray()) < MaxBound& THEN REDIM gByteArray(1 TO MaxBound&)

      IF GetDIBits(hDC, hBitmap, 0, bm.bmHeight, gByteArray(1), bi, %DIB_RGB_COLORS) THEN
         IF GdipCreateBitmapFromScan0(bm.bmWidth, bm.bmHeight, bm.bmWidth * 4, %PixelFormat32bppARGB, gByteArray(1), img) = 0 THEN
            FUNCTION = img
         END IF
      END IF
   END IF
END FUNCTION
Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com

Gary Beene

Jose,
In POFFS, I saw a post that used GetCurrentObject to return a value to use in GDIPCreateBitmapFromHBITMAP, like this:
hBitmap = GetCurrentObject(hGraphicDC, %OBJ_Bitmap)
GDIpCreateBitmapFromHBITMAP( hBitmap, ByVal %Null, pImage)


But when I place that code in the following example, the program doesn't crash but the file it creates is definitely not a bitmap. 

It it obvious why this doesn't work as the POFFS example suggested? Unfortunately, the POFFS example wasn't a compilable example, so I didn't have a complete example to work from.

'Compilable Example:
#Compile Exe
#Dim All
#Debug Error On     'catch array/pointer errors
#Debug Display On   'display untrapped errors
#Include "Gdiplus.inc"
#Include "Gdiputils.inc"

%IDC_Graphic = 200 : %IDC_ButtonSave = 201 : %IDC_ButtonLoad = 202
Global hDlg, hGraphicDC As Dword

Function PBMain() As Long
   'initialize GDIPlus
   Local token As Dword, StartupInput As GdiplusStartupInput
   StartupInput.GdiplusVersion = 1
   GdiplusStartup(token, StartupInput, ByVal %NULL)

   Dialog New Pixels, 0, "Test Code",300,300,350,350, %WS_OverlappedWindow To hDlg
   Control Add Button, hDlg, %IDC_ButtonSave,"Save", 10,10,100,20
   Control Add Graphic, hDlg, %IDC_Graphic,"", 20,40,300,300
   Graphic Attach hDlg, %IDC_Graphic
   Graphic Render "ruler.bmp", (0,0)-(299,299)
   Graphic Get DC To hGraphicDC
   Dialog Show Modal hDlg Call DlgProc

   'shut downn GDI+
   GdiplusShutdown token
End Function

CallBack Function DlgProc() As Long
   Select Case Cb.Msg
      Case %WM_Command
         Select Case Cb.Ctl
            Case %IDC_ButtonSave : GDIPLus_SaveImage("image/gif",  "test.bmp")
         End Select
   End Select
End Function

Sub GDIPLus_SaveImage(sMimeType As String, fName As String)
   Local s As String, sEncoderClsid As Guid, pImage, hBitmap As Dword
   s = GDIPlusGetEncoderClsid(sMimeType)
   sEncoderClsid = Guid$(s)

   hBitmap = GetCurrentObject(hGraphicDC, %OBJ_Bitmap)                  'get HBITMAP object from graphic control DC

   GDIpCreateBitmapFromHBITMAP( hBitmap, ByVal %Null, pImage)           'create GDI+ image (pImage)
   GdipSaveImageToFile(pImage,StrPtr(fName),sEncoderClsid, ByVal %Null) 'save to GIF file
   If pImage Then GdipDisposeImage(pImage)                              'cleanup
End Sub

José Roca

The documentation says "Do not pass to the GdipCreateBitmapFromHBITMAP function a GDI bitmap or a GDI palette that is currently (or was previously) selected into a device context."

Youy will have to try GdipCreateBitmapFromScan0.


Patrice Terrier

#6
Gary,

GdipCreateBitmapFromScan0, is your solution.

And if you want to deal well with GDIPLUS, then you should also consider to move to a plain 32-bit SDK graphic control.
There are plainty of examples on this forum.

...
Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com

Donald Ping

Case %IDC_ButtonSave : GDIPLus_SaveImage("image/gif",  "test.bmp")

If you want the extension to be GIF then change "test.bmp" to "test.gif"

GdipSaveImageToFile(pImage,StrPtr(fName),sEncoderClsid, ByVal %Null) 'save to GIF file

Should be:
fName = ucode$(fName)
GdipSaveImageToFile(pImage,StrPtr(fName),sEncoderClsid, ByVal %Null) 'save to GIF file

And then it will work. You have to send the filename as a unicode string pointer.

Patrice Terrier

#8
Donald,

Good catches ;)

Thus said, i wouldn't use GIF (because it is only 256 colors).
For me the best format to use is PNG (because of Alpha channel).

...
Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com

Gary Beene

Donald,
Yippee!  That did the trick. I love it when a piece of code does what I want - especially when the result is compact, as seems to be the case for the code to exchange graphics between DDT and GDI+ (I realize that the code may be short, and that short code often belies the complexity of the thought process needed to use the code). Funny thing, when I ran my code I got Chinese characters for the file name. That might have been a clue that there some unicode thing going on.

Sorry about the mixed bmp/gif references.  I played with the code on several file types and didn't synchronize all of the comments before posting.  My bad. 

I have GDI+ load image code from Jose, which also uses the UCode$ conversion, but in the POFFS examples I found, it wasn't used, leading me astray.  Could have simply been that the POFFS examples were incomplete snippets.  I should have stepped out of the POFFS box.

Thanks for the post.  That gets me moving further along.

Jose,
I've said before that people sometimes tell me things, and until I'm ready to listen the value of the advice doesn't always sink in.  That's how I felt this week when I reread your 2009 post to me, showing me how to load GIF/JPG/BMP and other file image types using GDI+.  At the time, I simply wasn't experienced enough with PowerBASIC to read through your code to the core 3 lines that comprised your basic response.  At the time, I had little experience in using INCLUDE files, wasn't that comfortable with reading other folks PowerBASIC code, was intimidated by you, and was looking for a quick answer rather than welcoming an introduction to a whole new branch of coding tools. All of that made me un-receptive to what you were trying to tell me.  I certainly appreciated the response, but wasn't mentally prepared to use it at the time.

Imagine my astonishment this week when I went back to that posting!  How could I have missed seeing the value of such an easy solution to the problem I was facing. Sometimes, beginners just don't get the point, but I got it this time.  I didn't gush over the answer then, but I'm very appreciative now!  The old saying about how you can lead a horse to water but can't make him drink seems definitely to apply to me at times.

I suspect I need to go back and reread all of the 2009 posts I made (that was my first year with PowerBASIC) and the responses I got.  I bet I'll find a lot of pearls whose value wasn't obvious at the time.

Patrice,
I'm usually selective about which image format I use - GIF for few-color cartoons or transparent images, JPG for images, BMP for lossless transfer of images.  But I must admit that I've done little to introduce PNG into my toolbox.  All of the software tools I work with seem to support PNG, as I suppose do my browsers. But in general, the GIF/BMP/JPG trio have satisfied my needs.  Have you run into situation where using PNG made any real difference in being able to do what you wanted to do?  Your graphics needs are more serious than mine, as I would suspect are your requirements.

Just to clarify, I'm an enormous fan of API - use it all the time, plan to use it even more.  When I say SDK, I'm only referring to the additional use of the API CreateWindowEx to create windows, and the corresponding avoidance of using CONTROL ADD (and derivative commands) from PowerBASIC.  We're on the same page here, yes?

Regarding your suggestion about moving to a plain 32-bit SDK graphic control - I am interested in understanding how to work with both coding styles.  As you know, I post a lot of code that is pretty basic stuff, usually DDT code that is aimed at new-intermediate programmers.  I hope my value added has been clarity/simplification of code to demonstrate the fundamental concepts of a topic.  Now that I'm comfortable with DDT, and know enough to appreciate what SDK might do for me, I've reached a point where I want to begin working more with SDK..

To that end, I've started work on on re-writing my website PowerBASIC tutorials - which are DDT intensive - to equally cover the use of SDK.  In part, because I know from comments like you, Jose, and others that there are benefits to its use.  Also in part because I know that the PowerBASIC Help file gives zilch support in this area. I'd like to help bridge the education gap - provide an easier transition than I think newer users have today.  I'd like to give the average PowerBASIC newbie help in comparing the two, giving them a balanced view of the two approaches. I'm unlikely ever to recommend that a programmer go all one way or the other.

As I get more into my own SDK experience cycle, I'm sure I'll call on you, Jose and others to help me get past sticking points.  I've been a dance instructor in my life, and I always told my students that to be a good dancer, you just have to dance about 1,000 dances.  Programming is like that.  I can't learn SDK by reading about it, I have to use it over and over to fully understand it at a level from which I can get the most value.  That's next on my list of godos (and it is such a long list!  :)

Thanks guys!

Patrice Terrier

Gary

QuoteRegarding your suggestion about moving to a plain 32-bit SDK graphic control - I am interested in understanding how to work with both coding styles.  As you know, I post a lot of code that is pretty basic stuff, usually DDT code that is aimed at new-intermediate programmers.  I hope my value added has been clarity/simplification of code to demonstrate the fundamental concepts of a topic.  Now that I'm comfortable with DDT, and know enough to appreciate what SDK might do for me, I've reached a point where I want to begin working more with SDK

Since VISTA-AERO, all drawings are ultimatly rendered in 32-bit mode using DWM, and the use of the alpha channel is a mandatory to work with AERO.

Thus the PNG format is the best choice to use with modern OS, and it is exactly for the same reason that the new VISTA/SEVEN Icon are using it.

When you have time, give a look at my tutorials in the "SDK programming" section, there is a lot to learn here, and also in the BassBox source code. Especialy if you want to learn more about GDI+, and want to create custom graphic controls that could be used with VISTA/SEVEN.

...

Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com

José Roca

Quote
I have GDI+ load image code from Jose, which also uses the UCode$ conversion, but in the POFFS examples I found, it wasn't used, leading me astray.  Could have simply been that the POFFS examples were incomplete snippets.  I should have stepped out of the POFFS box.

Probably they called a wrapper function and the conversion to unicode was being done inside the function. To end with this mess is why I wrote the API headers. If we use the same headers, we can share code without problems.

In the new headers for PB10, you no longer will have to use UCODE$/ACODE$ and STRPTR. The parameters for unicode string parameters no longer are DWORDs, but WSTRING or WSTRINGZ where appropriate, e.g.


FUNCTION GdiPlusSaveImageToFile (BYVAL pImage AS DWORD, BYREF wszFileName AS WSTRINGZ, BYREF wszMimeType AS WSTRINGZ) AS LONG


I also have added a few more wrappers, such:


' ========================================================================================
' Initilizes GDI+
' Returns a token. Pass the token to GdiplusShutdown when you have finished using GDI+.
' ========================================================================================
FUNCTION GdiPlusInit (OPTIONAL BYVAL version AS DWORD) AS DWORD

  LOCAL hStatus AS LONG
  LOCAL token AS DWORD
  LOCAL StartupInput AS GdiplusStartupInput

  IF version < 1 THEN version = 1
  StartupInput.GdiplusVersion = version
  hStatus = GdiplusStartup(token, StartupInput, BYVAL %NULL)
  FUNCTION = token

END FUNCTION
' ========================================================================================


and


' ========================================================================================
' Shows the specified image in the specified window at the specified coordinates.
' * hWnd = [in] Handle of the window.
' * wszFileName = Path of the file.
' * x, y = Vertical and horizontal coordinates.
' ========================================================================================
FUNCTION GdiPlusShowImageFromFileXY (BYVAL hWnd AS DWORD, BYREF wszFileName AS WSTRINGZ, BYVAL x AS LONG, BYVAL y AS LONG, OPTIONAL BYVAL nWidth AS LONG, OPTIONAL BYVAL nHeight AS LONG) AS LONG

  LOCAL hr AS LONG
  LOCAL hDC AS DWORD
  LOCAL pImage AS DWORD
  LOCAL pGraphics AS DWORD

  IF IsWindow(hWnd) = 0 THEN FUNCTION = %E_INVALIDARG : EXIT FUNCTION
  IF LEN(wszFileName) = 0 THEN FUNCTION = %E_INVALIDARG : EXIT FUNCTION
  hDC = GetDC(hWnd)
  hr = GdipLoadImageFromFile(wszFileName, pImage)
  IF hr <> %StatusOk THEN FUNCTION = hr : EXIT FUNCTION
  IF pImage THEN
     IF nWidth = 0 THEN hr = GdipGetImageWidth(pImage, nWidth)
     IF nHeight = 0 THEN hr = GdipGetImageHeight(pImage, nHeight)
     hr = GdipCreateFromHDC(hDC, pGraphics)
     IF pGraphics THEN hr = GdipDrawImageRectI(pGraphics, pImage, x, y, nWidth, nHeight)
     hr = GdipDisposeImage(pImage)
     IF pGraphics THEN hr = GdipDeleteGraphics(pGraphics)
  END IF
  ReleaseDC hWnd, hDC

END FUNCTION
' ========================================================================================


José Roca

 
My headers include a graphic control, that I wrote because I can't use the DDT one, that works well with GDI, GDI+ and Direct2D. It has an optional virtual buffer and automatic scroll bars, something. In the version included in the upcoming new headers, it also initializes and shutdowns GDI+ for you if you pass BYVAL %TRUE in the lpParam parameter of CreateWindowEx when you create it, something not possible with CONTROL ADD.

This is a small demo using pure SDK:


#COMPILE EXE
#DIM ALL
'%UNICODE = 1

' // Header files for imported files
#INCLUDE ONCE "WinCtrl.inc"    ' // Window wrapper functions
#INCLUDE ONCE "GDIPLUS.INC"    ' // GDI+
#INCLUDE ONCE "GraphCtx.INC"   ' // Graphic control

%IDC_GRCTX = 101

' ========================================================================================
' The following sample code draws a line.
' Change it with your own code.
' ========================================================================================
SUB GDIP_Render (BYVAL hdc AS DWORD)

   LOCAL hStatus AS LONG
   LOCAL pGraphics AS DWORD
   LOCAL pPen AS DWORD

   hStatus = GdipCreateFromHDC(hdc, pGraphics)

   ' // Create a Pen
   hStatus = GdipCreatePen1(GDIP_ARGB(255, 255, 0, 0), 1, %UnitPixel, pPen)

   ' // Draw the line
   GdipDrawLineI pGraphics, pPen, 0, 0, 200, 100

   ' // Cleanup
   IF pPen THEN GdipDeletePen(pPen)
   IF pGraphics THEN GdipDeleteGraphics(pGraphics)

END SUB
' ========================================================================================

' ========================================================================================
' Main
' ========================================================================================
FUNCTION WINMAIN (BYVAL hInstance AS DWORD, BYVAL hPrevInstance AS DWORD, BYVAL lpszCmdLine AS ASCIIZ PTR, BYVAL nCmdShow AS LONG) AS LONG

   LOCAL hwndMain AS DWORD
   LOCAL wcex AS WNDCLASSEX
#IF %DEF(%UNICODE)
   LOCAL szClassName AS WSTRINGZ * 80
   LOCAL szCaption AS WSTRINGZ * 255
#ELSE
   LOCAL szClassName AS ASCIIZ * 80
   LOCAL szCaption AS ASCIIZ * 255
#ENDIF

'   ' // Initialize GDI+
'   LOCAL GdipToken AS DWORD
'   GdipToken = GdiPlusInit

   ' // Register the window class
   szClassName        = "MyClassName"
   wcex.cbSize        = SIZEOF(wcex)
   wcex.style         = %CS_HREDRAW OR %CS_VREDRAW
   wcex.lpfnWndProc   = CODEPTR(WndProc)
   wcex.cbClsExtra    = 0
   wcex.cbWndExtra    = 0
   wcex.hInstance     = hInstance
   wcex.hCursor       = LoadCursor (%NULL, BYVAL %IDC_ARROW)
   wcex.hbrBackground = GetStockObject(%WHITE_BRUSH)
   wcex.lpszMenuName  = %NULL
   wcex.lpszClassName = VARPTR(szClassName)
   wcex.hIcon         = LoadIcon (%NULL, BYVAL %IDI_APPLICATION) ' // Sample, if resource icon: LoadIcon(hInst, "APPICON")
   wcex.hIconSm       = LoadIcon (%NULL, BYVAL %IDI_APPLICATION) ' // Remember to set small icon too..
   RegisterClassEx wcex

   ' // Window caption
   szCaption = "GdipDrawLine"

   ' Create a window using the registered class
   hwndMain = CreateWindowEx(%WS_EX_CONTROLPARENT, _           ' extended style
                             szClassName, _                    ' window class name
                             szCaption, _                      ' window caption
                             %WS_OVERLAPPEDWINDOW OR _
                             %WS_CLIPCHILDREN, _               ' window style
                             0, _                              ' initial x position
                             0, _                              ' initial y position
                             400, _                            ' initial x nSize
                             300, _                            ' initial y nSize
                             %NULL, _                          ' parent window handle
                             0, _                              ' window menu handle
                             hInstance, _                      ' program instance handle
                             BYVAL %NULL)                      ' creation parameters

   ' // Center the window
   Window_Center hwndMain

   ' // Initialize the graphic control
   InitGraphCtx

   ' // Get the size of the client area
   LOCAL rc AS RECT
   GetClientRect(hwndMain, rc)

   ' // Create an instance of the graphic control
   LOCAL hCtrl AS DWORD
   hCtrl = CreateWindowEx(0, "GRAPHCTX", "", %WS_CHILD OR %WS_VISIBLE OR %WS_TABSTOP, _
           rc.Left, rc.Top, rc.Right, rc.Bottom, hwndMain, %IDC_GRCTX, hInstance, BYVAL %TRUE)

   ' // Get the memory device context of the graphic control
   LOCAL hdc AS DWORD
   hdc = GraphCtx_GetDc(hCtrl)

   ' // Draw the graphics
   GDIP_Render hdc

   

   ' // Show the window
   ShowWindow hwndMain, nCmdShow
   UpdateWindow hwndMain

   ' // Message handler loop
   LOCAL uMsg AS tagMsg
   WHILE GetMessage(uMsg, %NULL, 0, 0)
      IF IsDialogMessage(hwndMain, uMsg) = 0 THEN
         TranslateMessage uMsg
         DispatchMessage uMsg
      END IF
   WEND

   ' // Shutdown GDI+
'   GdiplusShutdown GdipToken

   FUNCTION = uMsg.wParam

END FUNCTION
' ========================================================================================

' ========================================================================================
' Main Window procedure
' ========================================================================================
FUNCTION WndProc (BYVAL hwnd AS DWORD, BYVAL wMsg AS DWORD, BYVAL wParam AS DWORD, BYVAL lParam AS LONG) AS LONG

   LOCAL hCtrl AS DWORD
   LOCAL hDC AS DWORD

   SELECT CASE wMsg

      CASE %WM_COMMAND
         SELECT CASE LO(WORD, wParam)
            CASE %IDCANCEL
               IF HI(WORD, wParam) = %BN_CLICKED THEN
                  SendMessage hwnd, %WM_CLOSE, 0, 0
                  EXIT FUNCTION
               END IF
         END SELECT

      CASE %WM_DESTROY
         ' // Close the main window
         PostQuitMessage 0
         EXIT FUNCTION

   END SELECT

   FUNCTION = DefWindowProc(hwnd, wMsg, wParam, lParam)

END FUNCTION
' ========================================================================================


Notice that if you unrem %UNICODE = 1, the application becomes fully unicode. Although you still use RegisterClassEx, CreateWindowEx, DefWindowProc, etc., the application will call RegisterClassExA, CreateWindowExA, DefWindowProcA if %UNICODE is not defined, and RegisterClassExW, CreateWindowExW, DefWindowProcW if it is.