Eigene DrawImage-Funktion mit LockBits zeichnet nicht das, was sie sollte

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

Es gibt 7 Antworten in diesem Thema. Der letzte Beitrag () ist von ichduersie.

    Eigene DrawImage-Funktion mit LockBits zeichnet nicht das, was sie sollte

    Guten Morgen.

    Ich arbeite derzeit an einer eigenen DrawImage-Funktion mit LockBits. Ich erhoffe mir von Ihr einen schnelleren Zeichenvorgang als mit Graphics.DrawImage. Um ein Bild auf ein anderes zu zeichnen übertrage Ich jedes Pixel einzeln, aber das Ergebnis ist irgendwie nicht das, was Ich mir erhofft hatte. Hier ist mal der Quellcode:

    BitmapGraphics (für die Pixeloperationen mit LockBits auf einem Bitmap)

    C#-Quellcode

    1. ​using System;
    2. using System.Drawing;
    3. using System.Drawing.Imaging;
    4. using System.Runtime.InteropServices;
    5. namespace WindowsFormsApplication1
    6. {
    7. public class BitmapGraphics
    8. {
    9. Bitmap source = null;
    10. IntPtr Iptr = IntPtr.Zero;
    11. BitmapData bitmapData = null;
    12. public byte[] Pixels { get; set; }
    13. public int Depth { get; private set; }
    14. public int Width { get; private set; }
    15. public int Height { get; private set; }
    16. public BitmapGraphics(Bitmap source)
    17. {
    18. this.source = source;
    19. }
    20. /// <summary>
    21. /// Lock bitmap data
    22. /// </summary>
    23. public void LockBits()
    24. {
    25. try
    26. {
    27. // Get width and height of bitmap
    28. Width = source.Width;
    29. Height = source.Height;
    30. // get total locked pixels count
    31. int PixelCount = Width * Height;
    32. // Create rectangle to lock
    33. Rectangle rect = new Rectangle(0, 0, Width, Height);
    34. // get source bitmap pixel format size
    35. Depth = System.Drawing.Bitmap.GetPixelFormatSize(source.PixelFormat);
    36. // Check if bpp (Bits Per Pixel) is 8, 24, or 32
    37. if (Depth != 8 && Depth != 24 && Depth != 32)
    38. {
    39. throw new ArgumentException("Only 8, 24 and 32 bpp images are supported.");
    40. }
    41. // Lock bitmap and return bitmap data
    42. bitmapData = source.LockBits(rect, ImageLockMode.ReadWrite,
    43. source.PixelFormat);
    44. // create byte array to copy pixel values
    45. int step = Depth / 8;
    46. Pixels = new byte[PixelCount * step];
    47. Iptr = bitmapData.Scan0;
    48. // Copy data from pointer to array
    49. Marshal.Copy(Iptr, Pixels, 0, Pixels.Length);
    50. }
    51. catch (Exception ex)
    52. {
    53. throw ex;
    54. }
    55. }
    56. /// <summary>
    57. /// Unlock bitmap data
    58. /// </summary>
    59. public void UnlockBits()
    60. {
    61. try
    62. {
    63. // Copy data from byte array to pointer
    64. Marshal.Copy(Pixels, 0, Iptr, Pixels.Length);
    65. // Unlock bitmap data
    66. source.UnlockBits(bitmapData);
    67. }
    68. catch (Exception ex)
    69. {
    70. throw ex;
    71. }
    72. }
    73. /// <summary>
    74. /// Get the color of the specified pixel
    75. /// </summary>
    76. /// <param name="x"></param>
    77. /// <param name="y"></param>
    78. /// <returns></returns>
    79. public Color GetPixel(int x, int y)
    80. {
    81. Color clr = Color.Empty;
    82. // Get color components count
    83. int cCount = Depth / 8;
    84. // Get start index of the specified pixel
    85. int i = ((y * Width) + x) * cCount;
    86. if (i > Pixels.Length - cCount)
    87. throw new IndexOutOfRangeException();
    88. if (Depth == 32) // For 32 bpp get Red, Green, Blue and Alpha
    89. {
    90. byte b = Pixels[i];
    91. byte g = Pixels[i + 1];
    92. byte r = Pixels[i + 2];
    93. byte a = Pixels[i + 3]; // a
    94. clr = Color.FromArgb(a, r, g, b);
    95. }
    96. if (Depth == 24) // For 24 bpp get Red, Green and Blue
    97. {
    98. byte b = Pixels[i];
    99. byte g = Pixels[i + 1];
    100. byte r = Pixels[i + 2];
    101. clr = Color.FromArgb(r, g, b);
    102. }
    103. if (Depth == 8)
    104. // For 8 bpp get color value (Red, Green and Blue values are the same)
    105. {
    106. byte c = Pixels[i];
    107. clr = Color.FromArgb(c, c, c);
    108. }
    109. return clr;
    110. }
    111. /// <summary>
    112. /// Set the color of the specified pixel
    113. /// </summary>
    114. /// <param name="x"></param>
    115. /// <param name="y"></param>
    116. /// <param name="color"></param>
    117. public void SetPixel(int x, int y, Color color)
    118. {
    119. // Get color components count
    120. int cCount = Depth / 8;
    121. // Get start index of the specified pixel
    122. int i = ((y * Width) + x) * cCount;
    123. if (Depth == 32) // For 32 bpp set Red, Green, Blue and Alpha
    124. {
    125. Pixels[i] = color.B;
    126. Pixels[i + 1] = color.G;
    127. Pixels[i + 2] = color.R;
    128. Pixels[i + 3] = color.A;
    129. }
    130. if (Depth == 24) // For 24 bpp set Red, Green and Blue
    131. {
    132. Pixels[i] = color.B;
    133. Pixels[i + 1] = color.G;
    134. Pixels[i + 2] = color.R;
    135. }
    136. if (Depth == 8)
    137. // For 8 bpp set color value (Red, Green and Blue values are the same)
    138. {
    139. Pixels[i] = color.B;
    140. }
    141. }
    142. }
    143. }
    Code von codeproject.com/Tips/240428/Wo…bitmap-faster-with-Csharp

    Die Anwendung

    C#-Quellcode

    1. ​private void Form1_Load(object sender, EventArgs e)
    2. {
    3. Bitmap bmp = new Bitmap(50, 50);
    4. using (Graphics g = Graphics.FromImage(bmp))
    5. {
    6. g.Clear(Color.Red);
    7. }
    8. Bitmap tzg = new Bitmap(100, 100);
    9. using (Graphics g = Graphics.FromImage(tzg))
    10. {
    11. g.Clear(Color.Black);
    12. }
    13. tzg = DrawImage(bmp, tzg, new Point(10, 10));
    14. pictureBox1.Image = tzg;
    15. }
    16. private Bitmap DrawImage(Bitmap sourceImage, Bitmap destinationImage, Point location)
    17. {
    18. BitmapGraphics bg = new BitmapGraphics(sourceImage);
    19. BitmapGraphics bgx = new BitmapGraphics(destinationImage);
    20. int cx = location.X;
    21. int cy = location.Y;
    22. bg.LockBits();
    23. bgx.LockBits();
    24. //Jedes Pixel auf dem sourceImage durchgehen und auf dem destinationImage an der Position cx, cy zeichnen
    25. for (int x = 0; x < sourceImage.Width; x++)
    26. {
    27. for (int y = 0; y < sourceImage.Height; y++)
    28. {
    29. if (cx >= destinationImage.Width) continue;
    30. if (cy >= destinationImage.Height) continue;
    31. bgx.SetPixel(cx, cy, bg.GetPixel(x, y));
    32. cy++;
    33. }
    34. cx++;
    35. }
    36. bg.UnlockBits();
    37. bgx.UnlockBits();
    38. return destinationImage;
    39. }



    Was eigentlich rauskommen sollte, sieht so aus:


    Was jedoch rauskam sieht so aus:


    Bin schon im Einzelschritt komplett durchgesteppt, aber finde den Fehler nicht. Könnt Ihr mir da bitte helfen?


    LG

    ~ides
    am besten als erstes die TryCatchens raus - was soll denn das: alle Fehler fangen, und dann weiter-werfen?
    Damit du ja nicht die Zeile angezeigt bekommst, wo der Fehler auftritt?

    Mehr kann ich dazu nicht sagen - da bräuchte ich ein Beispiel-projekt, um richtig debuggen zu können.

    Alternativ könntest du dich auch mit meiner PixelHelper-Klasse beschäftigen - die ermöglicht auch direktes Rumwerkeln in den Bitmap-Daten: activevb.de/cgi-bin/tippupload/show/261/Convolution_Filter
    Vorteil wäre, dasses was lauffähiges zum Downloaden ist.
    @ErfinderDesRades: Den oberen Code habe Ich nicht selbst geschrieben. Die Quelle stehe ja auch unten im Spoiler. War aber wohl ein ganz heller am Werk... ;)

    @nafets: Kenne Ich nicht. Werde Ich mir mal anschauen, Danke.

    Melde mich dann wieder.
    Wenns nur ums drawen geht, probier auch Graphics.DrawImageUnscaled(). Das ist zig-fach schneller als das normale .DrawImage. BiBlt ist nochmal 4-fach schneller (wenn ich mich recht erinnere), aber auch nur, wenn mans richtig anfasst, und es ausserdem unmanaged belassen kann (also nicht noch eine Bitmap daraus erzeugen und so Scherze).

    ichduersie schrieb:

    Was eigentlich rauskommen sollte, sieht so aus:
    Du musst cy innerhalb der x-Schleife jedes Mal neu initialisieren:
    Spoiler anzeigen

    C#-Quellcode

    1. private Bitmap DrawImage(Bitmap sourceImage, Bitmap destinationImage, Point location)
    2. {
    3. BitmapGraphics bg = new BitmapGraphics(sourceImage);
    4. BitmapGraphics bgx = new BitmapGraphics(destinationImage);
    5. int cx = location.X;
    6. //int cy = location.Y;
    7. bg.LockBits();
    8. bgx.LockBits();
    9. //Jedes Pixel auf dem sourceImage durchgehen und auf dem destinationImage an der Position cx, cy zeichnen
    10. for (int x = 0; x < sourceImage.Width; x++, cx++)
    11. {
    12. int cy = location.Y; // Dies hier
    13. if (cx >= destinationImage.Width) continue;
    14. for (int y = 0; y < sourceImage.Height; y++, cy++)
    15. {
    16. if (cy >= destinationImage.Height) continue;
    17. bgx.SetPixel(cx, cy, bg.GetPixel(x, y));
    18. }
    19. }
    20. bg.UnlockBits();
    21. bgx.UnlockBits();
    22. return destinationImage;
    23. }

    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!
    Ja klar. Meine Güte, das kommt bei raus, wenn man 5h lang ohne Pause arbeitet. Macht natürlich Sinn, dass der Y-Wert bei jedem X-Wert wieder zurückgesetzt werden muss. :facepalm:
    Danke @RodFromGermany!

    @ErfinderDesRades: Ich konnte mit ​DrawImageUnscaled keinen Unterschied zum normalen ​DrawImage feststellen.

    Werde das jetzt mal mit LockBits und dem BitBliting probieren. Wir sehen dann ja was raus kommt.

    Vorerst mal Danke für alle Antworten.

    LG