Wednesday, 30 September 2009

Primera aplicación con Thundax Box Manager (Parte III)

Ya está disponible la última versión estable de la aplicación Thundax Box Manager que incorpora el framework para el diseño de grafos. En ésta última versión he corregido los siguientes temas:
  • detección de la posición del mouse para evitar colisiones
  • selección múltiple con el SHIFT.
  • Alineación (Top, Bottom, Left and Right) de las cajas seleccionadas.
  • Copiado y pegado de las propiedades
Ahora funciona correctamente todo el tema del dibujado y repintado sin flickering y la verdad que la navegación es bastante buena. Podemos crear diagramas dinámicos como los que siguen a continuación:

En menos de 5 minutos podemos tener grafos totalmente personalizados y lo mejor de todo es que podemos aplicar el algoritmo Dijkstra y obtener el camino mínimo:

Además las pantallas de propiedades han cambiado ligeramente para mostrar más propiedades:

Aquí podréis obtener la última versión de la aplicación y el diagrama:

NOTA: Lo he comprimido con Winrar.

Implementación de un patrón Composite

El patrón composite permite construir objetos complejos a partir de unos más simples. Éste lo he implementado en mi unnamed framework para la copia de propiedades entre objetos. Si os fijáis en la estructura de la plataforma, tanto la Tbox como TLine tienen propiedades muy similares con muy pocas diferencias. Dada la similitud entre éstas he decidido aplicar éste patrón para obtener unas propiedades abstractas las cuáles generan diferentes propiedades para TLine y TBox. De ésta manera puedo construir propiedades más complejas a través de unas más simples. Ésto me permite también separar totalmente de la lógica de control el concepto de propiedad dentro de los objetos. Antes cuando modificaba un objeto lo hacia todo en sí, ahora solo tengo en cuenta las propiedades (y éstas son las importantes). Al objeto caja le tiene que dar igual si le modifican alguna de sus propiedades básicas de dibujo, es el propio objeto propiedad quien se tiene que enterar de éstos cambios y negociarlos con los diferentes objetos.

La nueva estructura de las propiedades es la siguiente:

Ahora tanto la estructura de Tbox como TLine ha quedado reducida a:

Como podéis ver la gestión de las conexiones la he migrado a una unidad nueva ya que es mejor tenerlo todo separado.

Con la solución propuesta anteriormente, ahora podemos hacer ésto desde el lienzo personalizado:

Ésto nos permite copiar las propiedades de nuestros objetos utilizando la TAbstractProperty, de ésta manera la podemos pasar a cualquier objeto ya que todas las propiedades heredan de ésta clase.

Si nos fijamos en la siguiente imagen veréis que tanto la primera caja como la línea tiene asignada la misma propiedad:

En enlace de la aplicación es el siguiente:
NOTA: La aplicación ha cambiado substancialmente y el intentar cargar un diagrama anterior a ésta versión puede que acabe en error. Aún tengo que gestionar todo el tema de los ficheros y su almacenado.


Tuesday, 29 September 2009

Primera aplicación con Thundax Box Manager (Parte II)

Aquí os dejo la última versión de Thundax Box Manager que incluye el algoritmo Dijkstra para la búsqueda del camino más corto (versión mejorada). Ahora el algoritmo funciona correctamente y permite generar grafos de hasta 100 vértices (realmente podríamos poner los que quisiéramos). En las siguientes imágenes podréis ver como se busca el camino más corto marcando un vértice inicial y uno final. Si os fijáis el código no es muy extenso y es de fácil comprensión (tampoco he tardado mucho en sacarlo, la idea y su funcionamiento los tenía muy claro). Ahora simplemente es cuestión de tiempo el empezar a automatizar ideas y empezar a dibujar cosas pre-tratadas para hacer más amena la inserción de bloques en el lienzo. También tengo pensado mejorar la capacidad de copiar y pegar propiedades, ya que he notado que la edición es bastante lenta y hay que mejorar eso. También el clonado de objetos debería ser más fácil y desde el teclado poder hacer alguna cosa. Cada vez que manejo la herramienta me voy dando cuenta de las cosas que aún le faltan y es que aún está bastante verde, pero por lo menos hay una buena base, bien estructurada con una buena implementación hacia la Orientación a Objetos, Persistencia de datos y un buen uso de los patrones de diseño.


Aquí os dejo los enlaces para la descarga de la aplicación que además incorpora el diagrama. Tan solo tenéis que dejar el diagrama en la carpeta donde esté el ejecutable y hacer un load. Luego os aparecerá el mismo diagrama que veis aquí.

