更改位图像素颜色

8

我想在位图中将白色像素的颜色更改。我写了以下代码,但速度非常慢!我想检查像素的颜色是否是白色,如果是白色,则将其更改为黑色。

有人能建议更好的方法吗?

procedure TForm1.Button1Click(Sender: TObject);
var
  BitMap1 : TBitmap;
  X, Y, Size : Integer;

  P: Cardinal;
begin
  BitMap1 := TBitmap.Create;
  bitMap1.LoadFromFile('image1.bmp');

  for Y := 0 to Bitmap1.Height - 1 do
  begin
    for X := 0 to Bitmap1.width  * size - 1 do
    begin
    p := BitMap1.Canvas.Pixels[X,Y];
    if p = 255 then
      BitMap1.Canvas.Pixels[X,Y] := 0;

    end;
  end;

  Image1.Picture.Assign(BitMap1);
end;

1
为了快速访问位图像素,请使用ScanLine属性。您可以在这里找到一些阅读材料,例如here - TLama
1
@TLama 的教程非常棒。非常感谢。 - jimsweb
1
你的代码有几个问题。首先,X 应该只到 Bitmap1.Width - 1。其次,白色的颜色是 clWhite,即 16777215,而不是 255。(第三,你忘记释放位图了。)[还有一些小问题:p 最好改为 TColor0 最好改为 clBlack] - Andreas Rejbrand
2
啊,Canvas.Pixels[]的灾难又来了! - dthorpe
1
@tlama Embarcadero 应该将其标记为已过时,以便任何使用都会生成警告。 - dthorpe
显示剩余3条评论
4个回答

12

在处理大量像素的情况下,使用ScanLine属性来访问位图像素,因为Pixels访问速度较慢。如果要替换自定义颜色并支持24位和32位位图,可以使用以下代码:

procedure ReplaceColor(ABitmap: TBitmap; ASource, ATarget: TColor);
type
  TRGBBytes = array[0..2] of Byte;
var
  I: Integer;
  X: Integer;
  Y: Integer;
  Size: Integer;
  Pixels: PByteArray;
  SourceColor: TRGBBytes;
  TargetColor: TRGBBytes;
const
  TripleSize = SizeOf(TRGBBytes);
begin
  case ABitmap.PixelFormat of
    pf24bit: Size := TripleSize;
    pf32bit: Size := SizeOf(TRGBQuad);
  else
    raise Exception.Create('Bitmap must be 24-bit or 32-bit format!');
  end;

  for I := 0 to TripleSize - 1 do
  begin
    // fill the array of bytes with color channel values in BGR order,
    // the same would do for the SourceColor from ASource parameter:
    // SourceColor[0] := GetBValue(ASource);
    // SourceColor[1] := GetGValue(ASource);
    // SourceColor[2] := GetRValue(ASource);
    // but this is (just badly readable) one liner
    SourceColor[I] := Byte(ASource shr (16 - (I * 8)));
    // the same do for the TargetColor array from the ATarget parameter
    TargetColor[I] := Byte(ATarget shr (16 - (I * 8)));
  end;

  for Y := 0 to ABitmap.Height - 1 do
  begin
    // get a pointer to the currently iterated row pixel byte array
    Pixels := ABitmap.ScanLine[Y];
    // iterate the row horizontally pixel by pixel
    for X := 0 to ABitmap.Width - 1 do
    begin
      // now imagine, that you have an array of bytes in which the groups of
      // bytes represent a single pixel - e.g. the used Pixels array for the
      // first 2 pixels might look like this for 24-bit and 32-bit bitmaps:

      // Pixels   [0][1][2]     [3][4][5]
      // 24-bit    B  G  R       B  G  R
      // Pixels   [0][1][2][3]  [4][5][6][7]
      // 32-bit    B  G  R  A    B  G  R  A

      // from the above you can see that you'll need to multiply the current
      // pixel iterator by the count of color channels to point to the first
      // (blue) color channel in that array; and that's what that (X * Size)
      // is for here; X is a pixel iterator, Size is size of a single pixel:          

      // X * 3    (0 * 3)       (1 * 3)
      //           ⇓             ⇓
      // Pixels   [0][1][2]     [3][4][5]
      // 24-bit    B  G  R       B  G  R

      // X * 4    (0 * 4)       (1 * 4)
      //           ⇓             ⇓
      // Pixels   [0][1][2][3]  [4][5][6][7]
      // 32-bit    B  G  R  A    B  G  R  A

      // so let's compare a BGR value starting at the (X * Size) position of
      // the Pixels array with the SourceColor array and if it matches we've
      // found the same colored pixel, if so then...
      if CompareMem(@Pixels[(X * Size)], @SourceColor, TripleSize) then
        // copy the TargetColor color byte array values to that BGR position
        // (in other words, replace the color channel bytes there)
        Move(TargetColor, Pixels[(X * Size)], TripleSize);
    end;
  end;
