Merge branch 'release/1.3' into feature/ImproveFileFormatSupport

This commit is contained in:
Robin Krom 2022-02-03 00:09:49 +01:00
commit 43a1e964d5
No known key found for this signature in database
GPG key ID: BCC01364F1371490
4 changed files with 292 additions and 88 deletions

View file

@ -667,8 +667,8 @@ EndSelection:<<<<<<<4
if (imageStream != null)
{
byte[] dibBuffer = new byte[imageStream.Length];
imageStream.Read(dibBuffer, 0, dibBuffer.Length);
var infoHeader = BinaryStructHelper.FromByteArray<BITMAPINFOHEADER>(dibBuffer);
_ = imageStream.Read(dibBuffer, 0, dibBuffer.Length);
var infoHeader = BinaryStructHelper.FromByteArray<BITMAPINFOHEADERV5>(dibBuffer);
if (!infoHeader.IsDibV5)
{
Log.InfoFormat("Using special DIB <v5 format reader with biCompression {0}", infoHeader.biCompression);
@ -816,8 +816,6 @@ EndSelection:<<<<<<<4
return sb.ToString();
}
private const int BITMAPFILEHEADER_LENGTH = 14;
/// <summary>
/// Set an Image to the clipboard
/// This method will place images to the clipboard depending on the ClipboardFormats setting.
@ -869,7 +867,7 @@ EndSelection:<<<<<<<4
{
// Create the stream for the clipboard
dibStream = new MemoryStream();
var dibBytes = DibHelper.ConvertToDib(imageToSave);
var dibBytes = ((Bitmap)imageToSave).ConvertToDib();
dibStream.Write(dibBytes,0, dibBytes.Length);
// Set the DIB to the clipboard DataObject
@ -890,7 +888,7 @@ EndSelection:<<<<<<<4
dibV5Stream = new MemoryStream();
// Create the BITMAPINFOHEADER
BITMAPINFOHEADER header = new BITMAPINFOHEADER(imageToSave.Width, imageToSave.Height, 32)
var header = new BITMAPINFOHEADERV5(imageToSave.Width, imageToSave.Height, 32)
{
// Make sure we have BI_BITFIELDS, this seems to be normal for Format17?
biCompression = BI_COMPRESSION.BI_BITFIELDS
@ -1127,7 +1125,7 @@ EndSelection:<<<<<<<4
/// <returns></returns>
public static IEnumerable<string> GetImageFilenames(IDataObject dataObject)
{
string[] dropFileNames = (string[]) dataObject.GetData(DataFormats.FileDrop);
string[] dropFileNames = (string[])dataObject.GetData(DataFormats.FileDrop);
if (dropFileNames != null && dropFileNames.Length > 0)
{
var supportedExtensions = FileFormatHandlerRegistry.ExtensionsFor(FileFormatHandlerActions.LoadFromStream).ToList();

View file

@ -1,4 +1,25 @@
using System;
/*
* Greenshot - a free and open source screenshot tool
* Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom
*
* For more information see: https://getgreenshot.org/
* The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
@ -9,77 +30,91 @@ namespace Greenshot.Base.Core
/// <summary>
/// Though Greenshot implements the specs for the DIB image format,
/// it seems to cause a lot of issues when using the clipboard.
/// There is some research done about the DIB on the clipboard, this code was taking from:
/// There is some research done about the DIB on the clipboard, this code is based upon the information
/// <a href="https://stackoverflow.com/questions/44177115/copying-from-and-to-clipboard-loses-image-transparency">here</a>
/// </summary>
internal static class DibHelper
{
/// <summary>
/// Converts the image to Device Independent Bitmap format of type BITFIELDS.
/// This is (wrongly) accepted by many applications as containing transparency,
/// so I'm abusing it for that.
/// Converts the Bitmap to a Device Independent Bitmap format of type BITFIELDS.
/// </summary>
/// <param name="image">Image to convert to DIB</param>
/// <param name="sourceBitmap">Bitmap to convert to DIB</param>
/// <returns>The image converted to DIB, in bytes.</returns>
public static byte[] ConvertToDib(Image image)
public static byte[] ConvertToDib(this Bitmap sourceBitmap)
{
byte[] bm32bData;
int width = image.Width;
int height = image.Height;
// Ensure image is 32bppARGB by painting it on a new 32bppARGB image.
using (var bm32b = ImageHelper.CreateEmptyLike(image, Color.Transparent, PixelFormat.Format32bppArgb))
{
using (var graphics = Graphics.FromImage(bm32b))
{
graphics.DrawImage(image, new Rectangle(0, 0, bm32b.Width, bm32b.Height));
}
// Bitmap format has its lines reversed.
bm32b.RotateFlip(RotateFlipType.Rotate180FlipX);
bm32bData = GetImageData(bm32b, out var stride);
}
// BITMAPINFOHEADER struct for DIB.
uint hdrSize = 0x28;
var fullImage = new byte[hdrSize + 12 + bm32bData.Length];
var bitmapInfoHeader = MemoryMarshal.Cast<byte, BITMAPINFOHEADER>(fullImage.AsSpan());
if (sourceBitmap == null) throw new ArgumentNullException(nameof(sourceBitmap));
bitmapInfoHeader[0].biSize = hdrSize;
bitmapInfoHeader[0].biWidth = width;
bitmapInfoHeader[0].biHeight = height;
bool needsDisposal = false;
if (sourceBitmap.PixelFormat != PixelFormat.Format32bppArgb)
{
needsDisposal = true;
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));
}
sourceBitmap = clonedImage;
}
var bitmapSize = 4 * sourceBitmap.Width * sourceBitmap.Height;
var bitmapInfoHeaderSize = Marshal.SizeOf(typeof(BITMAPINFOHEADER));
var bitmapInfoSize = bitmapInfoHeaderSize + 3 * Marshal.SizeOf(typeof(RGBQUAD));
// Create a byte [] to contain the DIB
var fullBmpBytes = new byte[bitmapInfoSize + bitmapSize];
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
var bitmapInfoHeader = MemoryMarshal.Cast<byte, BITMAPINFOHEADER>(fullBmpSpan);
bitmapInfoHeader[0].biSize = (uint)bitmapInfoHeaderSize;
bitmapInfoHeader[0].biWidth = sourceBitmap.Width;
bitmapInfoHeader[0].biHeight = sourceBitmap.Height;
bitmapInfoHeader[0].biPlanes = 1;
bitmapInfoHeader[0].biBitCount = 32;
bitmapInfoHeader[0].biCompression = BI_COMPRESSION.BI_BITFIELDS;
bitmapInfoHeader[0].biSizeImage = (uint)bm32bData.Length;
bitmapInfoHeader[0].biXPelsPerMeter = (int)(image.HorizontalResolution * 39.3701);
bitmapInfoHeader[0].biYPelsPerMeter = (int)(image.VerticalResolution * 39.3701);
bitmapInfoHeader[0].biSizeImage = (uint)bitmapSize;
bitmapInfoHeader[0].biXPelsPerMeter = (int)(sourceBitmap.HorizontalResolution * 39.3701);
bitmapInfoHeader[0].biYPelsPerMeter = (int)(sourceBitmap.VerticalResolution * 39.3701);
// The aforementioned "BITFIELDS": 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;
// The aforementioned "BITFIELDS": colour masks applied to the Int32 pixel value to get the R, G and B values.
bitmapInfoHeader[0].bV5RedMask = 0x00FF0000;
bitmapInfoHeader[0].bV5GreenMask = 0x0000FF00;
bitmapInfoHeader[0].bV5BlueMask = 0x000000FF;
// 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);
try
{
// Get a span for the real bitmap bytes, which starts after the header
var bitmapSpan = fullBmpSpan.Slice(bitmapInfoSize);
// Make sure we also have a span to copy from
Span<byte> bitmapSourceSpan;
unsafe
{
bitmapSourceSpan = new Span<byte>(sourceBitmapData.Scan0.ToPointer(), sourceBitmapData.Stride * sourceBitmapData.Height);
}
// These are all 0. Since .net clears new arrays, don't bother writing them.
//Int32 biClrUsed = 0;
//Int32 biClrImportant = 0;
// Loop over all the lines and copy the top line to the bottom (flipping the image)
for (int y = 0; y < sourceBitmap.Height; y++)
{
var sourceY = (sourceBitmap.Height - 1) - y;
var sourceLine = bitmapSourceSpan.Slice(sourceBitmapData.Stride * sourceY, 4 * sourceBitmap.Width);
var destinationLine = bitmapSpan.Slice(y * 4 * sourceBitmap.Width);
sourceLine.CopyTo(destinationLine);
}
}
finally
{
sourceBitmap.UnlockBits(sourceBitmapData);
}
Array.Copy(bm32bData, 0, fullImage, hdrSize + 12, bm32bData.Length);
return fullImage;
}
/// <summary>
/// Gets the raw bytes from an image.
/// </summary>
/// <param name="sourceImage">The image to get the bytes from.</param>
/// <param name="stride">Stride of the retrieved image data.</param>
/// <returns>The raw bytes of the image</returns>
public static byte[] GetImageData(Bitmap sourceImage, out int stride)
{
BitmapData sourceData = sourceImage.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), ImageLockMode.ReadOnly, sourceImage.PixelFormat);
stride = sourceData.Stride;
byte[] data = new byte[stride * sourceImage.Height];
Marshal.Copy(sourceData.Scan0, data, 0, data.Length);
sourceImage.UnlockBits(sourceData);
return data;
if (needsDisposal)
{
sourceBitmap.Dispose();
}
return fullBmpBytes;
}
}
}

View file

@ -332,13 +332,13 @@ namespace Greenshot.Base.Core
}
// Create BITMAPINFOHEADER for CreateDIBSection
BITMAPINFOHEADER bmi = new BITMAPINFOHEADER(captureBounds.Width, captureBounds.Height, 24);
BITMAPINFOHEADERV5 bmi = new BITMAPINFOHEADERV5(captureBounds.Width, captureBounds.Height, 24);
// Make sure the last error is set to 0
Win32.SetLastError(0);
// create a bitmap we can copy it to, using GetDeviceCaps to get the width/height
using SafeDibSectionHandle safeDibSectionHandle = GDI32.CreateDIBSection(desktopDcHandle, ref bmi, BITMAPINFOHEADER.DIB_RGB_COLORS, out _, IntPtr.Zero, 0);
using SafeDibSectionHandle safeDibSectionHandle = GDI32.CreateDIBSection(desktopDcHandle, ref bmi, BITMAPINFOHEADERV5.DIB_RGB_COLORS, out _, IntPtr.Zero, 0);
if (safeDibSectionHandle.IsInvalid)
{
// Get Exception before the error is lost

View file

@ -261,7 +261,7 @@ namespace Greenshot.Base.UnmanagedHelpers
public static extern SafeCompatibleDCHandle CreateCompatibleDC(SafeHandle hDC);
[DllImport("gdi32", SetLastError = true)]
public static extern SafeDibSectionHandle CreateDIBSection(SafeHandle hdc, ref BITMAPINFOHEADER bmi, uint Usage, out IntPtr bits, IntPtr hSection, uint dwOffset);
public static extern SafeDibSectionHandle CreateDIBSection(SafeHandle hdc, ref BITMAPINFOHEADERV5 bmi, uint usage, out IntPtr bits, IntPtr hSection, uint dwOffset);
[DllImport("gdi32", SetLastError = true)]
public static extern SafeRegionHandle CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);
@ -314,6 +314,15 @@ namespace Greenshot.Base.UnmanagedHelpers
}
}
[StructLayout(LayoutKind.Sequential)]
public struct RGBQUAD
{
public byte rgbBlue;
public byte rgbGreen;
public byte rgbRed;
public byte rgbReserved;
}
[StructLayout(LayoutKind.Sequential)]
public struct CIEXYZTRIPLE
{
@ -348,15 +357,168 @@ namespace Greenshot.Base.UnmanagedHelpers
[FieldOffset(28)] public int biYPelsPerMeter;
[FieldOffset(32)] public uint biClrUsed;
[FieldOffset(36)] public uint biClrImportant;
[FieldOffset(40)] public uint bV5RedMask;
[FieldOffset(44)] public uint bV5GreenMask;
[FieldOffset(48)] public uint bV5BlueMask;
[FieldOffset(52)] public uint bV5AlphaMask;
[FieldOffset(56)] public uint bV5CSType;
[FieldOffset(60)] public CIEXYZTRIPLE bV5Endpoints;
[FieldOffset(96)] public uint bV5GammaRed;
[FieldOffset(100)] public uint bV5GammaGreen;
[FieldOffset(104)] public uint bV5GammaBlue;
public const int DIB_RGB_COLORS = 0;
public BITMAPINFOHEADER(int width, int height, ushort bpp)
{
biSize = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADER)); // BITMAPINFOHEADER < DIBV4 is 40 bytes
biPlanes = 1; // Should allways be 1
biCompression = BI_COMPRESSION.BI_RGB;
biWidth = width;
biHeight = height;
biBitCount = bpp;
biSizeImage = (uint)(width * height * (bpp >> 3));
biXPelsPerMeter = 0;
biYPelsPerMeter = 0;
biClrUsed = 0;
biClrImportant = 0;
}
public bool IsDibV4
{
get
{
uint sizeOfBMI = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADERV4));
return biSize >= sizeOfBMI;
}
}
public bool IsDibV5
{
get
{
uint sizeOfBMI = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADERV5));
return biSize >= sizeOfBMI;
}
}
public uint OffsetToPixels
{
get
{
if (biCompression == BI_COMPRESSION.BI_BITFIELDS)
{
// Add 3x4 bytes for the bitfield color mask
return biSize + 3 * 4;
}
return biSize;
}
}
}
[StructLayout(LayoutKind.Explicit)]
public struct BITMAPINFOHEADERV4
{
[FieldOffset(0)] public uint biSize;
[FieldOffset(4)] public int biWidth;
[FieldOffset(8)] public int biHeight;
[FieldOffset(12)] public ushort biPlanes;
[FieldOffset(14)] public ushort biBitCount;
[FieldOffset(16)] public BI_COMPRESSION biCompression;
[FieldOffset(20)] public uint biSizeImage;
[FieldOffset(24)] public int biXPelsPerMeter;
[FieldOffset(28)] public int biYPelsPerMeter;
[FieldOffset(32)] public uint biClrUsed;
[FieldOffset(36)] public uint biClrImportant;
[FieldOffset(40)] public uint bV4RedMask;
[FieldOffset(44)] public uint bV4GreenMask;
[FieldOffset(48)] public uint bV4BlueMask;
[FieldOffset(52)] public uint bV4AlphaMask;
[FieldOffset(56)] public uint bV4CSType;
[FieldOffset(60)] public CIEXYZTRIPLE bV4Endpoints;
[FieldOffset(96)] public uint bV4GammaRed;
[FieldOffset(100)] public uint bV4GammaGreen;
[FieldOffset(104)] public uint bV4GammaBlue;
public const int DIB_RGB_COLORS = 0;
public BITMAPINFOHEADERV4(int width, int height, ushort bpp)
{
biSize = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADERV4)); // BITMAPINFOHEADER < DIBV5 is 40 bytes
biPlanes = 1; // Should allways be 1
biCompression = BI_COMPRESSION.BI_RGB;
biWidth = width;
biHeight = height;
biBitCount = bpp;
biSizeImage = (uint)(width * height * (bpp >> 3));
biXPelsPerMeter = 0;
biYPelsPerMeter = 0;
biClrUsed = 0;
biClrImportant = 0;
// V4
bV4RedMask = (uint)255 << 16;
bV4GreenMask = (uint)255 << 8;
bV4BlueMask = 255;
bV4AlphaMask = (uint)255 << 24;
bV4CSType = 0x73524742; // LCS_sRGB
bV4Endpoints = new CIEXYZTRIPLE
{
ciexyzBlue = new CIEXYZ(0),
ciexyzGreen = new CIEXYZ(0),
ciexyzRed = new CIEXYZ(0)
};
bV4GammaRed = 0;
bV4GammaGreen = 0;
bV4GammaBlue = 0;
}
public bool IsDibV4
{
get
{
uint sizeOfBMI = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADERV4));
return biSize >= sizeOfBMI;
}
}
public bool IsDibV5
{
get
{
uint sizeOfBMI = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADERV5));
return biSize >= sizeOfBMI;
}
}
public uint OffsetToPixels
{
get
{
if (biCompression == BI_COMPRESSION.BI_BITFIELDS)
{
// Add 3x4 bytes for the bitfield color mask
return biSize + 3 * 4;
}
return biSize;
}
}
}
[StructLayout(LayoutKind.Explicit)]
public struct BITMAPINFOHEADERV5
{
[FieldOffset(0)] public uint biSize;
[FieldOffset(4)] public int biWidth;
[FieldOffset(8)] public int biHeight;
[FieldOffset(12)] public ushort biPlanes;
[FieldOffset(14)] public ushort biBitCount;
[FieldOffset(16)] public BI_COMPRESSION biCompression;
[FieldOffset(20)] public uint biSizeImage;
[FieldOffset(24)] public int biXPelsPerMeter;
[FieldOffset(28)] public int biYPelsPerMeter;
[FieldOffset(32)] public uint biClrUsed;
[FieldOffset(36)] public uint biClrImportant;
[FieldOffset(40)] public uint bV4RedMask;
[FieldOffset(44)] public uint bV4GreenMask;
[FieldOffset(48)] public uint bV4BlueMask;
[FieldOffset(52)] public uint bV4AlphaMask;
[FieldOffset(56)] public uint bV4CSType;
[FieldOffset(60)] public CIEXYZTRIPLE bV4Endpoints;
[FieldOffset(96)] public uint bV4GammaRed;
[FieldOffset(100)] public uint bV4GammaGreen;
[FieldOffset(104)] public uint bV4GammaBlue;
[FieldOffset(108)] public uint bV5Intent; // Rendering intent for bitmap
[FieldOffset(112)] public uint bV5ProfileData;
[FieldOffset(116)] public uint bV5ProfileSize;
@ -364,9 +526,9 @@ namespace Greenshot.Base.UnmanagedHelpers
public const int DIB_RGB_COLORS = 0;
public BITMAPINFOHEADER(int width, int height, ushort bpp)
public BITMAPINFOHEADERV5(int width, int height, ushort bpp)
{
biSize = (uint) Marshal.SizeOf(typeof(BITMAPINFOHEADER)); // BITMAPINFOHEADER < DIBV5 is 40 bytes
biSize = (uint) Marshal.SizeOf(typeof(BITMAPINFOHEADERV5)); // BITMAPINFOHEADER < DIBV5 is 40 bytes
biPlanes = 1; // Should allways be 1
biCompression = BI_COMPRESSION.BI_RGB;
biWidth = width;
@ -378,32 +540,41 @@ namespace Greenshot.Base.UnmanagedHelpers
biClrUsed = 0;
biClrImportant = 0;
// V5
bV5RedMask = (uint) 255 << 16;
bV5GreenMask = (uint) 255 << 8;
bV5BlueMask = 255;
bV5AlphaMask = (uint) 255 << 24;
bV5CSType = 0x73524742; // LCS_sRGB
bV5Endpoints = new CIEXYZTRIPLE
// V4
bV4RedMask = (uint) 255 << 16;
bV4GreenMask = (uint) 255 << 8;
bV4BlueMask = 255;
bV4AlphaMask = (uint) 255 << 24;
bV4CSType = 0x73524742; // LCS_sRGB
bV4Endpoints = new CIEXYZTRIPLE
{
ciexyzBlue = new CIEXYZ(0),
ciexyzGreen = new CIEXYZ(0),
ciexyzRed = new CIEXYZ(0)
};
bV5GammaRed = 0;
bV5GammaGreen = 0;
bV5GammaBlue = 0;
bV4GammaRed = 0;
bV4GammaGreen = 0;
bV4GammaBlue = 0;
// V5
bV5Intent = 4;
bV5ProfileData = 0;
bV5ProfileSize = 0;
bV5Reserved = 0;
}
public bool IsDibV4
{
get
{
uint sizeOfBMI = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADERV4));
return biSize >= sizeOfBMI;
}
}
public bool IsDibV5
{
get
{
uint sizeOfBMI = (uint) Marshal.SizeOf(typeof(BITMAPINFOHEADER));
uint sizeOfBMI = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADERV5));
return biSize >= sizeOfBMI;
}
}