Archivo

Entradas Etiquetadas ‘Graficos’

Testeando XE2… (Estilos)

martes, 13 de septiembre de 2011 15 comentarios

Otra de las cosas de las que ya he hablado y que llaman la atención en esta nueva versión de Delphi, son los estilos. Es una primera implementación y es mejorable en muchos aspectos, pero es una paso más y un avance más para poder personalizar nuestras aplicaciones.

Hay que decir que el funcionamiento general es muy sencillo.

estilos1

Estaría bien, por ejemplo, mejorar el generador de estilos para que se pudieran importar estilos desde otros archivos similares. En mi caso el generador se ha mostrado “poco estable” cuando he intentado hacer algo más complejo (cambio de imágenes) que intentar modificar los colores del estilo cargado.

Por internet podéis descargar varios videos sobre el uso del «VCL Style Designer», que así es como se llama la herramienta para crear y modificar los estilos. Aunque es bastante sencilla e intuitiva. Una vez cargado el estilo, podemos modificar los diferentes elementos. Se puede exportar la imagen del estilo completo, modificarla desde un editor externo y luego volverla a cargar, para poder así asignar elementos de esa nueva imagen a los diferentes componentes. Se pueden cargar varias imágenes y combinar elementos de varias de ellas; Pero como he dicho en estas operaciones se ha mostrado «inestable»

Los componentes que derivan de los estándar cogen de forma correcta los estilos.  Lo que no he podido probar (y no parece que exista ahora solución -digo esto sin habérmelo mirado mucho-) es qué posibilidades hay para componentes «externos» que no deriven de uno estándar.

Una creado o modificado un estilo podemos cambiar los campos que identifican al creador, desde la misma herramienta de creación (autor, versión, página web y correo electrónico). Mientras que la previsualización desde el «Style Designer» funciona bastante bien (salvo cuando hay algun error que no muestra nada -tampoco el error-), desde el IDE de Delphi la previsualización es defectuosa (podrían haber utilizado el mismo método que desde el Designer) o «floja» por decirlo así.

En resumen, aunque el sistema tiene algunos detalles todavía faltos de pulir, en general me parece un acierto el poder dotar de esta posibilidad a las aplicaciones que se realicen. El sistema es muy sencillo de utilizar y aunque tiene algunas limitaciones, funciona bastante bien.

Actualmente al cargar instalar Delphi XE2 contamos contamos con 5 estilos que podemos utilizar.

Desde la web de Rodrigo podeís descargar algunos que él ya ha creado y es de esperar que los miembros de la comunidad vayan «colgando» los suyos propios a medida que los vayan creando (para eso supongo que se ha distribuido). Desde ahí mismo podéis consultar la explicación detallada de cómo crear nuevos estilos a partir de los existentes.

El código para cargar el estilo es bastante simple a partir de su nombre:

Var
  Estilo:TStyleManager;
  i:integer;
begin
  Estilo := TStyleManager.Create;
  try
    i := Random(6);
    Estilo.SetStyle(lEstilos[i]);
  finally
    Estilo.Free;
  end;</p>

En mi caso cargo los estilos de forma aleatoria a partir de un array donde los tengo definidos.

// Ini estilos</p>
lEstilos[0] := 'Windows';
lEstilos[1] := 'Aqua Graphite';
lEstilos[2] := 'Aqua Light Slate';
lEstilos[3] := 'Emerald Light Slate';
lEstilos[4] := 'Golden Graphite';
lEstilos[5] := 'Ruby Graphite';</p>

Hasta aquí esta primera visión. En general me ha parecido positivo. Algunos aspectos por pulir, pero creo que es un detalle más, que hace subir puntos a esta versión.

Un saludo.

Categories: Ejemplos, XE2 Tags: , , ,

Unir varias imágenes (TImage) en una sóla

jueves, 7 de octubre de 2010 Sin comentarios

Hay un ejemplo antiguo en la página en la sección de ejemplos, titulado «Trocear un bitmap en n imágenes a modo de puzle». Hoy ha salido en los foros una pregunta sobre la operación contraria, ¿Cómo unir varios bitmaps (TImage) en una única imagen?