El código fuente del algoritmo para la resolución del camino más corto utilizando DijKstra es el siguiente:


procedure TfrmMain.Dijkstra1Click(Sender: TObject);
const
NoWeight = 999999;
MaxVertexs = 12;
type
ArrayOfInteger = array[1..MaxVertexs] of Integer;
var
AdjacentList: array[1..MaxVertexs, 1..MaxVertexs] of Integer;
VertexList: ArrayOfInteger;
predecessor: ArrayOfInteger;
visiteds: array[1..MaxVertexs] of boolean;
vertexs, edges, firstNode, LastNode: integer;
i, j, pos1, pos2, min: integer;
Conn: TConnector;
textList: string;
DrawingList : ArrayOfInteger;
begin
vertexs := boxlist.count;
edges := connectorList.count;
firstNode := -1;
LastNode := -1;
for i := 0 to BoxList.count - 1 do
begin
if BoxList.items[i].boxColor = clLime then
firstNode := StrToInt(BoxList.items[i].getText);
if BoxList.items[i].boxColor = clFuchsia then
LastNode := StrToInt(BoxList.items[i].getText);
end;
if (firstNode = -1) or (LastNode = -1) then
exit;

RestoreDefaults1Click(Sender);

for i := 1 to MaxVertexs do
begin
for j := 1 to MaxVertexs do
begin
AdjacentList[i, j] := NoWeight;
if i = j then
AdjacentList[i, j] := 0
end;
DrawingList[i] := NoWeight;
end;

for i := 0 to edges - 1 do
begin
Conn := ConnectorList.items[i];
pos1 := StrToInt(Conn.SourceBox.GetText);
pos2 := StrToInt(Conn.targetBox.GetText);
AdjacentList[pos1, pos2] := StrToInt(Conn.Line.GetText);
if Conn.Line.ClassNameIs('TAbstractDottedDoubleArrowLine') or
Conn.Line.ClassNameIs('TAbstractSimpleDoubleArrowLine') then
AdjacentList[pos2, pos1] := StrToInt(Conn.Line.GetText);
end;

for i := 1 to vertexs do
begin
VertexList[i] := AdjacentList[firstNode, i];
predecessor[i] := firstNode;
visiteds[i] := false;
end;
VertexList[firstNode] := 0;
visiteds[firstNode] := true;

j := firstNode;
repeat
for i := 1 to vertexs do
if (not visiteds[i]) and (VertexList[i] > VertexList[j] + AdjacentList[j, i]) then
begin
VertexList[i] := VertexList[j] + AdjacentList[j, i];
predecessor[i] := j;
end;
min := NoWeight;
for i := 1 to vertexs do
if (not visiteds[i]) and (VertexList[i] < min) then
begin
min := VertexList[i];
j := i;
end;
if (min <> NoWeight) then
visiteds[j] := true;
until (visiteds[LastNode]) or (min = NoWeight);

textList := '';
DrawingList[firstNode] := LastNode;
j := firstNode;
while (LastNode <> firstNode) do
begin
j := j + 1;
LastNode := predecessor[LastNode];
DrawingList[j] := LastNode;
end;

i := 1;
while i < length(DrawingList) do
begin
for j := 0 to connectorList.count - 1 do
begin
Conn := ConnectorList.items[j];
if ((Conn.SourceBox.getText = IntToStr(DrawingList[i])) and
(Conn.TargetBox.getText = IntToStr(DrawingList[i+1]))) or
((Conn.SourceBox.getText = IntToStr(DrawingList[i+1])) and
(Conn.TargetBox.getText = IntToStr(DrawingList[i]))) then
begin
Conn.SourceBox.lineColor := clRed;
Conn.TargetBox.lineColor := clRed;
Conn.Line.LineColor := clRed;
end;
end;
i := i + 1;
end;
end;


En definitiva, creo que la aplicación va por buen camino y estoy contento porque por fin tengo una aplicación visual para poder tratar los diferentes datos que tengo en mente. Ya os iré contando!.


Primera aplicación con Thundax Box Manager (Parte I)

Bueno, lo prometido es deuda y aquí tenéis la primera representación a la utilización de éste nuevo framework que me he creado from scratch. Ésta primera aplicación utilizando mi unnamed framework consiste como no, en el dibujado de un grafo (dirigido o no) y el correspondiente cálculo del camino mínimo aplicando el algoritmo de Dijkastra. He tenido que tirar de mis apuntes de matemática discreta, pero estoy muy contento al poder realizar ésta primera aplicación sin muchas complicaciones en la utilización de los objetos personalizados. Esta implementación sencilla es muy fácil de utilizar, simplemente dibujamos todo el grafo con sus conexiones (con dirección simple o doblemente dirigidas, ésto provoca que en la lista de adyacentes se publican las dos direcciones, tanto la ida como la vuelta). Luego tenemos que marcar el origen y el final mediante unos colores concretos y marcamos los vértices y aristas con números para identificarlos y sus pesos.

