2006-02-13

SPECIAL EFFECTS ON IMAGES WITH GDI+

The information contained in this article is deprecated. Please refer to the articles published in UTMAG, on the same subject, using the GdiPlusX classes, that are more suitable to applying effects to images for VFP developers:

Special effects on images with new GDIPlus-X classes - Part 1
Special effects on images with new GDIPlus-X classes - Part 2

 

Sometimes we may need to make some adjustments in the colors of an image, like to make gamma adjustments, change saturation, contrast, brightness, hue, convert to grayscale, to increase or decrease a specific color, etc.

The most simple way to do that would be to "read" every single pixel of the image, extract the 4 integer argb values (alpha, red, green and blue), apply the adjustments to the color and then "redraw" every pixel.

The code below is based on bob powell's example from http://www.bobpowell.net/grayscale.htm , and shows us how we can convert an image to greyscale pixel by pixel. i strongly recommend you read the topic above, where you can find some other interesting examples about using gdi+.

 

The effective luminance of a pixel is calculated with the following formula: c = 0.3 red + 0.59 green + 0.11 blue
This luminance value can then be turned into a grayscale pixel using color.argb(c,c,c).

 



 

*!* Program : grayscale1
*!* Author  : VFPIMAGING
*!* Based on example from
http://www.bobpowell.net/grayscale.htm

lcsource = getpict()
lcdestination = addbs(justpath(lcsource))+ "grey_" +;
   juststem(lcsource)+".bmp"
local lobitmap as gpbitmap of ffc/_gdiplus.vcx

lobitmap = newobject("gpbitmap", home() + "ffc/_gdiplus.vcx")
lobitmap.createfromfile(lcsource)

local locolor as gpcolor of ffc/_gdiplus.vcx
locolor = newobject("gpcolor", home() + "ffc/_gdiplus.vcx")

local x, y, lnsourcecolor, lnluma
for y = 0 to lobitmap.imageheight - 1
   
for x = 0 to lobitmap.imagewidth - 1
       
lnsourcecolor = lobitmap.getpixel(x,y)
        locolor.init(lnsourcecolor)
       
lnluma = int(locolor.red * 0.3 + locolor.green * 0.59 + ;
        locolor.blue * 0.11)
        locolor.set(lnluma,lnluma,lnluma)
        lobitmap.setpixel(x,y,locolor.argb)
    next
next

loBitmap.SaveToFile(lcDestination, "image/jpeg", "quality=100")
return


 

It uses the technique of extracting each pixel colour, and redraws with the average between the 3 components (red, green and blue). according to bob powell, http://www.bobpowell.net/grayscale.htm "this isn't really a good example of how it should be done in a production situation but it does show the principles involved clearly".

 

But gdiplus brings us with the possibilities to apply such kind of big changes to image colors in a really fast way. but to do that, we need to use the imageattributes class. according to microsoft, "an imageattributes object contains information about how bitmap and metafile colors are manipulated during rendering. an imageattributes object maintains several color-adjustment settings, including color-adjustment matrices, grayscale-adjustment matrices, gamma-correction values, color-map tables, and color-threshold values."

Unfortunately, this class was not added to _gdiplus.vcx that came with vfp9. to make things easier, i've created a wrapper class to work with it, doing the needed api calls do gdiplus.dll.

Many people still did not move to vfp9. to these people, it's possible to work with gdi+, using alexander golovlev's GpImage2, a wrapper class created in 2003 originally published in ut. under the author's authorization i've added some functions that were missing to it, and it can be downloaded from http://sites.google.com/site/gpimage2/ , where you can see some samples of what can be done with the class. works with vfp7 and above. may work with vfp6 too, but have not tested yet.

My wrapper class that will deal with imageattributes, gpattributes can work with both _gdiplus.vcx and with GpImage2. if you already own vfp9, i recommend the use of _gdiplus.vcx.

The examples below will deal with vfp9 _gdiplus.vcx. gpimage users will see the equivalent code in the download at the end of this post. the principles are exactly the same. _gdiplus.vcx and gpimage.prg have just some different sintaxes.

