Skip to content

Commit f4f1eed

Browse files
Merge branch 'main' into js/colorspace-converter
2 parents 87de6b9 + 3b49c34 commit f4f1eed

File tree

162 files changed

+2722
-58
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

162 files changed

+2722
-58
lines changed

ImageSharp.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Qoi", "Qoi", "{E801B508-493
661661
tests\Images\Input\Qoi\wikipedia_008.qoi = tests\Images\Input\Qoi\wikipedia_008.qoi
662662
EndProjectSection
663663
EndProject
664+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Icon", "Icon", "{95E45DDE-A67D-48AD-BBA8-5FAA151B860D}"
665+
ProjectSection(SolutionItems) = preProject
666+
tests\Images\Input\Icon\aero_arrow.cur = tests\Images\Input\Icon\aero_arrow.cur
667+
tests\Images\Input\Icon\flutter.ico = tests\Images\Input\Icon\flutter.ico
668+
EndProjectSection
669+
EndProject
664670
Global
665671
GlobalSection(SolutionConfigurationPlatforms) = preSolution
666672
Debug|Any CPU = Debug|Any CPU
@@ -714,6 +720,7 @@ Global
714720
{670DD46C-82E9-499A-B2D2-00A802ED0141} = {E1C42A6F-913B-4A7B-B1A8-2BB62843B254}
715721
{5DFC394F-136F-4B76-9BCA-3BA786515EFC} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
716722
{E801B508-4935-41CD-BA85-CF11BFF55A45} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
723+
{95E45DDE-A67D-48AD-BBA8-5FAA151B860D} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
717724
EndGlobalSection
718725
GlobalSection(ExtensibilityGlobals) = postSolution
719726
SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795}

src/ImageSharp/Configuration.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
using System.Collections.Concurrent;
55
using SixLabors.ImageSharp.Formats;
66
using SixLabors.ImageSharp.Formats.Bmp;
7+
using SixLabors.ImageSharp.Formats.Cur;
78
using SixLabors.ImageSharp.Formats.Gif;
9+
using SixLabors.ImageSharp.Formats.Ico;
810
using SixLabors.ImageSharp.Formats.Jpeg;
911
using SixLabors.ImageSharp.Formats.Pbm;
1012
using SixLabors.ImageSharp.Formats.Png;
@@ -222,5 +224,7 @@ public void Configure(IImageFormatConfigurationModule configuration)
222224
new TgaConfigurationModule(),
223225
new TiffConfigurationModule(),
224226
new WebpConfigurationModule(),
225-
new QoiConfigurationModule());
227+
new QoiConfigurationModule(),
228+
new IcoConfigurationModule(),
229+
new CurConfigurationModule());
226230
}

src/ImageSharp/Formats/Bmp/BmpConstants.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ internal static class BmpConstants
1111
/// <summary>
1212
/// The list of mimetypes that equate to a bmp.
1313
/// </summary>
14-
public static readonly IEnumerable<string> MimeTypes = new[] { "image/bmp", "image/x-windows-bmp" };
14+
public static readonly IEnumerable<string> MimeTypes = new[]
15+
{
16+
"image/bmp",
17+
"image/x-windows-bmp",
18+
"image/x-win-bitmap"
19+
};
1520

1621
/// <summary>
1722
/// The list of file extensions that equate to a bmp.

src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

Lines changed: 196 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Diagnostics.CodeAnalysis;
77
using System.Numerics;
88
using System.Runtime.CompilerServices;
9+
using System.Runtime.InteropServices;
910
using SixLabors.ImageSharp.Common.Helpers;
1011
using SixLabors.ImageSharp.IO;
1112
using SixLabors.ImageSharp.Memory;
@@ -71,7 +72,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
7172
/// <summary>
7273
/// The file header containing general information.
7374
/// </summary>
74-
private BmpFileHeader fileHeader;
75+
private BmpFileHeader? fileHeader;
7576

7677
/// <summary>
7778
/// Indicates which bitmap file marker was read.
@@ -99,6 +100,15 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
99100
/// </summary>
100101
private readonly RleSkippedPixelHandling rleSkippedPixelHandling;
101102

103+
/// <inheritdoc cref="BmpDecoderOptions.ProcessedAlphaMask"/>
104+
private readonly bool processedAlphaMask;
105+
106+
/// <inheritdoc cref="BmpDecoderOptions.SkipFileHeader"/>
107+
private readonly bool skipFileHeader;
108+
109+
/// <inheritdoc cref="BmpDecoderOptions.UseDoubleHeight"/>
110+
private readonly bool isDoubleHeight;
111+
102112
/// <summary>
103113
/// Initializes a new instance of the <see cref="BmpDecoderCore"/> class.
104114
/// </summary>
@@ -109,6 +119,9 @@ public BmpDecoderCore(BmpDecoderOptions options)
109119
this.rleSkippedPixelHandling = options.RleSkippedPixelHandling;
110120
this.configuration = options.GeneralOptions.Configuration;
111121
this.memoryAllocator = this.configuration.MemoryAllocator;
122+
this.processedAlphaMask = options.ProcessedAlphaMask;
123+
this.skipFileHeader = options.SkipFileHeader;
124+
this.isDoubleHeight = options.UseDoubleHeight;
112125
}
113126

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

