Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public enum BmpBitsPerPixel : short
/// </summary>
Pixel1 = 1,

/// <summary>
/// 2 bits per pixel.
/// </summary>
Pixel2 = 2,

/// <summary>
/// 4 bits per pixel.
/// </summary>
Expand Down
53 changes: 43 additions & 10 deletions src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -836,7 +836,11 @@ private void ReadRgbPalette<TPixel>(Buffer2D<TPixel> pixels, byte[] colors, int
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
this.stream.Read(rowSpan);
if (this.stream.Read(rowSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}

int offset = 0;
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);

Expand Down Expand Up @@ -888,7 +892,11 @@ private void ReadRgb16<TPixel>(Buffer2D<TPixel> pixels, int width, int height, b

for (int y = 0; y < height; y++)
{
this.stream.Read(bufferSpan);
if (this.stream.Read(bufferSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}

int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);

Expand Down Expand Up @@ -943,7 +951,11 @@ private void ReadRgb24<TPixel>(Buffer2D<TPixel> pixels, int width, int height, b

for (int y = 0; y < height; y++)
{
this.stream.Read(rowSpan);
if (this.stream.Read(rowSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}

int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgr24Bytes(
Expand Down Expand Up @@ -971,7 +983,11 @@ private void ReadRgb32Fast<TPixel>(Buffer2D<TPixel> pixels, int width, int heigh

for (int y = 0; y < height; y++)
{
this.stream.Read(rowSpan);
if (this.stream.Read(rowSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}

int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
Expand Down Expand Up @@ -1007,7 +1023,10 @@ private void ReadRgb32Slow<TPixel>(Buffer2D<TPixel> pixels, int width, int heigh
// actually a BGRA image, and change tactics accordingly.
for (int y = 0; y < height; y++)
{
this.stream.Read(rowSpan);
if (this.stream.Read(rowSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}

PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.configuration,
Expand Down Expand Up @@ -1040,7 +1059,10 @@ private void ReadRgb32Slow<TPixel>(Buffer2D<TPixel> pixels, int width, int heigh
{
for (int y = 0; y < height; y++)
{
this.stream.Read(rowSpan);
if (this.stream.Read(rowSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}

int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
Expand All @@ -1058,7 +1080,11 @@ private void ReadRgb32Slow<TPixel>(Buffer2D<TPixel> pixels, int width, int heigh
// Slow path. We need to set each alpha component value to fully opaque.
for (int y = 0; y < height; y++)
{
this.stream.Read(rowSpan);
if (this.stream.Read(rowSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}

PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.configuration,
rowSpan,
Expand Down Expand Up @@ -1119,7 +1145,11 @@ private void ReadRgb32BitFields<TPixel>(Buffer2D<TPixel> pixels, int width, int

for (int y = 0; y < height; y++)
{
this.stream.Read(bufferSpan);
if (this.stream.Read(bufferSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}

int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);

Expand Down Expand Up @@ -1391,7 +1421,7 @@ private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out b
int colorMapSizeBytes = -1;
if (this.infoHeader.ClrUsed == 0)
{
if (this.infoHeader.BitsPerPixel is 1 or 4 or 8)
if (this.infoHeader.BitsPerPixel is 1 or 2 or 4 or 8)
{
switch (this.fileMarkerType)
{
Expand Down Expand Up @@ -1435,7 +1465,10 @@ private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out b

palette = new byte[colorMapSizeBytes];

this.stream.Read(palette, 0, colorMapSizeBytes);
if (this.stream.Read(palette, 0, colorMapSizeBytes) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for the palette!");
}
}

this.infoHeader.VerifyDimensions();
Expand Down
125 changes: 91 additions & 34 deletions src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// </summary>
private const int ColorPaletteSize4Bit = 64;

/// <summary>
/// The color palette for an 2 bit image will have 4 entry's with 4 bytes for each entry.
/// </summary>
private const int ColorPaletteSize2Bit = 16;

/// <summary>
/// The color palette for an 1 bit image will have 2 entry's with 4 bytes for each entry.
/// </summary>
Expand Down Expand Up @@ -125,19 +130,14 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32);
this.padding = bytesPerLine - (int)(image.Width * (bpp / 8F));

int colorPaletteSize = 0;
if (this.bitsPerPixel == BmpBitsPerPixel.Pixel8)
{
colorPaletteSize = ColorPaletteSize8Bit;
}
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel4)
{
colorPaletteSize = ColorPaletteSize4Bit;
}
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel1)
int colorPaletteSize = this.bitsPerPixel switch
{
colorPaletteSize = ColorPaletteSize1Bit;
}
BmpBitsPerPixel.Pixel8 => ColorPaletteSize8Bit,
BmpBitsPerPixel.Pixel4 => ColorPaletteSize4Bit,
BmpBitsPerPixel.Pixel2 => ColorPaletteSize2Bit,
BmpBitsPerPixel.Pixel1 => ColorPaletteSize1Bit,
_ => 0
};

byte[] iccProfileData = null;
int iccProfileSize = 0;
Expand Down Expand Up @@ -322,27 +322,31 @@ private void WriteImage<TPixel>(Stream stream, ImageFrame<TPixel> image)
switch (this.bitsPerPixel)
{
case BmpBitsPerPixel.Pixel32:
this.Write32Bit(stream, pixels);
this.Write32BitPixelData(stream, pixels);
break;

case BmpBitsPerPixel.Pixel24:
this.Write24Bit(stream, pixels);
this.Write24BitPixelData(stream, pixels);
break;

case BmpBitsPerPixel.Pixel16:
this.Write16Bit(stream, pixels);
this.Write16BitPixelData(stream, pixels);
break;

case BmpBitsPerPixel.Pixel8:
this.Write8Bit(stream, image);
this.Write8BitPixelData(stream, image);
break;

case BmpBitsPerPixel.Pixel4:
this.Write4BitColor(stream, image);
this.Write4BitPixelData(stream, image);
break;

case BmpBitsPerPixel.Pixel2:
this.Write2BitPixelData(stream, image);
break;

case BmpBitsPerPixel.Pixel1:
this.Write1BitColor(stream, image);
this.Write1BitPixelData(stream, image);
break;
}
}
Expand All @@ -351,12 +355,12 @@ private IMemoryOwner<byte> AllocateRow(int width, int bytesPerPixel)
=> this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding);

/// <summary>
/// Writes the 32bit color palette to the stream.
/// Writes 32-bit data with a color palette to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write32Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
private void Write32BitPixelData<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 4);
Expand All @@ -375,12 +379,12 @@ private void Write32Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
}

/// <summary>
/// Writes the 24bit color palette to the stream.
/// Writes 24-bit pixel data with a color palette to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write24Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
private void Write24BitPixelData<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = pixels.Width;
Expand All @@ -401,12 +405,12 @@ private void Write24Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
}

/// <summary>
/// Writes the 16bit color palette to the stream.
/// Writes 16-bit pixel data with a color palette to the stream.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write16Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
private void Write16BitPixelData<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = pixels.Width;
Expand All @@ -429,12 +433,12 @@ private void Write16Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
}

/// <summary>
/// Writes an 8 bit image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// Writes 8 bit pixel data with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <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 Write8Bit<TPixel>(Stream stream, ImageFrame<TPixel> image)
private void Write8BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
bool isL8 = typeof(TPixel) == typeof(L8);
Expand All @@ -443,7 +447,7 @@ private void Write8Bit<TPixel>(Stream stream, ImageFrame<TPixel> image)

if (isL8)
{
this.Write8BitGray(stream, image, colorPalette);
this.Write8BitPixelData(stream, image, colorPalette);
}
else
{
Expand Down Expand Up @@ -480,13 +484,13 @@ private void Write8BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image, Spa
}

/// <summary>
/// Writes an 8 bit gray image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// Writes 8 bit gray pixel data with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
/// <param name="colorPalette">A byte span of size 1024 for the color palette.</param>
private void Write8BitGray<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette)
private void Write8BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette)
where TPixel : unmanaged, IPixel<TPixel>
{
// Create a color palette with 256 different gray values.
Expand Down Expand Up @@ -518,12 +522,12 @@ private void Write8BitGray<TPixel>(Stream stream, ImageFrame<TPixel> image, Span
}

/// <summary>
/// Writes an 4 bit color image with a color palette. The color palette has 16 entry's with 4 bytes for each entry.
/// Writes 4 bit pixel data with a color palette. The color palette has 16 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <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 Write4BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image)
private void Write4BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
Expand Down Expand Up @@ -562,12 +566,65 @@ private void Write4BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image)
}

/// <summary>
/// Writes a 1 bit image with a color palette. The color palette has 2 entry's with 4 bytes for each entry.
/// Writes 2 bit pixel data with a color palette. The color palette has 4 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <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 Write2BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
{
MaxColors = 4
});
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize2Bit, AllocationOptions.Clean);

Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);

ReadOnlySpan<byte> pixelRowSpan = quantized.DangerousGetRowSpan(0);
int rowPadding = pixelRowSpan.Length % 4 != 0 ? this.padding - 1 : this.padding;
for (int y = image.Height - 1; y >= 0; y--)
{
pixelRowSpan = quantized.DangerousGetRowSpan(y);

int endIdx = pixelRowSpan.Length % 4 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 4;
int i = 0;
for (i = 0; i < endIdx; i += 4)
{
stream.WriteByte((byte)((pixelRowSpan[i] << 6) | (pixelRowSpan[i + 1] << 4) | (pixelRowSpan[i + 2] << 2) | pixelRowSpan[i + 3]));
}

if (pixelRowSpan.Length % 4 != 0)
{
int shift = 6;
byte pixelData = 0;
for (; i < pixelRowSpan.Length; i++)
{
pixelData = (byte)(pixelData | (pixelRowSpan[i] << shift));
shift -= 2;
}

stream.WriteByte(pixelData);
}

for (i = 0; i < rowPadding; i++)
{
stream.WriteByte(0);
}
}
}

/// <summary>
/// Writes 1 bit pixel data with a color palette. The color palette has 2 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <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 Write1BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image)
private void Write1BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
Expand Down Expand Up @@ -622,7 +679,7 @@ private void WriteColorPalette<TPixel>(Stream stream, ReadOnlySpan<TPixel> quant
Span<uint> colorPaletteAsUInt = MemoryMarshal.Cast<byte, uint>(colorPalette);
for (int i = 0; i < colorPaletteAsUInt.Length; i++)
{
colorPaletteAsUInt[i] = colorPaletteAsUInt[i] & 0x00FFFFFF; // Padding byte, always 0.
colorPaletteAsUInt[i] &= 0x00FFFFFF; // Padding byte, always 0.
}

stream.Write(colorPalette);
Expand Down
Loading