Retoques en el Blog

jueves, 12 de noviembre de 2009 Sin comentarios
Share Button

Hola a todos.
Bueno llevaba tiempo sin dedicarle algo de tiempo al blog en sí, y hoy me he puesto un poco a realizar tareas de mantenimiento.

Actualización a la versión 2.8.5 de WordPress sin problemas. Utilizando el procedimiento de siempre:

  1. Copia de seguridad de los Datos (NOTA1).
  2. Desactivar todos los plugIns.
  3. Copiar la nueva versión (ficheros), en mi caso de forma manual con FileZilla Portable.
  4. Activar los PlugIns de nuevo y realizar otra Copia de los Datos.

NOTA1: Para las copias de la Base de Datos recomiendo el plugIn de WordPress llamado WordPress Database Backup; No sólo por el mero hecho de realizar copias (cosa que se hace con poco trabajo desde el panel de MySQL) sino porque además incluye Copias Programadas, que se pueden guardar en el servidor o enviar por correo cada x tiempo.

Añadidas encuestas. Pues eso, que he estado probando el plugIn de encuestas. Está en marcha y espero ver cómo funciona y cómo responden los usuarios.

Puntuar entradas. También he añadido este plugin que permite a los usuarios expresar su «nivel de satisfacción» con las entradas del blog y con los comentarios de otros usuarios.

Feed de otros Blogs. En la perte inferior derecha aparecen una especio de Blog-roll, entradas de otros blog relacionados con Delphi y que tienen su cabida en este. Si queréis que añada alguno más, no tenéis más que decirlo, así como en la sección de enlaces.

A parte algunas actualizaciones menores en la forma de mostrar el código (WP-Sintax).