133146
switch (this.infoHeader.Compression)
134147
{
135-
case BmpCompression.RGB:
136-
if (this.infoHeader.BitsPerPixel == 32)
137-
{
138-
if (this.bmpMetadata.InfoHeaderType == BmpInfoHeaderType.WinVersion3)
139-
{
140-
this.ReadRgb32Slow(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
141-
}
142-
else
143-
{
144-
this.ReadRgb32Fast(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
145-
}
146-
}
147-
else if (this.infoHeader.BitsPerPixel == 24)
148-
{
149-
this.ReadRgb24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
150-
}
151-
else if (this.infoHeader.BitsPerPixel == 16)
152-
{
153-
this.ReadRgb16(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
154-
}
155-
else if (this.infoHeader.BitsPerPixel <= 8)
156-
{
157-
this.ReadRgbPalette(
158-
stream,
159-
pixels,
160-
palette,
161-
this.infoHeader.Width,
162-
this.infoHeader.Height,
163-
this.infoHeader.BitsPerPixel,
164-
bytesPerColorMapEntry,
165-
inverted);
166-
}
148+
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 32 && this.bmpMetadata.InfoHeaderType is BmpInfoHeaderType.WinVersion3:
149+
this.ReadRgb32Slow(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
150+
151+
break;
152+
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 32:
153+
this.ReadRgb32Fast(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
154+
155+
break;
156+
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 24:
157+
this.ReadRgb24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
158+
159+
break;
160+
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 16:
161+
this.ReadRgb16(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
162+
163+
break;
164+
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is <= 8 && this.processedAlphaMask:
165+
this.ReadRgbPaletteWithAlphaMask(
166+
stream,
167+
pixels,
168+
palette,
169+
this.infoHeader.Width,
170+
this.infoHeader.Height,
171+
this.infoHeader.BitsPerPixel,
172+
bytesPerColorMapEntry,
173+
inverted);
174+
175+
break;
176+
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is <= 8:
177+
this.ReadRgbPalette(
178+
stream,
179+
pixels,
180+
palette,
181+
this.infoHeader.Width,
182+
this.infoHeader.Height,
183+
this.infoHeader.BitsPerPixel,
184+
bytesPerColorMapEntry,
185+
inverted);
167186

168187
break;
169188

@@ -839,6 +858,108 @@ private void ReadRgbPalette<TPixel>(BufferedReadStream stream, Buffer2D<TPixel>
839858
}
840859
}
841860

861+
/// <inheritdoc cref="ReadRgbPalette"/>
862+
private void ReadRgbPaletteWithAlphaMask<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, byte[] colors, int width, int height, int bitsPerPixel, int bytesPerColorMapEntry, bool inverted)
863+
where TPixel : unmanaged, IPixel<TPixel>
864+
{
865+
// Pixels per byte (bits per pixel).
866+
int ppb = 8 / bitsPerPixel;
867+
868+
int arrayWidth = (width + ppb - 1) / ppb;
869+
870+
// Bit mask
871+
int mask = 0xFF >> (8 - bitsPerPixel);
872+
873+
// Rows are aligned on 4 byte boundaries.
874+
int padding = arrayWidth % 4;
875+
if (padding != 0)
876+
{
877+
padding = 4 - padding;
878+
}
879+
880+
Bgra32[,] image = new Bgra32[height, width];
881+
using (IMemoryOwner<byte> row = this.memoryAllocator.Allocate<byte>(arrayWidth + padding, AllocationOptions.Clean))
882+
{
883+
Span<byte> rowSpan = row.GetSpan();
884+
885+
for (int y = 0; y < height; y++)
886+
{
887+
int newY = Invert(y, height, inverted);
888+
if (stream.Read(rowSpan) == 0)
889+
{
890+
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
891+
}
892+
893+
int offset = 0;
894+
895+
for (int x = 0; x < arrayWidth; x++)
896+
{
897+
int colOffset = x * ppb;
898+
for (int shift = 0, newX = colOffset; shift < ppb && newX < width; shift++, newX++)
899+
{
900+
int colorIndex = ((rowSpan[offset] >> (8 - bitsPerPixel - (shift * bitsPerPixel))) & mask) * bytesPerColorMapEntry;
901+
902+
image[newY, newX] = Bgra32.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIndex]));
903+
}
904+
905+
offset++;
906+
}
907+
}
908+
}
909+
910+
arrayWidth = width / 8;
911+
padding = arrayWidth % 4;
912+
if (padding != 0)
913+
{
914+
padding = 4 - padding;
915+
}
916+
917+
for (int y = 0; y < height; y++)
918+
{
919+
int newY = Invert(y, height, inverted);
920+
921+
for (int i = 0; i < arrayWidth; i++)
922+
{
923+
int x = i * 8;
924+
int and = stream.ReadByte();
925+
if (and is -1)
926+
{
927+
throw new EndOfStreamException();
928+
}
929+
930+
for (int j = 0; j < 8; j++)
931+
{
932+
SetAlpha(ref image[newY, x + j], and, j);
933+
}
934+
}
935+
936+
stream.Skip(padding);
937+
}
938+
939+
for (int y = 0; y < height; y++)
940+
{
941+
int newY = Invert(y, height, inverted);
942+
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
943+
944+
for (int x = 0; x < width; x++)
945+
{
946+
pixelRow[x] = TPixel.FromBgra32(image[newY, x]);
947+
}
948+
}
949+
}
950+
951+
/// <summary>
952+
/// Set pixel's alpha with alpha mask.
953+
/// </summary>
954+
/// <param name="pixel">Bgra32 pixel.</param>
955+
/// <param name="mask">alpha mask.</param>
956+
/// <param name="index">bit index of pixel.</param>
957+
private static void SetAlpha(ref Bgra32 pixel, in int mask, in int index)
958+
{
959+
bool isTransparently = (mask & (0b10000000 >> index)) is not 0;
960+
pixel.A = isTransparently ? byte.MinValue : byte.MaxValue;
961+
}
962+
842963
/// <summary>
843964
/// Reads the 16 bit color palette from the stream.
844965
/// </summary>
@@ -1333,6 +1454,11 @@ private void ReadInfoHeader(BufferedReadStream stream)
13331454
this.metadata.VerticalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetadata.DefaultVerticalResolution));
13341455
}
13351456

