C# Bitmap in Graustufen umwandeln und dabei zusehen klappt nicht

locomarco

Commander
Registriert
Aug. 2009
Beiträge
2.446
Hi,

ich hab hier ne Methode um ein Bitmap in Graustufen umzuwandeln.
Die funktioniert auch so wie sie soll.

Code:
        private Bitmap toGreyscale(Bitmap source)
        {
            Bitmap outputImage = new Bitmap(source);

            for (int y = 0; y < source.Height; y++)
            {
                for (int x = 0; x < source.Width; x++)
                {
                    int r, g, b;

                    r = source.GetPixel(x, y).R;
                    g = source.GetPixel(x, y).G;
                    b = source.GetPixel(x, y).B;

                    int greyscale = (r + g + b) / 3;

                    Color greyscalecolor = new Color();
                    greyscalecolor = Color.FromArgb(greyscale, greyscale, greyscale);

                    outputImage.SetPixel(x, y, greyscalecolor);
                }
            }
            return outputImage;
        }
Das Ergebnis sieht man dann natürlich erst wenn alles fertig ist.
Jetzt wollte ich das aber so umbauen, das man dabei zusehen kann wie Pixel für Pixel geändert wird.

Hab das also in nen Timer gepackt und ein klein wenig umgebaut:

Code:
        private void button1_Click(object sender, EventArgs e)
        {
            outputImage = new Bitmap(bmp);
            x = 0;
            y = 0;

            t1.Start();
        }

Code:
        int x, y;
        int r, g, b;
        int greyscale;
        Color greyscalecolor = new Color();

        void t1_Tick(object sender, EventArgs e)
        {    
            if (x < bmp.Width)
            {
                r = bmp.GetPixel(x, y).R;
                g = bmp.GetPixel(x, y).G;
                b = bmp.GetPixel(x, y).B;

                greyscale = (r + g + b) / 3;

                greyscalecolor = Color.FromArgb(greyscale, greyscale, greyscale);

                outputImage.SetPixel(x, y, greyscalecolor);
                pictureBox2.Image = outputImage;
                pictureBox2.Refresh();
                
                x++;

                if (x >= bmp.Width)
                {
                    y++;
                    x = 0;
                }
            }
        }

Ergebnis von toGreyscale():

unbenannt2arne.png


Das vom Timer:

unbenanntctdq.png


Ich verstehe einfach nicht warum das nicht Zeile für Zeile durchläuft.
Vielleicht sieht einer von euch den Fehler...


Grüße
 
Offtopic: Alle RGB-Werte zusammenzählen und Durchschnitt ausrechnen ist eigentlich nicht der ganz korrekte Weg, um Grauwerte zu erhalten. Wie so oft ist Wiki dein Freund.

Ontopic: Ich schätze mal, dass es daran liegt, dass irgendwo die Variablen nur lokal behandelt werden. Aber dazu müsste man wohl den ganzen Code sehen.
 
Zuletzt bearbeitet:
Schon mal nachgeschaut, ob der Timer überhaupt ausgeführt wird?
Ich hab jetzt grad kein C# drauf, aber es sieht für mich so aus, als würde er den Timer gar nicht starten.

Den Graustufen-wert wird auch über die Gesamthelligkeit ausgerechnet.
Dieser wird mit der Formel:

Gesamthelligkeit = 0,3 * Rot + 0,59 * Grün + 0,11 * Blau

berechnet. Die Konstanten kommen wegen der Anzahl der Zapfen der jeweiligen Farbe zu stande.
 
Zuletzt bearbeitet:
Wenn der Timer nicht starten würde, hätte ich doch in der zweiten Picturebox gar kein Bild.
 
Also bei mir funktioniert dein Programm. Wenn ich auf Button1 drücke, erscheint das Bild erstmal in Farbe und wird dann Stück für Stück in Graustufen gewandelt.
 
Es dauert halt saulange, also er braucht 10Sekunde für eine Zeile oder so. Wobei ich das Delay vom Timer verkürzt habe.
 
@cx01: du hast recht, es funktioniert wirklich. Hätte nicht gedacht, das es so extrem langsam ist.

@Whiz-zarD: werd ich mir nachher mal ansehn.

EDIT: Auch mit dem FastBitmap ist das leider nich schneller.
 
Zuletzt bearbeitet:
Vielleicht hilft dir der Code hier:
http://www.codeguru.com/cpp/g-m/bitmap/specialeffects/article.php/c1683
Siehe Function 2: Fade from color to grayscale
Ist glaub in C++ aber sollte kein Problem sein, dass zu lesen und anzuwenden...

