Mostrando las entradas con la etiqueta GDI. Mostrar todas las entradas
Mostrando las entradas con la etiqueta GDI. Mostrar todas las entradas

18 de diciembre de 2021

Guardar un informe de VFP a una resolución más alta con FoxyPreviewer

Articulo Original: Saving a VFP Report at a higher resolution with FoxyPreviewer
http://vfpimaging.blogspot.com/2021/03/saving-vfp-report-at-higher-resolution.html
Autor: Cesar Ch.
Traducido por: Luis María Guayán


Visual FoxPro 9 trajo la posibilidad de guardar nuestros informes como imágenes, utilizando la clase ReportListener y el método OutputPage:

loListener.OutputPage(lnPage, "\MyReportImage.PNG", 104) && PNG file type

Esto nos trae algunas imágenes útiles, pero pésimas en cuanto a calidad. Por ejemplo, la muestra "COLORS.FRX" me brinda una imagen de 816 x 1056 píxeles, una imagen de muy mala calidad, y mas aun si estamos pensando en imprimirla o manipularla.

Pero el método "OutputPage" también nos permite dibujar la página del informe en cualquier tamaño de imagen deseado, pasando un identificador GDI+ Graphics en lugar del nombre de archivo ampliamente utilizado.

Aquí está la muestra de trabajo: observe que el motor de informes funciona solo con 96 DPI, por lo que para tener una mejor calidad, debe gurdar en dimensiones más grandes.

Use la función GETREPORTPAGEEX para obtener sus informes con mayor resolución, aquí está la lista de parámetros

  • tcFile: el nombre del archivo de la imagen de destino
  • toListener: el ReportListener asociado con el informe actual
  • tnPage: el número de página del informe
  • tnEncoder: 100=EMF, 101=TIFF, 102=JPEG, 103=GIF, 104=PNG, 105=BMP
  • tnScale: el factor de escala que se aplicará a la imagen. 1=Predeterminado (baja calidad), 10=Super alta calidad
  • tnWidth: el ancho de la imagen de salida (opcional, si se usa "tnScale")
  • tnHeight: la altura de la imagen de salida (opcional, si se usa "tnScale")
DO FoxyPreviewer.App

LOCAL loListener AS REPORTLISTENER
LOCAL lcFile, lnPage, lnFileType
m.loListener			  = CREATEOBJECT("FoxyListener")
m.loListener.LISTENERTYPE = 3

REPORT FORM (ADDBS(_Samples) + "Solution\Reports\Colors.FRX") OBJECT m.loListener

m.lnFileType = 104 && PNG
	&& 100 - EMF
	&& 101 - TIFF
	&& 102 - JPEG
	&& 103 - GIF
	&& 104 - PNG
	&& 105 - BMP

FOR m.lnPage = 1 TO m.loListener.PAGETOTAL
	m.lcFile = "c:\temp\Test__" + SYS(2015) + "__" + ALLTRIM(STR(m.lnPage)) + ".png"
	GetReportPageEx(m.lcFile, m.loListener, m.lnPage, m.lnFileType, 5) && 5 times bigger image than default
	* For the default lower quality image, use:
	*   loListener.OutputPage(lnPage, "c:\Test" + ALLTRIM(STR(lnPage)) + ".png", lnFileType)
ENDFOR
m.loListener = NULL
RETURN


PROCEDURE GetReportPageEx(tcFile, toListener AS REPORTLISTENER, tnPage, tnEncoder, tnScale, tnWidth, tnHeight)
	LOCAL lhGfx
	*!*	100 - image type EMF
	*!*	101 - image type TIFF
	*!*	102 - image type JPEG
	*!*	103 - image type GIF
	*!*	104 - image type PNG
	*!*	105 - image type BMP
	m.tnEncoder	= EVL(m.tnEncoder, 104) && Default = 104-PNG
	m.tnScale	= EVL(m.tnScale, 1)
	IF EMPTY(m.tnWidth)
		m.tnWidth  = m.toListener.GETPAGEWIDTH()  / 10 * m.tnScale
		m.tnHeight = m.toListener.GETPAGEHEIGHT() / 10 * m.tnScale
	ENDIF

	#DEFINE Gdiplus_PixelFormat_32BppArgb		0x0026200a
	#DEFINE OUTPUTDEVICETYPE_GDIPLUS 			1

	LOCAL loBMP AS GpBitmap OF ADDBS(HOME()) + "/FFC/_GDIPLUS.VCX"
	m.loBMP = NEWOBJECT("GpBitmap", ADDBS(HOME()) + "/FFC/_GDIPLUS.VCX")
	m.loBMP.CREATE(m.tnWidth, m.tnHeight, Gdiplus_PixelFormat_32BppArgb)

	LOCAL loGfx AS GpGraphics OF ADDBS(HOME()) + "/FFC/_GDIPLUS.VCX"
	m.loGfx = NEWOBJECT('GpGraphics', ADDBS(HOME()) + "/FFC/_GDIPLUS.VCX")
	m.loGfx.CreateFromImage(m.loBMP)
	m.lhGfx = m.loGfx.GetHandle()

	m.toListener.OUTPUTPAGE(m.tnPage, m.lhGfx, OUTPUTDEVICETYPE_GDIPLUS, 0, 0, m.tnWidth, m.tnHeight, 0, 0, m.tnWidth, m.tnHeight)
	m.loBMP.SaveToFile(m.tcFile, "image/png")
ENDPROC

28 de agosto de 2021

Olvídese de TXTWIDTH - use GdipMeasureString

Articulo original: Forget TXTWIDTH - use GdipMeasureString
https://doughennig.blogspot.com/2006/04/forget-txtwidth-use-gdipmeasurestring.html
Autor: Doug Hennig
Traducido por: Luis María Guayán


Durante años, hemos utilizado código como el siguiente para determinar el ancho de una cadena:

lnWidth = txtwidth(lcText, lcFontName, lnFontSize, ;
  lcFontStyle)
lnWidth = lnWidth * fontmetric(6, lcFontName, ;
  lnFontSize, lcFontStyle)

Este código funciona bien en muchas situaciones, pero no en una en particular: cuando se define el ancho de un objeto en un informe.

El valor calculado anteriormente está en píxeles, por lo que debe convertir el valor a FRU (las unidades utilizadas en los informes, que son 1/10000 de pulgada); debe multiplicar por 104,166 (10000 FRU por pulgada / 96 píxeles por pulgada). En lugar de hacer todo ese trabajo, puede utilizar el método GetFRUTextWidth del objeto auxiliar FFC _FRXCursor:

loFRXCursor = newobject('FRXCursor', ;
  home() + 'FFC\_FRXCursor.vcx')
lnWidth = loFRXCursor.GetFRUTextWidth(lcText, ;
  lcFontName, lnFontSize, lcFontStyle)

El problema es que esto en realidad no le da el valor correcto. El motivo es que los informes usan GDI + para la representación y GDI + representa los objetos un poco más grandes de lo esperado.

Para ver este problema, haga lo siguiente:

use home() + 'samples\data\customer'
loFRXCursor = newobject('FRXCursor', ;
  home() + 'FFC\_FRXCursor.vcx')
select max(loFRXCursor.GetFRUTextWidth(trim(company), ;
  'Arial', 10)) from customer into array laWidth
wait window laWidth[1]

Obtengo 22500. Ahora cree un informe, agregue un campo, ingrese "empresa" como expresión y hágalo 2.25 pulgadas de ancho (22500 FRU / 10000 FRU por pulgada). Obtenga una vista previa del informe. La elipsis reveladora al final de algunos valores indica que el tamaño del campo no era lo suficientemente amplio.

Esto me volvió loco durante años. Descubrí un factor empírico "fudge" para agregar al ancho calculado; 19 píxeles (1979.154 FRU) parecían funcionar la mayor parte del tiempo, pero ocasionalmente encontraba que no era suficiente para algunos valores.

Afortunadamente, dado que los informes usan GDI +, podemos usar una función GDI + para calcular con precisión el ancho. GdipMeasureString determina varias cosas sobre la cadena especificada, incluido el ancho. Aún mejor, VFP 9 viene con un objeto contenedor de GDI + para que no tenga que comprender la API de GDI + para llamar a GdipMeasureString.

Para mostrar un ejemplo del uso de las clases contenedoras de GDI +, eche un vistazo a esta función:

function GetWidth(tcText, tcFontName, tnFontSize)
local loGDI, ;
  loFont, ;
  lnChars, ;
  lnLines, ;
  loSize
loGDI = newobject('GPGraphics', ;
  home() + 'FFC\_GDIPlus.vcx')
loFont = newobject('GPFont', ;
  home() + 'FFC\_GDIPlus.vcx', '', tcFontName, ;
  tnFontSize, 0, 3)
loGDI.CreateFromHWnd(_screen.HWnd)
lnChars = 0
lnLines = 0
loSize  = loGDI.MeasureStringA(tcText, loFont, , , ;
  @lnChars, @lnLines)
lnWidth = loSize.W
release loGDI, loFont, loSize
return lnWidth

Ahora intente lo siguiente:

select max(GetWidth(trim(company), ;
  'Arial', 10)) from customer into array laWidth
wait window ceiling(laWidth[1] * 104.166)

Esto da 23838. Cambie el ancho del campo en el informe a 2,384 pulgadas y vuelva a obtener una vista previa. Esta vez los valores encajan correctamente.

El único problema ahora es que este código puede tardar mucho en ejecutarse si hay muchos registros porque para cada llamada, se crean un par de objetos contenedores de GDI + y se realiza alguna configuración de GDI +. Creé una clase contenedora para GdipMeasureString llamada SFGDIMeasureString que funciona de manera mucho más eficiente.

Veamos esta clase en secciones. Aquí está el comienzo: define algunas constantes, la clase y sus propiedades:

* Estos #DEFINEs se toman de
* home() + 'ffc\gdiplus.h'

#define GDIPLUS_FontStyle_Regular     0
#define GDIPLUS_FontStyle_Bold        1
#define GDIPLUS_FontStyle_Italic      2
#define GDIPLUS_FontStyle_BoldItalic  3
#define GDIPLUS_FontStyle_Underline   4
#define GDIPLUS_FontStyle_Strikeout   8
#define GDIPLUS_STATUS_OK       0
#define GDIPLUS_Unit_Point            3

define class SFGDIMeasureString as Custom
  oGDI    = .NULL.
    && a reference to a GPGraphics object
  oFormat = .NULL.
    && a reference to a GPStringFormat object
  oFont   = .NULL.
    && a reference to a GPFont object
  oSize   = .NULL.
    && a reference to a GPSize object
  nChars  = 0
   && the number of characters fitted in the
    && bounding box
  nLines  = 0
    && the number of lines in the bounding box
  nWidth  = 0
    && the width of the bounding box
  nHeight = 0
    && the height of the bounding box
  nStatus = 0
    && the status code from GDI+ functions

El método Init crea una instancia de algunos objetos auxiliares y declara la función GdipMeasureString. Destruye los objetos miembros con armas nucleares:

function Init
  This.oGDI    = newobject('GPGraphics', ;
    home() + 'ffc\_gdiplus.vcx')
  This.oFormat = newobject('GPStringFormat', ;
    home() + 'ffc\_gdiplus.vcx')
  This.oFont   = newobject('GPFont', ;
    home() + 'ffc\_gdiplus.vcx')
  This.oSize   = newobject('GPSize', ;
    home() + 'ffc\_gdiplus.vcx')
  declare integer GdipMeasureString ;
    in gdiplus.dll ;
    integer nGraphics, string cUnicode, ;
    integer nLength, integer nFont, ;
    string cLayoutRect, integer nStringFormat, ;
    string @cRectOut, integer @nChars, ;
    integer @nLines
endfunc

function Destroy
  store .NULL. to This.oGDI, This.oFormat, ;
    This.oFont, This.oSize
endfunc

MeasureString determina las dimensiones del cuadro delimitador para la cadena especificada:

function MeasureString(tcString, tcFontName, ;
  tnFontSize, tcStyle)
  local lcStyle, ;
    lnStyle, ;
    lnChars, ;
    lnLines, ;
    lcBoundingBox, ;
    lnGDIHandle, ;
    lnFontHandle, ;
    lnFormatHandle, ;
    lcRectF, ;
    lnStatus, ;
    llReturn
  with This

* Asegúrese de que los parámetros se pasen correctamente.

    do case
      case vartype(tcString) <> 'C' or ;
        empty(tcString)
        error 11
        return .F.
      case pcount() > 1 and ;
        (vartype(tcFontName) <> 'C' or ;
        empty(tcFontName) or ;
        vartype(tnFontSize) <> 'N' or ;
        not between(tnFontSize, 1, 128))
        error 11
        return .F.
      case pcount() = 4 and ;
       (vartype(tcStyle) <> 'C' or ;
        empty(tcStyle))
        error 11
        return .F.
    endcase

* Configure el objeto Font si se especificaron la fuente y el tamaño.

    if pcount() > 1
      lcStyle = iif(vartype(tcStyle) = 'C', ;
        tcStyle, '')
      .SetFont(tcFontName, tnFontSize, lcStyle)
    endif pcount() > 1

