2007-06-22

Blur Images with GdiPlus X

blurring images is also very easy.


the simplest technique is to resize the original image to a much smaller size, and then resize it to the original size.


the reason is very obvious, when we enhance the dimensions of any image we have a loss of quality, causing the blur effect.


 

important
requires vfp9 and gdiplusx to run. 
please make sure that you have the latest version!
http://www.codeplex.com/vfpx/wiki/view.aspx?title=gdiplusx&referringtitle=home

 


* init gdiplusx
do locfile("system.app")


with _screen.system.drawing


* load source image
local lobmp as xfcbitmap
lobmp = .
bitmap.new(getpict())


* get a rectangle with the bitmap dimensions
local lorect as xfcrectangle
lorect = lobmp.getbounds()


* initialize the graphics object to be able to draw in the image
local logfx as xfcgraphics
logfx = .graphics.fromimage(lobmp)
logfx.
clear(.color.white)


local lnreducefactor as integer
lnreducefactor = 10 && the image will be reduced in 10 times


* get the thumbnail with the desired size
local lodestbmp as xfcimage
lodestbmp = lobmp.getthumbnailimage(lobmp.
width / lnreducefactor, lobmp.height / lnreducefactor)
 
* draw the image, showing the intensity of the cyan channel.
logfx.drawimage(lodestbmp, lorect)
lobmp.
save("c:\blurred.jpg", .imaging.imageformat.jpeg)

run
/n explorer.exe c:\blurred.jpg


endwith
return


 

 original image 

 

 factor = 4 

factor = 8


factor = 8


factor = 12


factor = 20


 


 

original image

factor = 4


factor = 8


factor = 12

factor = 20

2007-06-18

Rotate and Flip images with GdiPlusX

Here's some adapted code from a previous post that used _gdiplus.vcx, but this time using gdiplusx:

Rotating and / or flipping images is a very simple task for gdi+. To see the different results possible, change the constant value in the variable lnEnumRotateFlip in the code below.

 

Important
Requires VFP9 and GdiplusX to run.  

https://github.com/VFPX/GDIPlusX

 



* Init GdiPlusX
DO System.app

LOCAL loBmp as xfcBitmap
LOCAL lnEnumRotateFlip

WITH _SCREEN.SYSTEM.Drawing

loBmp = .Bitmap.FromFile(GETPICT())
lnEnumRotateFlip = .RotateFlipType.Rotate90FlipNone  && change this value with the presented below
loBmp.
rotateflip(lnEnumTotateFlip)

* to save the image as png
loBmp.Save("c:\RotateFlip.png", .imaging.imageformat.png)

ENDWITH
RUN /N EXPLORER.EXE RotateFlip.png



 
 

rotatenoneflipnone 0  






rotate90flipnone   1  



rotate180flipnone  2  



rotate270flipnone  3  



rotatenoneflipx    4  



rotate90flipx      5  



rotate180flipx     6  



rotate270flipx     7  


2007-06-17

Bo Durban is blogging !!!

I'm very happy to announce that my guru, Bo Durban, has just started his blog.

http://blog.moxiedata.com/

Working with him in the GdiplusX library has been very inspiring, exciting and pleasant. I've really learned a lot with him in the last year.

Don't forget to update your readers, because Bo's stuff is genius !

Welcome to the blogosphere Bo!

2007-06-14

VFPPaint - Flexible Drawing and Paint utility with GdiPlus X





sometimes users need to make some basic modifications in images that are part of the application, and it is not comfortable for them too have to look for files, choose places to save, etc... in the vast majority of cases, ms-paint does the job, but i always needed it to be customized... and i couldn't.


after being challenged by frank cazabon in a foxite thread, i finally took the courage to create my own "paint" application, using gdi+.


vfppaint works much like mspaint, and creates a canvas that permits you to draw whatever you want with the mouse.


it's just one single 50k scx file that does all the job, in partnership with the gdiplusx library.





important
requires vfp9 and gdiplusx to run. 
the latest stable version of gdiplusx is dated 11th may 2007 - alpha 0.8a. please make sure that you have the latest version, because vfppaint uses some functions that were added recently.
http://www.codeplex.com/vfpx/wiki/view.aspx?title=gdiplusx&referringtitle=home



