Entwickler-Ecke

Multimedia / Grafik - Pixel-Farbe in Bitmap ändern


Christoph1972 - Di 09.03.10 15:07
Titel: Pixel-Farbe in Bitmap ändern
Hallo Leute,

ich möchte Pixel von einem „schwarz“ –weiß Image verändern. Und zwar möchte ich die Farbe der schwarzen Pixel in eine beliebige Farbe ändern. Bisher habe ich das so gemacht und bin damit gut gefahren:


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
private Bitmap ChangeColor(Image image, Color color)
{
    Bitmap pic = new Bitmap(image);
    Bitmap newPic = new Bitmap(pic.Width, pic.Height);

    ColorMap[] cMap = new ColorMap[1];

    cMap[0] = new ColorMap();
    cMap[0].OldColor = Color.Black;
    cMap[0].NewColor = color;

    ImageAttributes imageAtributes = new ImageAttributes();
    imageAtributes.SetRemapTable(cMap);

    using (Graphics g = Graphics.FromImage(newPic))
    {
        g.DrawImage(pic, new Rectangle(00, pic.Width, pic.Height), 00, pic.Width, pic.Height, GraphicsUnit.Pixel, imageAtributes);
    }

    return newPic;
}


Nun ist es aber so, das in einigen Images die Grafik abgestuft ist, also dunkel grau - bis schwarze Pixel. Da komme ich mit der Methode natürlich nicht weiter.

Es wäre kein Problem, wenn alle Pixel die nicht weiß sind, in schwarz umgewandelt werden. Das wäre genau das was ich benötige.

Hat jemand eine Idee, wie alle Farben die in dem Image enthalten sind in eine ColorMap bekomme? Wobei ich nicht die GetPixel Methode mit einer Schleife verwenden möchte, das dauert viel zulange.

Mit einer ColorMatrix könnte es auch gehen, ich habe nur keine Vorstellung, wie die Matrix für schwarz abgebildet werden muss.Diese Matrix macht ein Image schwarz weiß, mit Grauabstufungen:


C#-Quelltext
1:
ColorMatrix colorMatrix = new ColorMatrix(new float[][] { new float[] { 0.299F, 0.299F, 0.299F, 00 }, new float[] { 0.587F, 0.587F, 0.587F, 00 }, new float[] { 0.114F, 0.114F, 0.114F, 00 }, new float[] { 00010 }, new float[] { 00001 } });                    


Ehrlich gesagt verstehe ich die ColorMatrix nicht wirklich und bin daher nicht in der Lage sie so zu ändern, dass sie nur schwarz abbildet.


Besteht eventuell eine Möglichkeit es "einfach" über die ImageAtributes zu lösen?


Vielleicht hat ja jemand eine Idee. Ich befürchte das es viel einfacher ist als ich denke, aber wer weis.......


Christian S. - Di 09.03.10 22:58

Hast Du mal geschaut, wie schnell das ist, wenn Du es mittels LockBits und den entsprechenden Operationen direkt an den Bytes machst? Ich habe das ein paar Mal benutzt und der Geschwindigkeitsvorteil gegenüber GetPixel / SetPixel ist sehr, sehr deutlich.


Christoph1972 - Mi 10.03.10 00:08

Ah, vielen Dank für die Info! Von LookBits und den entsprechenden Operationen habe ich bisher nichts gehört. Ich werde mich darüber informieren. Anregungen und Hinweise sind wie immer willkommen!


danielf - Mi 10.03.10 09:22

Hallo,