* Inicializar las variables de salida utilizadas en GdipMeasureString.

    lnChars       = 0
    lnLines       = 0
    lcBoundingBox = replicate(chr(0), 16)

* Obtenga los identificadores de GDI + que necesitamos.

    lnGDIHandle = .oGDI.GetHandle()
    if lnGDIHandle = 0
      .oGDI.CreateFromHWnd(_screen.HWnd)
      lnGDIHandle = .oGDI.GetHandle()
    endif lnGDIHandle = 0
    lnFontHandle   = .oFont.GetHandle()
    lnFormatHandle = .oFormat.GetHandle()

* Obtenga el tamaño del cuadro de diseño.

    lcRectF = replicate(chr(0), 8) + ;
      .oSize.GdipSizeF

* Llame a la función GdipMeasureString para obtener las dimensiones
* del cuadro delimitador para la cadena especificada.

    .nStatus = GdipMeasureString(lnGDIHandle, ;
      strconv(tcString, 5), len(tcString), ;
      lnFontHandle, lcRectF, lnFormatHandle, ;
      @lcBoundingBox, @lnChars, @lnLines)
    if .nStatus = GDIPLUS_STATUS_OK
      .nChars  = lnChars
      .nLines  = lnLines
      .nWidth  = ctobin(substr(lcBoundingBox, ;
         9, 4), 'N')
      .nHeight = ctobin(substr(lcBoundingBox, ;
        13, 4), 'N')
      llReturn = .T.
    else
      llReturn = .F.
    endif .nStatus = GDIPLUS_STATUS_OK
  endwith
  return llReturn
endfunc

GetWidth es un método de utilidad que devuelve el ancho de la cadena especificada:

function GetWidth(tcString, tcFontName, ;
  tnFontSize, tcStyle)
  local llReturn, ;
    lnReturn
  with This
    do case
      case pcount() < 2
        llReturn = .MeasureString(tcString)
      case pcount() < 4
        llReturn = .MeasureString(tcString, ;
          tcFontName, tnFontSize)
      otherwise
        llReturn = .MeasureString(tcString, ;
          tcFontName, tnFontSize, tcStyle)
    endcase
    if llReturn
      lnReturn = .nWidth
    endif llReturn
  endwith
  return lnReturn
endfunc

SetSize establece las dimensiones del cuadro de diseño para la cadena:

function SetSize(tnWidth, tnHeight)
  if vartype(tnWidth) = 'N' and ;
    tnWidth >= 0 and ;
    vartype(tnHeight) = 'N' and tnHeight >=0
    This.oSize.Create(tnWidth, tnHeight)
  else
    error 11
  endif vartype(tnWidth) = 'N' ...
endfunc

SetFont establece el nombre, el tamaño y el estilo de la fuente que se utilizará:

function SetFont(tcFontName, tnFontSize, tcStyle)
  local lcStyle
  do case
    case pcount() <= 2 and ;
      (vartype(tcFontName) <> 'C' or ;
      empty(tcFontName) or ;
      vartype(tnFontSize) <> 'N' or ;
      not between(tnFontSize, 1, 128))
      error 11
      return .F.
    case pcount() = 3 and ;
      vartype(tcStyle) <> 'C'
      error 11
      return .F.
  endcase
  lcStyle = iif(vartype(tcStyle) = 'C', tcStyle, '')
  lnStyle = iif('B' $ lcStyle, ;
      GDIPLUS_FontStyle_Bold, 0) + ;
    iif('I' $ lcStyle, ;
      GDIPLUS_FontStyle_Italic, 0) + ;
    iif('U' $ lcStyle, ;
      GDIPLUS_FontStyle_Underline, 0) + ;
    iif('-' $ lcStyle, ;
      GDIPLUS_FontStyle_Strikeout, 0)
  This.oFont.Create(tcFontName, tnFontSize, ;
    lnStyle, GDIPLUS_Unit_Point)
endfunc

Probemos el ejemplo anterior usando esta clase:

loGDI = newobject('SFGDIMeasureString', ;
  'SFGDIMeasureString.prg')
select max(loGDI.GetWidth(trim(company), 'Arial', 10)) ;
  from customer into array laWidth
wait window laWidth[1] * 10000/96

Esto es mucho más rápido que la función GetWidth presentada anteriormente. Lo siguiente se ejecutaría aún más rápido porque el objeto de fuente no tiene que inicializarse en cada llamada:

loGDI = newobject('SFGDIMeasureString', ;
  'SFGDIMeasureString.prg')
loGDI.SetFont('Arial', 10)
select max(loGDI.GetWidth(trim(company))) ;
  from customer into array laWidth
wait window laWidth[1] * 10000/96

Lo bueno de esta clase es que puede hacer mucho más que calcular el ancho de una cuerda. Es cy también determina la altura o el número de líneas que tomará una cadena en un cierto ancho (piense en establecer MEMOWIDTH en un cierto ancho y luego usar MEMLINES (), pero más rápido, más preciso y fuentes de apoyo).

Por ejemplo, tengo una clase de diálogo de mensaje genérico que utilizo para mostrar advertencias, errores y otros tipos de mensajes al usuario. No uso MESSAGEBOX () para esto porque mi clase admite varios botones con subtítulos personalizados. El problema es que los botones aparecen debajo de un cuadro de edición utilizado para mostrar el mensaje. Entonces, ¿cuánto espacio tengo que asignar para la altura del cuadro de edición? Si no especifico lo suficiente, el usuario debe desplazarse para ver el mensaje. Si especifico demasiado, los mensajes cortos se ven ridículos porque hay mucho espacio en blanco antes de los botones. Ahora, puedo hacer que el cuadro de edición tenga un tamaño arbitrario y usar SFGDIMeasureString para determinar la altura necesaria para el cuadro de edición para un mensaje dado, ajustando las posiciones de los botones dinámicamente. Para hacerlo, llamo al método SetSize para decirle a SFGDIMeasureString el ancho del cuadro de edición (paso un valor muy grande, como 10000, para la altura, por lo que no es un factor), luego llamo a MeasureString y uso el valor de la propiedad nHeight para la altura del cuadro de edición.

Estoy encontrando muchos más usos para esta clase. Espero que también te resulte útil.

6 de agosto de 2021

Iconos de Segoe MDL2 Assets en VFP9 con GDI+

Articulo original: Segoe MDL2 Assets Icons in VFP9 with Gdi+
http://vfpimaging.blogspot.com/2021/04/segoe-mdl2-assets-icons-in-vfp9-with-gdi.html
Autor: Cesar Ch.
Traducido por: Luis María Guayán


Como se discutió anteriormente en este blog, VFP no puede mostrar de forma nativa ningún carácter que tenga su CHR() mayor que 0xFF (decimal 255).

Hay varias fuentes muy interesantes que traen íconos muy interesantes y actualizados que podríamos usar en nuestras aplicaciones, como SEGOE MDL2 ASSETS, utilizado por Windows 10 en todas partes.

Los Unicodes se pueden obtener directamente a través de CharMap.EXE o en toda la web. Aquí hay un excelente punto de partida: https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font

Los ejemplos a continuación usan GDI+ para guardar cualquier carácter deseado como una imagen, lo que nos permitirá usar esas imágenes geniales en nuestras aplicaciones. Usan las clases _GDIPLUS.VCX FFC, pero también es muy fácil de adaptar a GdiPlusX, si es necesario.

¡Adáptalo a tus necesidades!

Básicamente, una función que recupera un solo carácter Unicode y lo guarda como un archivo de imagen.

Uso:
Para obtener el icono "Imprimir":

EXTRAIGA UN ÚNICO ICONO

lcFile = "Imprimir.bmp"
lcUnicode = "e749"
lcFont = "ACTIVOS SEGOE MDL2"
lnSize = 32 && píxeles
lnForeColor = RGB (0, 0, 255) && Negro
lnBackColor = RGB (255, 255, 255) && Blanco
= MakeImageFromUnicode (m.lcFile, lcUnicode, lcFont, lnSize, lnForeColor, lnBackColor)

Guarde el siguiente código como "MakeImageFromUnicode.prg":

FUNCTION MakeImageFromUnicode(tcFileName, tcUnicode, tcFontName, tnImgSize, tnForeColor, tnBackColor)
  *!* tcUnicode allows up to 2 characters, that will be drawn one over the other
  *!* Par1: Main Unicode
  *!* Par2: Socondary Unicode
  *!* Par3: Mode, where 0=Center, 1=TopLeft, 2=TopRight, 3=BottLeft, 4=BottRight
  *!* Par4: Size of the 2nd character

  LOCAL lnChars, lnFactor, lnFontHeight, lnFontSize, lnHeight, lnLines, lnNewFontSize, lnWidth
  LOCAL lqUnicode
  LOCAL lcUnicode1, lcUnicode2, lnMode, lnSize2
  IF EMPTY(m.tcFileName) OR EMPTY(m.tcUnicode) OR EMPTY(m.tcFontName) OR EMPTY(m.tnImgSize)
    RETURN
  ENDIF

  m.lnFontSize = 48
  m.lnWidth   = m.tnImgSize
  m.lnHeight   = m.tnImgSize

  * Create a font object using the text object's settings.
  LOCAL loFont0 AS GpFont OF HOME() + "FFC/_GdiPlus.vcx"
  m.loFont0 = NEWOBJECT('gpFont', HOME() + 'FFC/_GdiPlus.vcx')
  m.loFont0.CREATE(m.tcFontName, m.lnFontSize, 0, 3) && 0 = Font Style

  LOCAL loGfx0 AS GpGraphics OF HOME() + "FFC/_GdiPlus.vcx"
  m.loGfx0 = NEWOBJECT('gpGraphics', HOME() + 'FFC\_GdiPlus.vcx')
  m.loGfx0.CreateFromHWnd(_SCREEN.HWND)
  m.lnChars = 0
  m.lnLines = 0

  LOCAL loSize AS gpSize OF HOME() + "FFC/_GdiPlus.vcx"
  m.loSize  = m.loGfx0.MeasureStringA("A", m.loFont0, , , @m.lnChars, @m.lnLines)
  * lnFontWidth = loSize.W
  m.lnFontHeight  = m.loSize.H
  m.lnFactor    = m.lnFontHeight / m.tnImgSize
  m.lnNewFontSize  = INT(m.lnFontSize / m.lnFactor)

  * Create a font object using the text object's settings.
  LOCAL loFont AS GpFont OF HOME() + "FFC/_GdiPlus.vcx"
  m.loFont = NEWOBJECT('gpFont', HOME() + 'FFC/_GdiPlus.vcx')
  m.loFont.CREATE(m.tcFontName, m.lnNewFontSize, 0, 3) && 0 = Font Style

  LOCAL loBMP AS GpBitmap OF HOME() + "FFC/_GdiPlus.vcx"
  m.loBMP = NEWOBJECT("gpBitmap", HOME() + "FFC/_GdiPlus.vcx")
  #DEFINE GdiPlus_PixelFormat_32BPPARGB        0x0026200a
  m.loBMP.CREATE(m.lnHeight, m.lnHeight, GdiPlus_PixelFormat_32BPPARGB)

  LOCAL loGfx AS GpGraphics OF HOME() + "FFC/_GdiPlus.vcx"
  m.loGfx = NEWOBJECT('gpGraphics', HOME() + 'FFC/_GdiPlus.vcx')
  m.loGfx.CreateFromImage(m.loBMP)

  * Setting the Backcolor
  LOCAL loBackColor AS GpColor OF HOME() + "FFC/_GdiPlus.vcx"
  IF EMPTY(m.tnBackColor)
    m.loBackColor = 0xFFFFFFFF && White background
  ELSE
    m.loBackColor     = NEWOBJECT("gpColor", HOME() + 'FFC/_GdiPlus.vcx')
    m.loBackColor.FoxRGB = m.tnBackColor
  ENDIF
  m.loGfx.CLEAR(m.loBackColor) && Background

  * Create a rectangle
  LOCAL loRect AS GpRectangle OF HOME() + "FFC/_GdiPlus.vcx"
  m.loRect = NEWOBJECT("GPRectangle", HOME() + 'FFC/_GdiPlus.vcx', "", 0, 0, m.lnWidth, m.lnHeight)
  m.loRect.Y = m.loRect.Y + 1

  * Setting the Forecolor
  LOCAL loColor AS GpColor OF HOME() + "FFC/_GdiPlus.vcx"
  IF EMPTY(m.tnForeColor)
    m.tnForeColor = 0 && Black
  ENDIF
  m.loColor     = NEWOBJECT("gpColor", HOME() + 'FFC/_GdiPlus.vcx')
  m.loColor.FoxRGB = m.tnForeColor

  LOCAL loBrush AS GpSolidBrush OF HOME() + "FFC/_GdiPlus.vcx"
  m.loBrush = NEWOBJECT("gpSolidBrush", HOME() + 'FFC/_GdiPlus.vcx', "", m.loColor)

  * The character need to be drawn at the center of the image object
  * Get a basic string format object
  * StringAlignment enumeration
  * Applies to GpStringFormat::Alignment, GpStringFormat::LineAlignment
  #DEFINE GDIPLUS_STRINGALIGNMENT_Near  0  && in Left-To-Right locale, this is Left
  #DEFINE GDIPLUS_STRINGALIGNMENT_Center  1
  #DEFINE GDIPLUS_STRINGALIGNMENT_Far    2  && in Left-To-Right locale, this is Right
  LOCAL loStringFormat AS gpStringFormat OF HOME() + "FFC/_GdiPlus.vcx"
  m.loStringFormat = NEWOBJECT("GpStringFormat", HOME() + "FFC/_GdiPlus.vcx")
  m.loStringFormat.CREATE()
  m.loStringFormat.ALIGNMENT     = GDIPLUS_STRINGALIGNMENT_Center
  m.loStringFormat.LineAlignment = GDIPLUS_STRINGALIGNMENT_Center

  * Prepare the Unicode
  m.lcUnicode1 = GETWORDNUM(m.tcUnicode, 1, ",")
  m.lqUnicode   = LEFT(BINTOC(EVALUATE("0x" + m.lcUnicode1), "4RS"), 2)

  * Draw the string
  m.loGfx.DrawStringW(m.lqUnicode, m.loFont, m.loRect, m.loStringFormat, m.loBrush)
  m.lcUnicode2  = GETWORDNUM(m.tcUnicode, 2, ",")

  IF NOT EMPTY(m.lcUnicode2)
    m.lqUnicode  = LEFT(BINTOC(EVALUATE("0x" + m.lcUnicode2), "4RS"), 2)
    m.lnMode  = VAL(GETWORDNUM(m.tcUnicode, 3, ","))
    m.lnSize2  = VAL(GETWORDNUM(m.tcUnicode, 4, ","))
    m.lnSize2  = EVL(m.lnSize2, 100)

    lnNewFontSize = CEILING(m.lnNewFontSize * (lnSize2/100))
    m.loFont.CREATE(m.tcFontName, m.lnNewFontSize, 0, 3) && 0 = Font Style
    m.loStringFormat.ALIGNMENT     = GDIPLUS_STRINGALIGNMENT_Center
    m.loStringFormat.LineAlignment = GDIPLUS_STRINGALIGNMENT_Center

    m.loRect.w = INT(m.lnWidth  * (m.lnSize2 / 100))
    m.loRect.H = INT(m.lnHeight * (m.lnSize2 / 100))

    DO CASE
      CASE m.lnMode = 0 && No transformation, the 2nd image will be drawn over the original
        m.loRect.x = INT((m.lnWidth  - m.loRect.w) / 2)
        m.loRect.Y = INT((m.lnHeight - m.loRect.H) / 2)

      CASE m.lnMode = 1 && Top-Left
        m.loRect.x = 0
        m.loRect.Y = 0

      CASE m.lnMode = 2 && Top-Right
        m.loRect.x = m.lnWidth - m.loRect.w
        m.loRect.Y = 0

      CASE m.lnMode = 3 && Bottom-Left
        m.loRect.x = 0
        m.loRect.Y = m.lnHeight - m.loRect.H

      CASE m.lnMode = 4 && Bottom-Right
        m.loRect.x = m.lnWidth - m.loRect.w
        m.loRect.Y = m.lnHeight - m.loRect.H

      OTHERWISE
    ENDCASE
    m.loRect.Y = m.loRect.Y + 1
    m.loGfx.DrawStringW(m.lqUnicode, m.loFont, m.loRect, m.loStringFormat, m.loBrush)
  ENDIF

  * Save as image
  m.loBMP.SaveToFile(m.tcFileName, "image/bmp")

  RETURN
