Showing posts with label TCanvas. Show all posts
Showing posts with label TCanvas. Show all posts

Friday, 23 October 2009

Cálculo de rutas de transporte parte I

Después de unos días muy ajetreados y con mucho trabajo (y que no falte) he podido medio acabar la primera aplicación profesional sobre mi framework. Ésta se trata sobre el cálculo de rutas. He utilizado diversos tipos de algoritmos para su recorrido y búsqueda de diferentes caminos para obtener la ruta de un origen a un destino pasando por una serie de nodos. Luego vincularé la búsqueda de éstas rutas con mi anterior aplicación Thundax Genies Scanner y así poder crear mapas mentales de aplicaciones Scada para la búsqueda de caminos automáticos. En que consiste ésta idea?

Como podéis ver en la siguiente imagen:

Disponemos de un origen marcado en verde y un destino marcado en fucsia. Con mi framework he generado el diagrama y aplicado diversas propiedades a las diferentes cajas y conectores. El problema que se presenta, es el de calcular las diferentes rutas que hay de un punto a otro y guardarlas en algún sitio para poder utilizar éstos datos y evitar sobretodo el cálculo manualmente. Poco a poco, os iré explicando la utilizad de ésto y el propósito de su cálculo.

Por lo tanto, si nos fijamos en la imagen existen 2 rutas para ir del origen (1) al destino (19):

Así, calcularíamos:
RUTA1: 1-4-8-11-19 RUTA2: 1-5-9-11-19
  • ¿Dónde está la complicación aquí?
Parece fácil, no?. Pues no lo es. Es un problema bastante complicado a la que empiezan a haber cruzamientos entre nodos. Si aparecen diversas conexiones entre sí, el numero de combinaciones empieza a crecer exponencialmente.

Si observamos mi aplicación, veremos como se calculan las rutas automáticamente:

Ahora un ejemplo más real:

Como podéis comprobar, son los mismos nodos, pero ahora tenemos 7 rutas disponibles!.

El cálculo de sus rutas es el siguiente:

Aún estoy acabando de perfilar el algoritmo y calculando sus diferentes costes para una mejora en su rendimiento. Dentro de poco tendré disponible la versión para búsqueda de rutas con múltiples orígenes y múltiples destinos. Y poder realizar la búsqueda de rutas de diagramas como el siguiente:

Si os fijáis, incluso he creado un tipo de enlace nuevo con la imagen de una cadena. Éste enlace consiste en un enlace de bloqueo donde los nodos están fuertemente vinculados y también se tienen que tener en cuenta a la hora de escribir la ruta de transporte. Ésto significa que si queremos pasar por un nodo de éstos, el otro debe aparecer bloqueado (en términos de fluidos seria cerrar uno de los caminos).
  • ¿Cómo funciona el algoritmo?
El algoritmo que he creado es del tipo backtracking recorriendo los diferentes nodos y marcando las rutas visitadas. Lo que hago es crear una lista de adyacentes marcando la cantidad de conexiones que tiene cada nodo.

Primero calculo los orígenes y destinos en función del color de la caja:


for i := 0 to boxlist.count - 1 do
begin
if boxlist.items[i].properties.fillColor = clLime then
SetArrayValue(OrigenList, StrToInt(boxlist.items[i].properties.getText));
if boxlist.items[i].properties.fillColor = clFuchsia then
SetArrayValue(DestinoList, StrToInt(boxlist.items[i].properties.getText));
end;
Luego, nos centramos en calcular la lista de adyacentes en función del tipo de conexión. Además existe una lista de bloqueos en función del tipo de conector utilizado:


for i := 0 to connectorList.count - 1 do
begin
conn := connectorList.items[i];
pos1 := StrToInt(conn.SourceBox.properties.getText);
pos2 := StrToInt(conn.TargetBox.properties.getText);
if not conn.Line.ClassNameIs('TAbstractDottedDoubleLinkedArrowLine') and
not conn.Line.ClassNameIs('TAbstractSimpleDoubleLinkedArrowLine') then
SetAdjacentValue(AdjacentList, pos1, pos2)
else
begin
SetAdjacentValue(InterlockList, pos1, pos2);
SetAdjacentValue(InterlockList, pos2, pos1);
end;
end;


