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