ENDFUNC

La función también le permite crear nuevos íconos fusionando dos, en este caso, el ícono de Impresora y Configuración en la parte inferior derecha:

PERSONALIZA TUS ICONOS

* Setup the initial 5 variables
LOCAL lcFontName, lnImgSize, lnForeColor, lnBackColor, lcImageType
m.lcFontName  = "SEGOE MDL2 ASSETS"
m.lnImgSize	  = 64  && The desired bmp size in pixels
m.lnForeColor = RGB(0, 0, 0) && the ForeColor
m.lnBackColor = RGB(255, 255, 255) && the BackColor
m.lcImageType = "bmp" && available: bmp, jpg, gif, tif, png

EXTRACCIÓN DE TODOS LOS ICONOS DE UNA FUENTE

La función anterior se puede adaptar para extraer todos los caracteres de una fuente determinada, utilizando un bucle.

Las fuentes suelen tener algunos códigos que no se utilizan, por lo que en el siguiente código utilicé un truco simple para detectar las dimensiones vacías de la fuente, y cada vez que se cumplan las mismas condiciones en el bucle, se descartará el Unicode.

Simplemente ejecute el siguiente código para extraer todos los íconos de cualquier fuente determinada, con el tamaño y los colores de imagen deseados. ¡Ajuste las variables iniciales para que se adapten a sus necesidades!

* Setup the initial 5 variables
LOCAL lcFontName, lnImgSize, lnForeColor, lnBackColor, lcImageType
m.lcFontName  = "SEGOE MDL2 ASSETS"
m.lnImgSize    = 64  && The desired bmp SIZE IN PIXELS
m.lnForeColor = RGB(0, 0, 0) && the FORECOLOR
m.lnBackColor = RGB(255, 255, 255) && the BACKCOLOR
m.lcImageType = "bmp" && available: bmp, jpg, gif, tif, png

* Let's start
LOCAL lcEmptyUnicode, lcFileName, lcHex, lcUnicode, lnChars, lnEmptyH, lnEmptyW, lnFactor
LOCAL lnFontHeight, lnFontSize, lnFontWidth, lnHeight, lnLines, lnNewFontSize, lnWidth, loSizeReal, N

m.lnFontSize  = 48
m.lnWidth    = m.lnImgSize
m.lnHeight    = m.lnImgSize
m.lcImageType = LOWER(EVL(m.lcImageType, "bmp"))

* Create a rectangle
LOCAL loRect AS GpRectangle OF HOME() + "FFC/_GdiPlus.vcx"
m.loRect   = NEWOBJECT("GPRectangle", HOME() + 'FFC/_GdiPlus.vcx', "", 0, 0, m.lnWidth, m.lnHeight)
m.loRect.Y = m.loRect.Y + 1

* The character need to be drawn at the center of the image object
* Get a basic string format object
* StringAlignment enumeration
* Applies to GpStringFormat::Alignment, GpStringFormat::LineAlignment
#DEFINE GDIPLUS_STRINGALIGNMENT_Near  0  && IN LEFT-TO-RIGHT locale, THIS IS LEFT
#DEFINE GDIPLUS_STRINGALIGNMENT_Center  1
#DEFINE GDIPLUS_STRINGALIGNMENT_Far    2  && IN LEFT-TO-RIGHT locale, THIS IS RIGHT
LOCAL loStringFormat AS gpStringFormat OF HOME() + "FFC/_GdiPlus.vcx"
m.loStringFormat = NEWOBJECT("GpStringFormat", HOME() + "FFC/_GdiPlus.vcx")
m.loStringFormat.CREATE()
m.loStringFormat.ALIGNMENT     = GDIPLUS_STRINGALIGNMENT_Center
m.loStringFormat.LineAlignment = GDIPLUS_STRINGALIGNMENT_Center

* Create a font object using the text object's settings.
LOCAL loFont0 AS GpFont OF HOME() + "FFC/_GdiPlus.vcx"
m.loFont0 = NEWOBJECT('gpFont', HOME() + 'FFC/_GdiPlus.vcx')
m.loFont0.CREATE(m.lcFontName, m.lnFontSize, 0, 3) && 0 = FONT STYLE

LOCAL loGfx0 AS GpGraphics OF HOME() + "FFC/_GdiPlus.vcx"
m.loGfx0 = NEWOBJECT('gpGraphics', HOME() + 'FFC\_GdiPlus.vcx')
m.loGfx0.CreateFromHWnd(_SCREEN.HWND)

LOCAL loSize AS gpSize OF HOME() + "FFC/_GdiPlus.vcx"
m.lnChars     = 0
m.lnLines     = 0
m.loSize     = m.loGfx0.MeasureStringA("A", m.loFont0, , , @m.lnChars, @m.lnLines)
m.lnFontWidth  = m.loSize.W
m.lnFontHeight = m.loSize.H

m.lnFactor    = m.lnFontHeight / m.lnImgSize
m.lnNewFontSize  = INT(m.lnFontSize / m.lnFactor)

* Create a font object using the text object's settings.
LOCAL loFont AS GpFont OF HOME() + "FFC/_GdiPlus.vcx"
m.loFont = NEWOBJECT('gpFont', HOME() + 'FFC/_GdiPlus.vcx')
m.loFont.CREATE(m.lcFontName, m.lnNewFontSize, 0, 3) && 0 = FONT STYLE

* Get the measure of the empty character, that will be used to avoid saving it several times
m.lcEmptyUnicode = CHR(0) + CHR(0)
LOCAL loSizeEmpty AS gpSize OF HOME() + "FFC/_GdiPlus.vcx"
m.loSizeEmpty = m.loGfx0.MeasureStringW(m.lcEmptyUnicode, m.loFont, m.loRect, m.loStringFormat, @m.lnChars, @m.lnLines)
m.lnEmptyW    = m.loSizeEmpty.W
m.lnEmptyH    = m.loSizeEmpty.H

LOCAL loBMP AS GpBitmap OF HOME() + "FFC/_GdiPlus.vcx"
m.loBMP = NEWOBJECT("gpBitmap", HOME() + "FFC/_GdiPlus.vcx")
#DEFINE GdiPlus_PixelFormat_32BPPARGB        0x0026200a
m.loBMP.CREATE(m.lnHeight, m.lnHeight, GdiPlus_PixelFormat_32BPPARGB)

LOCAL loGfx AS GpGraphics OF HOME() + "FFC/_GdiPlus.vcx"
m.loGfx = NEWOBJECT('gpGraphics', HOME() + 'FFC/_GdiPlus.vcx')
m.loGfx.CreateFromImage(m.loBMP)

* Setting the Backcolor
LOCAL loBackColor AS GpColor OF HOME() + "FFC/_GdiPlus.vcx"
IF EMPTY(m.lnBackColor)
  m.loBackColor = 0xFFFFFFFF && White background
ELSE
  m.loBackColor     = NEWOBJECT("gpColor", HOME() + 'FFC/_GdiPlus.vcx')
  m.loBackColor.FoxRGB = m.lnBackColor
ENDIF

* Setting the Forecolor
LOCAL loColor AS GpColor OF HOME() + "FFC/_GdiPlus.vcx"
IF EMPTY(m.lnForeColor)
  m.lnForeColor = 0 && Black
ENDIF
m.loColor     = NEWOBJECT("gpColor", HOME() + 'FFC/_GdiPlus.vcx')
m.loColor.FoxRGB = m.lnForeColor

LOCAL loBrush AS GpSolidBrush OF HOME() + "FFC/_GdiPlus.vcx"
m.loBrush = NEWOBJECT("gpSolidBrush", HOME() + 'FFC/_GdiPlus.vcx', "", m.loColor)

FOR m.n = 0xe001 TO 0xf8b3 && the LAST available FOUND IN charmap
  m.lcHex     = TRANSFORM(m.n, "@0")
  m.lcHex     = STRTRAN(m.lcHex, "0x0000", "")
  m.lcFileName = FORCEEXT(m.lcHex, m.lcImageType)

  * Prepare the Unicode
  m.lcUnicode   = LEFT(BINTOC(EVALUATE("0x" + m.lcHex), "4RS"), 2)

  m.loSizeReal = m.loGfx0.MeasureStringW(m.lcUnicode, m.loFont, m.loRect, m.loStringFormat, @m.lnChars, @m.lnLines)
  IF m.loSizeReal.W == m.pnEmptyW AND m.loSizeReal.H == m.pnEmptyH
    LOOP
  ENDIF

  m.loGfx.CLEAR(m.loBackColor) && Background

  * Draw the string
  m.loGfx.DrawStringW(m.lcUnicode, m.loFont, m.loRect, m.loStringFormat, m.loBrush)

  * Save as image
  m.loBMP.SaveToFile(m.lcFileName, "image/" + m.lcImageType)
ENDFOR

* Clear GDI+ objects
m.loRect         = NULL
m.loStringFormat = NULL
m.loColor        = NULL
m.loBackColor    = NULL
m.loBrush        = NULL
m.loSize         = NULL
m.loSizeEmpty    = NULL
m.loGfx0         = NULL
m.loGfx          = NULL
m.loBMP          = NULL
m.loFont0        = NULL
m.loFont         = NULL

RETURN

IMPORTANTE

No olvide que todas las fuentes tienen licencia. Eso significa que primero debe verificar si está autorizado a distribuir las imágenes generadas. Asegúrese de leer el EULA y ver qué puede o no puede hacer con ellos, ¿de acuerdo?

