Skip to content

Commit

Permalink
Merge branch 'main' into heic-support
Browse files Browse the repository at this point in the history
  • Loading branch information
ynse01 authored Jun 24, 2024
2 parents aa44d0c + 3b49c34 commit 9ee8d92
Show file tree
Hide file tree
Showing 175 changed files with 2,797 additions and 73 deletions.
7 changes: 7 additions & 0 deletions ImageSharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Heif", "Heif", "{BA5D603A-C
tests\Images\Input\Heif\jpeg444_xnconvert.avif = tests\Images\Input\Heif\jpeg444_xnconvert.avif
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Icon", "Icon", "{95E45DDE-A67D-48AD-BBA8-5FAA151B860D}"
ProjectSection(SolutionItems) = preProject
tests\Images\Input\Icon\aero_arrow.cur = tests\Images\Input\Icon\aero_arrow.cur
tests\Images\Input\Icon\flutter.ico = tests\Images\Input\Icon\flutter.ico
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -727,6 +733,7 @@ Global
{5DFC394F-136F-4B76-9BCA-3BA786515EFC} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
{E801B508-4935-41CD-BA85-CF11BFF55A45} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
{BA5D603A-C84C-43E5-B300-8BB886B02936} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
{95E45DDE-A67D-48AD-BBA8-5FAA151B860D} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795}
Expand Down
4 changes: 4 additions & 0 deletions src/ImageSharp/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
using System.Collections.Concurrent;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Cur;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Heif;
using SixLabors.ImageSharp.Formats.Ico;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png;
Expand Down Expand Up @@ -226,4 +228,6 @@ public void Configure(IImageFormatConfigurationModule configuration)
new WebpConfigurationModule(),
new QoiConfigurationModule(),
new HeifConfigurationModule());
new IcoConfigurationModule(),

Check failure on line 231 in src/ImageSharp/Configuration.cs

View workflow job for this annotation

GitHub Actions / Build (false, ubuntu-latest, net8.0, 8.0.x, -x64, false)

; expected

Check failure on line 231 in src/ImageSharp/Configuration.cs

View workflow job for this annotation

GitHub Actions / Build (false, ubuntu-latest, net8.0, 8.0.x, -x64, false)

Invalid token ',' in class, record, struct, or interface member declaration

Check failure on line 231 in src/ImageSharp/Configuration.cs

View workflow job for this annotation

GitHub Actions / Build (false, ubuntu-latest, net8.0, 8.0.x, -x64, false)

; expected

Check failure on line 231 in src/ImageSharp/Configuration.cs

View workflow job for this annotation

GitHub Actions / Build (false, ubuntu-latest, net8.0, 8.0.x, -x64, false)

Invalid token ',' in class, record, struct, or interface member declaration

Check failure on line 231 in src/ImageSharp/Configuration.cs

View workflow job for this annotation

GitHub Actions / Build (false, macos-13, net8.0, 8.0.x, -x64, false)

; expected

Check failure on line 231 in src/ImageSharp/Configuration.cs

View workflow job for this annotation

GitHub Actions / Build (false, macos-13, net8.0, 8.0.x, -x64, false)

Invalid token ',' in class, record, struct, or interface member declaration

Check failure on line 231 in src/ImageSharp/Configuration.cs

View workflow job for this annotation

GitHub Actions / Build (false, macos-13, net8.0, 8.0.x, -x64, false)

; expected

Check failure on line 231 in src/ImageSharp/Configuration.cs

View workflow job for this annotation

GitHub Actions / Build (false, macos-13, net8.0, 8.0.x, -x64, false)

Invalid token ',' in class, record, struct, or interface member declaration
new CurConfigurationModule());

Check failure on line 232 in src/ImageSharp/Configuration.cs

View workflow job for this annotation

GitHub Actions / Build (false, ubuntu-latest, net8.0, 8.0.x, -x64, false)

; expected

Check failure on line 232 in src/ImageSharp/Configuration.cs

View workflow job for this annotation

GitHub Actions / Build (false, ubuntu-latest, net8.0, 8.0.x, -x64, false)