end;

并且用法:

procedure TForm1.Button1Click(Sender: TObject);
var
  Bitmap: TBitmap;
begin
  Bitmap := TBitmap.Create;
  try
    Bitmap.LoadFromFile('d:\Image.bmp');
    ReplaceColor(Bitmap, clWhite, clBlack);
    Image1.Picture.Assign(Bitmap);
  finally
    Bitmap.Free;
  end;
end;

对于使用纯GDI和最多具有256种颜色的位图,您可以使用CreateMappedBmp函数。


1
这仅适用于24位位图。当然,你应该至少包含一个“Assert”。 - Andreas Rejbrand

9
你应该使用扫描线来处理此问题。示例代码如下:
procedure ChangeWhiteToBlack(var Bitmap: TBitmap);
var
  scanline: PRGBTriple;
  y: Integer;
  x: Integer;
begin
  Assert(Bitmap.PixelFormat = pf24bit);
  for y := 0 to Bitmap.Height - 1 do
  begin
    scanline := Bitmap.ScanLine[y];
    for x := 0 to Bitmap.Width - 1 do
    begin
      with scanline^ do
      begin
        if (rgbtBlue = 255) and (rgbtGreen = 255) and (rgbtRed = 255) then
          FillChar(scanline^, sizeof(TRGBTriple), 0);
      end;
      inc(scanline);
    end;
  end;
end;

尝试以下步骤:

procedure TForm5.FormCreate(Sender: TObject);
var
  bm: TBitmap;
begin
  bm := TBitmap.Create;
  try
    bm.LoadFromFile('C:\Users\Andreas Rejbrand\Desktop\test.bmp');
    ChangeWhiteToBlack(bm);
    bm.SaveToFile('C:\Users\Andreas Rejbrand\Desktop\test2.bmp');
  finally
    bm.Free;
  end;
end;

更新:您只需要对代码进行非常小的修改即可使其在32位位图上运行:

procedure ChangeWhiteToBlack32(var Bitmap: TBitmap);
var
  scanline: PRGBQuad;
  y: Integer;
  x: Integer;
begin
  Assert(Bitmap.PixelFormat = pf32bit);
  for y := 0 to Bitmap.Height - 1 do
  begin
    scanline := Bitmap.ScanLine[y];
    for x := 0 to Bitmap.Width - 1 do
    begin
      with scanline^ do
      begin
        if (rgbBlue = 255) and (rgbGreen = 255) and (rgbRed = 255) then
          FillChar(scanline^, sizeof(TRGBQuad), 0);
      end;
      inc(scanline);
    end;
  end;
end;

事实上,你可以这样做。
procedure ChangeWhiteToBlack24(var Bitmap: TBitmap);
var
  scanline: PRGBTriple;
  y: Integer;
  x: Integer;
begin
  Assert(Bitmap.PixelFormat = pf24bit);
  for y := 0 to Bitmap.Height - 1 do
  begin
    scanline := Bitmap.ScanLine[y];
    for x := 0 to Bitmap.Width - 1 do
    begin
      with scanline^ do
      begin
        if (rgbtBlue = 255) and (rgbtGreen = 255) and (rgbtRed = 255) then
          FillChar(scanline^, sizeof(TRGBTriple), 0);
      end;
      inc(scanline);
    end;
  end;
