2013-03-08 18 views
6

Próbuję zmienić kolor piksela mapy bitowej, jeśli jest biały. Napisałem następujący kod. Ale jest strasznie wolno !. Chcę sprawdzić, czy kolor piksela jest biały, czy nie, a jeśli jest biały, zmień kolor na czarny.zmiana koloru piksela bitmapy

Czy ktoś może zaproponować lepsze podejście?

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

Do szybkiego dostępu do bitmapy pikseli użyć 'własność ScanLine'. Możesz znaleźć coś do przeczytania, np. ['here'] (http://stackoverflow.com/q/13583451/960757). – TLama

+1

@TLama Cudowny samouczek. Wielkie dzięki – jimsweb

+0

Cieszę się, że Ci się podoba! – TLama

Odpowiedz

7

Powinieneś użyć linii skanowania. Przykład:

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; 

Aby to wypróbować:

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; 

Update: Trzeba jedynie bardzo niewielką modyfikację kodu, aby pracować na 32-bitowych bitmap Zamiast:

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; 

W rzeczywistości można wykonać

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; 

jeśli nie chcesz wykonać pojedynczej procedury, która działa zarówno z 24-bitowymi, jak i 32-bitowymi bitmapami, tak jak zrobiła to TLama. [Jedną z zalet posiadania dwóch odrębnych procedur jest to, że te krótkie procedury są łatwiejsze do odczytania (i utrzymać).]

+0

czy ten kod nie zadziała, jeśli Bitmap.PixelFormat = pf32bit? – jimsweb

+1

@jimsweb: Nie. Ale zmiana wymagana do dostosowania do 32-bitowego przypadku jest bardzo niewielka. –

10

Na pewno użyć właściwości ScanLine dostęp do bitmap pikseli skoro pracujesz z dużej tablicy pikseli gdzie dostęp Pixels jest powolny. Zastępowania kolorów do wyboru ze wsparciem dla 24-bitowych i 32-bitowych bitmap, chciałbym użyć czegoś takiego:

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; 

a Zastosowanie:

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; 

do czysta GDI i mapy bitowe zawierające co najwyżej 256 kolorów można użyć funkcji CreateMappedBmp.

+1

Działa to tylko wtedy, gdy bitmapa jest oczywiście 24-bitowa.Powinieneś prawdopodobnie dołączyć "Assert". –

+0

@Andreas, lepiej teraz? – TLama

+0

+1 Tak, teraz obejmujecie oba przypadki. –

0
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"); 
    }