1
0
mirror of https://github.com/greenshot/greenshot.git synced 2025-03-12 05:25:25 -07:00

Better documentation for the DibHelper [skip ci]

This commit is contained in:
Robin Krom 2022-02-03 09:10:05 +01:00
parent 9ac6d22c96
commit 6b90f2c3ff
No known key found for this signature in database
GPG Key ID: BCC01364F1371490

@ -35,15 +35,20 @@ namespace Greenshot.Base.Core
/// </summary>
internal static class DibHelper
{
private const double DpiToPelsPerMeter = 39.3701;
/// <summary>
/// Converts the Bitmap to a Device Independent Bitmap format of type BITFIELDS.
/// </summary>
/// <param name="sourceBitmap">Bitmap to convert to DIB</param>
/// <returns>The image converted to DIB, in bytes.</returns>
/// <returns>byte{} with the image converted to DIB</returns>
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<byte, BITMAPINFOHEADER>(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<byte, RGBQUAD>(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<byte> bitmapSourceSpan;
unsafe
{
bitmapSourceSpan = new Span<byte>(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();