VEA TAMBIEN

30 de julio de 2021

Capturando pantallas con GdiPlus-X

Artículo original: CAPTURING SCREENS WITH GDIPLUS-X
Autor: Cesar Ch.
Traducido por: Luis María Guayán


Capturar una pantalla con Gdiplus-X es una tarea muy fácil también.

Básicamente todo lo que tenemos que hacer, es llamar al método FromScreen() de la clase de Bitmap. Para facilitar esta tarea, este método brinda diferentes posibilidades.

IMPORTANTE:

Todas los ejemplos siguientes utilizan la nueva librería GDIPlus-X, que está todavía en la versión ALFA, pero es estable y confiable para hacer la mayoría de las tareas de GDI+. Descargue la última versión estable de VFPx:

https://github.com/VFPX/GDIPlusX

1 - Capturar una pantalla de un Formulario enviando el hWnd del formulario o el formulario como un objeto

_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx"))) 

LOCAL loCaptureBmp AS xfcBitmap
WITH _Screen.System.Drawing
   loCaptureBmp = .Bitmap.FromScreen(Thisform.HWnd)
   * Could be also:
   * loCaptureBmp = _screen.system.Drawing.Bitmap.FromScreen(Thisform)
   loCaptureBmp.Save("c:\Captured.png", .Imaging.ImageFormat.Png)
ENDWITH

2 - Capturar la pantalla entera

En este caso no es necesario pasar parámetros

_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx"))) 

LOCAL loCaptureBmp AS xfcBitmap
WITH _Screen.System.Drawing
   loCaptureBmp = .Bitmap.FromScreen()
   loCaptureBmp.Save("c:\CapturedScreen.png", .Imaging.ImageFormat.Png)
ENDWITH

3 - Capturar la pantalla de un formulario recortando sus bordes y título.

Para esta tarea utilizamos la función SYSMETRIC() para obtener la medida de los elementos de la pantalla, tales como la altura del título, borde superior e izquierdo. Entonces utilizamos otra posibilidad, enviando el hWnd, y las coordenadas del formulario que será capturado.

_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx"))) 

LOCAL lnTitleHeight, lnLeftBorder, lnTopBorder
lnTitleHeight = SYSMETRIC(9)
lnLeftBorder = SYSMETRIC(3)
lnTopBorder = SYSMETRIC(4)

LOCAL loCaptureBmp AS xfcBitmap


WITH _Screen.System.Drawing
   loCaptureBmp = .Bitmap.FromScreen(;
      Thisform.HWnd, ;
      lnLeftBorder, ;
      lnTitleHeight + lnTopBorder, ;
      Thisform.Width, ;
      Thisform.Height)

   loCaptureBmp.Save("c:\Captured.png", .Imaging.ImageFormat.Png)
ENDWITH

4 - Capturar todos los formularios de la pantalla

Esto también es muy fácil. Sólo cree un ciclo por todas los formularios de _Screen, y capture cada uno de ellos enviando el Form.hWnd como parámetro.

_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx"))) 

LOCAL loCaptureBmp AS xfcBitmap
LOCAL n
LOCAL loForm AS Form
n = 1

WITH _Screen.System.Drawing

FOR EACH loForm IN _Screen.Forms
   loCaptureBmp = .Bitmap.FromScreen(loForm.HWnd)
   loCaptureBmp.Save("c:\CapturedForm" + TRANSFORM(n) + ".png", .Imaging.ImageFormat.Png)
   n = n + 1
ENDFOR

ENDWITH

16 de julio de 2021

Información de imágenes con GDI+

Artículo original: Image Info with GdiPlus X
http://vfpimaging.blogspot.com/2007/04/image-info-with-gdiplus-x.html
Autor: Cesar Ch
Traducido por: Ana María Bisbé York


Otra sencilla tarea para GDI+

Para obtener alguna información básica de imágenes, tal como, Ancho, Alto, Resolución y Formato de pixeles, todo lo que necesitamos es Iniciar un objeto Image de GDI+ y tomar algunos valores de las propiedades, como se muestra debajo.

IMPORTANTE:

Todos los ejemplos que se muestran a continuación utilizan la nueva biblioteca GDIPlus-X, que está aún en versión ALPHA, pero es realmente estable y fiable para hacer la gran mayoría de las tareas de GDI+. Descargue la versión más estable de VFPx:

https://github.com/VFPX/GDIPlusX

Image Properties

LOCAL lcImage
lcImage = GETPICT()
IF EMPTY(lcImage)
  RETURN
ENDIF
_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx")))
LOCAL loImg AS xfcImage
WITH _SCREEN.System.Drawing
  loImg = .Image.FromFile(lcImage)
    IF ISNULL(loImg)
      MESSAGEBOX("No se pudo cargar el archivo imagen")
      RETURN 
    ENDIF
  * Obtener el nombre de formato de pixeles (PixelFormat )
  LOCAL lnPix, lcPixFormat
  lnPix = loImg.PixelFormat 
  DO CASE 
    CASE lnPix = .Imaging.PixelFormat.Format1bppIndexed 
      lcPixFormat = "1bppIndexed"
    CASE lnPix = .Imaging.PixelFormat.Format4bppIndexed 
      lcPixFormat = "4bppIndexed"
    CASE lnPix = .Imaging.PixelFormat.Format8bppIndexed 
      lcPixFormat = "8bppIndexed"
    CASE lnPix = .Imaging.PixelFormat.Format16bppGrayScale
      lcPixFormat = "16bppGrayScale"
    CASE lnPix = .Imaging.PixelFormat.Format16bppRGB555
      lcPixFormat = "16bppRGB555"
    CASE lnPix = .Imaging.PixelFormat.Format16bppRGB565
      lcPixFormat = "16bppRGB565"
    CASE lnPix = .Imaging.PixelFormat.Format16bppARGB1555
      lcPixFormat = "16bppARGB1555"
    CASE lnPix = .Imaging.PixelFormat.Format24bppRGB
      lcPixFormat = "24bppRGB"
    CASE lnPix = .Imaging.PixelFormat.Format32bppRGB
      lcPixFormat = "32bppRGB"
    CASE lnPix = .Imaging.PixelFormat.Format32bppARGB
      lcPixFormat = "32bppARGB"
    CASE lnPix = .Imaging.PixelFormat.Format32bppPARGB
      lcPixFormat = "32bppPARGB"
    CASE lnPix = .Imaging.PixelFormat.Format48bppRGB
      lcPixFormat = "48bppRGB"
    CASE lnPix = .Imaging.PixelFormat.Format64bppPARGB
      lcPixFormat = "64bppPARGB"
    OTHERWISE 
      lcPixFormat = "No identificado"
  ENDCASE
ENDWITH

LOCAL lcInfo
lcInfo = ;
  "Ancho : " + TRANSFORM(loImg.Width) + SPACE(25) +;
  "Alto : " + TRANSFORM(loImg.Height) + CHR(13) +;
  "Resolución - Vertical : " + TRANSFORM(loImg.VerticalResolution) + SPACE(6) +;
  "Horizontal : " + TRANSFORM(loImg.HorizontalResolution) + CHR(13) +;
  "Formato de pixeles : " + lcPixFormat
MESSAGEBOX(lcInfo, 64, "Propiedades de imagen para " + JUSTFNAME(lcImage))

16 de junio de 2021

Rotar y voltear imágenes con GdiPlusX

Artículo original: Rotate and Flip images with GdiPlusX
https://vfpimaging.blogspot.com/2007/06/vfppaint-flexible-drawing-and-paint.html
Autor: Cesar Ch.
Traducido por: Ana María Bisbé York


Alguna gente ha estado preguntando sobre Rotar/ Voltear imágenes con GdiPlusX.

He aquí un código adaptado de un artículo anterior que utilizaba _GdiPlus.vcx; pero esta vez utilizando GdiPlusX.

Rotar y/o voltear imágenes es una tarea muy sencilla para Gdi+. Para ver los diferentes resultados posibles, cambie el valor de la constante en la variable lnEnumRotateFlip del código que se muestra a continuación.

IMPORTANTE

Requiere VFP9 y GdiPlusX para ejecutarse

Asegúrese por favor de que tiene la última versión

https://github.com/VFPX/GDIPlusX

* Iniciar GdiPlusX
_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx")))
LOCAL loBMP as xfcBitmap
LOCAL lnEnumRotateFlip
WITH _SCREEN.System.Drawing
  loBMP = .Bitmap.FromFile(GETPICT())
  * Pruebe cambiando este valor por los que se muestran debajo 
  lnEnumRotateFlip = .RotateFlipType.Rotate90FlipNone 
  loBmp.RotateFlip(lnEnumRotateFlip)
  * Guardar la imagen como PNG 
  loBMP.Save("C:\RotateFlip.png", .Imaging.ImageFormat.Png)
ENDWITH 
RUN /N explorer.EXE RotateFlip.png 

RotateNoneFlipNone 0

RotateNoneFlipNone 1

RotateNoneFlipNone 2

RotateNoneFlipNone 3

RotateNoneFlipNone 4

RotateNoneFlipNone 5

RotateNoneFlipNone 6

RotateNoneFlipNone 7


19 de noviembre de 2020

Iconos Unicode en botones de Visual FoxPro

Articulo original: Unicode button icons in Visual FoxPro
http://vfpimaging.blogspot.com/2020/11/unicode-button-icons-in-visual-foxpro.html
Autor: Cesar Ch.
Traducido por: Luis María Guayán


Una gran dificultad que tienen los foxeros es actualizar sus interfaces de usuario. Dado que perdimos el soporte de MS, necesitamos hacer casi todos los cambios en la interfaz de usuario (UI) por nuestra cuenta. La interfaz de usuario de Windows 10 actual se basa en iconos monocromáticos, los de la familia SEGOE UI, SEGOE UI SYMBOL y SEGOE MDL2 ASSETS. Estas son fuentes de True Type, que traen toneladas de íconos, los que vemos por todas partes en Windows 10.

Desafortunadamente, no podemos acceder a esos iconos directamente en VFP, porque usan un rango mayor al CHR(255) admitido por VFP. Aún tenemos algunas opciones:

  1. Utilizar un ActiveX que admita Unicodes
  2. Utilizar "Real Window buttons", que admiten Unicodes - https://github.com/VFPX/Win32API/blob/master/samples/sample_274.md
  3. Obtener ayuda de GDI+ - gdiplus.dll y dibujarlos nosotros.

La tercera opción es muy buena, pero exige mucho, mucho trabajo. Afortunadamente, hice casi todo el trabajo pesado anteriormente en 2005, en la clase GradObjects, que originalmente se creó para hacer fondos y botones degradados en nuestros formularios. Recreó cada botón del formulario y lo redibujó en un archivo de imagen, lo que permite efectos de degradado, de paso del mouse y de deshabilitado.

Teniendo esto, solo necesito adaptarlo, dejando atrás los degradados ya casi abandonados, agregando soporte a unicodes y algunas adaptaciones para los efectos del mouse.

Aquí hay una versión actualizada de la muy buena y antigua clase GRADOBJECTS, que estaba destinada a generar botones y fondos degradados para nuestros formularios en 2005, en los tiempos de WinXP. Sigue siendo el mismo GradObjects, pero con algunas propiedades y características nuevas.

El uso es realmente muy simple:

  • Abra el proyecto GradObjects
  • Cree un formulario, agregue algunos botones
  • Lance una instancia de Gradobjects en el formulario
  • Establezca la propiedad FontName del botón en "SEGOE MDL2 ASSETS" o cualquier otra que desee.
  • Establezca la propiedad Caption del botón para aceptar unicodes, introduciendo los unicodes entre las etiquetas <UC> </UC>, por ejemplo:
    • Griego <UC> 03b5 03b9 03c1 03ae 03bd 03b7 </UC> - Esto muestra la palabra "Paz" en caracteres griegos, en cualquier fuente regular, como Arial, Tahoma, Segoe UI, etc.
    • Para obtener el icono de la impresora de SEGOE MDL2 ASSETS, establezca la fuente del botón y agregue lo siguiente a la propiedad Caption: "<UC> E749 </UC> "

Las propiedades predeterminadas de "GradObjects" le brindarán un aspecto degradado, pero todo lo que necesita es cambiar solo 5:

  • BackColor1: Numérico, el RGB del color de fondo
  • BackColor2: Configúrelo en .F. (Falso), porque no necesitamos gradientes aquí, ¿verdad?
  • CaptionForeColor: Numérico, el RGB del color de fondo
  • GradientMode: 0 - ¡No necesitamos degradados!
  • SelBackColor: Numérico, el RGB del color de fondo cuando se enfoca el botón o se pasa el mouse sobre él
  • SelForeColor: Numérico, el RGB del color de primer plano cuando se enfoca el botón o se pasa el mouse sobre él

¡Eso es todo!

Los Unicodes se pueden obtener directamente a través de CharMap.EXE o en toda la web. Aquí hay un excelente punto de partida: https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font