when you run vfppaint.scx it will ask you to select the folder where gdiplusx library is located.



features:


- draw points, rectangles lines and ellipses with the mouse.


- text drawing, choose any font, size and style, and it will appear at the place that you click on the image. (drag and drop to choose the best place)


- choose any color, from the palette or directly from the loaded image or canvas and select the pen width that you want to draw.


- select the shape or type of drawing in the graphical option buttons, and then go to the canvas (image object) and click the left mouse button.


- drag and drop is available for rectangles, lines and ellipses.


- basic undo features


- rotate and flip images


- resizing images


- printing and saving images


 


basic commands:

















































































load image to canvas
save image in desired image format
print image
undo last changes
pick color from palette
pick color from canvas
clear entire canvas with selected color
rotate left
rotate right
flip image horizontally
flip image vertically
draw points
draw rectangle
draw ellipse
draw line
fill rectangle
fill ellipse
draw string
resize image
select font, size and type


 


all images are generated without disk access, using the new pictureval property of the image control. for this specific case, i didn't use the imagecanvas that ships with gdiplusx because i needed some extra customization. after the latest release of gdiplusx, getting the pictureval property of an image has became very simplified, with the new methods added to the image class - getpictureval and getpicturevalfromhbitmap.


as sometimes we need to work with an image that is bigger than our screen,  the drawing canvas was put in a "in toplevelform" that has scrollbars.


another cool feature is the "undo" possibilities. vfppaint stores the previous bitmap in a cache. this can be improved too, maybe you need to "undo" in higher level. for this, very few code is needed.


when drawing on the canvas, try dragging and dropping the objects. for this, the "undo" feature was highly used, because the shape is drawn, and when the user moves the mouse, it restores the original image from the buffer and draws again in the new position.



 


when a big sized image is loaded, first it asks if the canvas needs to be resized.



scrollbars appear in the image to help dealing with it. in a first moment, i thought of using the very nice ctl32 scrollable container from carlos aloatti and malcolm greene, but i gave up because i needed the pictureval property, that is present only in the default image control. so, there are currently 2 forms in the screenshot below. one is a "in toplevel" form that contains just one image object, and resides in the main vfppaint form. the other form is a "toplevel form". the image object from the "child form" changes its size automatically, depending on the image dimensions. when this object is resized, the child form automatically resizes itself, and shows and adjusts the scrollbars if needed.



 


resizing sample:



 


vfppaint also accepts that you pass an image file as a parameter. this way, you'll be able to open the form loading automatically the desired image to edit. run the form like this:


do form vfppaint with "eyes.gif"


in case you pass a gif image as a parameter, it will be automatically converted to 24bpp. that's because gifs are indexed images. a classic example of indexed images are monochrome. pure monochrome images use only 1 bit per pixel. so, in just one byte you can store information of 8 pixels ! in the case of gifs, they are usually 8 bits per pixel, so in one bit we can have a value that ranges from 0 to 255 - that's exactly the quantity of colors supported by gifs ! unfortunately, the redistributable version of gdi+ has a limitation when working with images created in indexed pixel formats, such as 1bppindexed (0x00030101), 4bppindexed (0x00030402) and 8bppindexed (0x00030803). if you try to draw on this kind of images you'll always receive an error message when you'll create the graphics object, that permits to you to draw on the image. more detailed info about this can be found in this post: drawing on gifs or indexed pixel format images with gdi+  http://weblogs.foxite.com/vfpimaging/archive/2006/03/18/1302.aspx . that's why gifs are converted to a 24bpp format before initializing the painting possibilities.



this still has a lot that can be improved, like adding special effects, such as brightness, contrast, hue adjustments, cutting and pasting image portions, saving and retrieving images or pieces of image in the clipboard, etc.


but the main thing is that this totally customizable. the more important in this form is the technique that was used, and you'll see that there's not too much code there.


this still needs to be more tested. feel free to call me if you find any problems or have suggestions. maybe some of your needs can be of interest to be added to vfppaint, and i'll be happy to improve it.


any feedback is very welcome!


 