Lo veremos mejor con un ejemplo:

Como veis en el grafo, todas las aristas son de doble sentido y tiene marcado el peso de pasar por ése camino. Los vertices (TBox) estan numerados y tenemos marcado también con colores el origen (1 - clLime) y el final (6 - clFucshia). Hay que marcarlos siempre con éstos colores para que funcione. Una vez hecho ésto, vamos a Extension -> DijKstra Algorithm y nos devolverá el camino mínimo marcado en rojo.

Aún tengo que arreglar alguna cosa del algoritmo, porque aún no afina muy bien, pero de momento la cosa funciona correctamente, y permite de una manera rápida, sencilla y gráfica, montar nuestros propios grafos y luego calcular el camino mínimo.

Aquí os dejo el código fuente del algoritmo:
  • Dijkstra algorithm with Delphi:


procedure TfrmMain.Dijkstra1Click(Sender: TObject);
var
AdjacentList: array[1..MaxVertexs, 1..MaxVertexs] of Integer;
VertexList: ArrayOfInteger;
predecessor: ArrayOfInteger;
visiteds: array[1..MaxVertexs] of boolean;
vertexs, edges, firstNode, LastNode: integer;
i, j, pos1, pos2, min: integer;
Conn: TConnector;
textList: string;
begin
vertexs := boxlist.count;
edges := connectorList.count;
firstNode := -1;
LastNode := -1;
for i := 0 to BoxList.count - 1 do
begin
if BoxList.items[i].boxColor = clLime then
firstNode := StrToInt(BoxList.items[i].getText);
if BoxList.items[i].boxColor = clFuchsia then
LastNode := StrToInt(BoxList.items[i].getText);
end;
if (firstNode = -1) or (LastNode = -1) then
exit;

for i := 1 to MaxVertexs do
for j := 1 to MaxVertexs do
begin
AdjacentList[i, j] := NoWeight;
if i = j then
AdjacentList[i, j] := 0
end;

for i := 0 to edges - 1 do
begin
Conn := ConnectorList.items[i];
pos1 := StrToInt(Conn.SourceBox.GetText);
pos2 := StrToInt(Conn.targetBox.GetText);
AdjacentList[pos1, pos2] := StrToInt(Conn.Line.GetText);
if Conn.Line.ClassNameIs('TAbstractDottedDoubleArrowLine') or
Conn.Line.ClassNameIs('TAbstractSimpleDoubleArrowLine') then
AdjacentList[pos2, pos1] := StrToInt(Conn.Line.GetText);
end;

for i := 1 to vertexs do
begin
VertexList[i] := AdjacentList[firstNode, i];
predecessor[i] := firstNode;
visiteds[i] := false;
end;
VertexList[firstNode] := 0;
visiteds[firstNode] := true;

j := firstNode;
repeat
for i := 1 to LastNode do
if (not visiteds[i]) and (VertexList[i] > VertexList[j] + AdjacentList[j, i]) then
begin
VertexList[i] := VertexList[j] + AdjacentList[j, i];
predecessor[i] := j;
end;
min := NoWeight;
for i := 1 to vertexs do
if (not visiteds[i]) and (VertexList[i] < min) then
begin
min := VertexList[i];
j := i;
end;
if (min <> NoWeight) then
visiteds[j] := true;
until (visiteds[LastNode]) or (min = NoWeight);

textList := '';
while (LastNode <> firstNode) do
begin
textList := textList + IntToStr(LastNode);
LastNode := predecessor[LastNode];
end;

textList := Swap(textList + IntToStr(FirstNode));

i := 1;
while i < length(textList) do
begin
for j := 0 to connectorList.count - 1 do
begin
Conn := ConnectorList.items[j];
if ((Conn.SourceBox.getText = textList[i]) and
(Conn.TargetBox.getText = textList[i + 1])) or
((Conn.SourceBox.getText = textList[i + 1]) and
(Conn.TargetBox.getText = textList[i])) then
begin
Conn.SourceBox.lineColor := clRed;
Conn.TargetBox.lineColor := clRed;
Conn.Line.LineColor := clRed;
end;
end;
i := i + 1;
end;
end;