También he creado un MIRROR (http://neftali-mirror.site11.com/) a la página principal, que sirve, no sólo como eso -mirror-, sino también como una especie de «banco de pruebas» y como «copia de seguridad» a los contenidos del blog (que en estos momentos, mientras escribo esto, está caído).

Un saludo.

Categories: General, Wordpress Tags:

Google Maps en Delphi – II

lunes, 29 de junio de 2009 37 comentarios
Share Button

En la última entrada acerca de la API de Google Maps, vimos cómo mostrar un Mapa utilizando la API en un programa Delphi, cómo centrarlo en una posición predefinida y utilizando un Zoom predeterminado.

Además como seleccionar entre los diferentes controles que podemos añadir al mapa (Zoom, Vista general, tipo de mapa…).

También cómo colocar una marca en una ubicación y cómo generar una ventana con información asociada a esa marca (NOTA1).

NOTA1: De forma similar se pueden colocar otros elementos sobre el mapa, como:

  • Líneas, polilíneas y polígonos.
  • Marcas con iconos personalizados.
  • Rutas

Una vez visto cómo mostrar el mapa, lo que nos queda es ver cómo podemos, desde nuestro programa Delphi, interactuar con él; Es decir, que el usuario pueda modificar determinadas características del mapa que tiene en pantalla, y nosotros podamos recuperar esos cambios, para utilizarlos en nuestro programa.

Vamos a continuar con el ejemplo visto en las entradas anteriores; Mostraremos al usuario un Mapa ubicado en una determinada posición. El usuario debe poder «reubicar» la vista del mapa y modificar el Zoom con que está visualizando el mapa, y esos son los valores que obtendremos para posteriormente almacenarlos y actualizarlos en nuestro programa.

  • Coordenada de Longitud
  • Coordenada de Latitud
  • Zoom actual del mapa

La forma de conseguirlo, es añadir a la página web el código necesario para capturar eventos que se produzcan en el mapa. Incluiremos controles de edición, donde se almacenan la longitud/latuitud y Zoom, que después recuperaremos desde el programa Delphi.

GEvent.addListener(map, "click", function (overlay,point)
GEvent.addListener(map, "zoomend", function (oldLevel, newLevel)
GEvent.addListener(map, "mousemove", function(latlng)

Aunque uno no esté muy familiarizado con el tema (yo mismo no lo estoy mucho  ;-D ), no hacen falta muchas explicaciones para comprender los eventos; «Capturaremos» en OnClick, OnZoomEnd y OnMouseMove sobre el mapa. Aquí se puede acceder a la lista de eventos, métodos y propiedades de la clase Gmap.

En el caso del evento OnClick, por ejemplo, utilizaremos un código como este:

GEvent.addListener(map, "click", function (overlay,point){
if (point){
document.posicion.x.value=point.x
document.posicion.y.value=point.y
document.posicion.z.value=map.getZoom()
TipoMapa = map.getCurrentMapType().getName()
document.posicion.t.value=TipoMapa
}

Obtenemos información del punto actual (utilizando un parámetro) y el Zoom actual y el tipo de Mapa utilizando métodos de la clase GMap. Todos ellos se almacenan (como hemos comentado antes) en componentes de tipo Text, que nos sirven como «paso intermedio» para luego capturar esos valores de la página Web desde nuestro programa.

Como resultado final, os dejo el ejemplo que se adjunta con esta entrada. En él he incluído/integrado la parte de Geocodificación (visto en las entradas I y II), y el código y modificaciones necesarias para recuperar posición, Zoom y tipo de Mapa (este incluído a última hora) que el usuario selecciona en un Mapa.

Información capturada

La información de los lugares puede ser guardada y recuperada de un fichero de texto; La estructura es bastante simple y no hace falta mayor explicación. Se almacena en el mismo directorio de la aplicación y recibe el nombre de «Lugares.txt».

Recuperar y Guardar ubicaciones

La información que podemos capturar del Mapa (utilizando la clase GMap) y de otros elementos que estén incluíds en el mapa (imágenes, líneas, marcas,…) es muy extensa y está detallada en las referencias de la API de Google Maps.

Personalmente, creo que las posibilidades de ampliación son muy grandes y bastantes más sencillas de lo que a priori puede parecer.

Se puede descargar el ejemplo completo desde aquí.
<DESCARGAR EJEMPLO>

Espero que estas entradas hayan sido de utilidad. Como siempre cualquier comentario, corrección, sugerencia,… es bienvenida. ;-D

Un saludo.

ACTUALIZACIÓN (10 Febrero 2012): Dado el cambio de política de Google, ahora es necesario (obligatorio) colocar la key que se proporciona desde Google para poder utilizar la API. Es necesario modificar el fichero _mapa.txt del recurso y  recompilarlo utilizando RC.CMD.

 

Cargar datos de un TXT a un TDataset (utilizando ADO) – Parte 2

viernes, 22 de mayo de 2009 21 comentarios
Share Button

Continuando con esta entrada (parte 1), nos queda ver como hacer algo similar, pero con un archivo de texto cuyos datos están separador utilizando algun caracter especial (TAB, coma, punto y coma,…)

Por defecto, para la lectura de un fichero de texto medianto ADO (Jet 4.0) se utiliza la información que hay en el registro de windows, que se considera la configuración por defecto. Esta configuración se encuentra en la clave:
‘\SOFTWARE\Microsoft\Jet\4.0\Engines\Text’

Dentro de HKEY_LOCAL_MACHINE y en el valor Format.

De todas formas, para tener un mayor control sobre el procesos para acceder a los datos del fichero de texto, es recomentable (altamente recomendable diría yo) crear un fichero de esquema.
El fichero de esquema siempre tienen el nombre schema.ini y se encuentra en la misma carperta del origen de datos. En el fichero de esquema se definen:

  • El formato del archivo.
  • El nombre, la longitud y el tipo de cada campo (columnas).
  • El juego de caractreres utilizado en el archivo de datos.
  • Conversiones especiales para los tipos de datos.

Si tuviéramos un archivo similar a este (aunque aquí he utilizado para el ejemplo el separador ‘‘ que no parace muy adecuado):

Sierra eléctrica-1-250
Machete-5-2.70
Detergente-1-10
Delantal-2-7.25
Afilador-3-5
Cortacesped-6-95
Televisor plasma-2-200
Caja clavos-4-12
Ordenador-1-300
Caja lápices-2-11
Paquete folios-1-10

En nuestro archivo schema.ini debemos añadir lo siguiente para conseguir un acceso correcto a los datos:

  • Format=Delimited(-); Para definir nuestro separador de campos
  • ColNameHeader=False;  Puesto que no están definidas dentro del archivo de texto los títulos de las columnas.
  • A continuación nombres y tipos de las columnas.
  • CharacterSet=ANSI; Para definir la codificación del texto.
  • MaxScanRows=0; Define cuantas líneas escanea el motor para determinar el tipo de datos de cada columna. 0 para todas.

Finalmente nuestro archivo de esquema quedará de compleatado de la siguiente manera:

 [Datos.txt]
Format=Delimited(-)
ColNameHeader=False
Col1=Producto char
Col2=Cantidad Integer 
Col3="Precio Total" currency 
MaxScanRows=0
CharacterSet=ANSI

El ejemplo completo para acceder a los datos de un TXT utilizando las opciones del Registro de Windows, se pueden descargar desde aquí.
<Descargar ejemplo>

El ejemplo completo para acceder a los datos de un TXT utilizando las opciones de un archivo de esquema schema.ini, se pueden descargar desde aquí.
<Descargar ejemplo>

Cargar datos de un TXT a un TDataset (utilizando ADO)

viernes, 22 de mayo de 2009 Sin comentarios
Share Button

Una de las muchas posibilidades que ADO provee para acceder a diferentes tipos de datos es la que podemos utilizar para acceder a datos de un fichero de Texto, siempre que estos estén mínimamente organizados.

Básicamente trabajamos con dos tipos de ficheros de texto:

  • Los que los campos son de ancho fijo y por lo tanto no hace falta separador.
  • Aquellos en los que los datos utilizan un separador de campos. Ya sea «punto y coma», «coma» o cualquier otro.

Nuestro primer archivo de datos es como este:

____________________________________________
202548777102120545041
ESTO 1546564LOOTRO
202548345134534545451 EST2 1542344LOOTRO
202548777102120557087 EST3 1546789LOOTRO
752323654273654726534 EST4 2343344UNOMAS
112543456329324792347 EST5 5521973MASAUN
_____________________________________________

He marcado en el primer registro los campos de diferente color para que se diferencien cláramente (4 campos).

Para un archivo de ancho fijo podemos configurar la conexión (TADOConnection) de la siguiente forma:

Provider=Microsoft.Jet.OLEDB.4.0;Data Source=.\;
Extended Properties="text;HDR=Yes;FMT=Fixed";
Persist Security Info=False

Configuramos el proveedor como Jet 4.0, en este caso en directorio de la Base de Datois está definido como .\ (el mismo de la aplicación) y en las propiedades extendidas es donde realmente se define el tipo de archivo y la organización de los datos (Text y Fixed).

Sólo nos queda definir en el mismo directorio el fichero schema.ini donde detallaremos la estructura de las columnas de nuestro fichero. Éste sería un fichero correcto para este caso:

[datos.txt]
Format=FixedLength
ColNameHeader=False
Col1=ID char Width 21
Col2=Nombre char Width 6
Col3=Valor Integer Width 7
Col4=Descripción char Width 6
CharacterSet=ANSI

De esta forma podemos acceder a los datos del fichero mediante un TADOTable y trabajar con ellos utilizando todas las posibilidades que nos brinde este componente.

El ejemplo completo con el código fuente, el fichero de datos y el fichero de esquema se puede descargar desde aquí.

<Descargar ejemplo>

Continuación (parte2); Ficheros con datos delimitados por un separador.

Google Maps en Delphi – I

jueves, 14 de mayo de 2009 6 comentarios
Share Button

Después de las dos entradas sobre Codificacióin Geográfica (*), toca entrar de lleno en la Visualización e Integración de Google Maps en nuestro programa, que por otra parte era el objetivo inicial de esta serie de entradas. La Codificación Geográfica (obtención de Latitud y Loingitud a partir de una dirección postal) era un «escollo» a salvar en nuestro camino, hasta nuestro verdadero objetivo.

(*) Lo normal es tener en nuestros programas/aplicaciones una serie de direcciones postales o direcciones de correo que deseamos mostrar o señalar en un Mapa. Para utilizar esas direccones con la API de Google Maps necesitamos obtener las coordenadas de Latitud y Longitud apropiadas. Para ello ya vimos  aquí (Codificación Geográfica – I) y aquí (Codificación Geográfica – II) cómo Google provee métodos para obtener las coordenadas de posicionamiento a partir de direcciones postales.

La idea para conseguir la integración es sencilla. Utilizar un componente TWebBrowser para mostrar una página HTML. Desde Delphi debemos «modificar» esa página para que muestre lo que nosotros deseemos.

Utilizando una sencilla página HTML con un Script, podemos visualizar el mapa correspondiente a coordenadas de Latitud y Longitud.  Con un componente TWebBrowser podemos visualizar esa página dentro de una aplicación delphi.

La página en sí sólo contiene el Script para Visualizar un Mapa.
Dentro del Script vemos cómo se crea un elemento de tipo «map»:

map = new GMap2(document.getElementById("map"));

Se añaden los controles que queremos visualizar en el mapa; En este caso, la selección para el tipo de mapa y control de zoom y posición grande.

        // Controles del mapa
        map.addControl(new GMapTypeControl());
        map.addControl(new GLargeMapControl());

El tipo de mapa que queremos que inicialmente aparezca (Mapa, visión por satélite o lo que Google llama híbrido).

        // Tipo de mapa
        map.setMapType(G_NORMAL_MAP);

Y por último colocamos la posición (latitud y longitud) donde queremos que el mapa salga centrado.

Pero no acaban aquí (ni mucho menos) las posibilidades de Google Maps. Una de las opciones, que dentro de lo sencillo, es muy interesante, es la inclusión de marcas señalando puntos del mapa o incluso ventanas de información no es mucho más complejo.

Para añadir una nueva marca, basta con conocer el punto exacto donde queremos situarla y utilizar un objeto GMarker para mostrarla en el mapa. En cuanto a la ventana, de información, sólo necesitamos el código HTML necesario para crearla.
En este ejemplo la ventana tiene un código sencillo (texto, imagen y link) que se almacena en una variable.

// VENTANA (HTML)
 var html="<img src="http://neftali.clubdelphi.com/images/imagen_neftali_60x54.jpg" alt="" width="60" height="54" />
" + "by Neftalí -Germán Estévez-  2009 <strong>
<a href="http://neftali.clubdelphi.com"
 target="_blank">http://neftali.clubdelphi.com</a>
</strong>
<a href="http://neftali.clubdelphi.com" target="_blank">
    </a>
";

Para mostrarla basta con que utilicemos en método OpenInfoWindowHtml (cuyo nombre es bastante descriptivo, por cierto ;-D ) de la siguiente forma:

    // Visualización de la mrca
    var point = new GLatLng(41.381655,2.122829);
    var marker = new GMarker(point);
    marker.openInfoWindowHtml(html);

Hay más opciones y basta con echarle una vistazo a la API de Google Maps para conocerlas. Además hay muchos ejemplos de utilización que pueden servir para aclarar los conceptos y para ver más cláramente cómo se utilizan los diferentes elementos.

Todo lo visto hasta aquí, está puesto en práctica en un ejemplo Delphi.

Podéis descargar el código completo (incluye EXE -comprimido UPX-) desde aquí:
<Descargar Ejemplo>

Y aquí acabamos esta entrada, que no será la última. Me dejo para una segunda parte, algunos detalles finales, como interactuar con el mapa y obtener datos de él y fusionar los dos conceptos vistos hasta ahora.

Nos vemos.

(Google Maps – API) Codificación Geográfica – II

jueves, 7 de mayo de 2009 4 comentarios
Share Button

Continuando con la entrada (Google Maps – API) Codificación Geográfica – I, vamos a ver cómo obtener más datos de este servicio de Google Maps.

Si habéis revisado el ejemplo sencillo que vimos en la primera entrada, os habréis fijado que hay un parámetro al final de la dirección del que no hemos hablado. Se trata del formato de salida para los datos. Los formatos que acepta el API de Google Maps son los siguientes:

  • XML: Formato extendido en XML/KLM.
  • KLM: Formato extendido en KL; Se diferencia del anterior en los tipos MIME.
  • JSON: La salida de este formato se guarda en formato de objeto JSON (Javascript Object Notation).
  • CSV: Formato comprimido separado por comas (el que se utilizó en el ejemplo).

Para utilizar un formato diferente, basta con añadir al final de la línea de petición el formato deseado:

&amp;output=csv    &amp;output=klm     &amp;output=xml     &amp;output=json

Retomando el tema inicial, vamos a utilizar uno de los formatos extendidos para obtener más información de una dirección dada, además de las coordenadas de Latitud y Longitud.
Si realizamos una petición HTTP utilizando el formato json obtenemos una respuesta como esta por parte del servidor:

{
  "name": "Barcelona,spain",
  "Status": {
    "code": 200,
    "request": "geocode"
  },
  "Placemark": [ {
    "id": "p1",
    "address": "Barcelona, España",
    "AddressDetails":
       {"Country":
        {"CountryNameCode": "ES","CountryName": "España","AdministrativeArea":
          {"AdministrativeAreaName": "CT","SubAdministrativeArea":
            {"SubAdministrativeAreaName": "Barcelona","Locality":
              {"LocalityName": "Barcelona"}}}},"Accuracy": 4},
    "ExtendedData": {
      "LatLonBox": {
        "north": 41.4682658,
        "south": 41.3199988,
        "east": 2.2261223,
        "west": 2.0524766
      }
    },
    "Point": {
      "coordinates": [ 2.1699187, 41.3879170, 0 ]
    }
  } ]
}

Utilizando un sencillo «parser» con este resultado podemos extraer la información para poder utilizarla en nuestros programas.

El ejempo que se ve en la imagen puede descargarse desde aquí.

He creado además una clase derivada de TThread que permite acceder a la imagen. Me ha parecido que en futuros usos me va a ser más útil así, aunque realmente todavía no la he probado en un entorno «multithread».

TThreadGeoCode = class(TThread)
  private
    FDireccion: string;
    FAllText: TStrings;
    FGeoStatusCode: Integer;
    FGeoAddress: string;
    FGeoCountryCode: string;
    FGeoCountryName: string;
    FGeoAdminArea: string;
    FGeoAddressLine: string;
    FGeoLocalityName: string;
    FGeoPostalCode: string;
    FGeoAccuracy: integer;
    FGeoLatitud: string;
    FGeoLongitud: string;

    procedure _ExtractResult();
    function _StatusCodeToStr(ACode:Integer):string;
    function _AccuracyToStr(ACode:integer):string;
  protected
    procedure Execute; override;
  public
    destructor Destroy; override;
    constructor Create(ADireccion:string;
                       ThreadPriority:TThreadPriority=tpNormal);

    // Direccion a buscar
    property Direccion:string read FDireccion write FDireccion;
    // Salida
    property AllText:TStrings read FAllText write FAllText;
    // propiedades de posición
    property GeoStatusCode:Integer read FGeoStatusCode write FGeoStatusCode;
    property GeoAddress:string read FGeoAddress write FGeoAddress;
    property GeoCountryName:string read FGeoCountryName write FGeoCountryName;
    property GeoCountryCode:string read FGeoCountryCode write FGeoCountryCode;
    property GeoAdminArea:string read FGeoAdminArea write FGeoAdminArea;
    property GeoAddressLine:string read FGeoAddressLine write FGeoAddressLine;
    property GeoLocalityName:string read FGeoLocalityName
       write FGeoLocalityName;
    property GeoPostalCode:string read FGeoPostalCode write FGeoPostalCode;
    property GeoAccuracy:integer read FGeoAccuracy write FGeoAccuracy;
    property GeoLatitud:string read FGeoLatitud write FGeoLatitud;
    property GeoLongitud:string read FGeoLongitud write FGeoLongitud;

  published

  end;

Y aun podemos extraer más, ya que si la dirección es ambigua (pero correcta), google puede devolver más de un resultado. De esta forma, con una dirección tipo:

Obtendremos por parte de Google la lista de direcciones correcta que pueden corresponder a esta calle. Cada una de ellas presenta la estructura mostrada anteriormente variando el identificador (id):

  "Placemark": [ {
    "id": "p1",
...
    "id": "p2",
...

Sucesivamente para las distintas direcciones correctas y posibles para esa combinación: Alava, Burgos, Ciudad Real, Cuenca,…

A partir de aquí, no costaría mucho para modificar el ejemplo anterior de forma que se puedan extraer y mostrar los datos, no sólo de la primera dirección, sino de todas las devueltas en la petición.

Hasta aquí estos dos artículos (I y II), que sirven a modo de introducción y prefacio del próximo que estoy preparando.

Está claro, que la idea final y el objetivo que persigo es poder integrar en un programa Delphi las características de Google Maps. El problema actual es, que en nuestras Base de Datos/programas normalmente no tenemos codificadas nuestras direcciones con Latitud/Longitud, así aque para llegar a nueastro objetivo antes tenemos que conseguir estos dos parámetros. Ahí es donde nos és útil el concepto de «Codificación geográfica».

Una vez que tenemos Latitud y Longitud para nuestra dirección, ya podemos avanzar un paso más…

(Google Maps – API) Codificación Geográfica – I

martes, 5 de mayo de 2009 17 comentarios
Share Button

Codificación Geográfica Se conoce como Codificación Geográfica, al proceso o sistema de transformar una dirección «o similar» en coordenadas geográficas de Longitud y Latitud, para posteriormente ser utilizadas en sistemas de posicionamiento (por ejemplo).

El API de Google incluye este servicio, que es el que se utiliza cuando buscamos una dirección desde Google Maps.

Normalmente este acceso se hace desde una página web con peticiones HTTP; Lo que vamos a ver es cómo integrar este servicio en un programa Delphi.

Un poco más arriba he comentado que la codificación geográfica se basa en dar una entrada (dirección) para obtener unas coordenadas. También he añadido el «similar», puesto que en el caso de Google Maps, podemos dar como entrada otras cosas que expresamente no son una dirección.

Así, podemos utilizar como dirección:

  • 08901,Spain
  • c/Balmes,214,Barcelona,Spain
  • Sevilla,Spain
  • Alhambra,Granada,Spain

Para hacer una prueba de lo que podemos obtener, basta con utilizar un componente TidHTTP de las Indy para realizar la petición. Coloca un componente TidHTTP en un formulario y un botón con el siguiente código:

procedure TForm1.btn1Click(Sender: TObject);
const
  STR_WEB = 'http://maps.google.com/maps/geo?q=';
  STR_OUT = '&amp;output=csv';    // formato de salida
var
  Stream: TStringStream;
  Str, res:string;
begin
  // ini
  Stream := TStringStream.Create('');
  // proteccion para liberar
  try
    Str := STR_WEB + edt1.Text + STR_OUT;
    idhttp2.Get(Str, Stream);
    edt2.Text := Stream.DataString;
  finally
    FreeAndNil(Stream);
  end;
end;

Una imagen de lo que obtenemos  una vez realizada la petición es la siguiente:

Se puede descargar el código completo de este ejemplo desde aquí.

Y lo que obtenemos como respuesta en este caso es:

200,8,41.3979638,2.1515206

Los dos últimos parámetros son los que buscabamos y nos indican las coordenadas de ese punto (latitud y longitud).

Para comprobar que so correctas, basta con ir a la web de Google Maps, copiar estas dos coordenadas en el cuadro de búsqueda y pulsar sobre el botón de <Buscar en el Mapa>.

A parte de esto, Google Maps nos devuelve, en este caso, dos números más; El primero corresponde al un «código de Estado» o «código de retorno» de la consulta que hemos realizado; En él se devuelve información por parte del servidor. Y el segundo es lo que se conoce como «Accuracy» o «Exactitud», que corresponde justamente a eso; Al nivel de exactitud que Google asigna a la respuesta, segun la dirección que le hemos dado.

El código de retorno, en este caso, es un 200, que corresponde a la constante «G_GEO_SUCCESS» (consulta correcta).

El nivel de exactitud, en este caso, es un 8 (máxima precisión).

Los niveles de exactitud con lo que trabaja Google Maps son los siguientes:

  • 0:  Ubicación desconocida.
  • 1:  Precisión a nivel de país.
  • 2:  Precisión a nivel de región.
  • 3: Precisión a nivel de subregión.
  • 4: Precisión a nivel de ciudad o pueblo.
  • 5: Precisión a nivel de código postal.
  • 6: Precisión a nivel de calle.
  • 7: Precisión a nivel de intersección.
  • 8: Precisión a nivel de dirección.

Así en el ejemplo que hemos visto antes obteníamos una Exactitud de 8 (a nivel de dirección), mientras que si íntroducimos una dirección del tipo;

<08905,Barcelona,Spain>

Obtendremos un código 5 (a nivel de C.P.)

NOTA: La descripción detallada de los «códigos de precisión» se puede encontrar aquí; Y la descripción detallada de todos los «códigos de retorno» se puede encontrar aquí.


Estamos de vuelta!!!

lunes, 4 de mayo de 2009 4 comentarios
Share Button

¡¡Bueno, ya estamos de vuelta!!
Después de unos días con poblemas en el servidor y para restaurar copias de seguridad, parece que todo está correcto.

Tal vez, al restaurar las copias se haya perdido algun enlace/adjunto/…
Si detectáis alguno de estos problemas, os pediría que me lo hiciérais saber, por favor.

Sea con un comentario en esta entrada o vía mail.

Gracias y un saludo.

P.D: Como compensación (;-D) estos días he estado peleándome con Google Maps, GeoPosicionamiento y las API’s de GeoCode de Google. Cuando tenga algo «maquetado» prometo pulicar sobre el tema.

(Update) Componente TLogDisk

lunes, 23 de marzo de 2009 2 comentarios
Share Button

Actualizado el componente TLogDisk a la versión 1.2.

  • Añadido evento nuevo para detectar cuando se añade un nuevo elemento al Log. El evento incluye la cadena añadida.
  • Ampliados los procedimientos para añadir datos al Log. Incluyen (sobrecarga) nuevos tipos de datos y son más flexibles que en la versión anterior.
  • Añadido evento que «salta» al guardar el Log a disco. Útil para detectar el guardado y poder acceder en disco a los datos completos.

Imagen del componente

1
2
3
4
    LogDisk.Log('Grabado el fichero: ', FileSaveAs1.Dialog.FileName);
    LogDisk.Log('Fichero Guardado', True);
    LogDisk.Log(mmLog.Lines);
    LogDisk.SaveToDisk;

Descripción completa del componente y descarga aquí.

Categories: Componentes Tags: , ,

Redimensionar una imagen (Antialiasing)

lunes, 9 de marzo de 2009 13 comentarios
Share Button

Hace unos días nos encontramos con el problema (no muy grande ;-D ) de añadir a una aplicación delphi existente la posibilidad de incluir una imagen seleccionada por el usuario. A priori la imagen era un JPG,  de la cual se debía crear una miniatura (thumbnail) a unas dimensiones determinadas (180 x 115) y ambas debían subir a un directorio determinado. Ningun problema.  Aquí mismo había un par de procedimientos de Domingo Seoane para redimensdionar una imagen.
En concreto modificando un poco el procedimiento Proporcional conseguí lo que necesitaba. Que si la imagen original no era exactamente de las mismas proporciones que la que necesitaba (miniatura) esta rellenara con un color «neutro» (en este caso el blanco) los bordes laterales.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Esta otra mantiene la relacion entre alto y ancho
procedure Proporcional(Imagen: TGraphic; Ancho, Alto: Integer);
var
  Bitmap: TBitmap;
  Rect:TRect;
begin
  Bitmap:= TBitmap.Create;
  try
    Bitmap.Width:= Ancho;
    Bitmap.Height:= Alto;
 
    /// Calculos para que quede proporcional
    if  (Ancho/Imagen.Width) &lt; (Alto/Imagen.Height) then  begin
      Alto:= Trunc((Ancho*Imagen.Height)/Imagen.Width);
    end
    else begin
      Ancho:= Trunc((Imagen.Width*Alto)/Imagen.Height);
    end;
 
    // posición nueva
    // Hay que centarla para que queden márgenes iguales a ambos lados
    Rect.Left := ((Bitmap.Width - Ancho) div 2);
    Rect.Top := ((Bitmap.Height - Alto) div 2);
    Rect.Right:= Rect.Left + Ancho;
    Rect.Bottom := Rect.Top + Alto;
 
    // Color neutro para márgenes
    Bitmap.Canvas.Brush.Color := clRed;
    // copiar
    Bitmap.Canvas.FillRect(Bitmap.Canvas.ClipRect);
    Bitmap.Canvas.StretchDraw(Rect,Imagen);
    Imagen.Assign(Bitmap);
  finally
    Bitmap.Free;
  end;
end;

Hice un par de pruebas con imágenes y el resultado no fue exactamente lo que yo esperaba. El procedimiento era correcto, y funcionaba bien, pero las imagenes en minuatura presentaban Aliasing. Y siendo las miniaturas bastante pequeñas el efecto se notaba bastante.

Reducir tamaño de una imagen

Imagen generada

Ampliando un poco la imagen y comparándola con una generada con cualquier programa sencillo de retoque fotográfico se apreciaba bastante la diferencia entre ambas.

Comparación de las imágenes
Esto es lo que se conoce como aliasing. Se pueden encontrar múltiples definiciones y explicaciones de este problema en Internet (wiki), así que no explicaré aquí de que se trata.


APLICAR ANTIALIASING

La teoría dice que esto se soluciona aplicando algoritmos de altializasing, así me he puesto a hacer unas pruebas a ver qué resultado obtenía.Mi idea es modificar el color de cada uno de los pixels de la imagen teniendo en cuenta en color de los pixels que hay a su alrededor.

Qué pixels seleccionemos para ello y cuantos (distancia) determinará que el resultado sea más o menos satisfactorio, pero también afectará al tiempo de cálculo. Por lo que he leído esto es lo que se conoce como Supersampling/Multisampling.
Un ejemplo de diferentes selecciones de pixels se puede ver en la imagen siguiente:

En cada uno de estos casos se variará el color del pixel central teniendo en cuenta los colores de los pixels que hay a su alrededor.

Tipos de selección

A partir de aquí me he propuesto hacer algunas pruebas (sencillas) para comprobar si en los resultados se notaban cambios a simple vista.


PRUEBAS DE ALGORITMOS

Para los ejemplos he realizado una imagen sencilla, con varias líneas inclinadas, donde se aprecian bastantes «dientes de sierra» y algunas circunferencias. La imagen inicial es la que se ve en la figura siguiente con un tamaño inicial de 457 x 273 pixels.

La idea es reducir el tamaño de esa imagen hasta la mitad (más o menos) y a una cuarta parte aplicando antes un algoritmo de antialiasing sencillo escogiendo diferentes puntos para modificar el color de los pixels.

Para la reducción de tamaño, he utilizado un procedimiento estandard para reducir el tamaño de imágenes BPL utilizando (StretchDraw), pero en este caso, antes de hacer la reducción he probado a aplicar los algoritmos de AntiAliasing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Esta cambia el alto y ancho, estirando la imagen si es necesario
procedure Redimensionar(Imagen:TBitmap; Ancho, Alto: Integer);
var
  Bitmap: TBitmap;
begin
 
  Bitmap:= TBitmap.Create;
 
  // Aplicamos antialiasing
  Antialiasing(Imagen, Bitmap);
  Imagen.Assign(Bitmap);
 
  // reducir
  try
    Bitmap.Width:= Ancho;
    Bitmap.Height:= Alto;
    Bitmap.Canvas.StretchDraw(Bitmap.Canvas.ClipRect, Imagen);
    Imagen.Assign(Bitmap);
  finally
    Bitmap.Free;
  end;
end;

Para modificar el color lo que he probado es a sumar los colores de los puntos escogidos al del pixel actual y luego hacer la media para obtener un color resultante; Así por ejemplo, para calcular el nuevo color de un pixel teniendo en cuenta el pixel superior y el inferior de la misma columna, utilizo un código como este:

1
2
3
4
5
6
  // R1 es el componente Red del pixel actual y R2 y R3 los del sup. e inferior.
  R1:=Round(R1 + R2 + R3 ) div 3;
  G1:=Round(G1 + G2 + R3 ) div 3;
  B1:=Round(B1 + B2 + b3 ) div 3;
  // color resultante
  Result := RGB(R1,G1,B1);

Lo que he hecho en las pruebas es aplicar a la imagen, esta mismo procedimiento, pero teniendo en cuenta diferentes selecciones de puntos.

  1. Seleccionando 2 puntos; Superior e inferior.
  2. Seleccionando 4 puntos; Superior, inferior, izquierda y derecha.
  3. Seleccionando 8 puntos. Los 8 puntos que hay alrededor del pixels actual.
  4. Seleccionando 8 puntos y aplicando ponderación al actual. Utilizar los 8 pixels que hay alrededor del actual, pero aplicando más peso (más valor) al pixels actual (a su color) que a los del resto. En mi caso el pisel actual tiene un peso de 4, mientras que el resto queda con un pero 1.

En un primer ejemplo he aplicado los dos primeros (2 y 4 pixels), pensando que no habría grandes cambios y la verdad es qe me ha sorprendido, ya que tomando tan sólo 2 puntos ya se notan algunos cambios y tomando 4 las dioferencias ya son bastante apreciables.

Descargar ejemplo 1

El resultado obtenido por este ejemplo es el siguiente:

La imagen superior es el original (redimensionado tal como lo hace delphi), y las dos inferiores son a las que se les ha aplicado el procedimiento de Antialiasing antes de redimensionarlas. En una escogiendo 2 los pixels laterales y en la otra los 4 pixels que rodean al del cálculo. Superior, inferior,  izquierdo y derecho.

Como se puede ver, con dos pixels únicamente, ya hay zonas (1, 3 y 5) donde se aprecian diferencias. Seguramente en estas más que en otras porque la selección de pixels no es homogénea (de ahí que en las líneas horizontales se aprecie más mejora).

Cuando se aplica el algoritmo teniendo en cuenta los 4 pixels de alrededor, se aprecia (2, 3, 4 y 5) ya bastantes diferencias.

En el segundo ejemplo he aplicado los 4 casos comentados antes.

Descargar ejemplo 2

El resultado de este segundo ejemplo es el siguiente:

En este caso entre los dos últimos no se aprecia diferencia visible, pero sí entre escoger 4 puntos y 8 puntos. Ver los puntos marcados como 1 y 3.

Dado que no se aprecian grandes diferencias entre los dos últimos, he integrado en un último ejemplo el redimensionado y el procedimiento de Antialiasing, de forma que este segundo se realice de forma automática.

Descargar el ejemplo 3

CONSIDERACIONES FINALES

Aunque el ejemplo que se ha desarrallo aquí y el procedimiento parece que funcionan de manera aceptable, hay que tener en cuenta otros factores a la hora de realizar un algoritmo más completo.

En nuestro caso la distancia de pixel utilizada (muestreo) es una distancia 1; es decir, hemos seleccionado los pixels que hay más cercanos al que vamos a modificar. Podemos seleccionar pixels de distancias mayores (2 y 3); De esta forma el resultado puede ser más correcto, aunque esto también tiene que ver con el porcentaje de reducción del tamaño.

No es lo mismo reducir una imagen a la mitad de su tamaño, que al 10% del tamaño original.  Segun el caso el resultado puede ser mejor o peor si seleccionamos pixels a distancias 1,2 y 3 del pixels a calcular.

El procedimiento final para BMP’s quedaría así:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
type
  TRGBTripleArray = array[0..32767] of TRGBTriple;
  PRGBTripleArray = ^TRGBTripleArray;
...
// Esta cambia el alto y ancho, estirando la imagen si es necesario
procedure Redimensionar(Imagen:TBitmap; Ancho, Alto: Integer);
var
  Bitmap: TBitmap;
  //····························································
  // Procedimiento de Antialiasing con Distancia=1
  procedure Antialiasing(bmp1, bmp2:TBitmap);
  var
    r1,g1,b1:Integer;
    Y, X, j:integer;
    SL1, SL2, SL3: PRGBTripleArray;
  begin
 
    // Tamaño del bitmap destino
    bmp2.Height := bmp1.Height;
    bmp2.Width := bmp1.Width;
    // SCANLINE
    SL1 := bmp1.ScanLine[0];
    SL2 := bmp1.ScanLine[1];
    SL3 := bmp1.ScanLine[2];
 
    // reorrido para todos los pixels
    for Y := 1 to (bmp1.Height - 2) do begin
      for X := 1 to (bmp1.Width - 2) do begin
        R1 := 0;  G1 := 0; B1 := 0;
        // los 9 pixels a tener en cuenta
        for j := -1 to 1 do begin
          // FIla anterior
          R1 := R1 + SL1[X+j].rgbtRed    + SL2[X+j].rgbtRed    + SL3[X+j].rgbtRed;
          G1 := G1 + SL1[X+j].rgbtGreen  + SL2[X+j].rgbtGreen  + SL3[X+j].rgbtGreen;
          B1 := B1 + SL1[X+j].rgbtBlue   + SL2[X+j].rgbtBlue   + SL3[X+j].rgbtBlue;
        end;
        // Nuevo color
        R1:=Round(R1 div 9);
        G1:=Round(G1 div 9);
        B1:=Round(B1 div 9);
        // Asignar el nuevo
        bmp2.Canvas.Pixels[X, Y] := RGB(R1,G1,B1);
      end;
      // Siguientes...
      SL1 := SL2;
      SL2 := SL3;
      SL3 := bmp1.ScanLine[Y+1];
    end;
  end;
  //····························································  
begin
 
  Bitmap:= TBitmap.Create;
 
  // Aplicamos antialiasing
  Antialiasing(Imagen, Bitmap);
  Imagen.Assign(Bitmap);
 
  // reducir
  try
    Bitmap.Width:= Ancho;
    Bitmap.Height:= Alto;
    Bitmap.Canvas.StretchDraw(Bitmap.Canvas.ClipRect, Imagen);
    Imagen.Assign(Bitmap);
  finally
    Bitmap.Free;
  end;
end;