Although ms did not gift us with a complete gdi+ wrapper class, there's great information about it on msdn, most of them directed to .net and c users. the way the class works with other languages is almost the same. most of the examples can be really easily translated, and there's very good information about how gdi+ works. i am not an expert of imageattributes, all i know i've learned from msdn and some other great forums. at the end of this post i'll show you my preferred links.

Lets see how it can help us.

The code below will produce exactly the same result as the code presented before. just make sure that gpattributes is in your classlib or path.


*!* program : grayscale2
*!* author  : VFPIMAGING
*!* applying Image Attributes with Color matrix


if not "gpattrib" $ set("procedure")
  
set procedure to gpattrib additive
endif

lcsource = getpict()
lcdestination = addbs(justpath(lcsource))+ "grey2_" +;
    juststem(lcsource)+".bmp"

local loimage as gpimage of ffc/_gdiplus.vcx
local loatt as gpattrib
local lcmatrix as colormatrix

loimage = newobject('gpimage',home()+'ffc/_gdiplus.vcx')
loimage.createfromfile(lcsource)
lcmatrix = colormatrix(;
   
.30, .30, .30, 0, 0, ;
   
.59, .59, .59, 0, 0, ;
   
.11, .11, .11, 0, 0, ;
     0 ,   0,   0, 1, 0, ;
     0 ,   0,   0, 0, 1)

loatt = createobject("gpattrib")
loatt.setcolormatrix(lcmatrix)
loatt.applyimageattribute(loimage.gethandle())
loimage.savetofile(lcdestination, "image/jpeg", "quality=100")

return

 

What I did here was to apply a colormatrix to the image. according to msdn, "a 5x5 color matrix is a homogeneous matrix for a 4-space transformation. the element in the fifth row and fifth column of a 5x5 homogeneous matrix must be 1, and all of the other elements in the fifth column must be 0. color matrices are used to transform color vectors. the first four components of a color vector hold the red, green, blue, and alpha components (in that order) of a color. the fifth component of a color vector is always 1."

I am no expert on colormatrices. fortunately, msdn brings to us some really cool examples. enter www.msdn.com and search for "gdiplus colormatrix".

 
c11 c12 c13 c14 c15 à component r - red

c21 c22 c23 c24 c25 à component g - green

c31 c32 c33 c34 c35 à component b - blue

c41 c42 c43 c44 c45 à component a - alpha (transparency)

c51 c52 c53 c54 c55 à component brightness

 
 

In this 5x5 matrix, i'll classify some important positions :

 

c11 - c22 - c33 - control the contrast of a color

position 11 = red, 22 = green, 33 = blue

1 means no change

0 means to eliminate the component

 

c44 - control of opacity/alpha

 

c51 - c52 - c53 - control the brightness of a component

0 means no change

example : if you put 0.75 in position c51, the red component of each pixel in the image will be increased in 0.75

 


c54 - control the brightness of the opacity/alpha component

0 means no change

 

You can get some very interesting effects applying colormatrices :


Yere are some examples. to see the results, just change the contents of the local variable lcmatrix to these ones :

matrix1 - obtain negative of an image

 

lcmatrix = colormatrix(;

    -1, 0, 0, 0, 0, ;

    0 , -1, 0, 0, 0, ;

    0 , 0, -1, 0, 0, ;

    0 , 0, 0, 1, 0, ;

    1 , 1, 1, 0, 1)

 

matrix2 - obtain just the red components of an image

lcmatrix = colormatrix(;

    1 , 0, 0, 0, 0, ;

    0 , 0, 0, 0, 0, ;

    0 , 0, 0, 0, 0, ;

    0 , 0, 0, 1, 0, ;

    0 , 0, 0, 0, 1)

 

 

matrix3 - obtain just the blue components of an image


lcmatrix = colormatrix(;

    0 , 0, 0, 0, 0, ;

    0 , 0, 0, 0, 0, ;

    0 , 0, 1, 0, 0, ;

    0 , 0, 0, 1, 0, ;

    0 , 0, 0, 0, 1)


 

matrix4 - remove the blue components of an image

 