Podéis descargar la última versión de la aplicación aquí: ThundaxBoxManager v2.0.0 build 65.exe.
  • Enlaces de interés:
Dijkstra applet Demos.


Mejorando el diseño de una flecha (Parte II)

He detectado un pequeño error en la composición de la flecha. Cuándo el ángulo representado es igual a pi/2 o -pi/2 la flecha no se dibuja correctamente, solo aparece la punta sin el relleno, es decir, el polígono no se dibuja correctamente. La solución (como siempre) es bastante fácil y solo hay que tener en cuenta la distribución geométrica de los puntos de representación de la punta de flecha. El problema reside en la comparación utilizando reales. Éstos no se pueden comparar realmente, ya que siempre son diferentes internamente, por lo tanto cuando el programa intenta comparar los valores de pi/2 con el valor calculado de pi/2 dice que no son iguales:

La funcion consiste en mirar si el valor absoluto del angulo difiere de un ángulo de pi/2, y si realizamos la comparación con el debugger no dirá que los valores no son iguales, y visualmente lo son.

Para evitar ésto utilizaré una función que os mostré hace tiempo en uno de mis posts para la comparación de reales: Comparando reales con delphi.

Mediante éste último arreglo, el framework (aún estoy pensando en que nombre darle, pero al final caerá algo como TWAIN Technology without an Interesting Name) tiene bastante resuelto todo el tema del cálculo para el repintado de las diferentes estructuras.

El código final del dibujado de la flecha es el siguiente:


procedure TAbstractLine.DrawFashionArrow(Source, Target: TPoint);
function CalcPoint(p: TPoint; angle: double; Distance: integer): TPoint;
var
X, Y, M: double;
begin
if Comparar(Abs(angle),(PI / 2),'<>') then
begin
if Comparar(Abs(angle),(PI / 2),'<') then
Distance := -Distance;
M := Tan(angle);
X := p.X + Distance / sqrt(1 + sqr(M));
Y := p.Y + M * (X - p.X);
Result := Point(round(X), round(Y));
end
else
begin
if angle > 0 then
Distance := -Distance;
Result := Point(p.X, p.Y + Distance);
end;
end;

var
angle: double;
PArrow: array[1..4] of TPoint;
restColor: TColor;
begin
if (Distance(Source, Target) < 20.0) or (Distance(Source, Target) > 2000.0) then
exit;
angle := ArcTan2((Target.Y - Source.Y), (Target.X - Source.X));
PArrow[1] := Target;
PArrow[2] := CalcPoint(Target, angle + PI / 9, LenArrow);
PArrow[3] := CalcPoint(Target, angle, 2 * LenArrow div 3);
PArrow[4] := CalcPoint(Target, angle - PI / 9, LenArrow);
FCanvas.Pen.Width := 1;
if FInside then
FCanvas.Pen.Color := FSelectedColor
else
FCanvas.Pen.Color := FLineColor;
FCanvas.Brush.Style := bsSolid;
restColor := FCanvas.Brush.Color;
if Ffilled then
FCanvas.Brush.Color := FFillColor;
FCanvas.Polygon(PArrow);
FCanvas.Brush.Color := restColor;
end;


La actualización de la función de comparación también la dejo ya que faltaba un operador de comparación:


uses types, Math;

function Comparar(Value1, Value2: double; MethodComp: string): boolean;

const
RealMargin = 0.000001; //1e-6

implementation

function Comparar(Value1, Value2: double; MethodComp: string): boolean;
var
ret: boolean;
begin
ret := false;
if MethodComp = '=' then
begin
case CompareValue(Value1, Value2, RealMargin) of
EqualsValue: ret := true;
end;
end
else if MethodComp = '<>' then
begin
case CompareValue(Value1, Value2, RealMargin) of
LessThanValue: ret := true;
GreaterThanValue: ret := true;
end;
end
else if MethodComp = '>=' then
begin
case CompareValue(Value1, Value2, RealMargin) of
EqualsValue: ret := true;
GreaterThanValue: ret := true;
end;
end
else if MethodComp = '>' then
begin
case CompareValue(Value1, Value2, RealMargin) of
GreaterThanValue: ret := true;
end;
end
else if MethodComp = '<=' then
begin
case CompareValue(Value1, Value2, RealMargin) of
LessThanValue: ret := true;
EqualsValue: ret := true;
end;
end
else if MethodComp = '<' then
begin
case CompareValue(Value1, Value2, RealMargin) of
LessThanValue: ret := true;
end;
end;
result := ret;
end;


Ahora, el dibujado de las flechas es perfecto:

Espero que os sirva de ayuda.

Monday, 28 September 2009

Thundax Box Manager 2.0

Ya está disponible la última versión de la aplicación Thundax Box Manager 2.0, la cuál ajustando unos cuantos conceptos de diseño pasará a convertirse en un framework de trabajo para poder dibujar objetos sobre un canvas. En ésta versión he acabado de implementar la persistencia de objetos mediante Marshal y Unmarshal con apoyo mediante XML. Aunque trae bastante trabajo, es fácil de implementar y fácil de seguir sin tocar el diseño original de nuestras clases. Entre las últimas novedades que utiliza la herramienta puedo destacar:
  • Identificador único de objeto utilizando un GUID.
  • Persistencia del lienzo mediante una estructura simple en XML.
  • Tamaño del lienzo modificado a 1024 x 780 pixels.
Si analizamos el nuevo diseño de las clases podemos ver:

La famosa clase TBox, que implementa las interfaces ISerializable y ICloneable, permitiendo así que el objeto pueda ser clonado y serializado para su posterior almacenaje o persistencia.


Otra de las partes más importantes de la aplicación es la generación de las líneas y su cálculo. La factoría abstracta ha sido modificada levemente para poder realizar una llamada a un elemento de la factoría a través de su nombre.


TAbstractFactory = class(TObject)
private
FCanvas: TCanvas;
public
constructor Create(Canvas: TCanvas);
destructor Destroy; override;
function GetLine(): TAbstractLine; virtual; abstract;
function GetLineArrow(): TAbstractLine; virtual; abstract;
function GetLineDoubleArrow(): TAbstractLine; virtual; abstract;
function GetLineByName(name : String) : TAbstractLine; virtual; abstract;
end;


Entre los otros aspectos a destacar tenemos las diferentes implementaciones de:

TVertex es una clase mucho más compleja que el registro TPoint (éste último lo he tenido que eliminar por problemas de serialización con Delphi 2010). De ésta manera puedo tener control sobre los vértices y añadirles más propiedades y eventos a éstos.

Luego dispongo de las interfaces que me permiten implementar la clonación y serialización en mis clases básicas Tbox y TAbstractLine.

En las siguientes imágenes muestro las posibilidades que tiene la aplicación para poder crear diversos grafos conectados:


Si nos fijamos en las pantallas de propiedades, veremos que cada objeto está identificado con su identificador único GUID, el cuál aparece representado en el lateral de la pantalla de propiedades:

El tema de la generación del GUID con delphi es muy simple. Si lo queremos para nuestras interfaces, solo tenemos que pulsar CTRL+SHIFT+G '{F6676376-D14B-4860-AD5B-D920DF25E52D}' y obtendremos uno nuevo. Para hacer la llamada mediante una función o método, solo tenemos que utilizar la siguiente función:


uses
ComObj, ActiveX;

function CreateGuid(): string;

implementation

function CreateGuid(): string;
var
ID: TGUID;
begin
Result := '';
if CoCreateGuid(ID) = S_OK then
Result := GUIDToString(ID);
end;


Ahora los siguientes pasos consisten en utilizar ésta base desarrollada y generar aplicaciones más potentes con componente visual. Si os fijáis en la declaración de las clases, de momento he dejado un puntero personalizable en cada objeto TBox y TAbstractLine para que le podamos dar la utilización que queramos.

Gráficamente disponemos de lo siguiente:

Podemos utilizar cada uno de los objetos y colgar nuestro objeto personalizado. De ésta manera, la parte gráfica queda solucionada por TBox y la lógica la podemos hacer externa. En los próximos posts intentaré implementar alguna herramienta típica con mi framework a ver que sale.

En definitiva, el entorno es casi funcional y permite crear bonitos grafos conectados. He podido arreglar temas de aliasing, temas de intersección de rectas y trabajar con puntos en el espacio. Como veis no es tan trivial crear algo que dibuje objetos sobre un lienzo. Hay que disponer de "culturilla" general para poder realizar correctamente los cálculos matemáticos y saberlos transformar para que el ordenador los entienda. Como digo yo, realizar una integral a mano es relativamente fácil, ahora lo complicado es hacerlo para que el ordenador sea capaz de hacerla. Siempre me han fascinado las partes gráficas de los IDE's o de aplicaciones, y creo que es una parte bastante complicada de implementar.



Friday, 25 September 2009

Syntax helper

