Laden...

[gelöst] Bitmap-Randproblem beim Verkleinern mit InterpolationMode.HighQualityBilinear

Erstellt von herbivore vor 15 Jahren Letzter Beitrag vor 15 Jahren 10.498 Views
herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 15 Jahren
[gelöst] Bitmap-Randproblem beim Verkleinern mit InterpolationMode.HighQualityBilinear

Hallo Community,

wenn ich mit folgendem Code eine komplett weiße 100x100-Bitmap input.bmp mit DrawImage und InterpolationMode.HighQualityBilinear auf 25x25 skaliere, dann bekomme ich in output.bmp einen störenden grauen Rahmen am Rand. Natürlich passiert dieser Verdunkelung am Rand nicht nur bei komplett weißen Bildern, nur sieht man es da am besten (siehe Anhang; das Bild dort ist vergrößert dargestellt, damit man den Effekt besser erkennen kann).

Habt ihr irgendeine Idee, wie ich das abstellen kann?

Ich habe alle High*-Werte von InterpolationMode in Kombination mit allen gültigen PixelOffsetMode-Werten durchprobiert. Es kommt immer zu einem vergleichbaren Effekt. Das will nun so gar nicht zu dem "High" vor "Quality" passen. Mit den anderen InterpolationMode-Werten funktioniert es, aber die liefern bei realen Bildern eine zu schlechte Qualität und kommen daher nicht in Frage.

Ist das Problem bekannt? Gibt es eine Lösung?

Vielen Dank im Voraus!


using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;

static class App
{
   public static void Main (string [] astrArg)
   {
      Bitmap    bmpInput        = (Bitmap)Image.FromFile ("input.bmp");
      Bitmap    bmpOutput       = new Bitmap (25, 25, PixelFormat.Format24bppRgb);
      Graphics  gOutput         = Graphics.FromImage (bmpOutput);

      gOutput.InterpolationMode = InterpolationMode.HighQualityBilinear;
      gOutput.PixelOffsetMode   = PixelOffsetMode.Half;
      gOutput.CompositingMode   = CompositingMode.SourceCopy;

      gOutput.DrawImage (bmpInput, 0, 0, bmpOutput.Width, bmpOutput.Height);
      bmpOutput.Save ("output.bmp", ImageFormat.Bmp);
   }
}

herbivore

630 Beiträge seit 2007
vor 15 Jahren

Hallo,

Bitmap bmpOutput = new Bitmap(25, 25, PixelFormat.Format32bppArgb);

...behebt das Problem bei mir. Möglicherweise ein Bug? Könnte aber auch sein dass für ein "High Quality Antialaising" zu wenig Informationen in einem 24-Bit Bitmap vorhanden sind und es somit zu diesem Artefakt kommt.

Gruss
tscherno

To understand recursion you must first understand recursion

http://www.ilja-neumann.com
C# Gruppe bei last.fm

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 15 Jahren

Hallo tscherno,

sieht gut aus. Ich hatte zwar absichtlich 24bpp verwendet, aber ich werde es schon schaffen aus dem korrekten 32bpp Bild ein korrektes 24bpp Bild zu machen. Damit sollte das Problem gelöst sein. Vielen Dank!

herbivore

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 15 Jahren

Hallo tscherno, hallo ihr anderen,

zu früh gefreut. Es sieht zwar bei einem 32bpp Bild auf den ersten Blick alles recht weiß aus, aber wenn man die Pixel einzeln ausliest, sieht man, dass folgende Farben für den Rand und die Ecken verwendet werden:

Color [A=195, R=254, G=254, B=254]
Color [A=196, R=254, G=254, B=254]
Color [A=197, R=254, G=254, B=254]
Color [A=223, R=254, G=254, B=254]
Color [A=224, R=254, G=254, B=254]

Nur die Farbe der Innenfläche ist korrekt:

Color [A=255, R=255, G=255, B=255]

