2012-03-16

Draw rotated strings with GdiPlusX

Gdi+ provides many ways to draw rotated objects, like images, strings and shapes. Basically, we need to do a translate transformation to the location of the text, then a rotate transform through the required angle, and then writing the text as normal using drawstring.



This is the definition found in MSDN: "The rotation operation consists of multiplying the transformation matrix by a matrix whose elements are derived from the angle parameter. This method applies the rotation by prepending it to the transformation matrix."


IMPORTANT:
All samples below use the new GDIPlus-X library, that is still in ALPHA version, but is already stable and reliable to do the majority of GDI+ tasks. Download the latest stable release from GitHub:

* Initialize GdiPlus-X library
DO LOCFILE("System.App")

WITH _SCREEN.SYSTEM.Drawing

* Create a new and empty Bitmap
LOCAL loBmp as xfcBitmap
loBmp = .Bitmap.New(180,180)

* Create a Graphics object associated to the bitmap
LOCAL loGfx as xfcGraphics
loGfx = .Graphics.FromImage(loBmp)

* Clear the BackGround of the image with light blue
loGfx.Clear(.Color.FromRgb(230,230,255))

* Create a SolidBrush with Red Color
LOCAL loBrush AS xfcBrush
loBrush = .SolidBrush.New(.COLOR.FromRgb(255,0,0))
* The above statement could be also :
* loBrush = .SolidBrush.New(.COLOR.FromARgb(255,255,0,0))
* loBrush = .SolidBrush.New(.COLOR.Red)

* Create a Rectangle in which the rotated text will be drawn
LOCAL loRect AS xfcRectangle
loRect = .Rectangle.New(0, 0, loBmp.Width, loBmp.Height)

* Get a basic string format object, then set properties
LOCAL loStringFormat AS xfcStringFormat
loStringFormat = .StringFormat.New()
loStringFormat.ALIGNMENT = .StringAlignment.CENTER
loStringFormat.LineAlignment = .StringAlignment.CENTER

* Create a Font object
LOCAL loFont AS xfcFont
loFont = .FONT.New("Verdana",16, .FontStyle.Bold, .GraphicsUnit.POINT)

* After creating all needed objects, we can apply the rotation
* and draw the text

* Translate and Rotate
loGfx.TranslateTransform(loBmp.Width /2, loBmp.Height /2)
loGfx.RotateTransform(-45) && angle of 45 degrees
loGfx.TranslateTransform(-loBmp.Width /2, -loBmp.Height /2)

* Finally, draw the string
loGfx.DrawString("Rotated Text" + CHR(13) + CHR(10) + "GDIPlus-X is COOL !!!", ;
loFont, loBrush, loRect, loStringFormat)

* Reset Rotation
loGfx.ResetTransform()

* Save image to File
loBmp.Save("c:\rotated.png", .Imaging.ImageFormat.Png)

ENDWITH

* Show created image
RUN /N explorer.exe c:\rotated.png



TIME TO PLAY
For the above sample I used a cool trick to rotate the text at the center of the image, and not at the left edge, that is the default behavior. Now it's up to you to make some tests. Try omitting the two lines that call the Graphics.TranslateTransform, change the angle, using negative and positive angles, etc...

* Translate and Rotate
loGfx.TranslateTransform(loBmp.Width /2, loBmp.Height /2)
loGfx.RotateTransform(-45) && angle of 45 degrees
loGfx.TranslateTransform(-loBmp.Width /2, -loBmp.Height /2)


In the "Samples" folder of the latest GDIPlus-X library version there's another cool sample, "Rotation.Scx", in which another translation technique was used. All the codes are in the "BeforeDraw" method of the ImageCanvas object.


DRAW ROTATED STRINGS IN YOUR REPORTS !





The real reason for this post was a request from my friend from Foxbrasil Emerson Santon Reed, when he asked about creating a watermark of a big text rotated in 45 degrees in a report.
So, my part was just to adapt the GDI+ code shown above to the ReportListener class and Report that he provided.
Run the code below to see a rotated text centered at 45 degrees in a report:

* Create a sample Report
LOCAL i
CREATE CURSOR dummy (fld1 c(20), fld2 c(15))
FOR i=1 TO 100
  INSERT INTO dummy VALUES ("ReportListener with GdiPlus-X", "Visit CodePlex")
ENDFOR
SELECT dummy
CREATE REPORT _testreport FROM dummy

* Init GdiPlus-X
DO LOCFILE("System.App")

* Load Listener class
LOCAL loreportlistener
loreportlistener = CREATEOBJECT("MyReportListener")
loreportlistener.LISTENERTYPE = 1

* Call the report using our listener
REPORT FORM _testreport OBJECT loreportlistener
USE IN dummy
RETURN