El siguiente paso es marcar los diferentes nodos con su peso marcado por la cantidad de nodos conectados a un nodo.


for i := 0 to length(AdjacentList)-1 do
begin
AdjacentList[i].weight := getWeight(AdjacentList, AdjacentList[i].source);
if AdjacentList[i].weight = 1 then
AdjacentList[i].unique := true;
end;


Esto nos permite crear un diagrama con la siguiente información:



function TfrmMain.ExamineNodes(firstNode: integer; lastNode: integer; AdjacentList: TAdjacentArray): string;
var
nodeVisited: integer;
res: TArrayInteger;
i: integer;
father: integer;
begin
nodeVisited := firstNode;
father := nodeVisited;
while (nodeVisited <> lastNode) do
begin
res := GetConnections(AdjacentList, nodeVisited);
if length(res) = 0 then exit;
for i := 0 to length(res) - 1 do
begin
nodeVisited := res[i];
if isVisited(AdjacentList, father, nodeVisited) then
begin
visit(AdjacentList, father, nodeVisited);
setRoute(nodeVisited, father);
ExamineNodes(nodeVisited, lastNode, AdjacentList);
end;
end;
end;
end;


Cómo podéis ver el algoritmo es bastante simple pero se puede mejorar bastante. El coste de calculo es O(n2) para visitar todos los nodos. Aquí os dejo la última versión de la aplicación ThundaxBoxManager v2.3.0 build 57 con los diagramas de ejemplo. Para si resolución hay que ir al menú Citect y allí ejecutar la opción Backtracking2.


Friday, 9 October 2009

Plataforma de desarrollo : VLO Framework parte II

Ya he integrado formalmente el framework VLO 1.0 en mi aplicación Thundax Box Manager. Aun funciona de la misma manera pero el código está distribuido de otra forma. La aplicación utiliza la platforma VLO para poder trabajar con las diferentes cajas y conectarlas entre sí, o aplicar diferentes algoritmos sobre éstos para manejar diversos datos. He corregido varias cosas como la detección de múltiples conexiones sobre una caja y el posicionado correcto de las pantallas de propiedades, que al variar el esquema del observer no me había dado cuenta de que la posición de las pantallas de propiedades se ubican en función del canvas. Lo he modificado para que tenga en cuenta el parent y lo ubique en un lateral del formulario. Para la detección de la conexión solo he tenido que modificar el objeto conector y aplicar un esquema de búsqueda para ver si existen sus conectores:


function TConnectorList.exists(AObject: TConnector): Boolean;
var
i: integer;
found: boolean;
begin
i := 0;
found := false;
while (not found) and (i < self.Count) do
begin
found := (self.Items[i].HashSourceBox = AObject.HashSourceBox) and
(self.Items[i].HashTargetBox = AObject.HashTargetBox);
if not found then
found := (self.Items[i].HashSourceBox = AObject.HashTargetBox) and
(self.Items[i].HashTargetBox = AObject.HashSourceBox);
i := i + 1;
end;
result := found;
end;


Si intentamos realizar una operación no posible, nos aparecerá un mensaje indicando que las cajas ya están conectadas:


if not connectorList.Exists(conn) then
connectorList.Add(conn)
else
begin
FreeAndNil(conn);
ShowMessage('Box already connected!');
end;



También he puesto la información en el About indicando la versión del framework y un link a mi web: thundaxSoftware.org. Aunque no estén todas las aplicaciones allí, encontraréis otras. (tengo que unificar todas las aplicaciones y ordenarlo un poco).

Aquí os dejo la última versión de Thundax Box Manager para que podáis testear el framework.

Éste fin de semana intentaré postear algo más pero será complicado, ya que estaré en Londres asistiendo al seminario que da Andy Hunt sobre el pensamiento y aprendizaje pragmático. Vamos, que no tiene pérdida!.


Thundax Box Manager 2.3