Obwohl der RGB-Anteil der Farben am Rand mit je 254 ja fast ganz Weiß ist, bekommt man durch die in den Farben verwendet Transparenz (195-224) fast das gleiche Ergebnis, wenn man diese 32bpp Bild in eine leeres 24bpp Bild zeichnet, als wenn man das Originalbild direkt in das 24bpp Bild zeichnet. Es sieht dann also immer noch so aus, wie das Bild oben im Anhang.

Ok, man könnte die Transparenz ignorieren, aber dann stimmt die Farbe (wenn auch minimal; 254 statt 255) immer noch nicht.

Das Problem ist somit immer noch offen! Ist das tatsächlich ein Bug von DrawImage? Gibt es einen Workaround?

herbivore

1.130 Beiträge seit 2007
vor 15 Jahren

Ich glaube, das problem ist, dass der mittelwert bei diesem Filter gebildet wird. Wenn aber keine Pixel da sind, werden graue/transparente eingefügt.

man könnte sich die Pixeldaten holen und selber scalieren. Wenn man im alten Bild außerhalb des Bildes landet, muss man den Pixel halt nicht in den Durchschnittswert einrechnen anstatt ihn als grau/transparent zu betrachten.

Mann könnte versuchen den zu bearbeitenden ausschnitt zu setzten. (kp, ob das was bringt)

Ansonsten könnte man den Rand (äußerte Pixel) weiter nach außen kopieren, dann den Bereich, aus dem das bild scaliert werden soll setzen und dabei den neuen rand wieder wegschneiden.

Projekte:Jade, HttpSaver
Zum Rechtschreiben gibts doch schon die Politiker. Aber die bauen auch nur mist!

1.361 Beiträge seit 2007
vor 15 Jahren

Hallo herbivore,

dieser etwas seltsame Workaround soll helfen (habs selbs nicht probiert)
"Zeichne" über deine Grenzen hinaus: (dann soll auch die Mittelwertbildung klappen)


gOutput.DrawImage (bmpInput, -1, -1, bmpOutput.Width+1, bmpOutput.Height+1);

beste Grüße
zommi

//EDIT:eigentlich ist es ja genau das:

Ansonsten könnte man den Rand (äußerte Pixel) weiter nach außen kopieren, dann den Bereich, aus dem das bild scaliert werden soll setzen und dabei den neuen rand wieder wegschneiden.

Nur halt tricky im DrawImage.

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 15 Jahren

Hallo floste,

Wenn aber keine Pixel da sind, werden graue/transparente eingefügt.

Es sind wohl eher schwarze, denn wenn man ein komplett schwarzes Bild nimmt, dann ist der Rand schwarz und nicht anthrazit, wie das bei mittelgrauen Hilfspixeln zu erwarten gewesen wäre. Aber im Prinzip hast du natürlich recht. Nur finde ich das nicht besonders HighQuality. HighQuality wäre für mich, wenn DrawImage am Rand erkennen würde, dass es den Filter mit weniger Pixeln berechnen muss.

man könnte sich die Pixeldaten holen und selber scalieren.

Ja, klar, ich hatte sowas auch schon mal in C++ realisiert. Dabei wurden immer genau die Quellpixel zusammengerechnet, die quasi über dem Zielpixel liegen, wenn man sich beide Bitmaps gleich groß und deckungsgleich, dafür aber mit unterschiedlichen Kantenlängen der Pixel vorstellt. Wenn also ein Bild beispielsweise um den Faktor drei geschrumpft wird, dann werden je neun Pixel zu einem Pixel zusammengerechnet. Das ist aufwändig, aber eigentlich akkurat. Ich hatte gehofft, mir das sparen zu können.

Ansonsten könnte man den Rand (äußerte Pixel) weiter nach außen kopieren, dann den Bereich, aus dem das bild scaliert werden soll setzen und dabei den neuen rand wieder wegschneiden.

Klar könnte man machen, ist aber eher aufwändig und ein bisschen von hinten durch die Brust ins Auge. 🙂

Hallo zommi,

dieser etwas seltsame Workaround soll helfen

tut er leider nicht wirklich. Zwar kann man so den dunklen Rand zum Verschwinden bringen (genaugenommen verschwindet er erst ganz, wenn man zu Breite und Höhe jeweils zwei addiert), aber dann enthält das Zielbild eben auch nicht genau den Rand des Originalbilds, sondern ist entsprechend beschnitten. Ich will aber wirklich, den kompletten Bereich des Original-Bilds im Zielbild ... verkleinert natürlich.