download


http://weblogs.foxite.com/files/vfpimaging/vfppaint/vfppaint.zip


2007-04-08

Image Info with GdiPlusX

To obtain some basic image information, such as width, height, resolution and pixelformat, all we need is to initialize a Gdi+ image object and get some property values, as shown below:


Important:

The sample below uses the Gdiplus-X library, from VFPX
https://github.com/VFPX/GDIPlusX




LOCAL lcImage
m.lcImage = GETPICT()
IF EMPTY(m.lcImage)
    RETURN
ENDIF
DO LOCFILE("System.app")

LOCAL loImg AS xfcImage
WITH _SCREEN.SYSTEM.Drawing
    m.loImg = .IMAGE.FromFile(m.lcImage)
    IF ISNULL(m.loImg)
        MESSAGEBOX("could not load image file")
        RETURN
    ENDIF

    * get pixelFormat name
    LOCAL lnPix, lcPixFormat
    m.lnPix = m.loImg.PixelFormat
    DO CASE
        CASE m.lnPix = .Imaging.PixelFormat.Format1BppIndexed
            m.lcPixFormat = "1BppIndexed"
        CASE m.lnPix = .Imaging.PixelFormat.Format4BppIndexed
            m.lcPixFormat = "4BppIndexed"
        CASE m.lnPix = .Imaging.PixelFormat.Format8bppIndexed
            m.lcPixFormat = "8BppIndexed"
        CASE m.lnPix = .Imaging.PixelFormat.Format16bppGrayscale
            m.lcPixFormat = "16Bppgrayscale"
        CASE m.lnPix = .Imaging.PixelFormat.Format16bppRgb555
            m.lcPixFormat = "16BppRgb555"
        CASE m.lnPix = .Imaging.PixelFormat.Format16bppRgb565
            m.lcPixFormat = "16BppRgb565"
        CASE m.lnPix = .Imaging.PixelFormat.Format16bppArgb1555
            m.lcPixFormat = "16BppArgb1555"
        CASE m.lnPix = .Imaging.PixelFormat.Format24bpprgb
            m.lcPixFormat = "24BppRgb"
        CASE m.lnPix = .Imaging.PixelFormat.Format32bpprgb
            m.lcPixFormat = "32BppRgb"
        CASE m.lnPix = .Imaging.PixelFormat.Format32bppargb
            m.lcPixFormat = "32BppArgb"
        CASE m.lnPix = .Imaging.PixelFormat.Format32bpppargb
            m.lcPixFormat = "32BpppArgb"
        CASE m.lnPix = .Imaging.PixelFormat.Format48bpprgb
            m.lcPixFormat = "48BppRgb"
        CASE m.lnPix = .Imaging.PixelFormat.Format64bpppargb
            m.lcPixFormat = "64BpppArgb"
        OTHERWISE
            m.lcPixFormat = "unidentified"
    ENDCASE
ENDWITH

LOCAL lcInfo
m.lcInfo = ;
    "width : " + TRANSFORM(m.loImg.WIDTH) + SPACE(25) +;
    "height : " + TRANSFORM(m.loImg.HEIGHT) + CHR(13) +;
    "resolution - vertical : " + TRANSFORM(m.loImg.VerticalResolution) +  SPACE(6) +;
    "horizontal : " + TRANSFORM(m.loImg.HorizontalResolution) + CHR(13) +;
    "pixelFormat : " + m.lcPixFormat

MESSAGEBOX(m.lcInfo, 64, "Image properties for " + JUSTFNAME(m.lcImage))

2007-04-05

Full Justified Texts in your reports with GdiPlus X

This is a continuation of my previous post, Full-Justified Text with GdiPlusX . Now I'll show how we can use the new method DRAWSTRINGJUSTIFIED from GdiPlusX library to have full Justified texts in our reports.


 

There are 2 ways of using this feature in reports.

The most obvious is to use the picture saved in the samples from the previous post, and use it directly in the report.

But VFP9 brings so many other cool possibilities, that I couldn't leave them without a try, so I decided to create a subclass of the Report Listener that will transform some of the texts from my reports to Full-Justified. My idea was that the user would just need to add a "<FJ>" tag in the beginning of any string from a textbox in a report, to tell my ReportListener that it will draw the text using the DRAWSTRINGJUSTIFIED method from GdiPlusX.