Para obtener información más detallada, consulte la publicación original de GradObjects:

El objeto "GradObjects" transformará todos los CommandButtons, Graphical OptionButtons del mismo objeto principal. Utilice contenedores, si necesita efectos diferentes (o ninguno) para algunos controles individuales en sus formularios.

Empiece a jugar con el formulario de muestra "TESTUNICODEBTNS.SCX"

Descarga Botones Unicode


8 de diciembre de 2019

Redimensionar imagenes

He estado trabajando en un proyecto en el cual debía almacenar una imagen para emitir un certificado, para lo cual utilice un campo blob, pero algunas de las imágenes llegaban a mediar más de 1Mb y con las limitantes de VFP en cuanto al almacenamiento de datos decidí redimensionar las imágenes según el tamaño al que serán impresas.

Y gracias a PortalFox y sus colaboradores he aquí mi variante para este caso.

Caso de Uso
--Metodo Click() del boton cmdExplorar-- 
LOCAL lcPict
lcPict = GETPICT('bmp;gif;jpg;tif;png')
IF !EMPTY(lcPict)
  lcFile = FileToStr(ResizePicture(lcPict,320,240,100))
  REPLACE Certificado.foto WITH lcFile 
ENDIF 
La funcion requiere de 4 parametros:
tcFile   -> Archivo de imagen
tcWitdth -> Nuevo ancho de imagen
tcHeigth -> Nuevo alto de imagen
tcQuality -> Calidad para el redimencionamiento (0 - 100)
FUNCTION ResizePicture(tcFile as String, tcWidth as Integer, tcHeight as Integer, tcQuality as Integer)
  SET CLASSLIB TO HOME() + "/FFC/_GdiPlus.vcx"
  SET COMPATIBLE ON 

  LOCAL loGraphics as gpGraphics OF HOME() + "/FFC/_GdiPlus.vcx"
  LOCAL loImage    as gpImage    OF HOME() + "/FFC/_GdiPlus.vcx"
  LOCAL loBmpOut   as gpBitmap   OF HOME() + "/FFC/_GdiPlus.vcx"
  LOCAL lcNewPic  as String
  LOCAL lcExt     as Character

  IF !DIRECTORY(ADDBS(SYS(5) + SYS(2003)) + "Imagenes\temp\",1)
    MKDIR (ADDBS(SYS(5) + SYS(2003)) + "Imagenes\temp\")
  ENDIF 

  lcExt = LOWER(JUSTEXT(tcFile))
  lcNewPic = ADDBS(SYS(5) + SYS(2003)) + "Imagenes\temp\" + LOWER(JUSTSTEM(tcFile)) + CHR(46) + lcExt

  loGraphics = CREATEOBJECT("gpGraphics")
  loImage    = CREATEOBJECT("gpImage",m.tcFile)
  loBmpOut   = CREATEOBJECT("gpBitmap",m.tcWidth,m.tcHeight)

  loImage.CreateFromFile(m.tcFile)
  loGraphics.CreateFromImage(loBmpOut)
  loGraphics.DrawImageScaled(loImage,0,0,loBmpOut.ImageWidth,loBmpOut.ImageHeight)
  DO CASE 
    CASE lcExt = "bmp"
      loBmpOut.SaveToFile(lcNewPic,"image/bmp","quality=" + ALLT(STR(m.tcQuality)))
    CASE (lcExt = "jpg") OR (lcExt = "jpeg")
      loBmpOut.SaveToFile(lcNewPic,"image/jpeg","quality=" + ALLT(STR(m.tcQuality)))
    CASE lcExt = "gif"
      loBmpOut.SaveToFile(lcNewPic,"image/gif","quality=" + ALLT(STR(m.tcQuality)))
    CASE (lcExt = "tif") OR (lcExt = "tiff")
      loBmpOut.SaveToFile(lcNewPic,"image/tiff","quality=" + ALLT(STR(m.tcQuality)))
    CASE lcExt = "png"
      loBmpOut.SaveToFile(lcNewPic,"image/png","quality=" + ALLT(STR(m.tcQuality)))
  ENDCASE 

  RETURN lcNewPic
ENDFUNC 

Saludos

Hector Urrutia
El Salvador

2 de noviembre de 2019

Cómo poner una imagen sobre otra en un formulario

Cómo poner una imagen sobre otra en un formulario

Artículo original: How to put one image over another in a form
https://vfpimaging.blogspot.com/2007/10/how-to-put-one-image-over-another-in.html
Autor: Cesar Ch.
Traducido por: Luis María Guayán


Esto es una cosa muy básica y fácil.

La imagen que quedará en la parte superior debe tener algún tipo de transparencia establecida. Se recomienda que utilice un BMP de 24bits. VFP convertirá automáticamente la parte blanca de la imagen en transparente, como en la siguiente imagen, si pone la propiedad "BackStyle" del objeto Imagen en "0 - Transparente". No olvide de "Traer al frente" la imagen, desde la barra de herramienta de diseño.

El primer formulario muestra 2 imágenes separadas, pero con fondos opacos. Luego, en el segundo, configuro el fondo transparente, y luego cubro una parte de la imagen para que pueda ver cómo queda. Puede obtener más información en este artículo traducido al español en este Blog en: BMPs con fondos transparentes.

Otra buena manera es usar PNGs, que soportan ALPHA (transparencias), pero lo más recomendado, y tiene mejor soporte en VFP, es el uso de BMPs, tal como aquí se muestra.

Actualización del 30 de Octubre de 2007

Bernard Bout recuerda que "tenga en cuenta que TODAS las áreas blancas de una imagen serán transparente. Por ejemplo, en la segunda imagen puesta por Cesar, el fondo del formulario es visible a través de los ojos del hombre brasileño. Si esto no es lo que quiere, entonces usted debe "enmascarar" las partes blancas del mapa de bits que desea que NO SEA TRANSPARENTE. Simplemente abra la imagen y pinte las partes blancas que se quiere mantener de NEGRO. La imagen debe ser del mismo tamaño. Pinte todas las otras partes de blanco y guardarlo como un BMP pero con una extensión .MSK. VFP automáticamente usará este archivo para "enmascarar" las zonas de blancas que serán transparentes".

Para este caso, yo por lo general edito la imagen, y cambio el RGB "blanco" puro -RGB(255,255,255)- que quiero mantener, cerca de otro blanco, como RGB(254,254,254). Nadie se dará cuenta de la diferencia y funciona muy bien, con la ventaja de no tener que crear un archivo de imagen adicional.

De todos modos, si prefiere crear una máscara, elija para crear un formato de imagen de 1bpp MONOCHROMÁTICO. Por lo general la gente guarda en el formato "24bits". Pero como las imágenes .MSK son unicamente en blanco & negro, monocromático, tienen un tamaño de archivo más pequeño. Comparando una imagen monocromática, la imágen de 24bpp será 24 veces mayor en tamaño.

En MSPaint, por ejemplo, elija, "Guardar como..."

Otras notas importantes sobre archivos MSK

En la Ayuda para PictureVal:

"Ud. no puede especificar un archivo de máscara (MSK) con la propiedad PictureVal. Si Ud. está utilizando imágenes BMP y quiere una máscara, Ud. debe usar la propiedad Picture. Ud. también puede utilizar un formato diferente de imagen, como un GIF, que tiene definida la transparencia dentro de la imagen."

How to: Enhance Control Display: Una buena explicación (en inglés) acerca de los archivos .MSK.


14 de febrero de 2018

Gdiplus.vcx y SavetoFile

Artículo original: Gdiplus.vcx and SavetoFile
http://weblogs.foxite.com/vfpimaging/archive/2006/03/24/1338.aspx
Autor: Cesar Ch.
Traducido por: Ana María Bisbé York


¿Ve algo mal en el siguiente código?

LOCAL lcSource, lcDest
lcSource = GETPICT()
lcDest = JUSTPATH(lcSource) + "\_" + JUSTSTEM(lcSource)
LOCAL loImage AS GpImage OF HOME() + ffc\_gdiplus.vcx
loImage = NEWOBJECT("GpImage",HOME()+"ffc\_gdiplus.vcx")
loImage.CreateFromFile(lcSource)
loImage.SaveToFile(lcDest + ".png","Image/png")

Es un código muy sencillo, que se supone que cargue una imagen y la guarde en un PNG. ¡Pero no ocurre nada ! ¿Puede adivinar por qué?

El método SaveToFile de la clase GpImage de _gdiplus.vcx debe recibir un segundo parámetro, tal y como es requerido, el "tvCLSIDEncoder", que puede ser el CLSID del código a emplear, o un tipo MIME, por ejemplo "image/jpeg" o "image/png". Si se especifica un tipo MIME, entonces, el método se muestra dinámicamente el CLSID del código apropiado.

El 99% de nosotros pasa el tipo MIME, debido a que es fácil de recordar; pero  ¡ TENGA CUIDADO ! Porque el parámetro tipo MIME  distingue entre mayúsculas y minúsculas.

¡ Tiene que pasar este parámetro siempre en letras minúsculas !

El código mostrado antes, no funcionaba porque había escrito "Image/png" en lugar de "image/png"

Vaya, ¡ he perdido tanto tiempo en eso !

*()&)(*)%@&$(@&!)@*(_*(&!_()&*_)(&*+_*()

A propósito, los tipo MIME ( Multipurpose Internet Mail Extensions) de los códigos generados dentro de Microsoft Windows GDI+ son los siguientes:

image/bmp 
image/jpeg 
image/gif 
image/tiff 
image/png 

3 de febrero de 2018

Lograr el aspecto cristalino de Vista con Visual FoxPro

Artículo original: Vista’s Aero Glass for Visual FoxPro
http://www.sweetpotatosoftware.com/blog/index.php/2006/10/14/vista-s-aero-glass-for-visual-foxpro/
Autor: Craig Boyd
Traducido por: Ana María Bisbé York


El efecto cristalino

Hubo una pequeña tertulia sobre los formularios transparentes la primera vez que aparecieron, ahora ha alcanzado mayor relevancia con los efectos que brinda Windows vista. En mi opinión, el efecto de apariencia cristalina es el elemento de presentación que marcará a cualquier aplicación como una aplicación que comience a correr en Windows Vista. Por tanto, no es de sorprenderse que muchos desarrolladores están buscando las vías para reproducir un aspecto similar en sus aplicaciones. Bernard Bout tiene algunos ejemplos realmente muy buenos sobre esto en su blog. Con muy buen ojo para el diseño, una pequeña ayuda de Cesar Ch. y GDI+, y teniendo como guía el efecto cristalino de Windows Vista, Bernard me ha impresionado (como consecuencia, lo he agregado a mi lista del Blog). Por favor, vea las siguientes entrada de su blog para que vea sobre qué es de lo que estoy hablando.

Sobre Visual FoxPro en Vista

Crear una aplicación Visual FoxPro que tenga algún efecto Aero Glass es más que suficiente. Los formularios de nivel superior (ShowWindow = 2) están limitados automáticamente (barra de título, caja de controles y bordes) en Aero Glass. Es siempre algo bueno cuando los desarrolladores Visual FoxPro no tienen que saltar hacia lo más novedoso como las golosinas que ofrece Microsoft en un sistema operativo (¿Recuerdan cuando se agregó que VFP soportara los temas?). Esto es especialmente verdadero en este caso porque la alternativa no es muy bonita (vea la imagen debajo de un formulario VFP con ShowWindows = 0 ó 1).

Ha oído sobre esto antes

He oído que muchos de ustedes leyendo esto están pensando, "Craig, usted y los otros han mostrado los efectos cristalinos para formularios Visual FoxPro que se ejecutan en Windows Vista. No hay nada más aquí!". Sin embargo, lo que probablemente no ha visto (no estoy seguro de que se haya hecho antes) es el efecto Aero Glass en el área del cliente (área de superficie) de un formulario VFP y ESO, es lo que voy a mostrar cómo se hace. Vea la siguiente imagen para que vea un ejemplo de lo que estoy diciendo.

¿Cómo está hecho?

Para lograr esto empleé la función DwmExtendFrameIntoClientArea que viene en Desktop Window Manager API (dwmapi.dll) en Windows Vista.

La firma de esta función está declarada como sigue en MSDN:

HRESULT DwmExtendFrameIntoClientArea(
HWND hWnd,
const MARGINS *pMarInset
);

Y la estructura MARGINS (vea el parámetro pMarInset en el código debajo) está definido como sigue:

typedef struct _MARGINS{
int cxLeftWidth;
int cxRightWidth;
int cyTopHeight;
int cyBottomHeight;
} MARGINS, *PMARGINS

Que yo traduje en la siguiente declaración y código que agregué al Init() de un formulario de nivel superior en Visual FoxPro 9.0 que corre en Windows Vista.

DECLARE LONG DwmExtendFrameIntoClientArea IN dwmapi.dll Long hwnd, string @ pMarInset

LOCAL lnHwnd, lcMargin, lnGlassLeft, lnGlassRight, lnGlassTop, lnGlassBottom

m.lnHwnd = this.HWnd
m.lnGlassLeft = 50
m.lnGlassRight = 50
m.lnGlassTop = 50
m.lnGlassBottom = 50
m.lcMargin = BINTOC(m.lnGlassLeft, '4RS') ;
           + BINTOC(m.lnGlassRight, '4RS') ;
           + BINTOC(m.lnGlassTop, '4RS') ;
           + BINTOC(m.lnGlassBottom, '4RS')

DwmExtendFrameIntoClientArea(m.lnHwnd, @m.lcMargin)

Este código no tuvo ningún efecto en mi formulario de nivel superior, así que supe que aún estaba olvidando una pieza de mi rompecabezas. La respuesta vino de un artículo de Code Project. Se ve para la función DwmExtendFrameIntoClientArea que trabaje su magia en píxeles que son cambiados como parte del frame Aero Glass pueden hacer que el canal Alpha sea 0. Y con este artículo descubrí la vía más sencilla para pintar esos píxeles negros.

Entonces, lo que hice fue, en lugar de hacer un gancho a los mensajes de Windows y utilizar GDI+ para hacer esto (que hubiera podido) utilicé algunas formas VFP con su propiedad BackColor como RGB(0,0,0). Coloqué cuatro de estas formas alrededor de los lados del formulario y les arreglé su altura y ancho (dependiendo de dónde fueran colocadas). Empasté los valores que había asignado a m.lnGlassLeft, m.lnGlassRight, m.lnGlassTop, y m.lnGlassBottom (vea el código debajo donde yo imito una estructura MARGINS (márgenes). Una vez que hice esto, el formulario se vio como se muestra en la imagen en el diseñador de formularios de VFP. Y, cuando lo ejecuté, voila' - ¡ ya tengo el efecto Aero Glass en el área de cliente de un formulario VFP!

¿Y esto para qué es bueno?

Hay muy buenos ejemplos de Interfaz de usuario que han sido diseñadas utilizando esta técnica. Puede ver algunas de ellas si visita el artículo de Code Project que he indicado antes en esta entrada de blog. En pocas palabras, esto es bueno para la creación de magníficas interfaces de usuario para aplicaciones Visual FoxPro.

Un buen uso podría ser crear áreas de barra de estado en formularios de nivel superior que incluyan una barra de progreso y otros controles. La imagen que está debajo es solamente un ejemplo de cómo suena esto en un formulario VFP. Emplea el mismo código que he mostrado arriba excepto que solamente fijo lnGlassBottom en 50 (todo el resto fue 0) y solamente he utilizado la forma con el BackColor en negro.

Más trabajo por hacer

Esta prueba de concepto no completa lo que necesitamos algunas clases / controles VFP ya que tendríamos algunos problemas al colocarlas en el área de cliente que sea generado con un fondo cristalino. Esto se debe, como puede imaginar, que todo lo que sea negro (como ForeColor y BackColor) será generado como traslúcido. Un par de soluciones sobre este tema son emplear controles ActiveX o clases de Windows como hice en el formulario anterior con el control Microsoft ProgressBar Control, versión 5.0 (SP2). La otra vía podría ger generar los elementos gráficos empleando GDI+ y en este respecto _gdiplus.scx que viene con Visual FoxPro 9.0 y el proyecto GDIPlus en VFPX vendrá definitivamente integrado. En cualquier evento, es muy fácil conocer que este tipo de efecto Aero Glass es realmente posible de hacer en Visual FoxPro 9.0. Me ha sorprendido gratamente encontrar que es sencillo de implementar.

Hasta la próxima ... ¡Visual FoxPro es genial!

31 de enero de 2018

Código para crear una imagen al vuelo

"Utilizando el canal alpha en imágenes en Visual FoxPro" para Bernard Bout

Artículo original: Helper code to create image on the fly for "Using the Alpha Channel in Visual Foxpro Images" from Bernard Bout
https://vfpimaging.blogspot.com/2006/09/helper-code-to-create-image-on-fly-for_2447.html
Autor: Cesar Ch.
Traducido por: Ana María Bisbé York


He aquí otro código que asiste a Bernard Bout, al crear una imagen al vuelo, basado en su entrada de blog titulada: "Using the Alpha Channel in Visual Foxpro Images". Nuevamente, en este ejemplo, empleo las nuevas clases GDI+ del proyecto VFP-X que pueden ser descargadas desde: http://www.codeplex.com/Wiki/View.aspx?ProjectName=VFPX&title=GDIPlusX

Espero que esto ayude a Bernard !

Imagen original: Main.form.png

* Código para crear una imagen al vuelo parar
* Bernard Bout "Using the Alpha Channel in Visual Foxpro Images"
* http://weblogs.foxite.com/bernardbout/archive/2006/09/11/2436.aspx
* Dimensiones de la imagen 300 x 270 píxeles
* Hace toda la imagen totalmente transparente
* Dibuja un rectángulo amarillo claro - RGB(254,254,228) 
* centrado en la imagen principal.
* Guarda como PNG, para conservar la transparencia
_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx"))) 
WITH _SCREEN.System.Drawing
  * Crea un bitmap vacío
  LOCAL loBitmap AS xfcBitmap
  loBitmap = .Bitmap.New(300,270)
  * Inicializa el objeto gráfico
  LOCAL loGfx AS xfcGraphics
  loGfx = .Graphics.FromImage(loBitmap)
  * Hace toda la imagen transparente
  loGfx.Clear(.Color.FromARGB(0,0,0,0))
  * Dibuja el rectángulo amarillo
  loGfx.FillRectangle(.SolidBrush.New(.Color.FromRGB(254,254,228)), 10,9,278,249)
  * Guarda como PNG para mantener la transparencia
  loBitmap.Save("c:\BernardBoutAgain.png", .Imaging.ImageFormat.Png)
ENDWITH 
RETURN

26 de enero de 2018

Utilice parámetros codificados para guardar sus imágenes con GDI+

Artículo original: Use encoder parameters to save your images with GDI+
https://vfpimaging.blogspot.com/2006/06/use-encoder-parameters-to-save-your_3564.html
Autor: Cesar Ch.
Traducido por: Ana María Bisbé York


El método SaveToFile de la clase GpImage en _gdiplus.vcx en VFP9 "guarda el objeto imagen en un archivo de disco, utilizando especificados o formato de imagen y parámetros opcionales."

He aquí los más importantes:

QUALITY (soportado por JPEG)

Especifica el nivel de compresión al guardar la imagen JPEG. Un nivel de calidad de 0 se corresponde a la mayor compresión, y el nivel de 100 corresponde a la menor compresión.

Ejemplo 1: Guarda la imagen con 70 % de calidad

LOCAL lcSource
lcSource = GETPICT()
LOCAL loImage AS GpImage OF HOME() + ffc/_gdiplus.vcx
loImage = NEWOBJECT("GpImage", HOME() + "ffc/_gdiplus.vcx")
loImage.CreateFromFile(lcSource)
loImage.SaveToFile("c:\MyImage.jpg","image/jpeg", "quality=70")

TRANSFORMATION (soportado por JPEG)

GDI+ brinda las siguientes transformaciones que se pueden realizar con imágenes JPEG sin perder información:

  • Rotar 90 grados
  • Rotar 180 grados
  • Rotar 270 grados
  • Girar horizontalmente
  • Girar verticalmente

Cada caso de la lista anterior se corresponde a una Constante de Gdiplus, que necesita pasar junto con la "transformación"

Puede proceder a realizar la transformación sin pérdida de información si el archivo utilizado para construir el objeto Image es un archivo JPEG y tanto el ancho como el alto de la imagen son múltiplo de 16. Si el ancho y el alto de la imagen no son múltiplos de 16, GDI+ no hará su mejor esfuerzo para preservar la calidad de la imagen cuando aplique una transformación de rotación o giro.

Ejemplo 2: Guarda la imagen con calidad del 50% Y Rota 180 grados

#DEFINE EncoderValueTransformRotate90 13
#DEFINE EncoderValueTransformRotate180 14 
#DEFINE EncoderValueTransformRotate270 15 
#DEFINE EncoderValueTransformFlipHorizontal 16 
#DEFINE EncoderValueTransformFlipVertical 17
LOCAL lcSource
lcSource = GETPICT()
LOCAL loImage AS GpImage OF HOME() + ffc/_gdiplus.vcx
loImage = NEWOBJECT("GpImage", HOME() + "ffc/_gdiplus.vcx")
loImage.CreateFromFile(lcSource)
loImage.SaveToFile("c:\MyImage.jpg","image/jpeg", ;
  "quality=50, transformation=14")

¡ Esto realiza la rotación y el giro con una operación sencilla y rápida !

Para imágenes TIFF, he aquí los parámetros más comunes:

SAVEFLAG (soportado por TIFF)

Utilizado para la creación de imágenes con múltiples marcos

Vea mi artículo en UTMAG de Mayo 2006: Multiframe Images with GDI+

COMPRESSION (soportado por TIFF)

#DEFINE EncoderValueCompressionLZW 2 
#DEFINE EncoderValueCompressionCCITT3 3 
#DEFINE EncoderValueCompressionCCITT4 4 
#DEFINE EncoderValueCompressionRle 5 
#DEFINE EncoderValueCompressionNone 6

COLORDEPTH (soportado por TIFF)

Profundidad de color en Bytes por píxeles

Estos están definidos para GDI+; pero no están soportados por los codificadores estándar: 'COMPRESSION', 'SCANMETHOD', 'VERSION' y 'RENDERMETHOD'.

Fuente: MSDN

Vea también este post de mi blog: ROTATE / FLIP IMAGES WITH VFP9 AND GDI+

Nota de la traductora: Ese post está traducido bajo el título: "Rotar / invertir imágenes con VFP 9 y GDI+" y publicado en https://comunidadvfp.blogspot.com/2006/07/rotar-invertir-imagenes-con-vfp-9-y-gdi.html

12 de octubre de 2017

Como crear texto como un archivo de imagen con GdiPlusX

Artículo original: How to create text as image file with GdiPlusX
http://vfpimaging.blogspot.com/2007/11/how-to-create-text-as-image-file-with.html
Autor: Cesar Ch.
Traducido por: Luis María Guayán


Más que una vez que he visto a la gente pedir como crear imágenes que contengan algún texto. El ejemplo de abajo es realmente muy simple.
  • Crear una fuente
  • Medir el espacio que el texto necesitará
  • Crear una imagen con el tamaño necesario
  • Dibujar el texto
  • Guardar en el disco
IMPORTANTANTE
Para ejecutar se requiere VFP9 y GdiPlusX.
Por favor, asegúrese de que tiene la última versión, porque VFPPaint utiliza algunas de las funciones que se han añadido recientemente.
https://github.com/VFPX/GDIPlusX
DO LOCFILE("System.app")

WITH _SCREEN.SYSTEM.Drawing
  LOCAL lcText
  lcText = "GdiPlusX is Cool !!!"

  * Crear una fuante
  LOCAL loFont AS xfcFont
  loFont = _SCREEN.SYSTEM.Drawing.FONT.New("Verdana", 32, .FontStyle.BoldItalic)

  LOCAL loTmpBmp AS xfcBitmap
  loTmpBmp = .BITMAP.New(1,1)

  * Recuperar el objeto gráfico
  LOCAL loTmpGfx AS xfcGraphics
  loTmpGfx = .Graphics.FromImage(loTmpBmp)

  * Medir la cadena
  * tomar el tamaño necesario para el texto
  LOCAL loSize AS xfcSize
  loSize = loTmpGfx.MeasureString(lcText, loFont)

  LOCAL loNewBmp AS xfcBitmap
  loNewBmp = .BITMAP.New(CEILING(loSize.WIDTH),CEILING(loSize.HEIGHT))

  LOCAL loNewGfx AS xfcGraphics
  loNewGfx = .Graphics.FromImage(loNewBmp)

  * Hacer el fondo amarillo
  loNewGfx.CLEAR(.COLOR.Yellow)

  * Crear un pincel sólido
  LOCAL loBrush AS xfcSolidBrush
  loBrush = .SolidBrush.New(.COLOR.FromRGB(255,0,0)) && Rojo

  * Crear un objeto StringFormat para dibujar el texto centrado en la imagen
  LOCAL loStringFmt AS xfcStringFormat
  loStringFmt = .StringFormat.New()
  loStringFmt.ALIGNMENT = .StringAlignment.CENTER

  * Crear un ractángulo con las medidas del Bitmap
  LOCAL loRect AS xfcRectangleF
  loRect = loNewBmp.GetBounds()

  * Dibujar la cadena
  loNewGfx.DrawString(lcText, loFont, loBrush, loRect, loStringFmt)

  * Finalmente guardar la imagen
  lcImage = ADDBS(GETENV("TEMP"))+"MyText.Png"
  loNewBmp.SAVE(lcImage, .Imaging.ImageFormat.Png)

  * Mostrar la imagen
  DECLARE INTEGER ShellExecute IN shell32.DLL ;
    INTEGER hndWin, STRING cAction, STRING cFileName, ;
    STRING cParams, STRING cDir, INTEGER nShowWin
    
  ? ShellExecute(0, "Open", lcImage , "", "", 1)

ENDWITH


25 de febrero de 2016

Generador de imágenes Captcha con GDI+

Artículo original: Captcha Image Generator with GdiPlus-X
https://vfpimaging.blogspot.com/2012/03/captcha-image-generator-with-gdiplusx.html
Autor: Cesar Ch.
Traducido por: Ana María Bisbé York


Recientemente, estaba navegando por el Blog de Rick Stralh cuando encontré un escrito muy interesante, "A Captcha Image generator for FoxPro", en el cual el muestra cómo crear algunas imágenes "captcha".

Nota de la Traductora: CAPTCHA es el acrónimo de "Completely Automated Public Turing test to tell Computers and Humans Apart", y consiste en una verificación automatizada para determinar si el usuario que realiza la petición es humano o no.

Como ha dicho Rick Stralh en su escrito, "CAPTCHA muestra básicamente una imagen de verificación junto a un cuadro de texto que tiene que ser llenado para verificar la petición actual. No es un procedimiento infalible para la validación y tiene algunos problemas de accesibilidad; pero parece que es la solución más frecuente a este problema." La imagen generada contiene un texto aleatorio.

En este caso, Rick Stralh escogió crear una DLL en C++, y la llama desde VFP. Aquí mostraré cómo obtener los mismos resultados utilizando la nueva biblioteca GdiPlus-X.

El código que se muestra a continuación pudiera ser útil para otros propósitos también, porque se utilizaron algunas técnicas de dibujo muy interesantes.

Para obtener la cadena aleatoria, creé una función muy simple, que recibe como un parámetro de cantidad de caracteres que se crearán.

PROCEDURE CreateString(tnLength
  LOCAL lcText, lcChar, x, n
  lcText = ""
  FOR n = 1 TO tnLength
    x = INT(RAND() * 36)
    lcChar = IIF(x < 10, TRANSFORM(x), CHR(x + 55))
    lcText = lcText + lcChar
  ENDFOR
  RETURN lcText
ENDPROC

¡ A JUGAR !

IMPORTANTE:

Todos los ejemplos que se muestran emplean la nueva biblioteca GDIPlus-X, que está aun en versión ALPHA; pero es estable y fiable para hacer la mayoría de las tareas de GDI+. Descargue la última versión estable de Codeplex:

http://www.codeplex.com/Wiki/View.aspx?ProjectName=VFPX&title=GDIPlusX

EJEMPLO 1 - CREAR UN IMAGEN SENCILLA Y DIBUJE ALGÚN TEXTO

** Generador de imágenes Captcha 
** Crea una imagen muy sencilla que contiene una cadena
** Se basa en el código de Rick Strahl expuesto en su weblog
** http://www.west-wind.com/wconnect/weblog/ShowEntry.blog?id=556
** El siguiente ejemplo muestra como crear un sencillo Generador de imágenes Captcha 
LOCAL lcText AS Character 
lcText = CreateString(8) 
_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", ;
  LOCFILE("system.vcx","vcx")))

LOCAL loBmp AS xfcBitmap
LOCAL loFont AS xfcFont
LOCAL loSizeF AS xfcSizeF
LOCAL loScreenGfx AS xfcGraphics
LOCAL loGfx AS xfcGraphics 
WITH _SCREEN.System.Drawing
  loScreenGfx = .Graphics.FromHwnd(_Screen.HWnd)
  loFont = .Font.New("Tahoma",20) && Font Name, Size
  * Obtiene la altura y el ancho necesarios para la cadena
  loSizeF = loScreenGfx.MeasureString(lcText, loFont)
  * Crea un bitmap nuevo
  loBmp = .Bitmap.New(loSizeF.Width, loSizeF.Height)
  * Obtiene un objeto Graphics para dibujar
  loGfx = .Graphics.FromImage(loBmp)
  loGfx.Clear(.Color.White) 
  * Dibuja toda la cadena
  loGfx.DrawString(lcText, loFont, .Brushes.Black, 0, 0)
  * Guarda la imagen en disco
  loBmp.Save("c:\captcha1.png", .Imaging.ImageFormat.Png)
ENDWITH 
* Muestra la imagen en el explorador
RUN /n explorer.exe c:\captcha1.png
RETURN 

PROCEDURE CreateString(tnLength)
  LOCAL lcText, lcChar, x, n
  lcText = ""
  FOR n = 1 TO tnLength
    x = INT(RAND() * 36)
    lcChar = IIF(x < 10, TRANSFORM(x), CHR(x + 55))
    lcText = lcText + lcChar
  ENDFOR
  RETURN lcText
ENDPROC 

El ejemplo de arriba crea la imagen más sencilla, que contiene una cadena aleatoria dentro. para obtener el tamaño necesario para crear el Bitmap, en el primer momento, he creado un objeto Graphics desde _Screen. Como se puede ver a continuación, este objeto tiene solamente una tarea: permitirnos llamar a la función MeasureString para obtener el tamaño necesario para nuestro bitmap.

loScreenGfx = .Graphics.FromHwnd(_Screen.HWnd)
loFont = .Font.New("Tahoma",20) && FontName, FontSize
* Obtiene la altura y el ancho necesarios para la cadena
loSizeF = loScreenGfx.MeasureString(lcText, loFont)
* Crea un bitmap nuevo
loBmp = .Bitmap.New(loSizeF.Width, loSizeF.Height) 

EJEMPLO 2 - CREA UNA IMAGEN SENCILLA Y DIBUJA UN TEXTO SOBRE UN FONDO TRAMADO.

** Generador de imágenes Captcha 
** Crea una imagen muy sencilla que contiene una cadena con un fondo tramado
** Se basa en el código de Rick Strahl expuesto en su weblog
** http://www.west-wind.com/wconnect/weblog/ShowEntry.blog?id=556
** El siguiente ejemplo muestra como crear un sencillo Generador de imágenes Captcha 
LOCAL lcText AS Character 
lcText = CreateString(8) 
_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", ;
  LOCFILE("system.vcx","vcx")))

LOCAL loBmp AS xfcBitmap
LOCAL loFont AS xfcFont
LOCAL loSizeF AS xfcSizeF
LOCAL loScreenGfx AS xfcGraphics
LOCAL loGfx AS xfcGraphics 
WITH _SCREEN.System.Drawing
  loScreenGfx = .Graphics.FromHwnd(_Screen.HWnd)
  loFont = .Font.New("Tahoma",20) && Font Name, Size
  * Obtiene la altura y el ancho necesarios para la cadena
  loSizeF = loScreenGfx.MeasureString(lcText, loFont)
  * Crea un bitmap nuevo
  loBmp = .Bitmap.New(loSizeF.Width, loSizeF.Height)
  * Obtiene un objeto Graphics para dibujar
  loGfx = .Graphics.FromImage(loBmp)
  loGfx.Clear(.Color.White) 
  liRand = INT(RAND()*52) + 1 
  * Rellena el fondo del rectángulo con una brocha tramada de forma aleatoria
  loGfx.FillRectangle( ;
  .Drawing2D.HatchBrush.New(liRand, .Color.Black, .Color.White),;
  0,0,loBmp.Width,loBmp.Height)
  * Dibuja toda la cadena
  loGfx.DrawString(lcText, loFont, .Brushes.Black, 0, 0)
  * Guarda la imagen en disco
  loBmp.Save("c:\captcha2.png", .Imaging.ImageFormat.Png)
ENDWITH 
* Muestra la imagen en el explorador
RUN /n explorer.exe c:\captcha2.png
RETURN 

PROCEDURE CreateString(tnLength)
  LOCAL lcText, lcChar, x, n
  lcText = ""
  FOR n = 1 TO tnLength
    x = INT(RAND() * 36)
    lcChar = IIF(x < 10, TRANSFORM(x), CHR(x + 55))
    lcText = lcText + lcChar
  ENDFOR
  RETURN lcText
ENDPROC 

Este es exactamente el mismo ejemplo que utilizamos antes; pero agregando un fondo tramado. GDI+ ofrece 52 plantillas diferentes para brochas con tramas. En este ejemplo, preferí utilizar uno aleatorio. Verifíquelo y seleccione el que le guste más:

* Rellena el fondo del rectángulo con una brocha tramada de forma aleatoria
loGfx.FillRectangle( ;
.Drawing2D.HatchBrush.New(liRand, .Color.Black, .Color.White),;
0,0,loBmp.Width,loBmp.Height)

EJEMPLO 3 - DIBUJAR ALGÚN TEXTO CON UN FONDO COLOREADO UTILIZANDO COLORES ALEATORIOS

** Generador de imágenes Captcha 
** Crea una imagen muy sencilla que contiene una cadena 
** Cada carácter en color aleatorio diferente con un color de fondo aleatorio
** Se basa en el código de Rick Strahl expuesto en su weblog
** http://www.west-wind.com/wconnect/weblog/ShowEntry.blog?id=556
** El siguiente ejemplo muestra como crear un sencillo Generador de imágenes Captcha 
LOCAL lcText AS Character 
lcText = CreateString(8) 
_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", ;
  LOCFILE("system.vcx","vcx")))

