Scanline-Algorithmus

    • .NET (FX) 4.5–4.8
    • C#

    Es gibt 15 Antworten in diesem Thema. Der letzte Beitrag () ist von jvbsl.

      Scanline-Algorithmus

      Hallo,

      im Folgenden sei gezeigt, wie Dreiecke sehr schnell gefüllt werden können.
      Genutzt wird hierzu der sogenannte Scanline-Algorithmus.

      Dieser Algorithmus iteriert durch ein Array, in der sowohl die minimalen und maximalen X-Positionen der Seiten einer Figur gespeichert sind.

      Pro Zeile werden dann die minimale und maximale X-Komponente ausgelesen und in einer for-Schleife mit dem Interval [min, max] die jeweiligen Pixel zwischen diesen
      beiden gesetzt.

      Die Größe des Array ist die Höhe des Bildschirms multipliziert mit zwei, das jeweils die min, max Werte der jeweiligen Zeile abbildet.

      Auf den Algorithmus stieß ich _ in diesem Video: .

      Für Berechnung der jeweiligen Min-Max Werte ist lediglich lineare Algebra notwendig.

      Ein Dreieck wird aus drei Punkten gebildet.

      Diese drei Punkten werden jeweils mit Gerade verbunden und zu einem Dreieck ergänzt.

      Jedes Dreieck hat in der Regel einen oberen, mittleren und unteren Punkt.

      Den oberen Punkt nenne ich O, den mittleren M und analog dazu den unteren U.

      Es werden drei Strecken gebildet, die jeweils die Seiten
      OM
      MU
      UO
      repräsentieren.

      Eine Gerade hat folgende Darstellung:

      y = m * x + b

      Die Steigung lässt sich ganz schnell über den Steigungsdreieck berechnen:

      m = (b.y - a.y) / (b.x - b.y)

      Der Achsenabschnitt lässt sich folgendergestalt berechnen:

      b = y - m * x


      Weil wir die X Werte für jede Zeile (Y-Position) bestimmen möchten, müssen wir die lineare Gleichung nach x auflösen:

      x = (y - b) / m

      Weil aber eben genau diese Funktion mehrmals aufgerufen wird (in drei for Schleifen) und Division eine aufwändige Operation ist, berechnen wir im Konstruktor einfach

      m_frac = 1 / m

      und können dann bei jedem Iterationsschritt dann statt zu dividieren multiplizieren (was soweit ich weiß wesentlich schneller ist) :

      x = (y - b) * m_frac


      Es gibt jedoch einige Sonderfälle zu beachten, exemplarisch wenn es sich um ein rechtwinkliges Dreieck handelt.
      In so einem Fall würde die Steigung entweder 0 oder unendlich betragen weil entweder im Nenner oder Zähler eine 0 vor käme.

      Der Algorithmus sieht dann ungefähr so aus:


      1.) Punkte nach upper, mid, lower sortieren
      2.) Strecken bilden von upper zu mid, mid zu lower, lower zu upper
      3.) Index berechnen: Das heißt, analysieren ob Strecke OM MU im Min -oder Max-Bereich liegt
      4.) Die Zeilen der jeweiligen Geraden durch-iterieren und die Min-Max Werte setzen
      5.) _Draw() aufrufen, und als Parameter Y-Werte der Lower und Upper-Punkte übergeben
      6.) For-Schleife von Lower.Y bis Upper.Y, Min-Max auslesen, For-Schleife von Min bis Max, Pixel setzen...

      Zu Punkt 3:

      Hierfür ist es notwendig den Schnittpunkt zwischen OM und MU zu berechnen.

      Der Schnittpunkt lässt sich über die Formel:

      x = |(OM_b - MU_b) / (OM_m - MU_m)|

      berechnen.

      Die Betragsstriche aus dem Grund, weil so die Größe der jeweiligen Werte keine Rolle spielen. Es muss also nicht im vorhinein geprüft werden welche Zahl größer ist.
      Der dort zustande kommende X-Wert muss dann in eine der Gerade eingesetzt werden:

      y = m * x + b

      Mit dem Y-Wert wird dann eine neue - konstante Funktion - formuliert, und der X-Wert des Schnittpunktes mit UO ermittelt.
      Wenn nun dieser X-Wert größer ist als der X-Wert des Schnittpunktes zwischen OM MU, so ist die Gerade UO im maximalen Bereich, und vice versas.


      Hier eine Illustration:



      Der Code:

      C#-Quellcode

      1. ​public static int GetIndex(Line upMid, Line midDown, Line downUp)
      2. {
      3. float iX = 0;
      4. float iY = 0;
      5. float diffM = upMid.Slope - midDown.Slope;
      6. if (float.IsNaN(diffM) || float.IsInfinity(diffM))
      7. {
      8. iX = upMid.End.X;
      9. iY = upMid.End.Y;
      10. }
      11. else
      12. {
      13. iX = Math.Abs((upMid.AxisSection - midDown.AxisSection) / (upMid.Slope - midDown.Slope));
      14. iY = upMid.GetY(iX);
      15. }
      16. float inX = downUp.GetX(iY).Value;
      17. return iX > inX ? 1 : 0;
      18. }


      Die FillTriangle-Methode:

      C#-Quellcode

      1. private PointF _Up = new Point();
      2. private PointF _Mid = new Point();
      3. private PointF _Down = new Point();
      4. public void FillTriangle(PointF a, PointF b, PointF c)
      5. {
      6. if (a.Y <= b.Y && a.Y <= c.Y)
      7. _Up = a;
      8. else if (b.Y < a.Y && b.Y <= c.Y)
      9. _Up = b;
      10. else if (c.Y <= a.Y && c.Y <= b.Y)
      11. _Up = c;
      12. if (a.Y >= b.Y && a.Y >= c.Y)
      13. _Down = a;
      14. else if (b.Y >= a.Y && b.Y >= c.Y)
      15. _Down = b;
      16. else if (c.Y >= a.Y && c.Y >= b.Y)
      17. _Down = c;
      18. if (a != _Up && a.Y >= _Up.Y && a != _Down && a.Y <= _Down.Y)
      19. _Mid = a;
      20. else if (b != _Up && b.Y >= _Up.Y && b != _Down && b.Y <= _Down.Y)
      21. _Mid = b;
      22. else if (c != _Up && c.Y >= _Up.Y && c != _Down && c.Y <= _Down.Y)
      23. _Mid = c;
      24. if (_Up.Y >= Height)
      25. return;
      26. if (_Down.Y < 0)
      27. return;
      28. if (_Up.X >= Width && _Mid.X >= Width && _Down.X >= Width)
      29. return;
      30. if (_Up.X < 0 && _Mid.X < 0 && _Down.X < 0)
      31. return;
      32. Line upMidLine = new Line(_Up, _Mid);
      33. Line midDownLine = new Line(_Mid, _Down);
      34. Line downUpLine = new Line(_Down, _Up);
      35. int indexUpMid = Utilities.GetIndex(upMidLine, midDownLine, downUpLine);
      36. int indexMidDown = indexUpMid;
      37. int indexDownUp = 1 - indexMidDown;
      38. // up to mid
      39. for (float y = _Up.Y; y < _Mid.Y; y++)
      40. {
      41. _SetScanLineBuffer((int)y, (int)upMidLine.GetX(y), indexUpMid);
      42. }
      43. // mid to down
      44. for (float y = _Mid.Y; y < _Down.Y; y++)
      45. {
      46. _SetScanLineBuffer((int)y, (int)midDownLine.GetX(y), indexMidDown);
      47. }
      48. // mid to down
      49. for (float y = _Up.Y; y < _Down.Y; y++)
      50. {
      51. _SetScanLineBuffer((int)y, (int)downUpLine.GetX(y), indexDownUp);
      52. }
      53. _DrawScanLine((int)_Up.Y, (int)_Down.Y);
      54. }


      Die _DrawScanLine-Methode:

      C#-Quellcode

      1. private void _DrawScanLine(int yMin, int yMax)
      2. {
      3. Parallel.For((yMin >= 0 ? yMin : 0), (yMax <= Height ? yMax : Height), delegate (int y)
      4. {
      5. int xMin = _ScanLine[0 + y * 2];
      6. int xMax = _ScanLine[1 + y * 2];
      7. for (int x = xMin; x < xMax; x++)
      8. {
      9. if (x + 1 < 0 || x + 1 >= Width)
      10. continue;
      11. _BackBuffer.SetPixel(x, y, r, g, b);
      12. }
      13. });
      14. }


      Ergebnis:




      Liebe Grüße.

      _
      Und Gott alleine weiß alles am allerbesten und besser.

      φConst schrieb:

      die jeweiligen Pixel zwischen diesen
      beiden
      haben Integer-Koordinaten.
      Du rechnest jedoch mit Float bzw. Double-Koordinaten.
      Wenn Du Deinen Algo auf Integer umstellst, sollte er sicherer und auch ein wenig schneller laufen.
      Falls das Polygon mit einer Graphics-Instanz ansprechbar ist (PictureBox, Bitmap), gugst Du hier: Punkt-in-Polygon-Test nach Jordan funktioniert nicht
      Falls es sich um schnöde Freihandlinien in einer Graphics-Instanz handelt, nimm FloodFill: pinvoke.net/default.aspx/gdi32.FloodFill
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!
      Hallo
      das sollte eben eine Alternative zu Graphics-Fill werden, weil dieser keine Möglichkeit anbietet eine Textur auf ein Dreieck über Textur-Koordinaten, mittels baryzentrischen Koordinaten zu mappen.
      Wenn das ganze selber implementiert wird, ist dies umsetzbar: Die Methode _BackBuffer.SetPixel() kann mit zusätzlichen Funktionalitäten ergänzt werden.


      Liebe Grüße.

      Addendum: Es macht sehr wohl Sinn Float zu verwenden, weil die Steigung der Gerade selten ganzzahlig ist.

      Und Gott alleine weiß alles am allerbesten und besser.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „φConst“ ()

      φConst schrieb:

      weil die Steigung der Gerade selten ganzzahlig ist.
      Die Pixel haben ganzzahlige Koordinaten.
      Und es gibt Integer-Algorithmen, die dies effizient machen: de.wikipedia.org/wiki/Bresenham-Algorithmus
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!
      Hallo, ja das ist wahr; der Code ist ja aber im Grunde austauschbar.

      Hier eine Methode mit der man eben Texturen auf Dreiecke mappen kann:

      C#-Quellcode

      1. Vector4 v0, v1;
      2. float d00, d01, d11;
      3. float denom, denomFrac;
      4. bool _invalidateBarycentrics;
      5. private void _PutPixel(int x, int y, VertexPositionColor a, VertexPositionColor b, VertexPositionColor c)
      6. {
      7. // Code aus: https://gamedev.stackexchange.com/a/23745/112793
      8. #region Barycentric
      9. Vector4 v2 = new Vector4(x, y, 0, 1) - (a.Position * a.Factor);
      10. if (_invalidateBarycentrics)
      11. {
      12. v0 = b.Position * b.Factor - a.Position * a.Factor;
      13. v1 = c.Position * c.Factor - a.Position * a.Factor;
      14. d00 = Dot(v0, v0);
      15. d01 = Dot(v0, v1);
      16. d11 = Dot(v1, v1);
      17. denom = d00 * d11 - d01 * d01;
      18. denomFrac = 1.0f / denom;
      19. _invalidateBarycentrics = false;
      20. }
      21. float d20 = v2.X * v0.X + v2.Y * v0.Y;
      22. float d21 = v2.X * v1.X + v2.Y * v1.Y;
      23. float v = Math.Abs((d11 * d20 - d01 * d21) * denomFrac);
      24. float w = Math.Abs((d00 * d21 - d01 * d20) * denomFrac);
      25. float u = Math.Abs(1.0f - v - w);
      26. #endregion
      27. Color _Pixel = GetTexturePixel(a, b, c, u, v, w);
      28. _BackBuffer.SetPixel(x, y, _Pixel.R, _Pixel.G, _Pixel.B, 255);
      29. }
      30. public Color GetTexturePixel(VertexPositionColor a, VertexPositionColor b, VertexPositionColor c, float u, float v, float w)
      31. {
      32. float x = (a.TextureCoordinate.X * u + b.TextureCoordinate.X * v + c.TextureCoordinate.X * w) * _Texture.Width;
      33. float y = (a.TextureCoordinate.Y * u + b.TextureCoordinate.Y * v + c.TextureCoordinate.Y * w) * _Texture.Height;
      34. return _Texture.GetPixel((int)x, (int)y);
      35. }
      36. public static float Dot(Vector4 a, Vector4 b)
      37. {
      38. return a.X * b.X + a.Y * b.Y;
      39. }


      Ich weiß: Die Berechnung der baryzentrischen Koordinaten könnte man in eine externe Funktion verladen; Grund dies nichts zu tun:
      Die Methode _PutPixel() wird oft aufgerufen, und ein Methoden-Aufruf kostet Zeit.

      Ergebnis (ohne Debugger):


      Theoretisch könnte man damit eine eigene 3D-Engine in WinForms starten.
      Es gab's hier einen mal (GDI3D - 3D auch ohne DirectX) der sich daran versuchte und über die Geschwindigkeit von LockBits sich beschwerte.
      Über DirectBitmaps geht das imo ebenso gut und schnell: stackoverflow.com/a/34801225/9816636

      Man müsste halt noch Clipping et cetera implementieren.

      Liebe Grüße.

      _
      Und Gott alleine weiß alles am allerbesten und besser.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „φConst“ ()

      msdn.microsoft.com/en-us/libra…lattribute(v=vs.110).aspx
      Damit solltest das auch auslagern können, ich glaub das bekommt er schön geinlined...

      Dann wärs vlt. nicht schlecht wenn du System.Numerics vektoren verwendest(falls du das nicht schon tust) und evtl. auch noch an anderen stellen anwendest um bisschen SIMD auszunutzen...

      Das setzen der Pixel bei Lockbits ist bestimmt nicht das Problem, denn das ist einfach setzen von Memory. Klar Lock/Unlock kostet bissl Zeit, aber das ist erstmal nicht das Bottleneck.

      Eine Optiimierung ist z.B. dass man das Memory inkrementell durchgeht um das caching auszunutzen. Ebenso mit pointern+inkremennt arbeiten, spart man sich den range check so wie ständige multiplikationen
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---
      Hallo,
      Numerics Vektoren nutze ich nicht (bei der 3D Programmierung kann das aber dann doch eigentlich nützlich werden _), was genau meinst du mit caching?

      Das ich quasi zuerst durch den Y-Wert iteriere wegen der Index-Formel x+y*Width, sodass wenn y zuerst iteriert wird, x=1, x=2 x=... dann auch den Index 1, 2, et cetera ergibt?
      Das mache ich meines Wissens nach ja schon, siehe Methode: Scanline-Algorithmus

      Kannst du das mit Pointern+Inkrement weiter ausführen? Meinst du irgendwas mit *(p+n) ?

      Liebe Grüße.
      _
      Und Gott alleine weiß alles am allerbesten und besser.
      Das caching in diesem Fall ist ein positiver Nebeneffekt wenn man für den Prozessor vorhersehbare Datenzugriffe macht und dazu gehört auch eben Memory kontinuirlich durchzugene, aber wenn du das schon machst ist das grundsätzlich schonmal gut, wobei für Scanline es anders auch nicht wirklich Sinn ergibt^^

      Statt

      C#-Quellcode

      1. byte[] data;
      2. for (int i=0;i<xyz;i++)
      3. data[i] = 12;

      eben so:

      C#-Quellcode

      1. byte* data;
      2. for (var ptr=data;ptr<data+xyz;ptr++)
      3. *ptr = 12;

      Früher hat das zumindest mal Zeit gespart, da unnötige Array-Range checks gespart werden, was aber ok ist, wenn wir genau Wissen, dass wir im richtigen bereich sein werden. Wenn du aber nicht drumherum kommst memory anzufixen, dann kann es auf diese Art inzwischen auch langsamer sein, da das mit den Range-Checks für Arrays inzwischen relativ gut optimiert wurde, dass es bei schleifen eben nur am Anfang der Schleife geprüft wird ob der Range sinnvoll ist...
      Aber wenn du LockBits verwendest hast du es sowieso erstmal als IntPtr, also musst du auch kein Memory anfixen, was hierfür also perfekt geeignet ist...
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---
      Ich benutze keine LockBits, hab es mal nur so probiert für die Textur, also die Pixel der Textur werden mittels LockBits gelesen, das Setzen der Pixel
      des "Backbuffers" erfolgt über DirectBitmap:

      C#-Quellcode

      1. // Quelle: https://stackoverflow.com/a/34801225/9816636
      2. public class DirectBitmap : IDisposable
      3. {
      4. public Bitmap Bitmap { get; private set; }
      5. public byte[] Bits { get; private set; }
      6. public bool Disposed { get; private set; }
      7. public int Height { get; private set; }
      8. public int Width { get; private set; }
      9. protected GCHandle BitsHandle { get; private set; }
      10. private Graphics g;
      11. public DirectBitmap(int width, int height)
      12. {
      13. this.Width = width;
      14. this.Height = height;
      15. this.Bits = new byte[width * height * 4];
      16. this.BitsHandle = GCHandle.Alloc(this.Bits, GCHandleType.Pinned);
      17. this.Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, this.BitsHandle.AddrOfPinnedObject());
      18. g = Graphics.FromImage(Bitmap);
      19. }
      20. public static DirectBitmap LoadFromFile(string path)
      21. {
      22. Bitmap _src = new Bitmap(path);
      23. return LoadFromBitmap(_src);
      24. }
      25. public static DirectBitmap LoadFromBitmap(Bitmap source)
      26. {
      27. Bitmap _src = source;
      28. BitmapData _data = _src.LockBits(new Rectangle(0, 0, _src.Width, _src.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppPArgb);
      29. IntPtr _ptr = _data.Scan0;
      30. byte[] _matrix = new byte[_src.Width * _src.Height * 4];
      31. Marshal.Copy(_ptr, _matrix, 0, _matrix.Length);
      32. _src.UnlockBits(_data);
      33. DirectBitmap _dbmp = new DirectBitmap(_src.Width, _src.Height);
      34. for (int i = 0; i < _dbmp.Bits.Length; i++)
      35. _dbmp.Bits[i] = _matrix[i];
      36. _matrix = new byte[0];
      37. _src.Dispose();
      38. return _dbmp;
      39. }
      40. public void SetPixel(int x, int y, byte r, byte g, byte b)
      41. {
      42. int num = x * 4 + y * 4 * this.Width;
      43. this.Bits[num] = b;
      44. this.Bits[num + 1] = g;
      45. this.Bits[num + 2] = r;
      46. this.Bits[num + 3] = byte.MaxValue;
      47. }
      48. public Color GetPixel(int x, int y)
      49. {
      50. int num = x * 4 + y * 4 * this.Width;
      51. byte b = this.Bits[num];
      52. byte g = this.Bits[num + 1];
      53. byte r = this.Bits[num + 2];
      54. byte a = this.Bits[num + 3]; ;
      55. return Color.FromArgb(a, r, g, b);
      56. }
      57. public void Dispose()
      58. {
      59. bool disposed = this.Disposed;
      60. if (!disposed)
      61. {
      62. this.Disposed = true;
      63. this.Bitmap.Dispose();
      64. this.BitsHandle.Free();
      65. }
      66. }
      67. public void Clear()
      68. {
      69. g.Clear(Color.Black);
      70. }
      71. }


      Vielleicht könnte man da ebenfalls über Pointer arbeiten, ob das aber jetzt soviel schneller ist, ist diskutabel.

      Und ja: Bei einem Scan-Line Algorithmus inkrementiert man sowieso zuerst über Y dann X, was zwangsläufig ja auch mit der Architektur dieses Algorithmus' korrespondiert, netter Nebeneffekt ist eben das Ausnutzen des Cachings ^^


      Addendum:

      DirectBitmapPointer,

      C#-Quellcode

      1. // Quelle: https://stackoverflow.com/a/34801225/9816636
      2. public unsafe class DirectBitmapPointer
      3. {
      4. public Bitmap Bitmap { get; private set; }
      5. public byte* Bits { get; private set; }
      6. public bool Disposed { get; private set; }
      7. public int Height { get; private set; }
      8. public int Width { get; private set; }
      9. private Graphics g;
      10. public DirectBitmapPointer(int width, int height)
      11. {
      12. this.Width = width;
      13. this.Height = height;
      14. this.Bits = (byte*)Marshal.AllocHGlobal(Width * Height * 4);
      15. this.Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, (IntPtr)Bits);
      16. g = Graphics.FromImage(Bitmap);
      17. }
      18. public static DirectBitmapPointer LoadFromFile(string path)
      19. {
      20. Bitmap _src = new Bitmap(path);
      21. return LoadFromBitmap(_src);
      22. }
      23. public static DirectBitmapPointer LoadFromBitmap(Bitmap source)
      24. {
      25. Bitmap _src = source;
      26. BitmapData _data = _src.LockBits(new Rectangle(0, 0, _src.Width, _src.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppPArgb);
      27. IntPtr _ptr = _data.Scan0;
      28. byte[] _matrix = new byte[_src.Width * _src.Height * 4];
      29. Marshal.Copy(_ptr, _matrix, 0, _matrix.Length);
      30. _src.UnlockBits(_data);
      31. DirectBitmapPointer _dbmp = new DirectBitmapPointer(_src.Width, _src.Height);
      32. for (int i = 0; i < _matrix.Length; i++)
      33. _dbmp.Bits[i] = _matrix[i];
      34. _matrix = new byte[0];
      35. _src.Dispose();
      36. return _dbmp;
      37. }
      38. public void SetPixel(int x, int y, byte r, byte g, byte b)
      39. {
      40. int num = x * 4 + y * 4 * this.Width;
      41. *(Bits + num) = b;
      42. *(Bits + num + 1) = g;
      43. *(Bits + num + 2) = r;
      44. *(Bits + num + 3) = 255;
      45. }
      46. public Color GetPixel(int x, int y)
      47. {
      48. int num = x * 4 + y * 4 * this.Width;
      49. byte b = *(Bits + num);
      50. byte g = *(Bits + num + 1);
      51. byte r = *(Bits + num + 2);
      52. byte a = *(Bits + num + 3);
      53. return Color.FromArgb(a, r, g, b);
      54. }
      55. public void Dispose()
      56. {
      57. bool disposed = this.Disposed;
      58. if (!disposed)
      59. {
      60. this.Disposed = true;
      61. this.Bitmap.Dispose();
      62. Marshal.FreeHGlobal((IntPtr)Bits);
      63. }
      64. }
      65. public void Clear()
      66. {
      67. g.Clear(Color.Black);
      68. }
      69. }


      Ist in der Tat etwas schneller; Textur wird nun auch über DirectBitmap angesprochen, Ergebnis ohne Debugger:


      Liebe Grüße.

      _
      Und Gott alleine weiß alles am allerbesten und besser.

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „φConst“ ()

      ja die DirectBitmap hat nen paar unnötige Dinge drinne. Geh lieber direkt hin und nutze den _data.Scan0 ptr, die Daten darin sind bereits quasi gepinnt(weil es natives memory ist), also kein stress mit dem anpinnen -> somit performanter.
      Außerdem kann man sich das unnötige Copy der Daten komplett sparen und direkt bearbeiten. Das ganze geht sehr schön mit C# Pointern. Nächste Optimierung dabei ist, du kannst direkt int Pointer verwenden und deine Farben als int angeben, das nutzt nämlich dann aus, dass wir direkt ganze register auf einmal kopieren können. Wenn dir das mit int nicht gefällt, dann kannst du dir eine Color-struct machen, welche ARGB byte variablen im Memory in der zu deinem Bitmap korrespondenten Reihenfolge enthält, StructLayout dabei Sequentiell angeben. Sollte dann beinahe auf den selben Code wie mit int runtergebrochen werden, abhängig davon wie gut C# damit umgehen kann und das optimiert. Aber performanter als das hin- und herkopieren aller Daten ist es alle mal.
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---
      Hatte mich verguckt und Hauptsächlich LoadFromBitmap angeguckt... Sorry

      Das ab dem zweiten Absatz gilt trotzdem weiterhin und wären eben Optimierungen für SetPixel/Getpixel.

      Etwas für den Zugriff mit flachem Index wäre bestimmt auch hilfreich, denn ein inkrement ist schneller als eine Multiplikation und eine addition^^

      Achja wie immer für so kleine funktionen(wenns um perf geht): Inlining
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---
      @Moderation: Antrag auf Verschiebung des Threads in ein anderes Unter-Forum, exemplarisch Source-Code Austausch oder von mir aus OffTopic, weil diese Freischalterei ein wenig kontraproduktiv ist. Lieben Dank.

      @jvbsl:

      jvbsl schrieb:

      Etwas für den Zugriff mit flachem Index wäre bestimmt auch hilfreich, denn ein inkrement ist schneller als eine Multiplikation und eine addition^^



      Wie meinst du das denn mit flachem Index? Wie sähe das denn zB aus?
      Liebe Grüße.
      Und Gott alleine weiß alles am allerbesten und besser.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „φConst“ ()

      Naja einfach ein
      GetColor(int index) anstatt x/y. Manchmal ist x/y nicht so wichtig, oder man hat einfach index++. Wenn das ganze dann geinlined wird sollte das bei zumindest einer eindimensionalen Schleife zu einer Boundary-Check-Optimierung führen, sodass eben nicht bei jedem Aufruf überprüft wird obs innerhalb des Arrays ist oder nicht. Abgesehen von der Optimierung von * kombiniert mit + zu einem inkrement ;)
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---
      Neue berechnen ist wesentlich schneller als Dictionary^^
      Im Dictionary wird ja auch alles in buckets unterteilt und über die GetHashCode funktion in die Buckets aufgeteilt, das ist wesentlich komplizierter als die Multiplikation und Addition, mal abgesehen davon, dass es ein Methoden Call ist, .Net Core inlined es(so vermute ich, da eine struct nicht weiter vererbt werden kann, zumindest sealed class kann er viel inlinen - von interfaces) bei .Net Framework wäre ich mir aber tatsächlich nicht sicher. Abgesehen davon, dass du den Dictionary overhead haben wirst(auch allein schon der Speicherzugriff auf den index ist etwas, was durch das direkte berechnen weg bleibt)^^

      Edit: Da fällt mir noch ein, nen Enumerator kannst auch machen, aber bin mir gerade echt nicht sicher wie sinnvoll das ist evtl. einen eigenen Enumerator schreiben, denn Arrays scheinen nen class enumerator zu haben, struct wäre aber besser. Außerdem direkt zurückgeben und nicht IEnumerable...
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „jvbsl“ ()