Please note that I'm NO expert in the new Object Assisted Reports from VFP9. My experience using Report Listeners is really very limited. I'm sure that the report listener below can be improved, so, if you have any suggestion that can improve the performance or ease the process, please tell me !

My "FullJustifyListener" performs the following actions:

- Initializes GdiPlusX
- Creates a GDI+ Graphics object that will be used to draw in the report
- Stores in an array the required information needed to draw the string(Font, Size, Style and Color)
- Before Rendering the string, checks if the "<FJ>" tag is at the beginning of text - if yes, draws the string using the new method.

VERY IMPORTANT 

The main GDI+ part of the custom report listener is located at the procedure "Render" of the subclass.

Paste the code below, and save it as FJLISTENER.PRG in the samples folder of GdiPlusX 

DEFINE CLASS FullJustifyListener AS _ReportListener OF ;
        ADDBS(HOME(1)) + "FFC\_ReportListener.VCX"
    oGDIGraphics = NULL
    DIMENSION aRecords[1]

    * Before we run the report, we need to Make sure that GdiPlusX
    * was initialized, and create a new Graphics object
    FUNCTION BEFOREREPORT
        DODEFAULT()
        WITH THIS
            * Check if we already have the "System" object in "_Screen"
            IF NOT PEMSTATUS(_SCREEN, "System", 5)
                DO LOCFILE("System.App")
            ENDIF
            .oGDIGraphics = _SCREEN.SYSTEM.Drawing.Graphics.New()
            .SetFRXDataSession()
            DIMENSION .aRecords[reccount(), 12]
            .ResetDataSession()
        ENDWITH
    ENDFUNC

    FUNCTION BEFOREBAND(nBandObjCode, nFRXRecNo)
        THIS.SharedGDIPlusGraphics = THIS.GDIPLUSGRAPHICS
        THIS.oGDIGraphics.Handle   = THIS.SharedGDIPlusGraphics
        DODEFAULT(m.nBandObjCode, m.nFRXRecNo)
    ENDFUNC

    PROCEDURE RENDER(tnFRXRecNo, ;
              tnLeft, tnTop, tnWidth, tnHeight, ;
              nObjectContinuationType, ;
              cContentsToBeRendered, GDIPlusImage)
        LOCAL lcText
        m.lcText = THIS.aRecords(m.tnFRXRecNo, 1)

        IF VARTYPE(m.lcText) = "C" AND m.lcText = "<FJ>"
            m.lcText				 = SUBSTR(m.lcText, 5) && Remove the <FJ> tag from string
            THIS.oGDIGraphics.Handle = THIS.GDIPLUSGRAPHICS

            WITH _SCREEN.SYSTEM.Drawing

                * Create a GDI+ Rectangle which specifies where on the
                * surface we're drawing the text.
                LOCAL loRectF AS xfcRectangleF
                m.loRectF = .RectangleF.New(m.tnLeft, m.tnTop, m.tnWidth, m.tnHeight)

                * Create a Font Object based on the report original settings
                LOCAL loFont AS xfcFont
                m.loFont = .FONT.New(THIS.aRecords(m.tnFRXRecNo, 2) ;
                      , THIS.aRecords(m.tnFRXRecNo, 4), THIS.aRecords(m.tnFRXRecNo, 3) ;
                      , .GraphicsUnit.POINT)

                * If we have an opaque background set for the text, then draw a rectangle
                * using the background chosen background color
                IF THIS.aRecords[m.tnFRXRecno, 8] <> 0 && Alpha

                    * Retrieve colors for the background
                    LOCAL lnRed, lnGreen, lnBlue, lnAlpha
                    m.lnRed	  = THIS.aRecords[m.tnFRXRecno, 5]
                    m.lnGreen = THIS.aRecords[m.tnFRXRecno, 6]
                    m.lnBlue  = THIS.aRecords[m.tnFRXRecno, 7]
                    m.lnAlpha = THIS.aRecords[m.tnFRXRecno, 8]

                    * Create a Solid Brush that will be used to draw the Background
                    LOCAL loBackBrush AS xfcSolidBrush
                    m.loBackBrush = .SolidBrush.New(;
                          .COLOR.FromArgb(m.lnAlpha, m.lnRed, m.lnGreen, m.lnBlue))

                    * Draw the background rectangle
                    THIS.oGDIGraphics.FillRectangle(m.loBackBrush, m.tnLeft, m.tnTop, m.tnWidth, m.tnHeight)
                ENDIF

                * Retieve colors for the Text
                m.lnRed	  = THIS.aRecords[m.tnFRXRecno, 9]
                m.lnGreen = THIS.aRecords[m.tnFRXRecno, 10]
                m.lnBlue  = THIS.aRecords[m.tnFRXRecno, 11]
                m.lnAlpha = THIS.aRecords[m.tnFRXRecno, 12]

                * Create a Solid Brush that will be used to draw the text
                LOCAL loTextBrush AS xfcSolidBrush
                m.loTextBrush = .SolidBrush.New(;
                      .COLOR.FromArgb(m.lnAlpha, m.lnRed, m.lnGreen, m.lnBlue))

                * Finally, draw the text in FullJustified mode.
                THIS.oGDIGraphics.DrawStringJustified(m.lcText, m.loFont, m.loTextBrush, m.loRectF)
            ENDWITH
        ELSE

            * If we're not drawing a full justified string,
            * let VFP draw the text as usual.
            DODEFAULT(m.tnFRXRecNo, m.tnLeft, m.tnTop, m.tnWidth, m.tnHeight, ;
                  m.nObjectContinuationType, m.cContentsToBeRendered, m.GDIPlusImage)
        ENDIF

        * Since we already drew the text, we don't want the default
        * behavior to occur.
        NODEFAULT
    ENDPROC

    FUNCTION EVALUATECONTENTS(tnFRXRecNo, toObjProperties)
        * Get the FRX data
        THIS.aRecords[m.tnFRXRecno, 1]	= m.toObjProperties.TEXT
        THIS.aRecords[m.tnFRXRecno, 2]	= m.toObjProperties.FONTNAME
        THIS.aRecords[m.tnFRXRecno, 3]	= m.toObjProperties.FontStyle
        THIS.aRecords[m.tnFRXRecno, 4]	= m.toObjProperties.FONTSIZE
        THIS.aRecords[m.tnFRXRecno, 5]	= m.toObjProperties.FillRed
        THIS.aRecords[m.tnFRXRecno, 6]	= m.toObjProperties.FillGreen
        THIS.aRecords[m.tnFRXRecno, 7]	= m.toObjProperties.FillBlue
        THIS.aRecords[m.tnFRXRecno, 8]	= m.toObjProperties.FillAlpha
        THIS.aRecords[m.tnFRXRecno, 9]	= m.toObjProperties.PenRed
        THIS.aRecords[m.tnFRXRecno, 10]	= m.toObjProperties.PenGreen
        THIS.aRecords[m.tnFRXRecno, 11]	= m.toObjProperties.PenBlue
        THIS.aRecords[m.tnFRXRecno, 12]	= m.toObjProperties.PenAlpha
    ENDFUNC