LOCAL loBmp AS xfcBitmap
LOCAL loFont AS xfcFont
LOCAL loSizeF AS xfcSizeF
LOCAL loScreenGfx AS xfcGraphics
LOCAL loGfx AS xfcGraphics 
WITH _SCREEN.System.Drawing
  loScreenGfx = .Graphics.FromHwnd(_Screen.HWnd)
  loFont = .Font.New("Tahoma",20) && Font Name, Size
  * Obtiene la altura y el ancho necesarios para la cadena
  loSizeF = loScreenGfx.MeasureString(lcText, loFont)
  LOCAL lnWidth, lnHeight, lnCharWidth
  lnWidth = loSizeF.Width * 1.5 && Se estrecha al 50% para garantizar que quepan los caracteres rotados
  lnHeight = loSizeF.Height
  lnCharWidth = lnWidth / LEN(lcText) 
  * Crea un bitmap nuevo
  loBmp = .Bitmap.New(lnWidth, lnHeight)
  * Obtiene un objeto Graphics para dibujar
  loGfx = .Graphics.FromImage(loBmp)
  loGfx.Clear(.Color.White) 
  * Llena el fondo del rectángulo con una brocha sólida de color aleatorio
  * Necesitamos aquí un color pastel color, por lo que haremos que cada canal sea al menos 180 
  * Dibuja el rectángulo de fondo
  loGfx.FillRectangle( ;
  .SolidBrush.New(.Color.FromARGB(255, INT(RAND() * 76)+180, ;
    INT(RAND() * 76)+180, INT(RAND() * 76)+180)), ;
    0,0,loBmp.Width,loBmp.Height)
  * Dibuja cada carácter en un color aleatorio diferente
  LOCAL n, lcChar
  LOCAL loBrush AS xfcSolidBrush
  FOR n = 0 TO LEN(lcText)
    lcChar = SUBSTR(lcText, n, 1) 
    * Crea una brocha con un color aleatorio
    loBrush = .SolidBrush.New(.Color.FromRGB(INT(RAND() * 0xFFFFFF))) 
    * Dibuja el carácter
    loGfx.DrawString(lcChar, loFont, loBrush, (n-1) * lnCharWidth, 0)
  ENDFOR 
  * Guarda la imagen en disco
  loBmp.Save("c:\captcha3.png", .Imaging.ImageFormat.Png)