Und falls du schnelles Konvertieren bevorzugst, hier drei verschiedene Implementierungen, die unterschiedlich schnell gehen:
http://www.switchonthecode.com/tutorials/csharp-tutorial-convert-a-color-image-to-grayscale

Und noch meine eigene Lösung:
Wenn du das ganze in WPF umsetzen würdest, dann könntest du praktisch einfach das farbige Bild nehmen und das in Graustufen drüber legen und durchsichtig machen. Dann die Opacity von 0 zu 1 animieren...weiß nicht, ob das auch bei Forms ging, aber das ist ja alt und deswegen gibts WPF ;-)
 
Zuletzt bearbeitet:
Es ist so langsam, weil du den Timer ja so hoch eingestellt hast. Bei 100ms verarbeitet er nur 10 Pixel pro Sekunde. Selbst wenn du den auf 1ms runterstellst, würde es für ein 1024x768 Bild immernoch 12Minuten dauern.
Vlt wäre es sinnvoller, wenn in jedem Tick nicht ein Pixel, sondern eine Zeile umgewandelt wird.
 
@Erdmännchen: Ist zwar nett gemeint, mir gings aber darum zusehen zu können, wie das Bild Schritt für Schritt umgewandelt wird.

@cx01: Ich habs jetzt so gemacht, das pro Tick 15 Zeilen umgewandelt werden. Jetzt ists schnell genug.

Danke für eure Hilfe :)
 
Wenn ich du wäre würd ich es algorithmisch so lösen das es mehr stufenweise ausgraut aber als ganzes.
Der Algorithmus wäre dann unabhängig von der Auflösung (im Bezug auf die Pause) was sinnvoller wäre.
 
Hab die Anzahl der jeweils umgewandelten Zeilen aber sowieso abhängig von der Bildhöhe gemacht.

Code:
int count = y + (outputImage.Height/32);


Und vielleicht versteh ich deinen Ansatz nich, aber Stufenweise das ganze Bild ausgrauen wäre doch viel mehr Arbeit? Da müsste ich die Pixel ja um ein Vielfaches öfter färben.

Außer du meinst nicht mit Get- und SetPixel.
 
Zuletzt bearbeitet:
Wenn du weißt was du tust :D, kannst du das ganze auch unsafe machen und per Pointer das Bild abarbeiten, was dir im Falle des kontinuierlichen Ausgrauens einen starken Geschwindigkeitsvorsprung im Vergleich zu Get- & SetPixel bringen würde.

Code:
BitmapData bmpData;
byte* pixel;
int stride;
byte gP,bP;
bmpData = img.LockBits(new Rectangle(0, 0, img.Width, img.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
pixel = (byte*)bmpData.Scan0;
stride = bmpData.Stride - bmpData.Width * 3;
byte* blue, green, red;
byte newColor;
int w = img.Width, h = img.Height;
{
    for (int k = 0; k < h; k++)
    {
        for (int l = 0; l < w; l++)
        {
            blue = pixel;
            pixel++;
            green = pixel;
            pixel++;
            red = pixel;
            pixel++;
            newColor =(byte) ((*red + *green + *blue)/3);
            *red = newColor;
            *green = newColor;
            *blue = newColor;
        }
        pixel += stride;
    }
}
img.UnlockBits(bmpData);
 
Verwende statt eines Timers einen Thread, dann sollte das mit dem Ansehen auch kein Problem sein...
 
@DoNG: ne, da wüsst ich nich was ich tue :D

@Fonce: mit nem Thread hab ichs schon probiert, da meckert der Compiler aber immer weil ich die nicht-statische Methode in dem Thread nicht aufrufen darf oder die Parameter falsch wären. Muss dazu mal ein bisschen was lesen.
 
Auch könntest du Cross-Thread-Exceptions bekommen, wenn du im Thread dann das Image in der PictureBox ersetzt.

Dazu dann immer sicherstellen dass du im richtigen Thread bist, zB. so:
Code:
this.Invoke((Action)delegate
{
   ... //dein Code
});

Damit ist sichergestellt dass du im richtigen Thread (in dem die UI-Elemente erstellt wurden) herumfuhrwerkst und somit bekommst du keine Exceptions die dir sagen, dass das Element das du ändern willst, in einem anderen Thread erstellt wurden ;)
 
Da soll noch einmal einer behaupten Java und C# wären nahezu identisch.

Seltsam klingt es für meine Java-Ohren/Augen aber schon. Objekte liegen doch im heap, warum sollte denn da kein konkurrierender Zugriff aus verschiedenen Threads möglich sein bei entsprechendem locken?
 
Zurück
Oben