ENDDEFINE

To use it is very simple:

Open any report, and select a textbox that has a memo field associated to it. In the field properties, tab General, on expression, just add a "<FJ>" string before the original field. Supposing you had the expression "MyTable.MyMemoField", to have it justified, you'll change it to: "<FJ>" + MyTable.MyMemoField.

The next step is to make sure that VFP will use our new listener to render the report.

* Tell VFP that we'll be using the new report features
SET REPORTBEHAVIOR 90
LOCAL loReportListener
m.loReportListener = NEWOBJECT("FullJustifyListener", LOCFILE("FJListener.prg"))
* loReportListener = CREATEOBJECT("FullJustifyListener")
m.loReportListener.LISTENERTYPE = 1
REPORT FORM MyReport OBJECT m.loReportListener

  

  

 

 

UPDATE 04/07/2007

After some good suggestions from Victor Espinoza, below is a modified version of the "FJListener" class, that will receivethe <FJ> tag at the user data field for the textbox (under the "Other" tab), this way, the "<FJ>" tag wouldn't affect the reports if it was latter decided to use an alternative report listener.



* Program : FJLISTENER.PRG
* Purpose : Provides a Report Listener that allows rendering text in
*           Full Justify alignment.
* Author : Cesar Ch and Victor Espinoza
* Class based on article "Listening to a report" by Doug Hennig
* http://msdn2.microsoft.com/en-us/library/ms947682.aspx