ENDWITH 
* Muestra la imagen en el explorador
RUN /n explorer.exe c:\captcha3.png
RETURN 

PROCEDURE CreateString(tnLength)
  LOCAL lcText, lcChar, x, n
  lcText = ""
  FOR n = 1 TO tnLength
    x = INT(RAND() * 36)
    lcChar = IIF(x < 10, TRANSFORM(x), CHR(x + 55))
    lcText = lcText + lcChar
  ENDFOR
 RETURN lcText
ENDPROC 

Esta vez decidí crear un fondo utilizando una brocha sólida con un valor aleatorio en BackColor. Quise asegurarme de obtener un color pastel, por que utilicé:

INT(RAND() * 76) + 180 para cada canal de color RGB. Esta forma, cada canal tendrá al menos el valor 180, asegurando que obtenga un color brillante.

También muy sencillo dibujar cada color en un color aleatorio. Solamente hice un lazo de la cadena y dibujé cada caracter en su posición proporcional. Este momento, deseo tener cualquier color aleatorio, por tanto podría ser cualquier valor entre 0 (RGB[0,0,0] - negro) y 0xFFFFFF (RGB[255,255,255] - blanco).

* Crea una brocha con un color aleatorio 
loBrush = .SolidBrush.New(.Color.FromRGB(INT(RAND() * 0xFFFFFF))) 

EJEMPLO 4 - DIBUJAR ALGÚN TEXTO EN UN FONDO CON TRAMA COLOREADA UTILIZANDO COLORES ALEATORIOS CON ROTACIÓN DE CARACTERES.

