Hallo alle,
Die Geschwindigkeit von GetPixel und SetPixel ging mir schon längst auf die Nerven.
Da ich jetzt beim Lernen etwas weiter bin, habe ich eine Klasse gemacht, die dies weit in den Schatten stellt. Fast 800 mal schneller.
Die Klasse ist noch nicht fertig, aber für meine Bedürfnisse reicht es. Sollte Interesse bestehen, mache ich natürlich weiter.
Da ich noch Frischling bin in CSharp bind ich für jede Kritik und Anregung sehr dankbar.
Zunächst: So habe ich die Geschwindigkeit getestet:
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog of = new OpenFileDialog();
of.Filter = "Bilder (*.BMP;*.JPG;*.GIF;*.PNG)|*.BMP;*.JPG;*.GIF;*.PNG|All files (*.*)|*.*";
if (of.ShowDialog() == DialogResult.OK)
{
// Vorbereitungen
Bitmap test = (Bitmap)Image.FromFile(of.FileName); // Eine Bitmap laden Size: (755 x 666)
RoBitmap rob = new RoBitmap(test); // RoBitmap davon erzeugen
Bitmap test2 = new Bitmap(test.Width, test.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
// leere Bitmap gleicher Größe erstellen
RoBitmap rob2 = new RoBitmap(test2); // RoBitmap davon erzeugen
// Test 1 mit Orginal GetPixel/SetPixel Die Bitmap test wird Pixel für Pixel in test2 kopiert
DateTime now = DateTime.Now; // Zeit nehmen
for (int x = 0; x < test.Width; x++)
for (int y = 0; y < test.Height; y++)
test2.SetPixel(x, y, test.GetPixel(x, y));
TimeSpan usedTime = DateTime.Now - now;
MessageBox.Show(usedTime.ToString()); // 35,5 Sekunden
// Test 2 mit Orginal SetPixel und RoBitmap GetPixel
test2 = new Bitmap(test.Width, test.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
now = DateTime.Now;
for (int x = 0; x < test.Width; x++)
for (int y = 0; y < test.Height; y++)
test2.SetPixel(x, y, rob.GetPixel(x, y));
usedTime = DateTime.Now - now;
MessageBox.Show(usedTime.ToString()); // 22,14 Sekunden
// Test 3 mit RoBitmap SetPixel / GetPixel
test2 = new Bitmap(test.Width, test.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
now = DateTime.Now;
for (int x = 0; x < test.Width; x++)
for (int y = 0; y < test.Height; y++)
rob2.SetPixel(x, y, rob.GetPixel(x, y));
usedTime = DateTime.Now - now;
MessageBox.Show(usedTime.ToString()); // 4,45 Sekunden
// Test 4 mit RoBitmap SetPixel / GetPixel und Width/Height
test2 = new Bitmap(test.Width, test.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
now = DateTime.Now;
for (int x = 0; x < rob.Width; x++)
for (int y = 0; y < rob.Height; y++)
rob2.SetPixel(x, y, rob.GetPixel(x, y));
usedTime = DateTime.Now - now;
MessageBox.Show(usedTime.ToString()); // 0,047 Sekunden
pictureBox1.Image = rob2.Image; // kommt das Richtige Bild heraus?
}
}
Und nun die noch nicht fertige Klasse:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.IO;
using System.Windows.Forms;
namespace BildMischer
{
class RoBitmap
{
private byte[] bildDaten;
private Color[,] color;
private int width;
private int height;
Bitmap Bild;
Rectangle rect;
bool modified;
int bytes;
int stride;
System.Drawing.Imaging.PixelFormat pixelFormat;
System.Drawing.Imaging.ColorPalette colorPalette;
public RoBitmap(Bitmap bld)
{
Bild = bld;
SetzeWerte();
}
void SetzeWerte()
{
colorPalette = Bild.Palette;
pixelFormat = Bild.PixelFormat;
width = Bild.Width;
height = Bild.Height;
rect = new Rectangle(0, 0, width, height);
System.Drawing.Imaging.BitmapData bmpData =
Bild.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly,
Bild.PixelFormat);
IntPtr ptr = bmpData.Scan0;
stride = bmpData.Stride;
bytes = stride * height;
bildDaten = new byte[bytes];
System.Runtime.InteropServices.Marshal.Copy(ptr, bildDaten, 0, bytes);
Bild.UnlockBits(bmpData);
color = new Color[width, height];
switch (pixelFormat)
{
case System.Drawing.Imaging.PixelFormat.Format32bppArgb:
Format32BppArgb();
break;
case System.Drawing.Imaging.PixelFormat.Format24bppRgb:
Format24BppRgb();
break;
case System.Drawing.Imaging.PixelFormat.Format8bppIndexed:
Format8BppIndexed();
break;
case System.Drawing.Imaging.PixelFormat.Format4bppIndexed:
Format4BppIndexed();
break;
case System.Drawing.Imaging.PixelFormat.Format1bppIndexed:
Format1BppIndexed();
break;
}
modified = false;
}
void Format32BppArgb()
{
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
color[x, y] = Color.FromArgb(bildDaten[y * stride + x * 4 + 3], bildDaten[y * stride + x * 4 + 2], bildDaten[y * stride + x * 4 + 1], bildDaten[y * stride + x * 4]);
}
void Format24BppRgb()
{
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
color[x, y] = Color.FromArgb(bildDaten[y * stride + x * 3 + 2], bildDaten[y * stride + x * 3 + 1], bildDaten[y * stride + x * 3]);
}
void Format8BppIndexed()
{
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
color[x, y] = colorPalette.Entries[bildDaten[y * stride + x]];
}
void Format4BppIndexed()
{
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
if (x % 2 == 0)
color[x, y] = colorPalette.Entries[LowByte(bildDaten[y * stride + x / 2])];
else
color[x, y] = colorPalette.Entries[HighByte(bildDaten[y * stride + x / 2])];
}
void Format1BppIndexed()
{
int rest = width % 8;
byte bits;
int x, y;
for (y = 0; y < height; y++)
{
for (x = 0; x < width - 8; x += 8)
{
bits = bildDaten[y * stride + x / 8];
color[x, y] = colorPalette.Entries[(bits & 128) / 128];
color[x + 1, y] = colorPalette.Entries[(bits & 64) / 64];
color[x + 2, y] = colorPalette.Entries[(bits & 32) / 32];
color[x + 3, y] = colorPalette.Entries[(bits & 16) / 16];
color[x + 4, y] = colorPalette.Entries[(bits & 8) / 8];
color[x + 5, y] = colorPalette.Entries[(bits & 4) / 4];
color[x + 6, y] = colorPalette.Entries[(bits & 2) / 2];
color[x + 7, y] = colorPalette.Entries[bits & 1];
}
bits = bildDaten[y * stride + x / 8];
int teiler = 128;
for (int i = 0; i < rest; i++)
{
color[x + i, y] = colorPalette.Entries[(bits & teiler) / teiler];
teiler /= 2;
}
}
}
int HighByte(byte zahl)
{
return zahl >> 4;
}
int LowByte(byte zahl)
{
return zahl & 15;
}
public Color GetPixel(int x, int y)
{
return color[x, y];
}
public void SetPixel(int x, int y, Color col)
{
color[x, y] = col;
modified = true;
}
public int Width
{
get { return width; }
}
public int Height
{
get { return height; }
}
public Bitmap Image
{
set
{
Bild = value;
SetzeWerte();
}
get
{
if (!modified) return Bild;
switch (pixelFormat)
{
case System.Drawing.Imaging.PixelFormat.Format32bppArgb:
return ReturnFormat32BppArgb();
case System.Drawing.Imaging.PixelFormat.Format24bppRgb:
return ReturnFormat24BppRgb();
case System.Drawing.Imaging.PixelFormat.Format8bppIndexed:
//ReturnFormat8BppIndexed();
break;
case System.Drawing.Imaging.PixelFormat.Format4bppIndexed:
//ReturnFormat4BppIndexed();
break;
case System.Drawing.Imaging.PixelFormat.Format1bppIndexed:
//ReturnFormat1BppIndexed();
break;
}
return null;
}
}
Bitmap ReturnFormat24BppRgb()
{
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
{
bildDaten[y * stride + x * 3 + 2] = color[x, y].R;
bildDaten[y * stride + x * 3 + 1] = color[x, y].G;
bildDaten[y * stride + x * 3] = color[x, y].B;
}
System.Drawing.Imaging.BitmapData bmpData =
Bild.LockBits(rect, System.Drawing.Imaging.ImageLockMode.WriteOnly,
Bild.PixelFormat);
IntPtr ptr = bmpData.Scan0;
System.Runtime.InteropServices.Marshal.Copy(bildDaten, 0, ptr, bytes);
Bild.UnlockBits(bmpData);
modified = false;
return Bild;
}
Bitmap ReturnFormat32BppArgb()
{
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
{
bildDaten[y * stride + x * 4 + 3] = color[x, y].A;
bildDaten[y * stride + x * 4 + 2] = color[x, y].R;
bildDaten[y * stride + x * 4 + 1] = color[x, y].G;
bildDaten[y * stride + x * 4] = color[x, y].B;
}
System.Drawing.Imaging.BitmapData bmpData =
Bild.LockBits(rect, System.Drawing.Imaging.ImageLockMode.WriteOnly,
Bild.PixelFormat);
IntPtr ptr = bmpData.Scan0;
System.Runtime.InteropServices.Marshal.Copy(bildDaten, 0, ptr, bytes);
Bild.UnlockBits(bmpData);
modified = false;
return Bild;
}
}
}
Gruß Robert
Dieser Beitrag wurde 6 mal editiert, zum letzten Mal von Robertico am .
Der Ansatz ist prinzipiell nicht schlecht.
Allerdings musst du beim jedem Lesen/Zuweisen einer Bitmap immer den Speicher kopieren. Zumindest das Kopieren beim Lesen ließe sich durch Benutzung eines Dirty-Flags (bzw. modified) umgehen.
Der nächste Evolutionsschritt wäre direkt in den Speicher der Bitmap zu schreiben.
EDIT: Und der übernächste ist, das ganze von Bitmap abzuleiten um diesen ersetzen zu können...
meinst du jetzt das Marshal.Copy? Das ist doch sinnvoll, damit man nicht unsafe verwenden muss. Gerade für eine Bibliothek ist es aus meiner Sicht wünschenswert, unsafe zu vermeiden.
Und der Vorschlag, von Bitmap abzuleiten, scheitert daran, dass Bitmap sealed ist :-)
Ist es doch sealed? Verdammt. Ich hatte gestern gehofft, dass es das nicht ist; war jedoch zu faul nachzuschauen, da ich wusste, dass mich schon jemand korrigieren würde, falls es nicht stimmt...
Desweiteren ist mir der Bitmap-ctor public Bitmap ( int width, int height, int stride, PixelFormat format, IntPtr scan0 ) aufgefallen.
Zur Umsetzung von IntPtr in Byte[] bietet sich public static Object PtrToStructure ( IntPtr ptr, Type structureType ) an.
Für die Umwandlung des Byte[] in IntPtr bietet sich public static IntPtr UnsafeAddrOfPinnedArrayElement ( Array arr, int index ) an.
Ich habe das nicht ausprobiert, aber wenn es so funktioniert, wie ich mir das erhoffe und denke, kann man mit den Pointern arbeiten, ohne einen unsafe Kontext zu benutzen.
Eine zusätzliche Frage:
Mittels Reflection ist es doch möglich, protected und private Methoden aufzurufen. Kann man denn darüber nicht auch eine Ableitung einer sealed-Klasse erzeugen?
Mittels Reflection ist es doch möglich, protected und private Methoden aufzurufen. Kann man denn darüber nicht auch eine Ableitung einer sealed-Klasse erzeugen?
Reflection ist eine Laufzeitgeschichte. Die Definition einer Klasse ist Compilezeit. Natürlich könnte man zur Laufzeit Emit verwenden, aber das ist nichts anderes als ein Compileraufruf zur Laufzeit, d.h. man kann nur syntaktisch Korrektes emitieren. Also ich sehe keine Weg, sealed zu umgehen.
Auf Anregung von nils in eine txt-Datei gepackt und angehängt.
Die Endung cs ist komischerweise in myCSharp.de nicht erlaubt
Gruß Robert
PS: Als Admin ohne Probleme änderbar
public RoBitmap(Bitmap bld)
public RoBitmap(string fileName)
public Color[,] GetColorArray()
public Color GetPixel(int x, int y)
public void SetPixel(int x, int y, Color col)
public int Width
public int Height
public Bitmap Image
public Bitmap ConvertTo8Bpp()
public Bitmap ConvertTo8Bpp(int zusatz)
public Bitmap ConvertToGrayScale24bppBitmap()
public Bitmap ConvertToGrayScale8bppBitmap()
public Bitmap GetNegativ()
public Bitmap GetTransparent(Color transparentColor)
public Bitmap GetAllTransparent(byte tranparenz)
public Bitmap GetTransparent(Color transparentColor, byte tranparenz)
public Bitmap GetContrastedImage(float _contrastFactor)
public Bitmap GetFormat1BppIndexed()
public Bitmap GetFormat1BppIndexed(float brightness)
public Bitmap GetFormat1BppIndexed(float brightness, Color _color1, Color _color2)
public Hashtable ZaehleFarben()
public void Save(string fileName)
Original von kleines_eichhoernchen
Ja von Image ableiten ist ja möglich, nur dann hast du die Funktionen von Bitmap nicht
Stimmt. Aber die Kompatibilität wäre zumindest ein wenig erhöht worden.
Wichtig wären natürlich GetPixel und SetPixel gewesen. Keine Ahnung, warum die in der Image-Klasse fehlen.
Genauso wenig kann ich nachvollziehen, warum Bitmap sealed ist...
An sich fand ich die Idee und die Umsetzung schon ganz interessant, allerdings hat mich die Verzögerung beim Erstellen der Klasse gestört (z.B. 300dpi A4-Seite).
Man kann das Ganze noch weiter beschleunigen, indem man bei GetPixel und SetPixel direkt auf die Bilddaten zugreift. Neben der Vermeidung der anfänglichen Verzögerung kann man zumindest GetPixel bei entsprechender Optimierung nochmal um einiges beschleunigen, bei SetPixel hab' ich's noch nicht getestet. Und ausserdem wird der Speicher radikal entlastet.
Das erste was ich tue ist, die Bilddaten aus dem Bild zu holen. Dann damit arbeiten und erst wieder bei Gebrauch in das Bild zu schreiben.
Daher ist das so schnell.
Eine Verzögerung ist nur beim ersten Gebrauch der Klasse da durch:
Zitat
Damit das erste Bild nicht etwas länger braucht, sollte man :
@Robertico:
Du holst die Daten aus dem Bitmap mittels Marshal.Copy in ein eigenes Byte-Array.
Wenn du nun anstatt einer neuen Matrix mit x*y Color-Einträgen die entsprechende Farbe immer erst bei Bedarf in GetPixel aus dem Byte-Array berechnest, sparst du dir die Matrix und kannst je nach Pixelformat noch ein wenig schneller werden. Besonders die Color-Klasse hat noch einige Überprüfungen, die man übergehen kann (z.B. kann aufgund des Byte-Arrays kein Wert über 255 sein).
Mit InitializeColor habe ich keine Geschwindigkeitssprünge bemerkt.
Wichtig war mir besonders die Vermeidung der anfänglichen Verzögerung, da ich mehrere Bilddateien der Reihe nach einlese, analysiere und dann die nächste nehme.
Die Barcode-Analyse z.B. benötigt nur ca. 1 Sekunde, aber das Einlesen mit RoBitmap dauert 2 Sekunden. Und das ist bei mehreren 100 Dateien (Rechnungszentrum) schon ein Unterschied.
Ich hoffe, ich konnte meine Gedanken diesmal verständlicher formulieren, evtl. krieg' ich auch den Code nochmal vernünftig zusammen.
ich bin bei weitem kein C# Profi.
alles was ich hier produziere ist auf das wenige Wissen der Sprache, dass ich mir bisher angeeignet habe, aufgebaut.
Ich kenne noch lange nicht alle Möglichkeiten. Das einzige was ich gut kann, ist kombinieren.
Dann fummle ich mir daraus etwas zurecht.
Mein Wissen wird zwar täglich größer, aber wie hier bin ich immer sehr dankbar wenn mir jemand hilft, der mehr Ahnung hat.
Baue sie einfach um und hänge sie an. Mit der Zeit bekommen wir dann vielleicht eine super Klasse.
mit großem Interesse habe ich deinen Beitrag gelesen.
Ich denke, dass ich mit deinen Überlegungen zu der Klasse der Lösung meines Problems näher komme.
Was habe ich gemacht:
Ich habe mir eine Klasse zum Konvertieren von Bildformaten geschrieben, wobei ich die gegebenen Möglichkeiten der Framework und hier der Image-Klasse ausnutze. Laut "Petzold" sollte man immer die Image-Klasse nutzen, wenn man seine Aufgaben damit lösen kann.
Wo hab ich nun ein Problem? Wenn ich nach TIF oder GIF konvertieren lasse, dann wirft es mir immer eine Exception, wobei ich beim Debuggen festgestellt habe, dass das Pixelformat immer wieder auf PixelFormat.Format8bppIndexed zurückgesetzt wurde. Der resultierende Stream ist dann auch nur noch 2408 groß X(
Eine weitere Sache wäre noch die Speicherverwaltung? Da weiß ich auch noch nicht, ob das so alles optimal gelöst ist...
Vielleicht hat jemand noch eine Idee... ich komme hier jedenfalls nicht mehr weiter (nicht so richtig...).
Habe Unterstützung bekommen.
janismac hat sich bereit erklärt in der Klasse mal richtig Ordnung zu machen und Kommentare hinein zu schreiben.
Dann wird er es in eine DLL packen und hier zur Verfügung stellen.
Bei der Gelegenheit möchte ich noch ein Schnipselchen vorstellen, dass ich letztens gebraucht habe. Es tauscht im Bild eine Farbe gegen eine andere. Und das in gewohnter Schnelle.
public void TauscheFarbe(Color f1, Color f2)
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
if (color[x, y] == f1)
{
color[x, y] = f2;
}
}
}
}
kann man diese Klasse auch im Compact Framework nutzen? In der MSDN steht ja das der Namespace Drawing komplett vorhanden ist. Aber die ColorPalette scheint nicht vorhanden zu sein. PixelFormat macht keine Probleme.
Könnte man sich die ColorPalette dann irgendwie nachbauen? Oder mache ich irgendwas anders falsch?
das ist nen ziemlich netter Code leider fehlte mir die rückwandlung in 8bpp. Deswegen hab ich sie mal nach implementiert und anscheinend funktioniert sie auch.
Bitmap ReturnFormat8BppIndexed()
{
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
bildDaten[y * stride + x] = color[x, y].R;
System.Drawing.Imaging.BitmapData bmpData =
Bild.LockBits(rect, System.Drawing.Imaging.ImageLockMode.WriteOnly,
Bild.PixelFormat);
IntPtr ptr = bmpData.Scan0;
System.Runtime.InteropServices.Marshal.Copy(bildDaten, 0, ptr, bytes);
Bild.UnlockBits(bmpData);
modified = false;
return Bild;
}