end;

procedure ChangeWhiteToBlack32(var Bitmap: TBitmap);
var
  scanline: PRGBQuad;
  y: Integer;
  x: Integer;
begin
  Assert(Bitmap.PixelFormat = pf32bit);
  for y := 0 to Bitmap.Height - 1 do
  begin
    scanline := Bitmap.ScanLine[y];
    for x := 0 to Bitmap.Width - 1 do
    begin
      with scanline^ do
      begin
        if (rgbBlue = 255) and (rgbGreen = 255) and (rgbRed = 255) then
          FillChar(scanline^, sizeof(TRGBQuad), 0);
      end;
      inc(scanline);
    end;
  end;
end;

procedure ChangeWhiteToBlack(var Bitmap: TBitmap);
begin
  case Bitmap.PixelFormat of
    pf24bit: ChangeWhiteToBlack24(Bitmap);
    pf32bit: ChangeWhiteToBlack32(Bitmap);
  else
    raise Exception.Create('Pixel format must be pf24bit or pf32bit.');
  end;
end;

如果您不想像TLama那样制作适用于24位和32位位图的单个过程,可以考虑分别制作两个过程。这样做的好处之一是这些简短的过程更易于阅读(和维护)。


1
@jimsweb:不需要,但是将其适应32位的改变非常小。 - Andreas Rejbrand

1
procedure TForm1.Button1Click(Sender: TObject);
var
  BitMap1,
  BitMap2 : TBitmap;
  X, Y, Size : Integer;

  P: Cardinal;
begin
  BitMap1 := TBitmap.Create;
  BitMap1.LoadFromFile('image1.bmp');
  BitMap1.Transparent := true;
  BitMap1.TransparentColor := clWhite; // old color

  BitMap2 := TBitMap.Create;
  BitMap2.Height := BitMap1.Height;
  BitMap2.Width := BitMap1.Width;
  BitMap2.Canvas.Brush.Color := clBlack; // new color
  BitMap2.Canvas.FillRect(
   Rect(
     0,
     0,
     BitMap2.Width,
     BitMap2.Height
    )
  );
 
  BitMap2.Canvas.Draw(BitMap1);
  
  Image1.Picture.Assign(BitMap2);
  
  BitMap1.Free;
  BitMap2.Freel
end;

-1
    private void btnLoad2_Click(object sender, System.EventArgs e)
    {
        Bitmap myBitmap= new Bitmap(openFileDialog1.FileName);
        Bitmap myBitmap1 = new Bitmap("C:\\Documents and Settings\\Lalji\\Desktop\\image.png");
        for (int x = 0; x < myBitmap.Width; x++)
        {
            for (int y = 0; y < myBitmap.Height; y++)
            {
                // Get the color of a pixel within myBitmap.
                Color pixelColor = myBitmap.GetPixel(x, y);
                string pixelColorStringValue =
                    pixelColor.R.ToString("D3") + " " +
                    pixelColor.G.ToString("D3") + " " +
                    pixelColor.B.ToString("D3") + ", ";
                if (pixelColor.R.Equals(0) && pixelColor.G.Equals(0) && pixelColor.B.Equals(0))
                {
                    //MessageBox.Show("black pixel");
                }

                else if (pixelColor.R.Equals(255) && pixelColor.G.Equals(255) && pixelColor.B.Equals(255))
                {
                    //MessageBox.Show("white pixel");
                    myBitmap1.SetPixel(x, y, Color.White);
                }
                //switch (pixelColorStringValue)
                //{
                //    case "255 255 255":
                //        {
                //            // white pixel
                //            MessageBox.Show("white pixel");
                //            break;
                //        }
                //    case "000 000 000,":
                //        {
                //            // black pixel
                //           MessageBox.Show("black pixel");
                //            break;
                //        }
                //}
            }
        }

        myBitmap1.Save("C:\\Documents and Settings\\Lalji\\Desktop\\image1.png");
        MessageBox.Show("Process done");
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接