mirror of https://github.com/SixLabors/ImageSharp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
408 lines
17 KiB
408 lines
17 KiB
// Copyright (c) Six Labors and contributors.
|
|
// Licensed under the Apache License, Version 2.0.
|
|
using System;
|
|
using System.Buffers.Binary;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace SixLabors.ImageSharp.Formats.Bmp
|
|
{
|
|
/// <summary>
|
|
/// This block of bytes tells the application detailed information
|
|
/// about the image, which will be used to display the image on
|
|
/// the screen.
|
|
/// <see href="https://en.wikipedia.org/wiki/BMP_file_format"/>
|
|
/// </summary>
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
internal struct BmpInfoHeader
|
|
{
|
|
/// <summary>
|
|
/// Defines the size of the BITMAPCOREHEADER data structure in the bitmap file.
|
|
/// </summary>
|
|
public const int CoreSize = 12;
|
|
|
|
/// <summary>
|
|
/// Defines the size of the short variant of the OS22XBITMAPHEADER data structure in the bitmap file.
|
|
/// </summary>
|
|
public const int Os22ShortSize = 16;
|
|
|
|
/// <summary>
|
|
/// Defines the size of the BITMAPINFOHEADER (BMP Version 3) data structure in the bitmap file.
|
|
/// </summary>
|
|
public const int SizeV3 = 40;
|
|
|
|
/// <summary>
|
|
/// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it.
|
|
/// </summary>
|
|
public const int AdobeV3Size = 52;
|
|
|
|
/// <summary>
|
|
/// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks (including the alpha channel) are part of the info header instead of following it.
|
|
/// </summary>
|
|
public const int AdobeV3WithAlphaSize = 56;
|
|
|
|
/// <summary>
|
|
/// Defines the size of the BITMAPINFOHEADER (BMP Version 4) data structure in the bitmap file.
|
|
/// </summary>
|
|
public const int SizeV4 = 108;
|
|
|
|
/// <summary>
|
|
/// Defines the size of the biggest supported header data structure in the bitmap file.
|
|
/// </summary>
|
|
public const int MaxHeaderSize = SizeV4;
|
|
|
|
/// <summary>
|
|
/// Defines the size of the <see cref="HeaderSize"/> field.
|
|
/// </summary>
|
|
public const int HeaderSizeSize = 4;
|
|
|
|
public BmpInfoHeader(
|
|
int headerSize,
|
|
int width,
|
|
int height,
|
|
short planes,
|
|
short bitsPerPixel,
|
|
BmpCompression compression = default,
|
|
int imageSize = 0,
|
|
int xPelsPerMeter = 0,
|
|
int yPelsPerMeter = 0,
|
|
int clrUsed = 0,
|
|
int clrImportant = 0,
|
|
int redMask = 0,
|
|
int greenMask = 0,
|
|
int blueMask = 0,
|
|
int alphaMask = 0,
|
|
int csType = 0,
|
|
int redX = 0,
|
|
int redY = 0,
|
|
int redZ = 0,
|
|
int greenX = 0,
|
|
int greenY = 0,
|
|
int greenZ = 0,
|
|
int blueX = 0,
|
|
int blueY = 0,
|
|
int blueZ = 0,
|
|
int gammeRed = 0,
|
|
int gammeGreen = 0,
|
|
int gammeBlue = 0)
|
|
{
|
|
this.HeaderSize = headerSize;
|
|
this.Width = width;
|
|
this.Height = height;
|
|
this.Planes = planes;
|
|
this.BitsPerPixel = bitsPerPixel;
|
|
this.Compression = compression;
|
|
this.ImageSize = imageSize;
|
|
this.XPelsPerMeter = xPelsPerMeter;
|
|
this.YPelsPerMeter = yPelsPerMeter;
|
|
this.ClrUsed = clrUsed;
|
|
this.ClrImportant = clrImportant;
|
|
this.RedMask = redMask;
|
|
this.GreenMask = greenMask;
|
|
this.BlueMask = blueMask;
|
|
this.AlphaMask = alphaMask;
|
|
this.CsType = csType;
|
|
this.RedX = redX;
|
|
this.RedY = redY;
|
|
this.RedZ = redZ;
|
|
this.GreenX = greenX;
|
|
this.GreenY = greenY;
|
|
this.GreenZ = greenZ;
|
|
this.BlueX = blueX;
|
|
this.BlueY = blueY;
|
|
this.BlueZ = blueZ;
|
|
this.GammaRed = gammeRed;
|
|
this.GammaGreen = gammeGreen;
|
|
this.GammaBlue = gammeBlue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the size of this header
|
|
/// </summary>
|
|
public int HeaderSize { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the bitmap width in pixels (signed integer).
|
|
/// </summary>
|
|
public int Width { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the bitmap height in pixels (signed integer).
|
|
/// </summary>
|
|
public int Height { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the number of color planes being used. Must be set to 1.
|
|
/// </summary>
|
|
public short Planes { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the number of bits per pixel, which is the color depth of the image.
|
|
/// Typical values are 1, 4, 8, 16, 24 and 32.
|
|
/// </summary>
|
|
public short BitsPerPixel { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the compression method being used.
|
|
/// See the next table for a list of possible values.
|
|
/// </summary>
|
|
public BmpCompression Compression { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the image size. This is the size of the raw bitmap data (see below),
|
|
/// and should not be confused with the file size.
|
|
/// </summary>
|
|
public int ImageSize { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the horizontal resolution of the image.
|
|
/// (pixel per meter, signed integer)
|
|
/// </summary>
|
|
public int XPelsPerMeter { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the vertical resolution of the image.
|
|
/// (pixel per meter, signed integer)
|
|
/// </summary>
|
|
public int YPelsPerMeter { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the number of colors in the color palette,
|
|
/// or 0 to default to 2^n.
|
|
/// </summary>
|
|
public int ClrUsed { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the number of important colors used,
|
|
/// or 0 when every color is important{ get; set; } generally ignored.
|
|
/// </summary>
|
|
public int ClrImportant { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets red color mask. This is used with the BITFIELDS decoding.
|
|
/// </summary>
|
|
public int RedMask { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets green color mask. This is used with the BITFIELDS decoding.
|
|
/// </summary>
|
|
public int GreenMask { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets blue color mask. This is used with the BITFIELDS decoding.
|
|
/// </summary>
|
|
public int BlueMask { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets alpha color mask. This is not used yet.
|
|
/// </summary>
|
|
public int AlphaMask { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Color space type. Not used yet.
|
|
/// </summary>
|
|
public int CsType { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the X coordinate of red endpoint. Not used yet.
|
|
/// </summary>
|
|
public int RedX { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Y coordinate of red endpoint. Not used yet.
|
|
/// </summary>
|
|
public int RedY { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Z coordinate of red endpoint. Not used yet.
|
|
/// </summary>
|
|
public int RedZ { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the X coordinate of green endpoint. Not used yet.
|
|
/// </summary>
|
|
public int GreenX { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Y coordinate of green endpoint. Not used yet.
|
|
/// </summary>
|
|
public int GreenY { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Z coordinate of green endpoint. Not used yet.
|
|
/// </summary>
|
|
public int GreenZ { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the X coordinate of blue endpoint. Not used yet.
|
|
/// </summary>
|
|
public int BlueX { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Y coordinate of blue endpoint. Not used yet.
|
|
/// </summary>
|
|
public int BlueY { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Z coordinate of blue endpoint. Not used yet.
|
|
/// </summary>
|
|
public int BlueZ { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Gamma red coordinate scale value. Not used yet.
|
|
/// </summary>
|
|
public int GammaRed { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Gamma green coordinate scale value. Not used yet.
|
|
/// </summary>
|
|
public int GammaGreen { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Gamma blue coordinate scale value. Not used yet.
|
|
/// </summary>
|
|
public int GammaBlue { get; set; }
|
|
|
|
/// <summary>
|
|
/// Parses the BITMAPCOREHEADER (BMP Version 2) consisting of the headerSize, width, height, planes, and bitsPerPixel fields (12 bytes).
|
|
/// </summary>
|
|
/// <param name="data">The data to parse.</param>
|
|
/// <returns>Parsed header</returns>
|
|
/// <seealso href="https://msdn.microsoft.com/en-us/library/windows/desktop/dd183372.aspx"/>
|
|
public static BmpInfoHeader ParseCore(ReadOnlySpan<byte> data)
|
|
{
|
|
return new BmpInfoHeader(
|
|
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
|
|
width: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(4, 2)),
|
|
height: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(6, 2)),
|
|
planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(8, 2)),
|
|
bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(10, 2)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses a short variant of the OS22XBITMAPHEADER. It is identical to the BITMAPCOREHEADER, except that the width and height
|
|
/// are 4 bytes instead of 2, resulting in 16 bytes total.
|
|
/// </summary>
|
|
/// <param name="data">The data to parse.</param>
|
|
/// <returns>Parsed header</returns>
|
|
/// <seealso href="https://www.fileformat.info/format/os2bmp/egff.htm"/>
|
|
public static BmpInfoHeader ParseOs22Short(ReadOnlySpan<byte> data)
|
|
{
|
|
return new BmpInfoHeader(
|
|
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
|
|
width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)),
|
|
height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)),
|
|
planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)),
|
|
bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses the full BMP Version 3 BITMAPINFOHEADER header (40 bytes).
|
|
/// </summary>
|
|
/// <param name="data">The data to parse.</param>
|
|
/// <returns>The parsed header.</returns>
|
|
/// <seealso href="http://www.fileformat.info/format/bmp/egff.htm"/>
|
|
public static BmpInfoHeader ParseV3(ReadOnlySpan<byte> data)
|
|
{
|
|
return new BmpInfoHeader(
|
|
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
|
|
width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)),
|
|
height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)),
|
|
planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)),
|
|
bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)),
|
|
compression: (BmpCompression)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)),
|
|
imageSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)),
|
|
xPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)),
|
|
yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)),
|
|
clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)),
|
|
clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it.
|
|
/// 52 bytes without the alpha mask, 56 bytes with the alpha mask.
|
|
/// </summary>
|
|
/// <param name="data">The data to parse.</param>
|
|
/// <param name="withAlpha">Indicates, if the alpha bitmask is present.</param>
|
|
/// <returns>The parsed header.</returns>
|
|
/// <seealso href="https://forums.adobe.com/message/3272950#3272950"/>
|
|
public static BmpInfoHeader ParseAdobeV3(ReadOnlySpan<byte> data, bool withAlpha = true)
|
|
{
|
|
return new BmpInfoHeader(
|
|
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
|
|
width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)),
|
|
height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)),
|
|
planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)),
|
|
bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)),
|
|
compression: (BmpCompression)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)),
|
|
imageSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)),
|
|
xPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)),
|
|
yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)),
|
|
clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)),
|
|
clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)),
|
|
redMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(40, 4)),
|
|
greenMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(44, 4)),
|
|
blueMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(48, 4)),
|
|
alphaMask: withAlpha ? BinaryPrimitives.ReadInt32LittleEndian(data.Slice(52, 4)) : 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses the full BMP Version 4 BITMAPINFOHEADER header (108 bytes).
|
|
/// </summary>
|
|
/// <param name="data">The data to parse.</param>
|
|
/// <returns>The parsed header.</returns>
|
|
/// <seealso href="http://www.fileformat.info/format/bmp/egff.htm"/>
|
|
public static BmpInfoHeader ParseV4(ReadOnlySpan<byte> data)
|
|
{
|
|
if (data.Length != SizeV4)
|
|
{
|
|
throw new ArgumentException(nameof(data), $"Must be {SizeV4} bytes. Was {data.Length} bytes.");
|
|
}
|
|
|
|
return MemoryMarshal.Cast<byte, BmpInfoHeader>(data)[0];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a bitmap version 3 (Microsoft Windows NT) header to a buffer (40 bytes).
|
|
/// </summary>
|
|
/// <param name="buffer">The buffer to write to.</param>
|
|
public void WriteV3Header(Span<byte> buffer)
|
|
{
|
|
buffer.Clear();
|
|
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(0, 4), SizeV3);
|
|
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(4, 4), this.Width);
|
|
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(8, 4), this.Height);
|
|
BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(12, 2), this.Planes);
|
|
BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(14, 2), this.BitsPerPixel);
|
|
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(16, 4), (int)this.Compression);
|
|
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(20, 4), this.ImageSize);
|
|
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(24, 4), this.XPelsPerMeter);
|
|
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(28, 4), this.YPelsPerMeter);
|
|
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(32, 4), this.ClrUsed);
|
|
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(36, 4), this.ClrImportant);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a complete Bitmap V4 header to a buffer.
|
|
/// </summary>
|
|
/// <param name="buffer">The buffer to write to.</param>
|
|
public unsafe void WriteV4Header(Span<byte> buffer)
|
|
{
|
|
ref BmpInfoHeader dest = ref Unsafe.As<byte, BmpInfoHeader>(ref MemoryMarshal.GetReference(buffer));
|
|
|
|
dest = this;
|
|
}
|
|
|
|
internal void VerifyDimensions()
|
|
{
|
|
const int MaximumBmpDimension = 65535;
|
|
|
|
if (this.Width > MaximumBmpDimension || this.Height > MaximumBmpDimension)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"The input bmp '{this.Width}x{this.Height}' is "
|
|
+ $"bigger then the max allowed size '{MaximumBmpDimension}x{MaximumBmpDimension}'");
|
|
}
|
|
}
|
|
}
|
|
}
|