Invalid token ')' in class, record, struct, or interface member declaration

Check failure on line 232 in src/ImageSharp/Configuration.cs

View workflow job for this annotation

GitHub Actions / Build (false, ubuntu-latest, net8.0, 8.0.x, -x64, false)

; expected

Check failure on line 232 in src/ImageSharp/Configuration.cs

View workflow job for this annotation

GitHub Actions / Build (false, ubuntu-latest, net8.0, 8.0.x, -x64, false)

Invalid token ')' in class, record, struct, or interface member declaration

Check failure on line 232 in src/ImageSharp/Configuration.cs

View workflow job for this annotation

GitHub Actions / Build (false, macos-13, net8.0, 8.0.x, -x64, false)

; expected

Check failure on line 232 in src/ImageSharp/Configuration.cs

View workflow job for this annotation

GitHub Actions / Build (false, macos-13, net8.0, 8.0.x, -x64, false)

Invalid token ')' in class, record, struct, or interface member declaration

Check failure on line 232 in src/ImageSharp/Configuration.cs

View workflow job for this annotation

GitHub Actions / Build (false, macos-13, net8.0, 8.0.x, -x64, false)

; expected

Check failure on line 232 in src/ImageSharp/Configuration.cs

View workflow job for this annotation

GitHub Actions / Build (false, macos-13, net8.0, 8.0.x, -x64, false)

Invalid token ')' in class, record, struct, or interface member declaration
}
7 changes: 6 additions & 1 deletion src/ImageSharp/Formats/Bmp/BmpConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ internal static class BmpConstants
/// <summary>
/// The list of mimetypes that equate to a bmp.
/// </summary>
public static readonly IEnumerable<string> MimeTypes = new[] { "image/bmp", "image/x-windows-bmp" };
public static readonly IEnumerable<string> MimeTypes = new[]
{
"image/bmp",
"image/x-windows-bmp",
"image/x-win-bitmap"
};

/// <summary>
/// The list of file extensions that equate to a bmp.
Expand Down
235 changes: 196 additions & 39 deletions src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
Expand Down Expand Up @@ -71,7 +72,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// <summary>
/// The file header containing general information.
/// </summary>
private BmpFileHeader fileHeader;
private BmpFileHeader? fileHeader;

/// <summary>
/// Indicates which bitmap file marker was read.
Expand Down Expand Up @@ -99,6 +100,15 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// </summary>
private readonly RleSkippedPixelHandling rleSkippedPixelHandling;

/// <inheritdoc cref="BmpDecoderOptions.ProcessedAlphaMask"/>
private readonly bool processedAlphaMask;

/// <inheritdoc cref="BmpDecoderOptions.SkipFileHeader"/>
private readonly bool skipFileHeader;

/// <inheritdoc cref="BmpDecoderOptions.UseDoubleHeight"/>
private readonly bool isDoubleHeight;

/// <summary>
/// Initializes a new instance of the <see cref="BmpDecoderCore"/> class.
/// </summary>
Expand All @@ -109,6 +119,9 @@ public BmpDecoderCore(BmpDecoderOptions options)
this.rleSkippedPixelHandling = options.RleSkippedPixelHandling;
this.configuration = options.GeneralOptions.Configuration;
this.memoryAllocator = this.configuration.MemoryAllocator;
this.processedAlphaMask = options.ProcessedAlphaMask;
this.skipFileHeader = options.SkipFileHeader;
this.isDoubleHeight = options.UseDoubleHeight;
}

/// <inheritdoc />
Expand All @@ -132,38 +145,44 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken

switch (this.infoHeader.Compression)
{
case BmpCompression.RGB:
if (this.infoHeader.BitsPerPixel == 32)
{
if (this.bmpMetadata.InfoHeaderType == BmpInfoHeaderType.WinVersion3)
{
this.ReadRgb32Slow(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
}
else
{
this.ReadRgb32Fast(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
}
}
else if (this.infoHeader.BitsPerPixel == 24)
{
this.ReadRgb24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
}
else if (this.infoHeader.BitsPerPixel == 16)
{
this.ReadRgb16(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
}
else if (this.infoHeader.BitsPerPixel <= 8)
{
this.ReadRgbPalette(
stream,
pixels,
palette,
this.infoHeader.Width,
this.infoHeader.Height,
this.infoHeader.BitsPerPixel,
bytesPerColorMapEntry,
inverted);
}
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 32 && this.bmpMetadata.InfoHeaderType is BmpInfoHeaderType.WinVersion3:
this.ReadRgb32Slow(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);

break;
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 32:
this.ReadRgb32Fast(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);

break;
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 24:
this.ReadRgb24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);

break;
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 16:
this.ReadRgb16(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);

break;
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is <= 8 && this.processedAlphaMask:
this.ReadRgbPaletteWithAlphaMask(
stream,
pixels,
palette,
this.infoHeader.Width,
this.infoHeader.Height,
this.infoHeader.BitsPerPixel,
bytesPerColorMapEntry,
inverted);

break;
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is <= 8:
this.ReadRgbPalette(
stream,
pixels,
palette,
this.infoHeader.Width,
this.infoHeader.Height,
this.infoHeader.BitsPerPixel,
bytesPerColorMapEntry,
inverted);

break;

Expand Down Expand Up @@ -839,6 +858,108 @@ private void ReadRgbPalette<TPixel>(BufferedReadStream stream, Buffer2D<TPixel>
}
}

/// <inheritdoc cref="ReadRgbPalette"/>
private void ReadRgbPaletteWithAlphaMask<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, byte[] colors, int width, int height, int bitsPerPixel, int bytesPerColorMapEntry, bool inverted)
where TPixel : unmanaged, IPixel<TPixel>
{
// Pixels per byte (bits per pixel).
int ppb = 8 / bitsPerPixel;

int arrayWidth = (width + ppb - 1) / ppb;

// Bit mask
int mask = 0xFF >> (8 - bitsPerPixel);

// Rows are aligned on 4 byte boundaries.
int padding = arrayWidth % 4;
if (padding != 0)
{
padding = 4 - padding;
}

Bgra32[,] image = new Bgra32[height, width];
using (IMemoryOwner<byte> row = this.memoryAllocator.Allocate<byte>(arrayWidth + padding, AllocationOptions.Clean))
{
Span<byte> rowSpan = row.GetSpan();

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

int offset = 0;

for (int x = 0; x < arrayWidth; x++)
{
int colOffset = x * ppb;
for (int shift = 0, newX = colOffset; shift < ppb && newX < width; shift++, newX++)
{
int colorIndex = ((rowSpan[offset] >> (8 - bitsPerPixel - (shift * bitsPerPixel))) & mask) * bytesPerColorMapEntry;

image[newY, newX] = Bgra32.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIndex]));
}

offset++;
}
}
}

arrayWidth = width / 8;
padding = arrayWidth % 4;
if (padding != 0)
{
padding = 4 - padding;
}

for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);

for (int i = 0; i < arrayWidth; i++)
{
int x = i * 8;
int and = stream.ReadByte();
if (and is -1)
{
throw new EndOfStreamException();
}

for (int j = 0; j < 8; j++)
{
SetAlpha(ref image[newY, x + j], and, j);
}
}

stream.Skip(padding);
}

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

for (int x = 0; x < width; x++)
{
pixelRow[x] = TPixel.FromBgra32(image[newY, x]);
}
}
}

/// <summary>
/// Set pixel's alpha with alpha mask.
/// </summary>
/// <param name="pixel">Bgra32 pixel.</param>
/// <param name="mask">alpha mask.</param>
/// <param name="index">bit index of pixel.</param>
private static void SetAlpha(ref Bgra32 pixel, in int mask, in int index)
{
bool isTransparently = (mask & (0b10000000 >> index)) is not 0;
pixel.A = isTransparently ? byte.MinValue : byte.MaxValue;
}

/// <summary>
/// Reads the 16 bit color palette from the stream.
/// </summary>
Expand Down Expand Up @@ -1333,6 +1454,11 @@ private void ReadInfoHeader(BufferedReadStream stream)
this.metadata.VerticalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetadata.DefaultVerticalResolution));
}