lcmatrix = colormatrix(;

    1 , 0, 0, 0, 0, ;

    0 , 1, 0, 0, 0, ;

    0 , 0, 0, 0, 0, ;

    0 , 0, 0, 1, 0, ;

    0 , 0, 0, 0, 1)

 

 

matrix5 - scales the blue component of each pixel in the image by a factor of 2

 

lcmatrix = colormatrix(;

    1 , 0, 0, 0, 0, ;

    0 , 1, 0, 0, 0, ;

    0 , 0, 2, 0, 0, ;

    0 , 0, 0, 1, 0, ;

    0 , 0, 0, 0, 1)

 

 

matrix6 - adds 0.20 to the red component of each pixel in the image

 

lcmatrix = colormatrix(;

       1 , 0, 0, 0, 0, ;

       0 , 1, 0, 0, 0, ;

       0 , 0, 1, 0, 0, ;

       0 , 0, 0, 1, 0, ;

    0.20 , 0, 0, 0, 1)

 

 

You'll find some very interesting information on these links from msdn:

 

using a color matrix to transform a single color

translating colors

scaling colors

rotating colors

shearing colors

 

Changing gamma

We can also change the gamma of an image directly, with no need of specific colormatrices:



*!* program : gamma
*!* author : VFPIMAGING
*!* applying imageattributes with colormatrix
if not "gpattrib" $ set("procedure")
   set procedure to gpattrib additive
endif
lcsource = getpict()
lcdestination = addbs(justpath(lcsource))+ "grey2_" +;
    juststem(lcsource)+".bmp"
local loimage as gpimage of ffc/_gdiplus.vcx
local loatt as gpattrib
local lcmatrix as colormatrix
loimage = newobject('gpimage',home()+'ffc/_gdiplus.vcx')
loimage.createfromfile(lcsource)
lcmatrix = colormatrix(;
    1, 0, 0, 0, 0, ;
    0, 1, 0, 0, 0, ;
    0, 0, 1, 0, 0, ;
    0, 0, 0, 1, 0, ;
    0, 0, 0, 0, 1) && this matrix has no effect
loatt = createobject("gpattrib")
loatt.setcolormatrix(lcmatrix)
g = 2 && gamma value chosen
loatt.setgamma(g)
&& we set the gamma directly, no need of specific colormatrices
loatt.applyimageattribute(loimage.gethandle())
loimage.savetofile(lcdestination, "image/jpeg", "quality=100")



Applying more than one colormatrix


Sometimes we may need to apply more than one colormatrix to an image. we can of course apply one and the other next, but this will take much more time of process. We can reduce it drastically if we create another colormatrix with the result of the multiplication of all matrices needed. we need to multiply 2 at a time.

To make this matrices multiplication, i also added a function to do that, MultiplyColorMatrix(), included in gpattrib library.

*!* brightness increase of 20%
b = 0.20
lcmbright = colormatrix(;
    1, 0, 0, 0, 0, ;
    0, 1, 0, 0, 0, ;
    0, 0, 1, 0, 0, ;
    0, 0, 0, 1, 0, ;
    b, b, b, 0, 1)


*!* contrast decrease of 20%
c = 0.80
lcmcontrast = colormatrix(;
    c, 0, 0, 0, 0, ;
    0, c, 0, 0, 0, ;
    0, 0, c, 0, 0, ;
    0, 0, 0, 1, 0, ;
    0, 0, 0, 0, 1)


*!* we need to create a new colormatrix containing information from all matrices
lcmatrix = multiplycolormatrix(lcmbright,lcmcontrast)


*!* all is needed now is to apply the new matrix created to the image

Controlling colors, contrast, saturation, gamma

In this zip file you'll find an interesting example, that applies some colormatrices to images, run newmatrices.scx

 

 

You can download gpattrib from here.

http://weblogs.foxite.com/vfpimaging/files/2006/02/gpattrib.zip

 

What next ?

There are many other interesting effects that we can apply with Color matrices. on my next posts, I'll try to show some more.

There's already a continuation of this article, that you can access it from here.

http://weblogs.foxite.com/vfpimaging/archive/2006/02/22/1227.aspx

3 comments: