Browse Source

Move & simplify BmpHeader & BmpInfoHeader parsing logic

af/merge-core
Jason Nelson 8 years ago
parent
commit
f9830e3636
  1. 135
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  2. 23
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  3. 8
      src/ImageSharp/Formats/Bmp/BmpFileHeader.cs
  4. 65
      src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
  5. 7
      src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs
  6. 5
      src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
  7. 2
      src/ImageSharp/Formats/Bmp/ImageExtensions.cs

135
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.
@ -261,7 +261,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,8 +283,8 @@ 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;
@ -299,7 +299,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
length += length & 1;
byte[] run = new byte[length];
this.currentStream.Read(run, 0, run.Length);
this.stream.Read(run, 0, run.Length);
for (int i = 0; i < copyLength; i++)
{
buffer[count++] = run[i];
@ -356,7 +356,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 +402,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 +440,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 +465,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 +478,43 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
private void ReadInfoHeader()
{
byte[] data = new byte[BmpInfoHeader.MaxHeaderSize];
byte[] buffer = new byte[BmpInfoHeader.MaxHeaderSize]; // TODO: Stackalloc
// 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.");
}
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);
}
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 +522,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
private void ReadFileHeader()
{
byte[] data = new byte[BmpFileHeader.Size];
byte[] data = new byte[BmpFileHeader.Size]; // TODO: Stackalloc
this.currentStream.Read(data, 0, BmpFileHeader.Size);
this.stream.Read(data, 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(data);
}
/// <summary>
@ -593,7 +534,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
private void ReadImageHeaders(Stream stream, out bool inverted, out byte[] palette)
{
this.currentStream = stream;
this.stream = stream;
try
{
@ -640,7 +581,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
palette = new byte[colorMapSize];
this.currentStream.Read(palette, 0, colorMapSize);
this.stream.Read(palette, 0, colorMapSize);
}
if (this.infoHeader.Width > int.MaxValue || this.infoHeader.Height > int.MaxValue)

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

@ -18,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;
@ -53,17 +50,15 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32);
this.padding = bytesPerLine - (image.Width * (int)this.bitsPerPixel);
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

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

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Bmp
@ -57,14 +56,17 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </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)
{
fixed (BmpFileHeader* pointer = &this)
{
MemoryMarshal.AsBytes(new ReadOnlySpan<BmpFileHeader>(pointer, 1)).CopyTo(buffer);
}
// TODO: Big Endian Platforms
}
}
}

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

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Bmp
@ -14,28 +15,52 @@ namespace SixLabors.ImageSharp.Formats.Bmp
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct BmpInfoHeader
{
// TODO: Make readonly
/// <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>
@ -98,14 +123,40 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </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)
{
fixed (BmpInfoHeader* pointer = &this)
{
MemoryMarshal.AsBytes(new ReadOnlySpan<BmpInfoHeader>(pointer, 1)).CopyTo(buffer);
}
// TODO: Big Endian Platforms
}
}
}

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;

Loading…
Cancel
Save