** Generador de imágenes Captcha 
** Crea una imagen muy sencilla que contiene una cadena 
** Cada carácter en color aleatorio diferente y una ligera rotación
** Se basa en el código de Rick Strahl expuesto en su weblog
** http://www.west-wind.com/wconnect/weblog/ShowEntry.blog?id=556
** El siguiente ejemplo muestra como crear un sencillo Generador de imágenes Captcha 
LOCAL lcText AS Character 
lcText = CreateString(8) 
_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", ;
  LOCFILE("system.vcx","vcx")))

LOCAL loBmp AS xfcBitmap
LOCAL loFont AS xfcFont
LOCAL loSizeF AS xfcSizeF
LOCAL loScreenGfx AS xfcGraphics
LOCAL loGfx AS xfcGraphics 
WITH _SCREEN.System.Drawing
  loScreenGfx = .Graphics.FromHwnd(_Screen.HWnd)
  loFont = .Font.New("Tahoma",20) && Font Name, Size
  * Obtiene la altura y el ancho necesarios para la cadena
  loSizeF = loScreenGfx.MeasureString(lcText, loFont)
  LOCAL lnWidth, lnHeight, lnCharWidth
  lnWidth = loSizeF.Width * 1.5 && Se estrecha al 50% para garantizar que quepan los caracteres rotados
  lnHeight = loSizeF.Height
  lnCharWidth = lnWidth / LEN(lcText) 
  * Crea un bitmap nuevo
  loBmp = .Bitmap.New(lnWidth, lnHeight)
  * Obtiene un objeto Graphics para dibujar
  loGfx = .Graphics.FromImage(loBmp)
  loGfx.Clear(.Color.White) 
  * Llena el fondo del rectángulo con una brocha sólida de colores pastel aleatorios
  loGfx.FillRectangle( ;
  .Drawing2D.HatchBrush.New(INT(RAND()*52), ;
    .Color.FromARGB(255, INT(RAND() * 76)+180, INT(RAND() * 76)+180, INT(RAND() * 76)+180), ;
    .Color.FromARGB(255, INT(RAND() * 76)+180, INT(RAND() * 76)+180, INT(RAND() * 76)+180)), ;
    0,0,loBmp.Width,loBmp.Height)
  * Dibuja cada carácter en un color aleatorio diferente 
  * y un ángulo aleatorio de rotación desde -40 a +40 grados
  LOCAL n, lcChar
  LOCAL loBrush AS xfcSolidBrush
  LOCAL loMatrix AS xfcMatrix 
  FOR n = 0 TO LEN(lcText)
    lcChar = SUBSTR(lcText, n, 1) 
    * Crea una brocha con un color aleatorio
    loBrush = .SolidBrush.New(.Color.FromRGB(INT(RAND() * 0xFFFFFF))) 
    * Crea una matriz para aplicar la rotación de los caracteres
    loMatrix = _Screen.System.Drawing.Drawing2D.Matrix.New() 
    * Calcula el ángulo aleatorio
    lnAngle = INT(RAND() * 80) - 40
    * Rota al centro de cada posición de carácter
    loMatrix.RotateAt(lnAngle, ;
      .Point.New((n-1) * lnCharWidth + (lnCharWidth / 2), lnHeight / 2)) 
    * Asocia la matriz al objeto Graphics 
    loGfx.Transform = loMatrix 
    * Dibuja el carácter
    loGfx.DrawString(lcChar, loFont, loBrush, (n-1) * lnCharWidth, 0)
  ENDFOR 
  * Guarda la imagen en disco
  loBmp.Save("c:\captcha4.png", .Imaging.ImageFormat.Png)
ENDWITH 
* Muestra la imagen en el explorador
RUN /n explorer.exe c:\captcha4.png
RETURN 

PROCEDURE CreateString(tnLength)
  LOCAL lcText, lcChar, x, n
  lcText = ""
  FOR n = 1 TO tnLength
    x = INT(RAND() * 36)
    lcChar = IIF(x < 10, TRANSFORM(x), CHR(x + 55))
    lcText = lcText + lcChar
  ENDFOR
  RETURN lcText
ENDPROC

Aquí hicimos lo mismo; pero utilizando una brocha con trama para el fondo, y aplicando una ligera rotación de cada carácter. La rotación de caracteres ya se ha discutido en un escrito anterior http://weblogs.foxite.com/vfpimaging/2012/03/16/draw-rotated-strings-with-gdiplusx en cualquier caso, he aquí una corta explicación:

"System.Drawing" brinda un método que permite dibujar cualquier objeto (forma, texto y otra imagen) utilizando rotación, de cualquier ángulo. Para esto necesitamos crear un objeto Matriz, y utilizamos el método "RotateAt", pasando el punto central del objeto que se rotará. En este caso, necesito pasar el centro de la cadena.

*  Crea una matriz para aplicar la rotación de los caracteres 
loMatrix = .Drawing2D.Matrix.New() 
*  Calcula el ángulo aleatorio
lnAngle = INT(RAND() * 80) - 40
*  Rota al centro de cada posición de carácter
loMatrix.RotateAt(lnAngle, ;
  .Point.New((n-1) * lnCharWidth + (lnCharWidth / 2), lnHeight / 2)) 
*  Asocia la matriz al objeto Graphics 
loGfx.Transform = loMatrix 
* Dibuja el carácter
loGfx.DrawString(lcChar, loFont, loBrush, (n-1) * lnCharWidth, 0)

¿QUÉ MÁS?

Podemos hacer algunas otras cosas, tales como dibujar algunas líneas aleatorias en la imagen. Una forma podría ser simplemente agregando este sencillo código inmediatamente antes de guardar la imagen:

LOCAL x1, y1, x2, y2, lnColor
FOR n = 1 TO 10
  x1 = RAND() * lnWidth
  x2 = RAND() * lnWidth
  y1 = RAND() * lnHeight
  y2 = RAND() * lnHeight
  lnColor = RAND() * 0xFFFFFF
  loGfx.DrawLine(.Pen.New(.Color.FromRGB(lnColor),1),x1, y1, x2, y2)
ENDFOR

A pesar de que se conoce que estos son únicamente algunos CAPTCHA decoradores, puede ser muy útil en determinadas situaciones. Mi objetivo en este escrito es simplemente mostrar algunas técnicas para crear buenos captchas. Hay mucho que mejorar. Usted puede utilizar fuentes irregulares aleatorias, otros fondos, aplicar algunas transformaciones a caracteres, como escalar, cortes, nublados, efectos halo. ¡ Todo esto con GDI+ !

En la próxima liberación de la biblioteca, espero enviar algunos ejemplos que creen efectos interesantes en textos, tales como, algunos que se muestran debajo pudieran también ser útiles.

Manténgase en contacto.


11 de agosto de 2015

Marcas de agua en tus informes con GdiPlusX

Artículo original: Watermark your reports with GdiPlusX
http://vfpimaging.blogspot.com/2007/07/watermark-your-reports-with-gdiplusx.html
Autor: Cesar Ch.
Traducido por: Luis María Guayán


Aquí está un ReportListener que genera imágenes como marcas de agua en informes.

Muy simple para configurar, solo establezca algunas propiedades, como la imagen del logo, el ancho, la altura y el porcentaje de transparencia.

_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx")))

LOCAL loListener as ReportListener 
loListener = CREATEOBJECT("WatermarkListener")
loListener.LISTENERTYPE = 1
loListener.WaterMarkImage = ADDBS(HOME()) + "Graphics\Gifs\morphfox.gif"
loListener.WaterMarkType = 2 && 1 = Color ; 2 = Escala de grises
loListener.WaterMarkTransparency = 0.25 && 0 = Transparente ; 1 = Opaco
loListener.WaterMarkWidthRatio = 0.75 && 0 - 1
loListener.WaterMarkHeightRatio = 0.75 && 0 - 1

* Ejecute el informe usando el nuevo motor de informes (la salida objeto-asistida)
REPORT FORM (ADDBS(HOME()) + "samples/solution/europa/employeesmd.frx") OBJECT loListener
RETURN

Abajo están algunos ejemplos generados con la clase WaterMarkListener. (El código fuente está al final de este artículo)

IMPORTANTE

Requiere VFP9 y GdiPlusX para funcionar.

¡Por favor asegúrese que tiene la última versión!

http://www.codeplex.com/VFPX/Wiki/View.aspx?title=GDIPlusX&referringTitle=Home

1.

WaterMarkType = 1 && 1 = Color ; 2 = Escala de grises
WaterMarkTransparency = 1 && 0 = Transparent ; 1 = Opaco
WaterMarkWidthRatio = 0.90 && 0 - 1
WaterMarkHeightRatio = 0.90 && 0 - 1

2.

WaterMarkType = 1 && 1 = Color ; 2 = Escala de grises
WaterMarkTransparency = 0.25 && 0 = Transparent ; 1 = Opaco
WaterMarkWidthRatio = 0.50 && 0 - 1
WaterMarkHeightRatio = 0.50 && 0 - 1

3.

WaterMarkType = 2 && 1 = Color ; 2 = Escala de grises
WaterMarkTransparency = 0.10 && 0 = Transparent ; 1 = Opaco
WaterMarkWidthRatio = 1 && 0 - 1
WaterMarkHeightRatio = 1 && 0 - 1

4.

WaterMarkType = 2 && 1 = Color ; 2 = Escala de grises
WaterMarkTransparency = 0.25 && 0 = Transparent ; 1 = Opaco
WaterMarkWidthRatio = 0.75 && 0 - 1
WaterMarkHeightRatio = 0.75 && 0 - 1

_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx"))) 
LOCAL loListener as ReportListener 
loListener = CREATEOBJECT("WatermarkListener")
loListener.LISTENERTYPE = 1
loListener.WaterMarkImage = ADDBS(HOME()) + "Graphics\Gifs\morphfox.gif"
loListener.WaterMarkType = 2 && 1 = Color ; 2 = Escala de grises
loListener.WaterMarkTransparency = 0.25 && 0 = Transparent ; 1 = Opaco
loListener.WaterMarkWidthRatio = 0.75 && 0 - 1
loListener.WaterMarkHeightRatio = 0.75 && 0 - 1
* Ejecute el informe usando el nuevo motor de informes (la salida objeto-asistida)
REPORT FORM (ADDBS(HOME()) + "samples/solution/europa/employeesmd.frx") OBJECT loListener
RETURN
 
 
DEFINE CLASS WatermarkListener AS _ReportListener OF ADDBS(HOME()) + "FFC\" + "_ReportListener.VCX"
  NewPage = .T.
  oGDIGraphics = NULL
  WaterMarkImage = ""
  WaterMarkType = 1 && 1 = Color ; 2 = Escala de grises
  WaterMarkTransparency = 0.50 && 0 = Transparente ; 1 = Opaco
  WaterMarkWidthRatio = 0.50
  WaterMarkHeightRatio = 0.50

  FUNCTION BEFOREREPORT
    DODEFAULT()
    This.oGDIGraphics = _SCREEN.SYSTEM.Drawing.Graphics.New() && CREATEOBJECT('GPGraphics')
  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
        LOCAL lnX, lnY, lnWidth, lnHeight
        lnX = (1 - This.WaterMarkWidthRatio) / 2
        lnY = (1 - This.WaterMarkHeightRatio) / 2
        lnWidth = This.WaterMarkWidthRatio
        lnHeight = This.WaterMarkHeightRatio
        * Creo un Rectangulo de tamaño 60% de la página del informe
        LOCAL loRect AS xfcRectangle
        loRect = .Rectangle.New(lnX * This.sharedPageWidth, ;
          lnY * This.sharedPageHeight, ;
          This.sharedPageWidth * lnWidth, ;
          This.sharedPageHeight * lnHeight)
        * Cargo el archivo de imagen a GDI+
        LOCAL loBmp as xfcBitmap
        loBmp = .Bitmap.New(This.WaterMarkImage)
  
        LOCAL loClrMatrix AS xfcColorMatrix

        IF This.WaterMarkType = 2 && 1 = Color ; 2 = Escala de grises
          loClrMatrix = .Imaging.ColorMatrix.New( ; 
            .33, .33, .33, 0 , 0, ; 
            .33, .33, .33, 0 , 0, ; 
            .33, .33, .33, 0 , 0, ;
            0, 0, 0, This.WaterMarkTransparency, 0, ; 
            0, 0, 0, 0, 0)
        ELSE
          loClrMatrix = .Imaging.ColorMatrix.New()
          loClrMatrix.Matrix33 = This.WaterMarkTransparency
        ENDIF 
        LOCAL loAttr AS xfcImageAttributes
        loAttr = .Imaging.ImageAttributes.New() 
        loAttr.SetColorMatrix(loClrMatrix)
        This.oGdiGraphics.DrawImage(loBmp, loRect, loBmp.GetBounds(), 2, loAttr)
        This.NewPage = .F.
      ENDIF
    ENDWITH
    DODEFAULT(nFRXRecNo,;
      nLeft,nTop,nWidth,nHeight,;
      nObjectContinuationType, ;
      cContentsToBeRendered, GDIPlusImage)
  ENDPROC
ENDDEFINE