Tbitmap.scanline es una propiedad indexada de solo lectura que devuelve un puntero a una fila de pixeles de un bitmap.
Es la forma más rápida de acceder a los píxeles de una imagen aunque depende del formato de mapa de bits que se establezca desde la propiedad Pixelformat de la unit Graphics.
TPixelFormat = (pfDevice, pf1bit, pf4bit, pf8bit, pf15bit, pf16bit, pf24bit, pf32bit, pfCustom);
Ejemplo de carga un bitmap:
VAR
bmp: tbitmap;
BEGIN
bmp := tbitmap.Create;
TRY
IF OpenDialog1.Execute THEN
BEGIN
bmp.LoadFromFile(OpenDialog1.FileName);
bmp.PixelFormat := pf24bit;
Invalidate; { Mostrar la imagen }
END;
FINALLY
bmp.Free;
END;
END;
Cuando
creamos un mapa de bits cada pixel se inicializa al máximo valor, es
decir a 255 por eso el mapa de bits es blanco, ese el formato de pixel
predeterminado.
Un pixel en un mapa de bits de 24 bits se describe con 3 valores rojo, verde y azul, que se almacenan en la memoria en orden inverso: azul, verde y rojo.
El puntero a la matriz de bytes que hace scanline se vería de la siguiente forma:
Para obtener el resultado anterior hay que asignar el resultado de Scanline a un puntero de bytes: pByteArray
VAR
p: PByteArray;
BEGIN
p := FImage.ScanLine[0];
END;
Por ejemplo para poner el primer pixel de la primera fila de la imagen de color negro y el segundo de color blanco habría que hacer:
VAR
p: PByteArray;
BEGIN
p := FImage.ScanLine[0]; { lee la primera fila }
p[0] := 0; { primer pixel de color negro }
p[1] := 0;
p[2] := 0;
p[3] := 255; { segundo pixel de color blanco }
p[4] := 255;
p[5] := 255;
Invalidate;
END;
También podemos usar la siguiente estructura:
type
TRGBTriple = packed record
rgbtBlue: Byte;
rgbtGreen: Byte;
rgbtRed: Byte;
end;
Para conseguir que la segunda fila del bitmap sea de color negro:
type
PRGBTripleArray = ^TRGBTripleArray;
TRGBTripleArray = array[0..4095] of TRGBTriple;
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
Bitmap: TBitmap;
Pixels: PRGBTripleArray;
begin
Bitmap := TBitmap.Create;
try
Bitmap.Width := 3;
Bitmap.Height := 2;
Bitmap.PixelFormat := pf24bit;
// get pointer to the second row's raw data
Pixels := Bitmap.ScanLine[1];
// iterate our row pixel data array in a whole width
for I := 0 to Bitmap.Width - 1 do
begin
Pixels[I].rgbtBlue := 0;
Pixels[I].rgbtGreen := 0;
Pixels[I].rgbtRed := 0;
end;
Bitmap.SaveToFile('c:\Image.bmp');
finally
Bitmap.Free;
end;
end;
De lo anterior podemos observar que el primer pixel del bitmap se encuentra en el offset 0 de la primera fila, el segundo en el offset 3, el tercero en el offset 6 esto quiere decir que el byte en la ubicación x*3 es el componente azul, el byte de la ubicación x*3+1 es el componente verde y el x*3+2 es el rojo.
Entonces si quisiéramos poner una imagen en color negro habría que hacer lo siguiente:
VAR
p: PByteArray;
x: Integer;
y: Integer;
BEGIN { Iterar entre todas las líneas}
FOR y := 0 TO Pred(FImage.Height) DO
BEGIN
p := FImage.ScanLine[y];
FOR x := 0 TO Pred(FImage.Width) DO
BEGIN
p[x * 3] := 0;
p[x * 3 + 1] := 0;
p[x * 3 + 2] := 0;
END;
END;
END;
Ahora vamos a profundizar un poco más en este aspecto, hemos visto que cambiando los valores de los bytes de un pixel podemos conseguir los diferentes tipos de colores y si vamos un poco más allá conseguiremos espectaculares efectos visuales como los siguientes:
- Solarize
VAR
p: PByteArray;
x: Integer;
y: Integer;
BEGIN
FOR y := 0 TO Pred(FImage.Height) DO
BEGIN
p := FImage.ScanLine[y];
FOR x := 0 TO Pred(FImage.Width) DO
BEGIN
IF p[x * 3] > 127 THEN
p[x * 3] := 255 - p[x * 3];
IF p[x * 3 + 1] > 127 THEN
p[x * 3 + 1] := 255 - p[x * 3 + 1];
IF p[x * 3 + 2] > 127 THEN
p[x * 3 + 2] := 255 - p[x * 3 + 2];
END;
END;
END;
- Invertir colores
VAR
p: PByteArray;
x: Integer;
y: Integer;
BEGIN
FOR y := 0 TO Pred(FImage.Height) DO
BEGIN { get the pointer to the y line }
p := FImage.ScanLine[y];
FOR x := 0 TO Pred(FImage.Width) DO
BEGIN { modificar el color azul }
p[x * 3] := 255 - p[x * 3]; { modificar el color verde }
p[x * 3 + 1] := 255 - p[x * 3 + 1]; { modificar el color rojo }
p[x * 3 + 2] := 255 - p[x * 3 + 2];
END;
END;
END;
- Convertir a escala de grises
Se hace con la siguiente fórmula
Gray = (Red * 3 + Blue * 4 + Green * 2) div 9
VAR
p: PMyPixelArray;
x: Integer;
y: Integer;
gray: Integer;
BEGIN
FOR y := 0 TO Pred(FImage.Height) DO
BEGIN
p := FImage.ScanLine[y];
FOR x := 0 TO Pred(FImage.Width) DO
WITH p[x] DO
BEGIN
gray := (Red * 3 + Blue * 4 + Green * 2) DIV 9;
Blue := gray;
Red := gray;
Green := gray;
END;
END;
END;
- Hacer un espejo vertical
procedure TForm1.Button1Click(Sender: TObject);
procedure EspejoVertical(Origen,Destino:TBitmap);
var
x,y : integer;
Alto : integer;
Po,Pd : PByteArray;
tmpBMP : TBitmap;
LongScan : integer;
begin
{Si es un modo raro... pasamos}
case Origen.PixelFormat of
pfDevice,
pfCustom:
Raise exception.create( 'Formato no soportado'+#13+
'Bitmap Format not valid');
end;
{Calculamos variables intermedias}
Alto :=Image1.Picture.Bitmap.Height-1;
try
{Calculo de cuánto ocupa un scan}
LongScan:= Abs( Integer(Origen.ScanLine[0])-
Integer(Origen.ScanLine[1]) );
except
Raise exception.create( 'ScanLine Error...');
end;
{Cremos un bitmap intermedio}
tmpBMP:=TBitmap.Create;
with tmpBMP do
begin
{Lo asignamos, así se copia la paleta si la hay}
Assign(Origen);
{Esto es para que sea un bitmap nuevo... es un bug de D3 y D4}
Canvas.Pixels[0,0]:=Origen.Canvas.Pixels[0,0];
end;
{Damos la vuelta al bitmap}
for y:=0 to Alto do
begin
Po := Origen.ScanLine[y];
Pd := tmpBMP.ScanLine[Alto-y];
for x := 0 to LongScan-1 do
begin
Pd^[X]:=Po^[X];
end;
end;
{Lo asignamos al bitmap destino}
Destino.Assign(tmpBMP);
{Esto es para parchear un bug de Delphi 3 y Delphi4...}
Destino.Canvas.Pixels[0,0]:=tmpBMP.Canvas.Pixels[0,0];
tmpBMP.Free;
end;
begin
EspejoVertical( Image1.Picture.Bitmap,
Image1.Picture.Bitmap);
Image1.Refresh;
end;
- Ajustar el brillo
Se hace con la siguiente fórmula:
NewPixel = OldPixel + (1 * Percent) div 200
FUNCTION IntToByte(AInteger: Integer): Byte; INLINE;
BEGIN
IF AInteger > 255 THEN
Result := 255
ELSE IF AInteger < 0 THEN
Result := 0
ELSE
Result := AInteger;
END;
PROCEDURE TMainForm.AdjustBrightness(Percent: Integer);
VAR
p: PMyPixelArray;
x: Integer;
y: Integer;
amount: Integer;
BEGIN
amount := (255 * Percent) DIV 200;
FOR y := 0 TO Pred(FImage.Height) DO
BEGIN
p := FImage.ScanLine[y];
FOR x := 0 TO Pred(FImage.Width) DO
WITH p[x] DO
BEGIN
Blue := IntToByte(Blue + amount);
Green := IntToByte(Green + amount);
Red := IntToByte(Red + amount);
END;
END;
Invalidate;
END;
PROCEDURE TMainForm.ScanLineBrightnessClick(Sender: TObject);
VAR
amount: Integer;
BEGIN
amount := StrToInt(InputBox('Brightness Level', 'Enter a value from -100 to 100:', '50'));
AdjustBrightness(amount);
END;
Buen artículo. Enhorabuena.
ResponderEliminarMuchas gracias Germán !!
ResponderEliminar