Ya está disponible la versión 2.3 de mi aplicación utilizando mi framework VLO. En ésta versión las novedades son significativas y es que he modificado el tamaño del lienzo. Es decir, ahora podemos navegar por la ventana utilizando unos Scrollbar situados en la horizontal y la vertical del canvas. La manera fácil de implementar ésto es utilizando un TScroBox que permite incrustar una imagen dentro y visualizarla por completo. Pero el problema que se presenta aquí, es que al utilizar mi propio canvas (ThundaxCanvas) éste creará 2 ThundaxScrollBar y gestionarán la vista del lienzo. Lo que he hecho es modificar las coordenadas desde el punto de vista del observador de ésta manera el modelo se entera del cambio y redibuja otra vez toda la imagen posicionandola en la posición correcta. De igual manera podría implementar un zoom (ya lo haré más adelante) pero la estructura es la siguiente:

La visión del observador consiste en mostrar aquella región que es visible desde la aplicación. El lienzo tiene un tamaño de 1280 x 1024 pixels y no se puede mostrar todo por pantalla. Aquí está la razón del tener que implementar éste cambio de perspectiva.

La aplicación ahora tiene el siguiente aspecto:


La última versión no disponía de redimensionado de formulario, y ahora ya está disponible. También he corregido unos cuantos bugs del VLO framework sobre la eliminación de objetos, que no se eliminaban correctamente.


Si probáis de eliminar el nodo 9 en la penúltima versión veréis que aparece una excepción. En ésta última entrega ya no sucede.

Saturday, 3 October 2009

Thundax Box Manager 2.2

Ya está disponible ésta última versión con unas cuantas actualizaciones sobre la selección de proyectos. Aún es un poco básica pero funcional. He creado el siguiente paquete de instalación para la versión 2.2: Thundax Box Manager 2.2. Además trae 3 ejemplos de diagrama para que los cargues y juguéis con ellos.

Al iniciar la aplicación podremos hacer un load de los diagramas guardados:

Aún estoy trabajando en un gestor que me permita manejar mejor todos éstos diagramas, pero de momento dejo ésta versión beta para que la probéis. Los diagramas son altamente portables ya que están guardados en una estructura xml, por lo tanto con coger la carpeta del diagrama que está en la carpeta files\ (la reconoceréis porqué la carpeta acaba en .tbm).

Friday, 2 October 2009

Thundax Box Manager 2.1

Bueno, ya está disponible la última versión de mi aplicación. Ésta "obra de arte" permite ahora las imágenes embebidas de tal manera que podemos superponer sobre la caja una imagen para poder representar aquello que queremos. Estoy muy contento porqué la aplicación va viento en popa y estoy siguiendo al detalle la planificación sobre mi aplicación. Como ya sabréis lo que me habéis seguido a lo largo de éste año y medio, dispongo de mis datos en un CVS y los visualizo utilizando el Redmine. Ésta última herramienta me permite generar nuevas peticiones sobre mi programa y anotar todos los cambios y errores que me voy encontrando para no dejarme nada suelto. Además de mi propia wiki para poder ir subiendo la diferente documentación que voy generando de la aplicación. En ésta versión, la 2.1, encontraréis pocos cambios en lo que a comportamiento se refiere. Las últimas novedades son:
  • Visualización de la lista de Snapshots tomada
  • Corrección de las fugas de memória (Eurekalog)
  • Imagenes embebidas dentro de Tbox
Por lo tanto, ahora podremos hacer diagramas tan espectaculares como el siguiente:


La pantalla de propiedades ha cambiado un poco y ahora nos permite cargar una imagen en formato (.bmp) y hacer la previsualización en el formulario:

De momento la previsualización es de 175 x 75 píxels, lo que provoca que al cargar imágenes con un tamaño más grande se vean cortadas en el visor, pero en la visualización sobre el lienzo no habrá ningún problema.

La aplicación está preparada para poder guardar el diagrama y todos los datos son guardados en la carpeta /resources/images y solo acepta ésta carpeta. Aún queda el tema del guardado de los diagramas que en breve me pondré con ello.

También podéis ver la lista de snapshots que dispone la aplicación:



Podéis descargar la última versión aquí:
NOTA: Si encontráis algún error o Memory leak, por favor, haced un comentario en el blog. gracias.

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!.


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.



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.


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.