Efectos con Scanline




 How to use ScanLine property for 24-bit bitmaps? - Stack Overflow



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;

2 comentarios:

Simulación del movimiento de los electrones en un campo electrico

Espectacular simulación realizada con OpenGL del movimiento de los electrones cuando atraviesan un campo eléctrico. Como muestra la image...