2006-06-22

Gradient Backgrounds in your forms with GDI+ Part2

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.


7 comments:

  1. Super. Now if only you can show me how to gradient fill an irregular shape....bounded say by a black or other single color...


    Also is it possible to get a gradient fill for a rectangle :

    Colour2 -> Colour1 -> Colour2   ???


    Pretty please.

    ReplyDelete
  2. Tem como baixar algum download do exemplo citados?
    Check the download available in the "Attachments" (link direto para baixar os arquivos):

    ReplyDelete
  3. Cesar,

    Another super cool post. Thank you.


    Bernard,

    Maybe you can try Hatch values for an irregular shape - polygon filled with a bitmap.


    Vassilis Aggelakos

    ReplyDelete
  4. Hi Cesar!


    QUOTE: "I must confess that I don't feel smart to code in the way that I just presented in this post when I can substitute more than 70 lines of code with 5"


    In the US there's a story that goes something like this:


    "A refinery was unexpectedly shut down and management was in a panic. Management called in an internationally known consultant who arrived at the site, strolled through the gate, asked for a hammer and proceeded to hit valve #6B on the third deck of the catalytic cracker. Suddenly the refinery came to life and product was flowing again. When management got the bill for $10,000 they protested, "But you only spent 5 minutes at the site and just hit a valve with a hammer. Why did you charge so much?" The consultant responded, " I only charged you $25 to hit the valve, the rest was for knowing what to hit."


    You are the VFP communty's GDI hammer! :)


    Thank you for another great article!


    Malcolm


    PS: Oh yeah, what Bernard Bout said, too :)

    ReplyDelete
  5. Héctor Lizarraga RoblesAugust 15, 2006 at 5:17 PM

    Superb!!! The effect is excelent and the time to create the bmp is minimal.

    I´m working with forms that have a scrollbar and I notice that the gradient does not show in the hided-not scrolled part of the form. How can this be solved?

    Great work, thanks Cesar!
    Thanks for the kind words, I'm glad to know you liked it.
    I didn't try it yet, but maybe you could try making your image object bigger, to occupy the whole form width ???

    ReplyDelete
  6. man..that's incredible... sorry about my so bad english
    but... y just see the gdi+ in action on forms... tell me... can i use that on a... i don't know... shape or image... o something?... please... i need this information... because i'm working in my university tesis... and i wanna use it... thanks... your ideas are great!!!
    From Asuncion - Paraguay...
    Guille
    Hola Guille,
    Yes, you can apply gradients on any object. That may be a form, a container, a commandbutton, picture. What exactly are you triyng to do?
    Saludos
    Cesar

    ReplyDelete
  7. Versión en Español de este artículo en / Spanish version at http://www.portalfox.com/article.php?sid=2251

    ReplyDelete