En éste artículo os muestro una pequeña aplicación que hice para automatizar la conversión del código Delphi a HTML para utilizar la herramienta de Syntax Highlighter de Alex Gorbatchev. Con ésta herramienta podremos pasar el código que vamos a colgar en el blog al formato requerido para ésta fabulosa herramienta. Además he incrustado un visor para hacer la previsualización de cómo quedará el código una vez hagamos los cambios. Podéis descargaros aquí la última versión estable de mi aplicación ThundaxSyntaxHelper la cuál permite automatizar un poco más rápido la subida del código a nuestros blogs.

Luego si nos vamos a la pestaña de previsualización, nos aparece el resultado de formatear nuestro código:

Espero que os sirva de ayuda.

Aquí os dejo el código que se mostrará utilizando el Syntax Highlighter de Alex Gorbatchev:


procedure TForm3.GetText1Click(Sender: TObject);
var
i : integer;
text : string;
begin
memo2.Lines.Clear;
memo2.lines.add('<pre class="brush: delphi">');
for i := 0 to memo1.lines.count-1 do
begin
text := AnsiReplaceStr(memo1.lines[i],'<', '<');
text := AnsiReplaceStr(text,'>', '>');
memo2.lines.Add(text);
end;
memo2.lines.add('</pre>');
end;

procedure TForm3.AddFile(s: string);
var
LogFile: TextFile;
begin
AssignFile(LogFile, ExtractFilePath(ParamStr(0)) + 'test.html');
try
if FileExists(ExtractFilePath(ParamStr(0)) + 'test.html') then
Append(LogFile)
else
Rewrite(Logfile);
WRITELN(LogFile, s);
CloseFile(LogFile);
except
end;
end;

procedure TForm3.Preview1Click(Sender: TObject);
var
i : integer;
text : string;
begin
if fileExists(ExtractFilePath(ParamStr(0)) + 'test.html') then
DeleteFile(ExtractFilePath(ParamStr(0)) + 'test.html');

