Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
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.
// Esta otra mantiene la relacion entre alto y anchoprocedure 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 proporcionalif(Ancho/Imagen.Width) <(Alto/Imagen.Height)thenbegin
Alto:=Trunc((Ancho*Imagen.Height)/Imagen.Width);endelsebegin
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)div2);
Rect.Top:=((Bitmap.Height- Alto)div2);
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;
// 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) < (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.
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.
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.
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.
// Esta cambia el alto y ancho, estirando la imagen si es necesarioprocedure Redimensionar(Imagen:TBitmap; Ancho, Alto:Integer);var
Bitmap: TBitmap;begin
Bitmap:= TBitmap.Create;// Aplicamos antialiasing
Antialiasing(Imagen, Bitmap);
Imagen.Assign(Bitmap);// reducirtry
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;
Bitmap.Canvas.StretchDraw(Bitmap.Canvas.ClipRect, Imagen);
Imagen.Assign(Bitmap);finally
Bitmap.Free;end;end;
// 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 )div3;
G1:=Round(G1 + G2 + R3 )div3;
B1:=Round(B1 + B2 + b3 )div3;// color resultante
Result := RGB(R1,G1,B1);
// 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.
Seleccionando 2 puntos; Superior e inferior.
Seleccionando 4 puntos; Superior, inferior, izquierda y derecha.
Seleccionando 8 puntos. Los 8 puntos que hay alrededor del pixels actual.
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.
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.
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.
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.
type
TRGBTripleArray =array[0..32767]of TRGBTriple;
PRGBTripleArray =^TRGBTripleArray;...// Esta cambia el alto y ancho, estirando la imagen si es necesarioprocedure Redimensionar(Imagen:TBitmap; Ancho, Alto:Integer);var
Bitmap: TBitmap;//····························································// Procedimiento de Antialiasing con Distancia=1procedure 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 pixelsfor Y :=1to(bmp1.Height-2)dobeginfor X :=1to(bmp1.Width-2)dobegin
R1 :=0; G1 :=0; B1 :=0;// los 9 pixels a tener en cuentafor j :=-1to1dobegin// 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 div9);
G1:=Round(G1 div9);
B1:=Round(B1 div9);// 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);// reducirtry
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;
Bitmap.Canvas.StretchDraw(Bitmap.Canvas.ClipRect, Imagen);
Imagen.Assign(Bitmap);finally
Bitmap.Free;end;end;
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;
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
El componente TLogDisk sirve para facilitar el trabajo a un programador que necesite añadir un Log a sus programas. Basta con “soltar” el componente en un formulario del programa y activarlo.
Automáticamente el componente crea el fichero de Log, almacena datos de la aplicación (cabecera) y ofrece al programador métodos/rutinas para añadir datos de diferentes tipos al Log.
Algunas propiedades con las que cuenta el componente:
FileNameFormat: Permite que el componente cree el fichero de los utilizando diferentes formatos en el nombre.
FileSavePath: Permite definir la ubicación donde el componente guardará el fichero de log (mismo directorio de la aplicación o directorio de Logs).
LogWithTime: Activando esta propiedad el componente añade una marca de tiempo a los diferentes apuntes del Log.
MaxLogFiles: Controla el número máximo de ficheros de Log que se mantienen en el directorio de Logs. “O” para un número limitado; Con cualquier otro valor el componente se encarga de borrar los ficheros de Log más antiguos.
El código para añadir elementos al Log.
LogDisk.Log('Grabado el fichero: ', FileSaveAs1.Dialog.FileName);
LogDisk.Log('Grabado el fichero: ', FileSaveAs1.Dialog.FileName);
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Permite seleccionar componentes visuales (en Runtime) que haya en un form de forma visual como se hace con las imágenes en los programas de diseño o con los controles en el IDE de Delphi.
Además se pueden mover y redimensionar los controles seleccionados. Basta con soltar el control en el formulario, asignarle el control que se desea seleccionar/mover/redimensionar y activarlo.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Este componente permite detectar en la aplicación donde se utiliza la inactividad de teclado y de ratón pasado un determinado tiempo (de forma similar a cómo se activa el salvapantallas del sistema).
El tiempo que se quiere detectar de inactividad es configurable por el usuario en minutos y segundos. Basta con activar el componente, configurar el tiempo de inactividad; Pasado este tiempo de inactividad “saltará” un evento de aviso.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Permite modificar algunos aspectos en la visualización de un Grid estandard.
No deriva del DBGrid, si no que funciona como complemento al componente estandard de Delphi. (En construcción…)
La propiedad Flat permite modificar el aspecto del DBGrid.
Implementa métodos para mejorar el pintado en el Grid de las celdas de tipo Booleano y Memo;
Sustituye el texto por un checkbox y (MEMO) que aparece en los campos Memo por el texto del campo.
Además provee eventos para modificar colores del DBGrid:
OnPaintCell: Para pintar xeldas de un determinado color.
OnPaintCellExt: BIS del anterior con más parámetros.
OnPaintColumn: Permite pintar una columna de color.
OnPaintRow: Permite pintar una file de color.
OnPaintCellImage: Permite pintar imágenes en una celda.
OnChangeTitleCell: Modificar las celdas de título.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Permite gestionar de forma sencilla (sin código) algunas de las combinaciones de teclas más utilizadas en los formularios. Basta con soltar el componente sobre el formulario y activar las propiedades deseadas segun el comportamiento que queramos.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Permite guardar la posicion y tamaño de todos los componentes que se encuentran en el formulario. Utilizapara ello un fichero INI.
Basta con colocar el componente en el formulario y activarlo; Cuando la aplicación se cierra graba la posición/tamaño y al volver a ejecutarla los recupera de forma automática.
NOTA: Es indispensable para grabar la posición y tamaño de un componente que tenga definida la propiedad Name (para controles creados en RunTime).
Historial:
(version 1.4): Añadido un evento (OnNotExistComponent) que facilita el trabajo con componentes creados en tiempo de ejecución (runtime); Se han revisado los componentes para trabajar con Delphi 2005/2006.
(versión 1.3): El componente permite guardar propiedades de tipo TStrings (Lines, Items,…)
(versión 1.2): Para la versión 1.2 se ha añadido la posibilidad de poder añadir propiedades por parte del usuario que se grabarán (propiedad SavedProperties).
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…