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.
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.
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.
...
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
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
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.
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.
...
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.
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).
...
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!
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.
...
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
' ========================================================================================
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.