Search code examples
delphicolorspixelscanline

Fast way of swapping Red/Blue bytes using ScanLine


Currently, I loop the Canvas.Pixels[] property and read each pixel on a canvas to swap Red/Blue bytes around (for specific reasons). However, it takes an average of 2 seconds per picture, and I have over 8,000 pictures I need to convert (overnight). I understand I can use a method of ScanLine to accomplish this much faster, but I know nothing about ScanLine - it's a much lower level of coding than I'm comfortable with. What's the fastest way to accomplish this? I'm willing to wait some time for this to run through, but it would still be nice if I could chop that time in half or more.

Right now, this is the procedure I use:

procedure SwapBytes(var Bmp: TBitmap);
var
  X, Y: Integer;
  R, G, B: Byte;
  C: TColor;
begin
  for Y := 0 to Bmp.Height - 1 do begin
    for X := 0 to Bmp.Width - 1 do begin
      C:= Bmp.Canvas.Pixels[X,Y];
      R:= GetRValue(C);
      G:= GetGValue(C);
      B:= GetBValue(C);
      Bmp.Canvas.Pixels[X,Y]:= RGB(B, G, R)
    end;
  end;
end;

Added Note: An initial conversion of over 8,000 images is the first step of why I need this. However, I also will be using the same thing in our software to automatically convert any image on the spot, as needed. So a third-party converter won't work, because I cannot distribute this to our clients.


Solution

  • I would try something like follows. This version is only for 24-bit bitmaps:

    procedure SwapRedBluePixels(ABitmap: TBitmap);
    var
      X: Integer;
      Y: Integer;
      Red: Byte;
      Pixel: PRGBTriple;
    begin
      // check for the bit depth, it must be 24-bit if you use PRGBTriple pointer
      // for line scan; if it wouldn't the iterated line pointers would point to 
      // another place in the memory
      if ABitmap.PixelFormat <> pf24bit then
      begin
        ShowMessage('Your bitmap has color depth different from 24-bit');
        Exit;
      end;
      // iterate through the image vertically
      for Y := 0 to (ABitmap.Height - 1) do
      begin
        // access the line of pixels and get the pointer to the first pixel of 
        // that line
        Pixel := ABitmap.ScanLine[Y];
        // iterate through the scanned line pixels horizontally
        for X := 0 to (ABitmap.Width - 1) do
        begin
          // store the pixel's red channel value
          Red := Pixel.rgbtRed;
          // modify the pixel's red channel value
          Pixel.rgbtRed := Pixel.rgbtBlue;
          // modify the pixel's blue channel value
          Pixel.rgbtBlue := Red;
          // increment to get the next pixel pointer of the scanned line
          Inc(Pixel);
        end;
      end;
    end;
    

    Update 2:

    This version is for 24-bit and 32-bit bitmaps:

    procedure SwapRedBluePixels(ABitmap: TBitmap);
    var
      X: Integer;
      Y: Integer;
      Red: Byte;
      Size: Integer;
      Pixels: PByteArray;
    begin
      // check the color depth and set the size of the pixel arrangement
      case ABitmap.PixelFormat of
        pf24bit: Size := SizeOf(TRGBTriple);
        pf32bit: Size := SizeOf(TRGBQuad);
      else
        // if the image is not 24-bit or 32-bit go away
        begin
          ShowMessage('Your bitmap has unsupported color depth!');
          Exit;
        end;
      end;
    
      // iterate through the image vertically
      for Y := 0 to (ABitmap.Height - 1) do
      begin
        // access the line of pixels and get the pointer to the first pixel of
        // that line
        Pixels := ABitmap.ScanLine[Y];
        // iterate through the scanned line pixels horizontally
        // for 24-bit images the pixels are stored like
        // B -> G -> R -> B -> G -> R etc.
        // for 32-bit images the pixels are stored like
        // B -> G -> R -> A -> B -> G -> R -> A etc.
        // so we can simply use e.g. byte array and iterate through
        // it, if we have 24-bit image, we have to read each element,
        // if 32-bit we have to skip the alpha (reserved) channel
        for X := 0 to (ABitmap.Width - 1) do
        begin
          // store the pixel's red channel value
          Red := Pixels^[(X * Size) + 2];
          // modify the pixel's red channel value
          Pixels^[(X * Size) + 2] := Pixels^[(X * Size)];
          // modify the pixel's blue channel value
          Pixels^[(X * Size)] := Red;
        end;
      end;
    end;