schau mal hier [http://www.switchonthecode.com/tutorials/csharp-tutorial-convert-a-color-image-to-grayscale] habe ich ein Tutorial mit einem hilfreichen Vergleich gefunden. Selber habe ich damals für Bildoperationen auch die Bitlock-Methode verwendent, wie sie Christian angesprochen hat. Müsste aber erst nachschauen wie ich das damals 'genau' gemacht hab. Ansatzweise sah es so [http://www.codersource.net/microsoft-net/c-image-processing/image-processing-in-c.aspx] aus.

Wenn du damit nicht weiter kommst, kann ich gerne mal mein Projekt laden (gerade kein Zugriff) und schauen wie ich es da gemacht hab.

Gruß Daniel


Christoph1972 - Do 11.03.10 20:50

Hallo Leute,

ich musste leider feststellen, dass es doch keine gute Lösung ist, das Image komplett einzuschwärzen. Von daher hat sich die Fragestellung erledigt.

Was mich aber doch interessiert, ist die Optimierung der GetPixel Methode, mittels unsafe Aufruf. So wie ich es gemacht habe dauert es immer noch recht lange.



C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
unsafe void Test(Bitmap image)
{
        Color color;

        for (Int32 y = 0; y < image.Height; y++)
        {
            for (Int32 x = 0; x < image.Width; x++)
            {
                color = image.GetPixel(x, y);
            }
        }
    }
}



Ist das so korrekt?


Christian S. - Do 11.03.10 20:59

Wird unsafe nicht nur dann schneller, wenn man auch unsafe Operationen ausführt? :gruebel:

Auch hier würde ich wieder zum LockBits-Schema raten.


danielf - Do 11.03.10 21:04

Nein, so einfach ist es nicht ;)

Du hast die Methode zwar als unsafe deklariert, aber verwendest dennoch mit der GetPixel-Methode eine Methode die im managed Bereich läuft.

Zum Beispiel für 24bppRGB:


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
Bitmap oBitmap;

bitmapData data = oBitmap.LockBits( rBounds, ImageLockMode.Read, oBitmap.PixelFormat );

int iCurrentPosition = iY * data.Stride + (iX * 3);

Color color = Color.FromArgb(data[ iCurrentPosition+2 ],
                             data[ iCurrentPosition+1 ],
                             data[ iCurrentPosition ] );


Besser ist es aber das LockBits nur einmal zu machen, am besten in einer Art Helper-Class der du das bitmap übergibts auf dem du arbeiten willst.

Gruß daniel


Christoph1972 - Do 11.03.10 21:25

Hi,

vielen Dank für die schnellen Antworten! Ich hatte das mit unsafe aus der Hilfe, weiter wird da nicht drauf eingegangen. Ok, das LookBits werde ich mir mal anschauen.


Christian S. - Do 11.03.10 21:49

Hi!

Ich habe noch was bei mir auf der Platte gefunden. In grauer Vorzeit habe ich mal eine solche Klasse geschrieben, und nachdem ich gerade mal kurz drüber geguckt habe, scheint sie keine allzu schlimmen Jugendsünden zu enthalten. Außer einer: Das Disposable-Pattern war nicht richtig implementiert, was ich in Delphi Prism aber durch das hinzufügen eines Aspekts mit einer Zeile korrigieren konnte :mrgreen:

Ich hänge mal Quelltext und Kompilat an. Das Assembly behauptet, .NET 3.5 zu brauchen (das hängt mit dem Aspekt zusammen), läuft bei mir aber auch in einer .NET 2.0 Anwendung problemlos, wenn man die Warnungen ignoriert. ;-)

Quelltext (pas, 13.07 KB)
Kompilat mit unvollständiger XML-Doku (zip, 4.64 KB)

Benutzen tust Du's dann z.B. so:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
            var bmp = new Bitmap(80006000, System.Drawing.Imaging.PixelFormat.Format24bppRgb);

            var fast = new FastBitmap(bmp, true);

            for (int x = 0; x < 8000; x++)
                for (int y = 0; y < 6000; y++)
                fast.SetPixel(x, y, Color.Blue);

            pictureBox1.Image = fast.Bitmap;;

Beachte in der XML-Doku bitte aber auch die Hinweise zur AutoUpdate-Eigenschaft und dem entsprechenden Parameter in den Konstruktoren.

Grüße
Christian