Trotzdem vielen Dank euch beiden für die Anregungen. Wenn es keine andere Möglichkeit gibt, werde ich die verkleinerte Bitmap selber berechnen müssen.

herbivore

P
48 Beiträge seit 2008
vor 15 Jahren

Hallo herbivore,

probier mal folgendes

ImageAttributes aImageAtt = new ImageAttributes();
aImageAtt.SetWrapMode(WrapMode.Clamp);

Rectangle aDestRect = new Rectangle(0, 0, bmpOutput.Width, bmpOutput.Height);
gOutput.DrawImage (bmpInput, aDestRect, 0, 0, bmpInput.Width, bmpInput.Height, GraphicsUnit.Pixel, aImageAtt);

Der WrapMode gibt ja den Umbruchbereich an für zu große (und auch für zu kleine Bitmaps).
Mit Clamp sagst du halt, dass ein möglicher Farbverlauf nicht über die Objekt-Grenze hinweg berechnet wird.

Grüße,

psy

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 15 Jahren

Hallo psy,

vielen Dank. So wie du geschrieben hast, ging es noch nicht ganz, aber dadurch habe ich die Lösung gefunden. Mit

SetWrapMode (WrapMode.Clamp, Color.FromArgb (128, 128, 128));

konnte ich schon mal die Farbe der gedachten Pixel außen um das Bild herum einstellen. Durch die Wahl von 128, 128, 128 könnte man den Effekt abschwächen, inbesondere bei Bilder mit mittleren Farben. Aber damit nicht genug. Man kann auch

SetWrapMode (WrapMode.TileFlipXY);

verwenden, dann wird das Bild an allen vier Kanten quasi nach außen umgeklappt (und an den Ecken quasi diagonal in beide Richtungen), so dass sich die gedachten Pixel aus dem (geflippten) Bildinhalt selbst ergeben. Und das hat nun - soweit ich das überblicke - genau den richtigen Effekt, dass die Farbwerte am Rand immer korrekt berechnet werden.

Die Lösung sieht damit wie folgt aus:


using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;

static class App
{
   public static void Main (string [] astrArg)
   {
      Bitmap    bmpInput        = (Bitmap)Image.FromFile ("input.bmp");
      Bitmap    bmpOutput       = new Bitmap (25, 25, PixelFormat.Format24bppRgb);
      Graphics  gOutput         = Graphics.FromImage (bmpOutput);
      Rectangle rectOutput      = new Rectangle (0, 0, bmpOutput.Width, bmpOutput.Height);

      ImageAttributes ia = new ImageAttributes();
      ia.SetWrapMode (WrapMode.TileFlipXY);

      gOutput.InterpolationMode = InterpolationMode.HighQualityBilinear;
      gOutput.PixelOffsetMode   = PixelOffsetMode.Half;
      gOutput.CompositingMode   = CompositingMode.SourceCopy;

      gOutput.DrawImage (bmpInput, rectOutput,
                         0, 0, bmpInput.Width, bmpInput.Height,
                         GraphicsUnit.Pixel, ia);
      bmpOutput.Save ("output.bmp", ImageFormat.Bmp);
   }
}

Damit ziehe ich auch meine Kritik an dem HighQuality zurück. Es ist anscheinend doch an alles gedacht und sogar an noch mehr als an was ich gedacht habe. Mit dem WrapMode kann man die Berechnung der Rand-Pixel sehr flexibel an die eigenen Bedürfnisse anpassen.

Vielen Dank an alle, die geholfen haben.

herbivore

Suchhilfe: 1000 Worte

1.457 Beiträge seit 2004
vor 15 Jahren

Hallo herbivore,

Das gleiche Problem hatte ich auch, als ich Thumbnails von Bildern erstellte. Wenn ich keinen PixelFormat angebe, dann wird auch kein "Rand" erzeugt.

Aber WrapMode kannte ich bis dato noch gar nicht.