if (this.isDoubleHeight)
{
this.infoHeader.Height >>= 1;
}

ushort bitsPerPixel = this.infoHeader.BitsPerPixel;
this.bmpMetadata = this.metadata.GetBmpMetadata();
this.bmpMetadata.InfoHeaderType = infoHeaderType;
Expand Down Expand Up @@ -1362,9 +1488,9 @@ private void ReadFileHeader(BufferedReadStream stream)
// The bitmap file header of the first image follows the array header.
stream.Read(buffer, 0, BmpFileHeader.Size);
this.fileHeader = BmpFileHeader.Parse(buffer);
if (this.fileHeader.Type != BmpConstants.TypeMarkers.Bitmap)
if (this.fileHeader.Value.Type != BmpConstants.TypeMarkers.Bitmap)
{
BmpThrowHelper.ThrowNotSupportedException($"Unsupported bitmap file inside a BitmapArray file. File header bitmap type marker '{this.fileHeader.Type}'.");
BmpThrowHelper.ThrowNotSupportedException($"Unsupported bitmap file inside a BitmapArray file. File header bitmap type marker '{this.fileHeader.Value.Type}'.");
}

break;
Expand All @@ -1387,7 +1513,11 @@ private void ReadFileHeader(BufferedReadStream stream)
[MemberNotNull(nameof(bmpMetadata))]
private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out byte[] palette)
{
this.ReadFileHeader(stream);
if (!this.skipFileHeader)
{
this.ReadFileHeader(stream);
}

this.ReadInfoHeader(stream);

// see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517
Expand All @@ -1411,7 +1541,21 @@ private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out b
switch (this.fileMarkerType)
{
case BmpFileMarkerType.Bitmap:
colorMapSizeBytes = this.fileHeader.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize;
if (this.fileHeader.HasValue)
{
colorMapSizeBytes = this.fileHeader.Value.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize;
}
else
{
colorMapSizeBytes = this.infoHeader.ClrUsed;
if (colorMapSizeBytes is 0 && this.infoHeader.BitsPerPixel is <= 8)
{
colorMapSizeBytes = ColorNumerics.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel);
}

colorMapSizeBytes *= 4;
}

int colorCountForBitDepth = ColorNumerics.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel);
bytesPerColorMapEntry = colorMapSizeBytes / colorCountForBitDepth;

Expand Down Expand Up @@ -1442,7 +1586,7 @@ private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out b
{
// Usually the color palette is 1024 byte (256 colors * 4), but the documentation does not mention a size limit.
// Make sure, that we will not read pass the bitmap offset (starting position of image data).
if (stream.Position > this.fileHeader.Offset - colorMapSizeBytes)
if (this.fileHeader.HasValue && stream.Position > this.fileHeader.Value.Offset - colorMapSizeBytes)
{
BmpThrowHelper.ThrowInvalidImageContentException(
$"Reading the color map would read beyond the bitmap offset. Either the color map size of '{colorMapSizeBytes}' is invalid or the bitmap offset.");
Expand All @@ -1456,7 +1600,20 @@ private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out b
}
}

int skipAmount = this.fileHeader.Offset - (int)stream.Position;
if (palette.Length > 0)
{
Color[] colorTable = new Color[palette.Length / Unsafe.SizeOf<Bgr24>()];
ReadOnlySpan<Bgr24> rgbTable = MemoryMarshal.Cast<byte, Bgr24>(palette);
Color.FromPixel(rgbTable, colorTable);
this.bmpMetadata.ColorTable = colorTable;
}

int skipAmount = 0;
if (this.fileHeader.HasValue)
{
skipAmount = this.fileHeader.Value.Offset - (int)stream.Position;
}

if ((skipAmount + (int)stream.Position) > stream.Length)
{
BmpThrowHelper.ThrowInvalidImageContentException("Invalid file header offset found. Offset is greater than the stream length.");
Expand Down
Loading

0 comments on commit 9ee8d92

Please sign in to comment.