1457+
if (this.isDoubleHeight)
1458+
{
1459+
this.infoHeader.Height >>= 1;
1460+
}
1461+
13361462
ushort bitsPerPixel = this.infoHeader.BitsPerPixel;
13371463
this.bmpMetadata = this.metadata.GetBmpMetadata();
13381464
this.bmpMetadata.InfoHeaderType = infoHeaderType;
@@ -1362,9 +1488,9 @@ private void ReadFileHeader(BufferedReadStream stream)
13621488
// The bitmap file header of the first image follows the array header.
13631489
stream.Read(buffer, 0, BmpFileHeader.Size);
13641490
this.fileHeader = BmpFileHeader.Parse(buffer);
1365-
if (this.fileHeader.Type != BmpConstants.TypeMarkers.Bitmap)
1491+
if (this.fileHeader.Value.Type != BmpConstants.TypeMarkers.Bitmap)
13661492
{
1367-
BmpThrowHelper.ThrowNotSupportedException($"Unsupported bitmap file inside a BitmapArray file. File header bitmap type marker '{this.fileHeader.Type}'.");
1493+
BmpThrowHelper.ThrowNotSupportedException($"Unsupported bitmap file inside a BitmapArray file. File header bitmap type marker '{this.fileHeader.Value.Type}'.");
13681494
}
13691495

13701496
break;
@@ -1387,7 +1513,11 @@ private void ReadFileHeader(BufferedReadStream stream)
13871513
[MemberNotNull(nameof(bmpMetadata))]
13881514
private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out byte[] palette)
13891515
{
1390-
this.ReadFileHeader(stream);
1516+
if (!this.skipFileHeader)
1517+
{
1518+
this.ReadFileHeader(stream);
1519+
}
1520+
13911521
this.ReadInfoHeader(stream);
13921522

13931523
// see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517
@@ -1411,7 +1541,21 @@ private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out b
14111541
switch (this.fileMarkerType)
14121542
{
14131543
case BmpFileMarkerType.Bitmap:
1414-
colorMapSizeBytes = this.fileHeader.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize;
1544+
if (this.fileHeader.HasValue)
1545+
{
1546+
colorMapSizeBytes = this.fileHeader.Value.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize;
1547+
}
1548+
else
1549+
{
1550+
colorMapSizeBytes = this.infoHeader.ClrUsed;
1551+
if (colorMapSizeBytes is 0 && this.infoHeader.BitsPerPixel is <= 8)
1552+
{
1553+
colorMapSizeBytes = ColorNumerics.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel);
1554+
}
1555+
1556+
colorMapSizeBytes *= 4;
1557+
}
1558+
14151559
int colorCountForBitDepth = ColorNumerics.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel);
14161560
bytesPerColorMapEntry = colorMapSizeBytes / colorCountForBitDepth;
14171561

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

1459-
int skipAmount = this.fileHeader.Offset - (int)stream.Position;
1603+
if (palette.Length > 0)
1604+
{
1605+
Color[] colorTable = new Color[palette.Length / Unsafe.SizeOf<Bgr24>()];
1606+
ReadOnlySpan<Bgr24> rgbTable = MemoryMarshal.Cast<byte, Bgr24>(palette);
1607+
Color.FromPixel(rgbTable, colorTable);
1608+
this.bmpMetadata.ColorTable = colorTable;
1609+
}
1610+
1611+
int skipAmount = 0;
1612+
if (this.fileHeader.HasValue)
1613+
{
1614+
skipAmount = this.fileHeader.Value.Offset - (int)stream.Position;
1615+
}
1616+
14601617
if ((skipAmount + (int)stream.Position) > stream.Length)
14611618
{
14621619
BmpThrowHelper.ThrowInvalidImageContentException("Invalid file header offset found. Offset is greater than the stream length.");

0 commit comments

Comments
 (0)