for i := 0 to memo3.Lines.count-1 do
begin
AddFile(Memo3.lines[i]);
end;
for i := 0 to memo2.Lines.count-1 do
begin
AddFile(Memo2.lines[i]);
end;
AddFile('</html>');
text := ExtractFilePath(ParamStr(0)) + 'test.html';
text := AnsiReplaceStr(text,'\','/');
WebBrowser1.Navigate('file:///' + text);
end;


  • Enlaces de interés:
Integrando un syntax Highlighter para el código del blog.

Thursday, 24 September 2009

Corrigiendo el aliasing del texto inclinado

Prosiguiendo los cálculos realizados sobre mi aplicación Thundax Box Manager, en éste artículo daré importancia a la corrección del Aliasing que ocurre en el momento de inclinar el texto. La propia API de windows nos permite trabajar con las fuentes modificando los parámetros básicos de éstas y definiendo así su calidad. Si observamos la siguiente imagen sacada de la aplicación, notaréis como el sistema intenta dibujar las letras orientadas con una inclinación de la manera que puede. La verdad es que no se entienden muy bien y no queda muy bonito en la aplicación.

Si consultamos los parámetros de CreateFont, podemos ver que trabaja bastante bien con fuentes Truetype y que además permite corregir el aliasing ajustando un parámetro de su función. Mediante el parámetro fdwQuality [in]:
The output quality. The output quality defines how carefully GDI must attempt to match the logical-font attributes to those of an actual physical font. It can be one of the following values.
Podemos utilizar el valor: ANTIALIASED_QUALITY.
Font is antialiased, or smoothed, if the font supports it and the size of the font is not too small or too large.
Aquí podéis ver las diferentes pruebas realizadas cambiando el tipo de fuente y el tamaño:


Como podéis ver mediante el valor seleccionado podemos mejorar el aliasing de la fuente aplicando un filtro antialiasing sobre el texto rotado. Desde la aplicación, podemos ir probando las diferentes fuentes que tenemos instaladas pero he comprobado que hay muchas fuentes que no disponen de éste comportamiento. Al principio había dejado por defecto la fuente Tahoma, pero no funciona tan bien como la Calibri que es la que utiliza Microsoft Office. Ahora en la aplicación, las fuentes tienen mejor aspecto:


Para acabar de mejorar el método de pintado del texto, he arreglado un tema pendiente sobre centrar el texto en la línea. El cálculo es como siempre bastante fácil y solo tenemos que recurrir a la trigonometría para solucionar ésto.

El problema reside en que el texto empieza a dibujarse desde el centro de la línea, ya que se calcula el punto médio entre el origen y el final de la conexión. Pero claro, tenemos que tener en cuenta la longitud del texto escrito y desplazar el centro la mitad de ésta longitud para que el texto se muestre centrado:

En la siguiente imagen describo el cálculo con un poco más de precisión:

Utilizando la función Canvas.TextWidth podemos saber la longitud actual del texto en píxels, luego solo necesitamos dividir éste valor entre 2 para obtener la mitad de la longitud. Ahora sabiendo el ángulo de inclinación tenemos que calcular los valores de a y b para calcular el nuevo punto a partir de P.

La nueva función del cálculo de la orientación queda de la siguiente manera:


procedure TAbstractLine.DrawTextOrientation(Source, Target: TPoint);

function iif(condition: boolean; resultTrue: integer; resultFalse: integer): integer;
begin
Result := resultFalse;
if condition then
Result := resultTrue
end;

var
newFont, FontSelected: integer;
angle: double;
iAngle, i: integer;
Position: TPoint;
calc : double;
begin
if FDescription.text = '' then
exit;
angle := CalcAngleTwoPoints(Source, Target);
iAngle := round(angle);

if (iAngle >= 91) and (iAngle <= 180) then
iAngle := iAngle + 180;

if (iAngle >= 181) and (iAngle <= 269) then
iAngle := iAngle - 180;

for i := 0 to FDescription.Count - 1 do
begin
Position := Point((Target.X + Source.X) div 2, (Target.Y + Source.Y) div 2);
FCanvas.Font := FFontText;
calc := (FCanvas.TextWidth(FDescription[i]) div 2) * cos(DegToRad(iAngle));
Position.x := Position.x - round(calc);
calc := (FCanvas.TextWidth(FDescription[i]) div 2) * sin(DegToRad(iAngle));
Position.y := Position.y + round(calc);

SetBkMode(FCanvas.Handle, transparent);
newFont := CreateFont(-FfontText.Size, 0, iAngle * 10, 0, iif(fsBold in FfontText.Style, FW_BOLD, FW_NORMAL), iif(fsItalic in FfontText.Style, 1, 0), iif(fsUnderline in FfontText.Style, 1, 0), iif
(fsStrikeOut in FfontText.Style, 1, 0), ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, FF_DONTCARE, PChar(FfontText.Name));

Fcanvas.font.color := FfontText.color;
FontSelected := SelectObject(FCanvas.Handle, newFont);
TextOut(FCanvas.Handle, Position.X , Position.Y, PChar(FDescription[i]), Length(FDescription[i]));
SelectObject(FCanvas.Handle, FontSelected);
DeleteObject(newFont);
end;
end;


La última versión de la aplicación con éstos cambios la podéis encontrar aquí: Thundax Box Manager v1.0.0 build 159.exe.

Aquí os dejo con una imagen de la aplicación utilizando el corrector de aliasing:


  • Enlaces de interés:
Display rotated text.
Image Processing.
Graphic Algorithms.
Flicker Free TImage Delphi.
Win32 and COM Development.
Delphi DegToRad Command.


Wednesday, 23 September 2009

Implementando el Patrón Prototype

Continuando con la implementación de Thundax Box Manager, he cambiado el diseño de TBox para implementar un patrón prototipo y poder crear copias de ésta clase. Por lo tanto, utilizando el patrón Prototype podremos duplicar objetos clonando sus instancias. Con Delphi es un poco más complicado que con java y hay que picar un poco más de código. Si nos fijamos ahora en la configuración de Tbox veremos que ahora ya no hereda de TObject, sino que hay una interface de por medio llamada ICloneable y que nos permite la funcionalidad de poder clonar el objeto en si. No he encontrado ninguna implementación buena por la red de éste patrón, y la verdad es que no es muy complicada. Con otros lenguajes la cosa es más sencilla, por ejemplo java o C# donde ya existe la clonación de objetos.
Si observamos el nuevo diagrama UML del diseño de TBox tenemos:

La clase TBox implementa la interface ICloneable:

De ésta manera las instancias de TBox tienen la posibilidad de clonarse. Ésto me permite poder realizar copias de mis instancias en el Canvas.

En la última versión de ThundaxBoxManager v1.0.0 build 149.exe podremos realizar copias de los objetos incrustados utilizando el botón derecho del mouse y marcando las opciones de Copiar y Pegar. Además hay la opción de eliminar tanto cajas como conexiones.

Como podéis ver, el editor cada vez va cogiendo más color e incluso podemos realizar la exportación del gráfico a un fichero en bmp:

  • Enlaces de interés:
Creating Patterns in C#.
Cloning Objects.
Prototype Pattern.
Cast Constructor.
Explicit Interface Implementation.

Tuesday, 22 September 2009

tiOPF (Object Persistence Framework)

Hoy os traigo un framework bastante interesante, el tiOPF (Object Persistence Framework for Free Pascal & Delphi). A través de éste framework podemos gestionar la persistencia de nuestros objetos conectándolos a una Base de datos o otro medio que nos proponga éste. En la web encontraréis toda la información referente a su instalación (que es bastante fácil). Solo tenemos que descargarnos la última versión disponible de la red. Yo he descargado la versión tiOPF-Win32-2.5.0.1229.zip y la he probado sobre Delphi 2007. En algún feed de la red he encontrado que migraran el proyecto para hacerlo compatible con Delphi 2010 y así hacer un framework más potente. Una vez tenemos el framework en nuestro equipo, abrimos el proyecto de la ruta:
\tiOPF\tiOPF2\Compilers\Delphi2007\tiOPF_D2007.groupproj.

De ésta manera se nos abren los siguientes proyectos:


Hacemos un build de todos e instalamos el package tiOPFGUIDsgn2007.bpl. Una vez instalado aparecerá un mensaje con los componentes instalados:

Luego tenemos que acordarnos de enlazar las fuentes en Library Path:

Aquí os dejo los componentes instalados:

En la misma ruta del package, encontraréis una serie de ejemplos diferentes para la utilización de éste framework. Además en la web encontraréis un manual bastante completo de su funcionamiento y como empezar con éste framework.

En definitiva, os recomiendo que paséis por la web y le echéis un vistazo al trabajo realizado. La verdad es que está bastante bien y tiene bien solucionada la parte de persistencia con la Base de datos.

Monday, 21 September 2009

Mostrando texto inteligible en la rotación sobre un canvas

Continuando con el diseño apasionante de Thundax Box Manager, si os fijáis en la última imagen que hay en el post anterior, veréis que el texto no se entiende bien, pero por motivos de rotación. Si calculamos el ángulo entre 2 puntos del plano, éste es correcto al dibujar líneas, pero no al dibujar el texto, por eso tenemos que hacer una pequeña conversión para que el texto siempre se pueda leer. En un artículo anterior os comenté una función para poder dibujar el texto en la orientación que queramos, pero tenemos que tener en cuenta siempre la visión del observador. A la máquina le da igual si le escribes el texto del derecho o del revés, pero a nosotros no.

Si nos fijamos en la siguiente imagen representando los 4 cuadrantes:

Podemos ver que los cuadrantes 1 y 3 los tenemos que evitar en la medida de lo posible, y esto lo haremos realizando un sencillo cálculo posicionandonos en el cuadrante que más nos interese. Por lo tanto siempre que el sistema tienda a trabajar con el cuadrante 1, hay que pasarlo automáticamente al cuadrante 4. Hay que hacer lo mismo con el 3 hacia el 2.

Por lo tanto el cálculo a realizar sería el siguiente:


procedure TAbstractLine.DrawTextOrientation(Source, Target: TPoint);
function iif(condition: boolean; resultTrue: integer; resultFalse: integer): integer;
begin
Result := resultFalse;
if condition then
Result := resultTrue
end;

var
newFont, FontSelected: integer;
angle: double;
iAngle, i: integer;
Position: TPoint;
begin
if FDescription.text = '' then
exit;
angle := CalcAngleTwoPoints(Source, Target);
iAngle := round(angle);

if (iAngle >= 91) and (iAngle <= 180) then
iAngle := iAngle + 180;

if (iAngle >= 181) and (iAngle <= 269) then
iAngle := iAngle - 180;

for i := 0 to FDescription.Count - 1 do
begin
Position := Point((Target.X + Source.X) div 2, (Target.Y + Source.Y) div 2);
SetBkMode(FCanvas.Handle, transparent);
newFont := CreateFont(-FfontText.Size, 0, iAngle * 10, 0, iif(fsBold in FfontText.Style, FW_BOLD, FW_NORMAL), iif(fsItalic in FfontText.Style, 1, 0), iif(fsUnderline in FfontText.Style, 1, 0), iif
(fsStrikeOut in FfontText.Style, 1, 0), ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, FF_DONTCARE, PChar(FfontText.Name));

FontSelected := SelectObject(FCanvas.Handle, newFont);
TextOut(FCanvas.Handle, Position.X, Position.Y + (i * FfontText.Size), PChar(FDescription[i]), Length(FDescription[i]));
SelectObject(FCanvas.Handle, FontSelected);
DeleteObject(newFont);
end;
end;


Al final obtendremos un dibujo entendible al utilizar la aplicación ThundaxBoxManager, la cúal podéis descargar desde aquí: ThundaxBoxManager v1.0.0 build 125.exe.