Browse Source

Merge pull request #530 from carbon/bmp

Simplify Bmp Decoding / Encoding
af/merge-core
James Jackson-South 8 years ago
committed by GitHub
parent
commit
2b760dc78f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/ImageSharp/Formats/Bmp/BmpCompression.cs
  2. 256
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  3. 100
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  4. 19
      src/ImageSharp/Formats/Bmp/BmpFileHeader.cs
  5. 76
      src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
  6. 7
      src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs
  7. 5
      src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
  8. 2
      src/ImageSharp/Formats/Bmp/ImageExtensions.cs
  9. 21
      tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs

2
src/ImageSharp/Formats/Bmp/BmpCompression.cs

@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// Defines how the compression type of the image data
/// in the bitmap file.
/// </summary>
internal enum BmpCompression
internal enum BmpCompression : int
{
/// <summary>
/// Each image row has a multiple of four elements. If the

256
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -34,29 +34,29 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private const int Rgb16BMask = 0x1F;
/// <summary>
/// RLE8 flag value that indicates following byte has special meaning
/// RLE8 flag value that indicates following byte has special meaning.
/// </summary>
private const int RleCommand = 0x00;
/// <summary>
/// RLE8 flag value marking end of a scan line
/// RLE8 flag value marking end of a scan line.
/// </summary>
private const int RleEndOfLine = 0x00;
/// <summary>
/// RLE8 flag value marking end of bitmap data
/// RLE8 flag value marking end of bitmap data.
/// </summary>
private const int RleEndOfBitmap = 0x01;
/// <summary>
/// RLE8 flag value marking the start of [x,y] offset instruction
/// RLE8 flag value marking the start of [x,y] offset instruction.
/// </summary>
private const int RleDelta = 0x02;
/// <summary>
/// The stream to decode from.
/// </summary>
private Stream currentStream;
private Stream stream;
/// <summary>
/// The file header containing general information.
@ -163,18 +163,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Invert(int y, int height, bool inverted)
{
int row;
if (!inverted)
{
row = height - y - 1;
}
else
{
row = y;
}
return row;
return (!inverted) ? height - y - 1 : y;
}
/// <summary>
@ -261,7 +250,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
while (count < buffer.Length)
{
if (this.currentStream.Read(cmd, 0, cmd.Length) != 2)
if (this.stream.Read(cmd, 0, cmd.Length) != 2)
{
throw new Exception("Failed to read 2 bytes from stream");
}
@ -283,27 +272,29 @@ namespace SixLabors.ImageSharp.Formats.Bmp
break;
case RleDelta:
int dx = this.currentStream.ReadByte();
int dy = this.currentStream.ReadByte();
int dx = this.stream.ReadByte();
int dy = this.stream.ReadByte();
count += (w * dy) + dx;
break;
default:
// If the second byte > 2, signals 'absolute mode'
// If the second byte > 2, we are in 'absolute mode'
// Take this number of bytes from the stream as uncompressed data
int length = cmd[1];
int copyLength = length;
byte[] run = new byte[length];
this.stream.Read(run, 0, run.Length);
run.AsSpan().CopyTo(buffer.Slice(count));
count += run.Length;
// Absolute mode data is aligned to two-byte word-boundary
length += length & 1;
int padding = length & 1;
byte[] run = new byte[length];
this.currentStream.Read(run, 0, run.Length);
for (int i = 0; i < copyLength; i++)
{
buffer[count++] = run[i];
}
this.stream.Skip(padding);
break;
}
@ -348,7 +339,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
using (IManagedByteBuffer row = this.memoryManager.AllocateCleanManagedByteBuffer(arrayWidth + padding))
{
var color = default(TPixel);
TPixel color = default;
var rgba = new Rgba32(0, 0, 0, 255);
Span<byte> rowSpan = row.Span;
@ -356,7 +347,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
this.currentStream.Read(row.Array, 0, row.Length());
this.stream.Read(row.Array, 0, row.Length());
int offset = 0;
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
@ -402,7 +393,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
for (int y = 0; y < height; y++)
{
this.currentStream.Read(buffer.Array, 0, stride);
this.stream.Read(buffer.Array, 0, stride);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
@ -440,7 +431,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
for (int y = 0; y < height; y++)
{
this.currentStream.Read(row);
this.stream.Read(row);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.PackFromBgr24Bytes(row.Span, pixelSpan, width);
@ -465,7 +456,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
for (int y = 0; y < height; y++)
{
this.currentStream.Read(row);
this.stream.Read(row);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.PackFromBgra32Bytes(row.Span, pixelSpan, width);
@ -478,98 +469,44 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
private void ReadInfoHeader()
{
byte[] data = new byte[BmpInfoHeader.MaxHeaderSize];
byte[] buffer = new byte[BmpInfoHeader.MaxHeaderSize];
// read header size
this.currentStream.Read(data, 0, BmpInfoHeader.HeaderSizeSize);
int headerSize = BitConverter.ToInt32(data, 0);
if (headerSize < BmpInfoHeader.BitmapCoreHeaderSize)
this.stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize);
int headerSize = BitConverter.ToInt32(buffer, 0);
if (headerSize < BmpInfoHeader.CoreSize)
{
throw new NotSupportedException($"This kind of bitmap files (header size $headerSize) is not supported.");
throw new NotSupportedException($"ImageSharp does not support this BMP file. HeaderSize: {headerSize}.");
}
int skipAmmount = 0;
int skipAmount = 0;
if (headerSize > BmpInfoHeader.MaxHeaderSize)
{
skipAmmount = headerSize - BmpInfoHeader.MaxHeaderSize;
skipAmount = headerSize - BmpInfoHeader.MaxHeaderSize;
headerSize = BmpInfoHeader.MaxHeaderSize;
}
// read the rest of the header
this.currentStream.Read(data, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize);
this.stream.Read(buffer, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize);
switch (headerSize)
if (headerSize == BmpInfoHeader.CoreSize)
{
case BmpInfoHeader.BitmapCoreHeaderSize:
this.infoHeader = this.ParseBitmapCoreHeader(data);
break;
case BmpInfoHeader.BitmapInfoHeaderSize:
this.infoHeader = this.ParseBitmapInfoHeader(data);
break;
default:
if (headerSize > BmpInfoHeader.BitmapInfoHeaderSize)
{
this.infoHeader = this.ParseBitmapInfoHeader(data);
break;
}
else
{
throw new NotSupportedException($"This kind of bitmap files (header size $headerSize) is not supported.");
}
// 12 bytes
this.infoHeader = BmpInfoHeader.ParseCore(buffer);
}
// skip the remaining header because we can't read those parts
this.currentStream.Skip(skipAmmount);
}
/// <summary>
/// Parses the <see cref="BmpInfoHeader"/> from the stream, assuming it uses the BITMAPCOREHEADER format.
/// </summary>
/// <param name="data">Header bytes read from the stream</param>
/// <returns>Parsed header</returns>
/// <seealso href="https://msdn.microsoft.com/en-us/library/windows/desktop/dd183372.aspx"/>
private BmpInfoHeader ParseBitmapCoreHeader(byte[] data)
{
return new BmpInfoHeader
else if (headerSize >= BmpInfoHeader.Size)
{
HeaderSize = BitConverter.ToInt32(data, 0),
Width = BitConverter.ToUInt16(data, 4),
Height = BitConverter.ToUInt16(data, 6),
Planes = BitConverter.ToInt16(data, 8),
BitsPerPixel = BitConverter.ToInt16(data, 10),
// the rest is not present in the core header
ImageSize = 0,
XPelsPerMeter = 0,
YPelsPerMeter = 0,
ClrUsed = 0,
ClrImportant = 0,
Compression = BmpCompression.RGB
};
}
/// <summary>
/// Parses the <see cref="BmpInfoHeader"/> from the stream, assuming it uses the BITMAPINFOHEADER format.
/// </summary>
/// <param name="data">Header bytes read from the stream</param>
/// <returns>Parsed header</returns>
/// <seealso href="https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376.aspx"/>
private BmpInfoHeader ParseBitmapInfoHeader(byte[] data)
{
return new BmpInfoHeader
// >= 40 bytes
this.infoHeader = BmpInfoHeader.Parse(buffer.AsSpan(0, 40));
}
else
{
HeaderSize = BitConverter.ToInt32(data, 0),
Width = BitConverter.ToInt32(data, 4),
Height = BitConverter.ToInt32(data, 8),
Planes = BitConverter.ToInt16(data, 12),
BitsPerPixel = BitConverter.ToInt16(data, 14),
ImageSize = BitConverter.ToInt32(data, 20),
XPelsPerMeter = BitConverter.ToInt32(data, 24),
YPelsPerMeter = BitConverter.ToInt32(data, 28),
ClrUsed = BitConverter.ToInt32(data, 32),
ClrImportant = BitConverter.ToInt32(data, 36),
Compression = (BmpCompression)BitConverter.ToInt32(data, 16)
};
throw new NotSupportedException($"ImageSharp does not support this BMP file. HeaderSize: {headerSize}.");
}
// skip the remaining header because we can't read those parts
this.stream.Skip(skipAmount);
}
/// <summary>
@ -577,15 +514,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
private void ReadFileHeader()
{
byte[] data = new byte[BmpFileHeader.Size];
byte[] buffer = new byte[BmpFileHeader.Size];
this.currentStream.Read(data, 0, BmpFileHeader.Size);
this.stream.Read(buffer, 0, BmpFileHeader.Size);
this.fileHeader = new BmpFileHeader(
type: BitConverter.ToInt16(data, 0),
fileSize: BitConverter.ToInt32(data, 2),
reserved: BitConverter.ToInt32(data, 6),
offset: BitConverter.ToInt32(data, 10));
this.fileHeader = BmpFileHeader.Parse(buffer);
}
/// <summary>
@ -593,66 +526,59 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
private void ReadImageHeaders(Stream stream, out bool inverted, out byte[] palette)
{
this.currentStream = stream;
try
this.stream = stream;
this.ReadFileHeader();
this.ReadInfoHeader();
// see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517
// If the height is negative, then this is a Windows bitmap whose origin
// is the upper-left corner and not the lower-left. The inverted flag
// indicates a lower-left origin.Our code will be outputting an
// upper-left origin pixel array.
inverted = false;
if (this.infoHeader.Height < 0)
{
this.ReadFileHeader();
this.ReadInfoHeader();
// see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517
// If the height is negative, then this is a Windows bitmap whose origin
// is the upper-left corner and not the lower-left.The inverted flag
// indicates a lower-left origin.Our code will be outputting an
// upper-left origin pixel array.
inverted = false;
if (this.infoHeader.Height < 0)
{
inverted = true;
this.infoHeader.Height = -this.infoHeader.Height;
}
inverted = true;
this.infoHeader.Height = -this.infoHeader.Height;
}
int colorMapSize = -1;
int colorMapSize = -1;
if (this.infoHeader.ClrUsed == 0)
{
if (this.infoHeader.BitsPerPixel == 1 ||
this.infoHeader.BitsPerPixel == 4 ||
this.infoHeader.BitsPerPixel == 8)
{
colorMapSize = (int)Math.Pow(2, this.infoHeader.BitsPerPixel) * 4;
}
}
else
if (this.infoHeader.ClrUsed == 0)
{
if (this.infoHeader.BitsPerPixel == 1 ||
this.infoHeader.BitsPerPixel == 4 ||
this.infoHeader.BitsPerPixel == 8)
{
colorMapSize = this.infoHeader.ClrUsed * 4;
colorMapSize = (int)Math.Pow(2, this.infoHeader.BitsPerPixel) * 4;
}
}
else
{
colorMapSize = this.infoHeader.ClrUsed * 4;
}
palette = null;
palette = null;
if (colorMapSize > 0)
if (colorMapSize > 0)
{
// 256 * 4
if (colorMapSize > 1024)
{
// 256 * 4
if (colorMapSize > 1024)
{
throw new ImageFormatException($"Invalid bmp colormap size '{colorMapSize}'");
}
palette = new byte[colorMapSize];
this.currentStream.Read(palette, 0, colorMapSize);
throw new ImageFormatException($"Invalid bmp colormap size '{colorMapSize}'");
}
if (this.infoHeader.Width > int.MaxValue || this.infoHeader.Height > int.MaxValue)
{
throw new ArgumentOutOfRangeException(
$"The input bitmap '{this.infoHeader.Width}x{this.infoHeader.Height}' is "
+ $"bigger then the max allowed size '{int.MaxValue}x{int.MaxValue}'");
}
palette = new byte[colorMapSize];
this.stream.Read(palette, 0, colorMapSize);
}
catch (IndexOutOfRangeException e)
if (this.infoHeader.Width > int.MaxValue || this.infoHeader.Height > int.MaxValue)
{
throw new ImageFormatException("Bitmap does not have a valid format.", e);
throw new ArgumentOutOfRangeException(
$"The input bmp '{this.infoHeader.Width}x{this.infoHeader.Height}' is "
+ $"bigger then the max allowed size '{int.MaxValue}x{int.MaxValue}'");
}
}
}

100
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -3,7 +3,6 @@
using System;
using System.IO;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -19,9 +18,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
private int padding;
/// <summary>
/// Gets or sets the number of bits per pixel.
/// </summary>
private readonly BmpBitsPerPixel bitsPerPixel;
private readonly MemoryManager memoryManager;
@ -54,20 +50,15 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32);
this.padding = bytesPerLine - (image.Width * (int)this.bitsPerPixel);
// Do not use IDisposable pattern here as we want to preserve the stream.
var writer = new EndianBinaryWriter(Endianness.LittleEndian, stream);
var infoHeader = new BmpInfoHeader
{
HeaderSize = BmpInfoHeader.BitmapInfoHeaderSize,
Height = image.Height,
Width = image.Width,
BitsPerPixel = bpp,
Planes = 1,
ImageSize = image.Height * bytesPerLine,
ClrUsed = 0,
ClrImportant = 0
};
var infoHeader = new BmpInfoHeader(
headerSize: BmpInfoHeader.Size,
height: image.Height,
width: image.Width,
bitsPerPixel: bpp,
planes: 1,
imageSize: image.Height * bytesPerLine,
clrUsed: 0,
clrImportant: 0);
var fileHeader = new BmpFileHeader(
type: 19778, // BM
@ -75,63 +66,30 @@ namespace SixLabors.ImageSharp.Formats.Bmp
reserved: 0,
fileSize: 54 + infoHeader.ImageSize);
WriteHeader(writer, fileHeader);
this.WriteInfo(writer, infoHeader);
this.WriteImage(writer, image.Frames.RootFrame);
byte[] buffer = new byte[40]; // TODO: stackalloc
writer.Flush();
}
fileHeader.WriteTo(buffer);
/// <summary>
/// Writes the bitmap header data to the binary stream.
/// </summary>
/// <param name="writer">
/// The <see cref="EndianBinaryWriter"/> containing the stream to write to.
/// </param>
/// <param name="fileHeader">
/// The <see cref="BmpFileHeader"/> containing the header data.
/// </param>
private static void WriteHeader(EndianBinaryWriter writer, in BmpFileHeader fileHeader)
{
writer.Write(fileHeader.Type);
writer.Write(fileHeader.FileSize);
writer.Write(fileHeader.Reserved);
writer.Write(fileHeader.Offset);
}
stream.Write(buffer, 0, BmpFileHeader.Size);
/// <summary>
/// Writes the bitmap information to the binary stream.
/// </summary>
/// <param name="writer">
/// The <see cref="EndianBinaryWriter"/> containing the stream to write to.
/// </param>
/// <param name="infoHeader">
/// The <see cref="BmpFileHeader"/> containing the detailed information about the image.
/// </param>
private void WriteInfo(EndianBinaryWriter writer, BmpInfoHeader infoHeader)
{
writer.Write(infoHeader.HeaderSize);
writer.Write(infoHeader.Width);
writer.Write(infoHeader.Height);
writer.Write(infoHeader.Planes);
writer.Write(infoHeader.BitsPerPixel);
writer.Write((int)infoHeader.Compression);
writer.Write(infoHeader.ImageSize);
writer.Write(infoHeader.XPelsPerMeter);
writer.Write(infoHeader.YPelsPerMeter);
writer.Write(infoHeader.ClrUsed);
writer.Write(infoHeader.ClrImportant);
infoHeader.WriteTo(buffer);
stream.Write(buffer, 0, 40);
this.WriteImage(stream, image.Frames.RootFrame);
stream.Flush();
}
/// <summary>
/// Writes the pixel data to the binary stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="writer">The <see cref="EndianBinaryWriter"/> containing the stream to write to.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image">
/// The <see cref="ImageFrame{TPixel}"/> containing pixel data.
/// </param>
private void WriteImage<TPixel>(EndianBinaryWriter writer, ImageFrame<TPixel> image)
private void WriteImage<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
using (PixelAccessor<TPixel> pixels = image.Lock())
@ -139,11 +97,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
switch (this.bitsPerPixel)
{
case BmpBitsPerPixel.Pixel32:
this.Write32Bit(writer, pixels);
this.Write32Bit(stream, pixels);
break;
case BmpBitsPerPixel.Pixel24:
this.Write24Bit(writer, pixels);
this.Write24Bit(stream, pixels);
break;
}
}
@ -158,9 +116,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// Writes the 32bit color palette to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="writer">The <see cref="EndianBinaryWriter"/> containing the stream to write to.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="PixelAccessor{TPixel}"/> containing pixel data.</param>
private void Write32Bit<TPixel>(EndianBinaryWriter writer, PixelAccessor<TPixel> pixels)
private void Write32Bit<TPixel>(Stream stream, PixelAccessor<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4))
@ -169,7 +127,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra32Bytes(pixelSpan, row.Span, pixelSpan.Length);
writer.Write(row.Array, 0, row.Length());
stream.Write(row.Array, 0, row.Length());
}
}
}
@ -178,9 +136,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// Writes the 24bit color palette to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="writer">The <see cref="EndianBinaryWriter"/> containing the stream to write to.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="PixelAccessor{TPixel}"/> containing pixel data.</param>
private void Write24Bit<TPixel>(EndianBinaryWriter writer, PixelAccessor<TPixel> pixels)
private void Write24Bit<TPixel>(Stream stream, PixelAccessor<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3))
@ -189,7 +147,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgr24Bytes(pixelSpan, row.Span, pixelSpan.Length);
writer.Write(row.Array, 0, row.Length());
stream.Write(row.Array, 0, row.Length());
}
}
}