DEFINE CLASS FullJustifyListener AS _ReportListener OF ;
        ADDBS(HOME(1)) + "FFC\_ReportListener.VCX"
    oGDIGraphics = NULL
    DIMENSION aRecords[1]

    * Doug Hennig - If ListenerType hasn't already been set, set it based on whether the report
    * is being printed or previewed.
    FUNCTION LOADREPORT
    WITH THIS
        DO CASE
        CASE .LISTENERTYPE <> -1
        CASE .COMMANDCLAUSES.PREVIEW
            .LISTENERTYPE = 1
        CASE .COMMANDCLAUSES.OutputTo = 1
            .LISTENERTYPE = 0
        ENDCASE
    ENDWITH
    DODEFAULT()
    ENDFUNC

    * Before we run the report, we need to Make sure that GdiPlusX
    * was initialized, and create a new Graphics object
    FUNCTION BEFOREREPORT
    DODEFAULT()
    WITH THIS
        * Check if we already have the "System" object in "_Screen"
        IF NOT PEMSTATUS(_SCREEN, "System", 5)
            DO LOCFILE("System.App")
        ENDIF

        .oGDIGraphics = _SCREEN.SYSTEM.Drawing.Graphics.New()
        .SetFRXDataSession() && switches to the FRX cursor's datasession
        DIMENSION .aRecords(RECCOUNT(), 13)
        SCAN FOR "<FJ>" $ UPPER(USER)
            .aRecords(RECNO(), 13) = "FJ"
        ENDSCAN
        .ResetDataSession() && restores the datasession ID to the one the listener is in
    ENDWITH
    ENDFUNC

    FUNCTION BEFOREBAND(nBandObjCode, nFRXRecNo)
    THIS.SharedGDIPlusGraphics = THIS.GDIPLUSGRAPHICS
    THIS.oGDIGraphics.Handle   = THIS.SharedGDIPlusGraphics
    DODEFAULT(m.nBandObjCode, m.nFRXRecNo)
    ENDFUNC

    PROCEDURE RENDER(tnFRXRecNo, ;
          tnLeft, tnTop, tnWidth, tnHeight, ;
          nObjectContinuationType, ;
          cContentsToBeRendered, GDIPlusImage)
    IF VARTYPE(THIS.aRecords(m.tnFRXRecNo, 13)) = "C" AND ;
            THIS.aRecords(m.tnFRXRecNo, 13) == "FJ"
        LOCAL lcText
        m.lcText				 = THIS.aRecords(m.tnFRXRecNo, 1)
        THIS.oGDIGraphics.Handle = THIS.GDIPLUSGRAPHICS

        WITH _SCREEN.SYSTEM.Drawing
            * Create a GDI+ Rectangle which specifies where on the
            * surface we're drawing the text.
            LOCAL loRectF AS xfcRectangleF
            m.loRectF = .RectangleF.New(m.tnLeft, m.tnTop, m.tnWidth, m.tnHeight)

            * Create a Font Object based on the report original settings
            LOCAL loFont AS xfcFont
            m.loFont = .FONT.New(THIS.aRecords(m.tnFRXRecNo, 2) ;
                  , THIS.aRecords(m.tnFRXRecNo, 4), THIS.aRecords(m.tnFRXRecNo, 3) ;
                  , .GraphicsUnit.POINT)

            * If we have an opaque background set for the text, then draw a rectangle
            * using the background chosen background color
            IF THIS.aRecords[m.tnFRXRecno, 8] <> 0 && Alpha
                * Retrieve colors for the background
                LOCAL lnRed, lnGreen, lnBlue, lnAlpha
                m.lnRed	  = THIS.aRecords[m.tnFRXRecno, 5]
                m.lnGreen = THIS.aRecords[m.tnFRXRecno, 6]
                m.lnBlue  = THIS.aRecords[m.tnFRXRecno, 7]
                m.lnAlpha = THIS.aRecords[m.tnFRXRecno, 8]

                * Create a Solid Brush that will be used to draw the Background
                LOCAL loBackBrush AS xfcSolidBrush
                m.loBackBrush = .SolidBrush.New(;
                      .COLOR.FromArgb(m.lnAlpha, m.lnRed, m.lnGreen, m.lnBlue))

                * Draw the background rectangle
                THIS.oGDIGraphics.FillRectangle(m.loBackBrush, m.tnLeft, m.tnTop, m.tnWidth, m.tnHeight)
            ENDIF

            * Retieve colors for the Text
            m.lnRed	  = THIS.aRecords[m.tnFRXRecno, 9]
            m.lnGreen = THIS.aRecords[m.tnFRXRecno, 10]
            m.lnBlue  = THIS.aRecords[m.tnFRXRecno, 11]
            m.lnAlpha = THIS.aRecords[m.tnFRXRecno, 12]

            * Create a Solid Brush that will be used to draw the text
            LOCAL loTextBrush AS xfcSolidBrush
            m.loTextBrush = .SolidBrush.New(;
                  .COLOR.FromArgb(m.lnAlpha, m.lnRed, m.lnGreen, m.lnBlue))

            * Finally, draw the text in FullJustified mode.
            THIS.oGDIGraphics.DrawStringJustified(m.lcText, m.loFont, m.loTextBrush, m.loRectF)
        ENDWITH
    ELSE
        * If we're not drawing a full justified string,
        * let VFP draw the text as usual.
        DODEFAULT(m.tnFRXRecNo, m.tnLeft, m.tnTop, m.tnWidth, m.tnHeight, ;
              m.nObjectContinuationType, m.cContentsToBeRendered, m.GDIPlusImage)
    ENDIF

    * Since we already drew the text, we don't want the default
    * behavior to occur.
    NODEFAULT
    ENDPROC

    FUNCTION EVALUATECONTENTS(tnFRXRecNo, toObjProperties)
    * Get the FRX data
    THIS.aRecords[m.tnFRXRecno, 1]	= m.toObjProperties.TEXT
    THIS.aRecords[m.tnFRXRecno, 2]	= m.toObjProperties.FONTNAME
    THIS.aRecords[m.tnFRXRecno, 3]	= m.toObjProperties.FontStyle
    THIS.aRecords[m.tnFRXRecno, 4]	= m.toObjProperties.FONTSIZE
    THIS.aRecords[m.tnFRXRecno, 5]	= m.toObjProperties.FillRed
    THIS.aRecords[m.tnFRXRecno, 6]	= m.toObjProperties.FillGreen
    THIS.aRecords[m.tnFRXRecno, 7]	= m.toObjProperties.FillBlue
    THIS.aRecords[m.tnFRXRecno, 8]	= m.toObjProperties.FillAlpha
    THIS.aRecords[m.tnFRXRecno, 9]	= m.toObjProperties.PenRed
    THIS.aRecords[m.tnFRXRecno, 10]	= m.toObjProperties.PenGreen
    THIS.aRecords[m.tnFRXRecno, 11]	= m.toObjProperties.PenBlue
    THIS.aRecords[m.tnFRXRecno, 12]	= m.toObjProperties.PenAlpha
    ENDFUNC

ENDDEFINE

For now this seems good to me. But there's still some more to improve here. For the next version this can become a "Directive" to be used together with other custom report listeners, as Doug Hennig did in his great article "Listening to a Report"



Related links that helped me to understand how the Report Listener works:

Doug Hennig - Listening to a Report
Doug Hennig - Hyperlink your reports
Doug Hennig - Extending the Visual FoxPro 9 Reporting System
Cathy Pountney - VFP 9.0 Report Writer In Action
Cathy Pountney - What's New in the VFP 9 Report Writer
Garret Fitzgerald - Using the GDI+ FFCs to work with output