DEFINE CLASS myreportlistener AS _reportlistener OF ;
    ADDBS(HOME()) + "FFC\" + "_ReportListener.VCX"
  newPage = .T.
  oGdigraphics = NULL

  FUNCTION BEFOREREPORT
    DODEFAULT()
    This.ogdigraphics = _SCREEN.SYSTEM.drawing.graphics.new()
  ENDFUNC

  FUNCTION BEFOREBAND(nbandobjcode, nfrxrecno)
  #DEFINE FRX_OBJCOD_PAGEHEADER               1

  IF nbandobjcode==frx_objcod_pageheader
      This.NewPage = .T.
      IF NOT This.issuccessor
        This.sharedgdiplusgraphics = This.GDIPLUSGRAPHICS
      ENDIF
      This.ogdigraphics.handle = This.sharedgdiplusgraphics
  ENDIF
  DODEFAULT(nBandObjCode, nFRXRecNo)
  ENDFUNC

  PROCEDURE RENDER(nfrxrecno,;
     nleft,ntop,nwidth,nheight,;
     nobjectcontinuationtype, ;
     ccontentstoberendered, gdiplusimage)

     WITH _SCREEN.SYSTEM.drawing

     IF This.NewPage
        * Create a SolidBrush with Red Color
        LOCAL lobrush AS xfcbrush
        lobrush = .solidbrush.new(.COLOR.fromRgb(255,64,64))

        * Create a Rectangle in which the rotated text will be drawn
        LOCAL lorect AS xfcrectangle
        lorect = .rectangle.new(0, 0, This.sharedpagewidth,;
          This.sharedpageheight)

        * Get a basic string format object, then set properties
        LOCAL lostringformat AS xfcstringformat
        lostringformat = .stringformat.new()
        lostringformat.ALIGNMENT = .stringalignment.CENTER
        lostringformat.linealignment = .stringalignment.CENTER

        * Create a Font object
        LOCAL lofont AS xfcfont
        lofont = .FONT.new("Verdana",48, 0, .graphicsunit.POINT)

        * Translate and Rotate
        This.ogdigraphics.translatetransform(This.sharedpagewidth/2,;
          This.sharedpageheight/2)
        This.ogdigraphics.rotatetransform(-45)
        This.ogdigraphics.translatetransform(-This.sharedpagewidth/2,;
          -This.sharedpageheight/2)
        This.ogdigraphics.drawstring("Rotated Text" +CHR(13)+CHR(10)+;
          "GDIPlus-X is COOL !!!", ;
          lofont, lobrush, lorect, lostringformat)

        * Reset Rotation
        This.ogdigraphics.resettransform

        This.NewPage = .F.

     ENDIF
     ENDWITH
     DODEFAULT(nfrxrecno,;
        nleft,ntop,nwidth,nheight,;
        nobjectcontinuationtype, ;
        ccontentstoberendered, gdiplusimage)

  ENDPROC
ENDDEFINE



UPDATE 06-08-31
To make sure that the watermark will not be overwritten by opaque controls, the code below does the same thing shown above, but this time draws the watermark only after the footer band is totally rendered. The color used for this case was also changed from opaque to semitransparent, using Alpha of 128 ( 0 = totally transparent, 255 = opaque). So, here's the new ReportListener subclass to deal with this problem:

DEFINE CLASS myreportlistener AS _reportlistener OF ;
    ADDBS(HOME()) + "FFC\" + "_ReportListener.VCX"
  ogdigraphics = NULL

  FUNCTION BEFOREREPORT
    DODEFAULT()
    This.ogdigraphics = _Screen.System.Drawing.Graphics.New()
  ENDFUNC

  FUNCTION AFTERBAND(nbandobjcode, nfrxrecno)
  *-- FRX OBJCODE column values
  #DEFINE FRX_OBJCOD_TITLE                    0
  #DEFINE FRX_OBJCOD_PAGEHEADER               1
  #DEFINE FRX_OBJCOD_COLHEADER                2
  #DEFINE FRX_OBJCOD_GROUPHEADER              3
  #DEFINE FRX_OBJCOD_DETAIL                   4
  #DEFINE FRX_OBJCOD_GROUPFOOTER              5
  #DEFINE FRX_OBJCOD_COLFOOTER                6
  #DEFINE FRX_OBJCOD_PAGEFOOTER               7
  #DEFINE FRX_OBJCOD_SUMMARY                  8
  #DEFINE FRX_OBJCOD_DETAILHEADER             9
  #DEFINE FRX_OBJCOD_DETAILFOOTER            10

  IF nbandobjcode==frx_objcod_pagefooter
      IF NOT This.issuccessor
        This.sharedgdiplusgraphics = This.GDIPLUSGRAPHICS
      ENDIF
      This.ogdigraphics.handle = This.sharedgdiplusgraphics
   
      WITH _SCREEN.SYSTEM.drawing
        * Create a SolidBrush with Red semi transparent color
        LOCAL lobrush AS xfcbrush
        lobrush = .solidbrush.new(.COLOR.fromArgb(128,255,128,128))

        * Create a Rectangle in which the rotated text will be drawn
        LOCAL lorect AS xfcrectangle
        lorect = .rectangle.new(0, 0, This.sharedpagewidth,;
          This.sharedpageheight)

        * Get a basic string format object, then set properties
        LOCAL lostringformat AS xfcstringformat
        lostringformat = .stringformat.new()
        lostringformat.ALIGNMENT = .stringalignment.CENTER
        lostringformat.linealignment = .stringalignment.CENTER

        * Create a Font object
        LOCAL lofont AS xfcfont
        lofont = .FONT.new("Verdana",48, 0, .graphicsunit.POINT)

        * Translate and Rotate
        This.ogdigraphics.translatetransform(This.sharedpagewidth/2,;
          This.sharedpageheight/2)
        This.ogdigraphics.rotatetransform(-45)
        This.ogdigraphics.translatetransform(-This.sharedpagewidth/2,;
          -This.sharedpageheight/2)
        This.ogdigraphics.drawstring("Rotated Text" +CHR(13)+CHR(10)+;
          "GDIPlus-X is COOL !!!", ;
          lofont, lobrush, lorect, lostringformat)

        * Reset Rotation
        This.ogdigraphics.resettransform()

      ENDWITH
    ENDIF
    DODEFAULT(nBandObjCode, nFRXRecNo)
  ENDFUNC
ENDDEFINE


RELATED LINKS
Article from Bill Wagner  http://www.ftponline.com/vsm/2002_10/online/csharp_bwagner_10_31_02/

No comments:

Post a Comment