In my previous post with the same title, I showed how to create vertical gradient backgrounds in forms. as commented by Malcolm Greene, that same technique can be used to VFP containers.
According to MSDN, "the lineargradientbrush class defines a brush that paints a color gradient in which the color changes evenly from the starting boundary line of the linear gradient brush to the ending boundary line of the linear gradient brush. the boundary lines of a linear gradient brush are two parallel straight lines. The color gradient is perpendicular to the boundary lines of the linear gradient brush, changing gradually across the stroke from the starting boundary line to the ending boundary line. The color gradient has one color at the starting boundary line and another color at the ending boundary line."
The main parameters to be passed to the LinearGradientBrush are:
- point1 - starting point of the gradient. the starting boundary line passes through the starting point.
- point2 - ending point of the gradient. the ending boundary line passes through the ending point.
- color1 - ARGB color at the starting boundary line of this linear gradient brush.
- color2 - ARGB color at the ending boundary line of this linear gradient brush.
In that case, I created a one pixel wide image, with the form height. then I created a linear gradientbrush, using coordinate (0,0) as point1, coordinate (0,thisform.height) as point2. Color1 was the ARGB of any selected color, and color2 was white(RGB(255,255,255)). Inside this narrow image, I have drawn a rectangle using the lineargradientbrush, and saved in bmp format. VFP's image object was responsible to "stretch" this narrow image to fit the width of the form. That technique permitted to have a small image file.
Gdi+ provides horizontal, vertical, and diagonal linear gradients too ! So, using the same techniques, we can easily create other cool effects.
horizontal gradients
For horizontal, we need to pass as point1 and point2 two coordinates with the same "y". It is fundamental that the two points have the same second coordinate (y) to make the line connecting them horizontal.
diagonal gradients
For diagonal gradients, the two coordinates must create a diagonal line, like point (0,0) and point (50,50). This will create a diagonal line between those two points, starting from the top-left till the bottom-right edge of the form, like in the form below:
If we change the coordinates to coordinates (0,50) and (50,0), the diagonal line will end in the top edge of the form:
So, in our case, I just did this modification in the original code from the previous post:
LOCAL lnGradMode, lnGradPixels, lnHeight, lnWidth m.lnGradPixels = 100 && width of the gradient line m.lnGradMode = 4 && ranges from 1 to 4 DO CASE CASE m.lnGradMode = 1 && vertical x1 = 0 y1 = 0 x2 = 1 y2 = m.lnGradPixels CASE m.lnGradMode = 2 && horizontal x1 = 0 y1 = 0 x2 = m.lnGradPixels y2 = 1 CASE m.lnGradMode = 3 && diagonal topleft –> bottomright x1 = 0 y1 = 0 x2 = m.lnGradPixels y2 = m.lnGradPixels CASE m.lnGradMode = 4 && diagonal bottomleft –> topright x1 = 0 y1 = m.lnGradPixels x2 = m.lnGradPixels y2 = 0 ENDCASE m.lnWidth = IIF(m.lnGradMode = 1, 1, m.lnGradPixels) m.lnHeight = IIF(m.lnGradMode = 2, 1, m.lnGradPixels)
The lineargradientbrush will be created using the coordinates (x1,y1) and (x2,y2). The image will have the size lnwidth, lnheight. The variable lngradpixels means the size in pixels of the image to be created. In the previous post, i used "thisform.height". After some tests, I've seen that using 60 pixels will create a good quality gradient to be expanded by the image object to fit the whole form.
So, create any form, and add the following codes to load and destroy events. change the value of the variable lngradmode to any value between 1 and 4, to see the form drawn with 4 types of gradients. also change lnGradPixels to 5, 20, and 60. don't forget to resize the form.
load event
* LOAD EVENT LOCAL lcGradFile, lnGradMode, lnGradPixels, x1, y1, x2, y2, lnWidth, lnHeight m.lcGradFile = ADDBS(SYS(2023)) + SYS(2015) + ".bmp" THIS.ADDPROPERTY("cTempGradFile", m.lcGradFile) m.lnGradPixels = 60 m.lnGradMode = 4 DO CASE CASE m.lnGradMode = 1 && vertical m.x1 = 0 m.y1 = 0 m.x2 = 1 m.y2 = m.lnGradPixels CASE m.lnGradMode = 2 && horizontal m.x1 = 0 m.y1 = 0 m.x2 = m.lnGradPixels m.y2 = 1 CASE m.lnGradMode = 3 && diagonal topleft –> bottomright m.x1 = 0 m.y1 = 0 m.x2 = m.lnGradPixels m.y2 = m.lnGradPixels CASE m.lnGradMode = 4 && diagonal bottomleft –> topright m.x1 = 0 m.y1 = m.lnGradPixels m.x2 = m.lnGradPixels m.y2 = 0 ENDCASE m.lnWidth = IIF(m.lnGradMode = 1, 1, m.lnGradPixels) m.lnHeight = IIF(m.lnGradMode = 2, 1, m.lnGradPixels) IF FILE(m.lcGradFile) CLEAR RESOURCES (m.lcGradFile) ENDIF LOCAL lnRgbColor1 m.lnRgbColor1 = RGB(0,128,255) && blue * create gradient image SET CLASSLIB TO HOME() + "ffc/_gdiplus.vcx" ADDITIVE * create a colorobject and store argb color values to variables LOCAL loClr AS gpColor OF HOME() + "ffc/_gdiplus.vcx" LOCAL lnColor1, lnColor2 m.loClr = CREATEOBJECT("gpcolor") m.loClr.FoxRgb = m.lnRgbColor1 m.lnColor1 = m.loClr.argb m.loClr.FoxRgb = RGB(255,255,255) && white m.lnColor2 = m.loClr.argb * create a bitmap LOCAL loBmp AS gpBitmap OF HOME() + "ffc/_gdiplus.vcx" m.loBmp = CREATEOBJECT("gpbitmap") m.loBmp.CREATE(m.lnWidth, m.lnHeight) * get a bitmab graphics object LOCAL loGfx AS gpGraphics OF HOME() + "ffc/_gdiplus.vcx" m.loGfx = CREATEOBJECT("gpgraphics") m.loGfx.CreateFromImage(m.loBmp) * declare api DECLARE LONG gdipcreatelinebrushi IN GDIPLUS.DLL ; STRING point1, STRING point2, ; LONG color1, LONG color2, ; LONG wrapmode, LONG @linegradient * get a gradient brush LOCAL loBrush AS gpBrush OF HOME() + "ffc/_gdiplus.vcx" LOCAL hBrush && brush handle m.hBrush = 0 = gdipCreateLinebrushI(BINTOC(m.x1,"4rs") + BINTOC(m.y1,"4rs"), ; BINTOC(m.x2,"4rs") + BINTOC(m.y2,"4rs"), ; m.lnColor1, m.lnColor2, 0, @m.hBrush) m.loBrush = CREATEOBJECT("gpbrush") m.loBrush.sethandle(m.hBrush, .T.) * fill the bitmap with our gradient m.loGfx.FillRectangle(m.loBrush,0,0,m.lnWidth, m.lnHeight) m.loBmp.SaveToFile(m.lcGradFile,"image/bmp") THISFORM.ADDOBJECT("imgbackground","image") WITH THISFORM.imgbackground .STRETCH = 2 && stretch .WIDTH = THISFORM.WIDTH .HEIGHT = THISFORM.HEIGHT .ANCHOR = 15 && resize width and height .PICTURE = m.lcGradFile * .pictureval = filetostr(lcgradfile) * .pictureval = loadpicture(lcgradfile) * erase (lcgradfile) .ZORDER(1) .VISIBLE = .T. ENDWITH RETURN
destroy event
*DESTROY EVENT WITH THISFORM IF FILE(.cTempGradFile) CLEAR RESOURCES (.cTempGradFile) ERASE (.cTempGradFile) ENDIF ENDWITH
A slight modification was the addiction of the zorder(1), to make sure the image object added is the first one, and staying in the back of all objects of the form. Thanks Aílsom !
Thanks to Malcolm Greene, the codes from resize event were removed, and substituted with .anchor = 15 && resize width and height. VFP9 brought so many cool features, and I must admit that i've tested just a few of them till this moment.
Malcolm also suggested sending the binaries from the image created to the pictureval property of the image object. I don't know why, but in some machines the picture appeared rotated in a 90 degree. Also, in Windows NT, the white part of the gradient was replaced with transparency. So, for this moment, I'm keeping the codes at the destroy event to make sure the image created is deleted from the disk.
I'm sure that there is a good solution for that, maybe someone can explain that to me ?
New Gdi+ classes
Craig and Bo added to the new library apart from all the GDI+ functions, some other classes, that will make the use of GDI+ really easy and, most important, very intuitive. There will be no more need to make direct API calls to Gdi+, manage object handles. The creation of images will become really intuitive.
One of the main addictions is the imagecanvas class. That's a subclass of the image native class, that was created to receive the drawn image. the advantage to use this class is that it renders graphics super fast (according to Bo and Craig about up to 30 times faster than saving an image to disk and loading to an image object). There will be no disk access, once all the image is manipulated directly from the memory. The user will have only to put the codes to draw the image in the "BeforeDraw" method of the object and that's all.
In the case of gradient backgrounds of forms, we will have just to add the "imagecanvas" object, and in the beforedraw method of this object put this code. only 5 lines!!! Thanks to Craig Boyd for sending this sample to me.
beforedraw event
* Using GdiPlusX and the ImageCanvas * Place this code at the "BeforeDraw" event of the ImageCanvas class LOCAL loBrush AS xfcLinearGradientBrush WITH _SCREEN.SYSTEM.Drawing m.loBrush = .Drawing2d.LinearGradientBrush.New(THIS.Rectangle,; .COLOR.FromRgb(0,128,255), .COLOR.White,; .Drawing2d.LinearGradientMode.ForwardDiagonal) THIS.oGfx.FillRectangle(m.loBrush, THIS.Rectangle) ENDWITH
How do you fill using these classes ?
There are many more cool effects to create with lineargradientbrushes. By default, the color in a linear gradient changes uniformly. However, it is possible to customize a linear gradient so that the color changes in a non-uniform fashion, creating gradients using more than two colors.
From now on, I'll try to show these features using the new classes. I must confess that I don't feel smart to code in the way that I just presented in this post when I can replace more than 70 lines of code with 5 !
In the attached file I'm sending 2 forms. The first, gradient.scx creates a simple form, with codes in load and destroy events. Try also the form gradient2.scx, that will permit you to customize the gradient colors and directions on the fly.