19
src/ImageSharp/Formats/Bmp/BmpFileHeader.cs

@ -1,6 +1,10 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Bmp
{
/// <summary>
@ -13,6 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// All of the other integer values are stored in little-endian format
/// (i.e. least-significant byte first).
/// </remarks>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal readonly struct BmpFileHeader
{
/// <summary>
@ -44,12 +49,24 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// Gets any reserved data; actual value depends on the application
/// that creates the image.
/// </summary>
public int Reserved { get; }
public int Reserved { get; }
/// <summary>
/// Gets the offset, i.e. starting address, of the byte where
/// the bitmap data can be found.
/// </summary>
public int Offset { get; }
public static BmpFileHeader Parse(Span<byte> data)
{
return MemoryMarshal.Cast<byte, BmpFileHeader>(data)[0];
}
public unsafe void WriteTo(Span<byte> buffer)
{
ref BmpFileHeader dest = ref Unsafe.As<byte, BmpFileHeader>(ref MemoryMarshal.GetReference(buffer));
dest = this;
}
}
}

76
src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs

@ -1,5 +1,10 @@
// 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>
@ -8,28 +13,55 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// the screen.
/// <see href="https://en.wikipedia.org/wiki/BMP_file_format"/>
/// </summary>
internal sealed class BmpInfoHeader
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct BmpInfoHeader
{
/// <summary>
/// Defines the size of the BITMAPINFOHEADER data structure in the bitmap file.
/// </summary>
public const int BitmapInfoHeaderSize = 40;
public const int Size = 40;
/// <summary>
/// Defines the size of the BITMAPCOREHEADER data structure in the bitmap file.
/// </summary>
public const int BitmapCoreHeaderSize = 12;
public const int CoreSize = 12;
/// <summary>
/// Defines the size of the biggest supported header data structure in the bitmap file.
/// </summary>
public const int MaxHeaderSize = BitmapInfoHeaderSize;
public const int MaxHeaderSize = Size;
/// <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)
{
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;
}
/// <summary>
/// Gets or sets the size of this header
/// </summary>
@ -91,5 +123,39 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// or 0 when every color is important{ get; set; } generally ignored.
/// </summary>
public int ClrImportant { get; set; }
/// <summary>
/// Parses the full BITMAPINFOHEADER header (40 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/dd183376.aspx"/>
public static BmpInfoHeader Parse(ReadOnlySpan<byte> data)
{
return MemoryMarshal.Cast<byte, BmpInfoHeader>(data)[0];
}
/// <summary>
/// Parses the BITMAPCOREHEADER 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)));
}
public unsafe void WriteTo(Span<byte> buffer)
{
ref BmpInfoHeader dest = ref Unsafe.As<byte, BmpInfoHeader>(ref MemoryMarshal.GetReference(buffer));
dest = this;
}
}
}
}

7
src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs

@ -1,11 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Bmp
{
/// <summary>
@ -15,4 +10,4 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
// added this for consistancy so we can add stuff as required, no options currently availible
}
}
}

5
src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs

@ -1,11 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Bmp
{
/// <summary>

2
src/ImageSharp/Formats/Bmp/ImageExtensions.cs

@ -1,10 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.PixelFormats;

21
tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs

@ -0,0 +1,21 @@
using System;
using SixLabors.ImageSharp.Formats.Bmp;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Bmp
{
public class BmpFileHeaderTests
{
[Fact]
public void TestWrite()
{
var header = new BmpFileHeader(1, 2, 3, 4);
byte[] buffer = new byte[14];
header.WriteTo(buffer);
Assert.Equal("AQACAAAAAwAAAAQAAAA=", Convert.ToBase64String(buffer));
}
}
}
Loading…
Cancel
Save