Puede ser algo trivial para los que llevamos un tiempo en esto, pero no tanto para los que empiezan; Reconozco que yo al principio también me liaba con los Canvas, los Rect, los ClientRect y no sabía cual copiar en cada cual.  ;-)

El código para unir cuatro imágenes en una sóla formando un cuadro, es muy sencillo y sería algo así:

var
  ARect:TRect;
  path:string;
begin
 
  // Añadir los 4 canvas a la nueva imagen
  ARect.Left := 0;  ARect.Top := 0; ARect.Right := 48; ARect.Bottom := 48;
  imgDestino.Canvas.CopyRect(ARect, img1.Canvas, img1.ClientRect);
  ARect.Left := 48;  ARect.Top := 0; ARect.Right := 92; ARect.Bottom := 48;
  imgDestino.Canvas.CopyRect(ARect, img2.Canvas, img2.ClientRect);
  ARect.Left := 0;  ARect.Top := 48; ARect.Right := 48; ARect.Bottom := 92;
  imgDestino.Canvas.CopyRect(ARect, img3.Canvas, img3.ClientRect);
  ARect.Left := 48;  ARect.Top := 48; ARect.Right := 92; ARect.Bottom := 92;
  imgDestino.Canvas.CopyRect(ARect, img4.Canvas, img4.ClientRect);
 
  // Path para grabarla a disco
  path := ChangeFileExt(Application.ExeName, '.bmp');
  imgDestino.Picture.SaveToFile(path);
  MessageDlg('Se ha guardado la imagen con el nombre: ' +
             path, mtInformation, [mbOK], 0);

Si en lugar de unirlas formando un cuadro, se quieren unir de otra forma (las 4 en línea, por ejemplo), basta con cambiar las coordenadas de destino.

Si hubiera que cambiar el tamaño final, por ejemplo para reducirlo hasta el de las imágenes originales, se podrían aplicar técnicas de antialiasing al redimensionar.

Descargar ejemplo.

Un saludo.

Categories: Delphi, Ejemplos Tags: ,

Google Maps en Delphi – II

lunes, 29 de junio de 2009 37 comentarios

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.

 

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

jueves, 7 de mayo de 2009 4 comentarios

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

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


Redimensionar una imagen (Antialiasing)

lunes, 9 de marzo de 2009 13 comentarios

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;

Trocear un Bitmap a modo de puzzle en n imagenes

domingo, 2 de marzo de 2008 Sin comentarios

Éste ejemplo muestra cómo «trocear» una imagen (un bitmap en éste caso) en n pequeñas imágenes a «modo de cudrícula»; El número de imágenes (de ancho y de alto) se puede configurar y finalmente las n partes de la imagen se guardan en disco.

Imagen del ejemplo

Las imágenes resultantes de la división se guardan en disco.

Download Descargar ejemplo

Categories: Delphi, Ejemplos Tags: ,

Ejemplo de tratamiento de «Planos y figuras»

viernes, 4 de enero de 2008 5 comentarios

Es bantante común encontrarse en programación con aplicaciones que requieren una interface del tipo «plano y figuras». Son aquellos programas cuya entrada de datos se realiza a partir de un formulario que presenta un plano y donde se deben poder «colocar» elementos/objetos que después se utilizarán en el programa.

Planos de ejemplo

En éstos proyectos suele haber dos «modos de trabajo» básicos:

  • Modo Administrador: Se utiliza para configurar el escenario de trabajo. Seleccionar un Mapa, y crear los objetos, con las propiedades que luego utilizará el programa. Un mismo programa puede trabajar con varios escenarios.
  • Modo Explotación: Es el modo en que se trabaja con el programa normalmente; Se basa en una o varias configuraciones que se han creado anteriormente en el «modo administrador».

Como ayuda a éste tipo de Interfaces de cara al usuario, se han creado los componentes TSaveComps y TSelectOnRuntime que puedes encontrar en la sección de componentes de ésta página. Y como muestra para éstos dos componentes se ha creado ésta demo. Muestra cómo sería su utilización en una interface sencilla, que en éste caso simula un restaurante. Muestra el funcionamiento básico de ambos modos de trabajo.

Imagen de la demo

Download Descargar ejemplo