diff --git a/src/Greenshot.Base/Core/DibHelper.cs b/src/Greenshot.Base/Core/DibHelper.cs index 61cb84411..59af37414 100644 --- a/src/Greenshot.Base/Core/DibHelper.cs +++ b/src/Greenshot.Base/Core/DibHelper.cs @@ -35,15 +35,20 @@ namespace Greenshot.Base.Core /// internal static class DibHelper { + private const double DpiToPelsPerMeter = 39.3701; + /// /// Converts the Bitmap to a Device Independent Bitmap format of type BITFIELDS. /// /// Bitmap to convert to DIB - /// The image converted to DIB, in bytes. + /// byte{} with the image converted to DIB public static byte[] ConvertToDib(this Bitmap sourceBitmap) { if (sourceBitmap == null) throw new ArgumentNullException(nameof(sourceBitmap)); + var area = new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height); + + // If the supplied format doesn't match 32bpp, we need to convert it first, and dispose the new bitmap afterwards bool needsDisposal = false; if (sourceBitmap.PixelFormat != PixelFormat.Format32bppArgb) { @@ -51,22 +56,27 @@ namespace Greenshot.Base.Core var clonedImage = ImageHelper.CreateEmptyLike(sourceBitmap, Color.Transparent, PixelFormat.Format32bppArgb); using (var graphics = Graphics.FromImage(clonedImage)) { - graphics.DrawImage(sourceBitmap, new Rectangle(0, 0, clonedImage.Width, clonedImage.Height)); + graphics.DrawImage(sourceBitmap, area); } sourceBitmap = clonedImage; } + // All the pixels take this many bytes: var bitmapSize = 4 * sourceBitmap.Width * sourceBitmap.Height; + // The bitmap info hear takes this many bytes: var bitmapInfoHeaderSize = Marshal.SizeOf(typeof(BITMAPINFOHEADER)); + // The bitmap info size is the header + 3 RGBQUADs var bitmapInfoSize = bitmapInfoHeaderSize + 3 * Marshal.SizeOf(typeof(RGBQUAD)); - // Create a byte [] to contain the DIB + // Create a byte [] to contain the complete DIB (with .NET 5 and upwards, we could write the pixels directly to a stream) var fullBmpBytes = new byte[bitmapInfoSize + bitmapSize]; + // Get a span for this, this simplifies the code a bit var fullBmpSpan = fullBmpBytes.AsSpan(); // Cast the span to be of type BITMAPINFOHEADER so we can assign values - // TODO: in .NET 6 we could do a AsRef + // TODO: in .NET 6 we could do a AsRef, and even write to a stream directly var bitmapInfoHeader = MemoryMarshal.Cast(fullBmpSpan); + // Fill up the bitmap info header bitmapInfoHeader[0].biSize = (uint)bitmapInfoHeaderSize; bitmapInfoHeader[0].biWidth = sourceBitmap.Width; bitmapInfoHeader[0].biHeight = sourceBitmap.Height; @@ -74,34 +84,37 @@ namespace Greenshot.Base.Core bitmapInfoHeader[0].biBitCount = 32; bitmapInfoHeader[0].biCompression = BI_COMPRESSION.BI_BITFIELDS; bitmapInfoHeader[0].biSizeImage = (uint)bitmapSize; - bitmapInfoHeader[0].biXPelsPerMeter = (int)(sourceBitmap.HorizontalResolution * 39.3701); - bitmapInfoHeader[0].biYPelsPerMeter = (int)(sourceBitmap.VerticalResolution * 39.3701); + bitmapInfoHeader[0].biXPelsPerMeter = (int)(sourceBitmap.HorizontalResolution * DpiToPelsPerMeter); + bitmapInfoHeader[0].biYPelsPerMeter = (int)(sourceBitmap.VerticalResolution * DpiToPelsPerMeter); - // The aforementioned "BITFIELDS": color masks applied to the Int32 pixel value to get the R, G and B values. + // Specify the color masks applied to the Int32 pixel value to get the R, G and B values. var rgbQuads = MemoryMarshal.Cast(fullBmpSpan.Slice(Marshal.SizeOf(typeof(BITMAPINFOHEADER)))); rgbQuads[0].rgbRed = 255; rgbQuads[1].rgbGreen = 255; rgbQuads[2].rgbBlue = 255; - // Now copy the lines, in reverse to the byte array - var sourceBitmapData = sourceBitmap.LockBits(new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height), ImageLockMode.ReadOnly, sourceBitmap.PixelFormat); + // Now copy the lines, in reverse (bmp is upside down) to the byte array + var sourceBitmapData = sourceBitmap.LockBits(area, ImageLockMode.ReadOnly, sourceBitmap.PixelFormat); try { - // Get a span for the real bitmap bytes, which starts after the header + // Get a span for the real bitmap bytes, which starts after the bitmapinfo (header + 3xRGBQuad) var bitmapSpan = fullBmpSpan.Slice(bitmapInfoSize); - // Make sure we also have a span to copy from + // Make sure we also have a span to copy from, by taking the pointer from the locked bitmap Span bitmapSourceSpan; unsafe { bitmapSourceSpan = new Span(sourceBitmapData.Scan0.ToPointer(), sourceBitmapData.Stride * sourceBitmapData.Height); } - // Loop over all the lines and copy the top line to the bottom (flipping the image) - for (int y = 0; y < sourceBitmap.Height; y++) + // Loop over all the bitmap lines + for (int destinationY = 0; destinationY < sourceBitmap.Height; destinationY++) { - var sourceY = (sourceBitmap.Height - 1) - y; + // Calculate the y coordinate for the bottom up. (flipping the image) + var sourceY = (sourceBitmap.Height - 1) - destinationY; + // Make a Span for the source bitmap pixels var sourceLine = bitmapSourceSpan.Slice(sourceBitmapData.Stride * sourceY, 4 * sourceBitmap.Width); - var destinationLine = bitmapSpan.Slice(y * 4 * sourceBitmap.Width); + // Make a Span for the destination dib pixels + var destinationLine = bitmapSpan.Slice(destinationY * 4 * sourceBitmap.Width); sourceLine.CopyTo(destinationLine); } } @@ -110,6 +123,7 @@ namespace Greenshot.Base.Core sourceBitmap.UnlockBits(sourceBitmapData); } + // If we created a new bitmap, we need to dispose this if (needsDisposal) { sourceBitmap.Dispose();