Skip to content
Open
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
23 changes: 22 additions & 1 deletion src/ImageSharp/Common/Helpers/ColorNumerics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,28 @@ internal static class ColorNumerics
/// </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetBT709Luminance(ref Vector4 vector, int luminanceLevels)
=> (int)MathF.Round(Vector4.Dot(vector, Bt709) * (luminanceLevels - 1));
{
float luminance = Vector4.Dot(vector, Bt709);
if (float.IsNaN(luminance))
{
return 0;
}

// Floating-point pixel formats can contain values outside their normalized range.
#if !NET5_0_OR_GREATER
if (luminance < 0)
{
luminance = 0;
}
else if (luminance > 1)
{
luminance = 1;
}
#else
luminance = Math.Clamp(luminance, 0F, 1F);
#endif
return (int)MathF.Round(luminance * (luminanceLevels - 1));
}

/// <summary>
/// Gets the luminance from the rgb components using the formula
Expand Down
34 changes: 31 additions & 3 deletions src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;

namespace SixLabors.ImageSharp.Formats.Bmp
Expand Down Expand Up @@ -1199,6 +1200,7 @@ private static int CountBits(uint n)
private void ReadInfoHeader()
{
Span<byte> buffer = stackalloc byte[BmpInfoHeader.MaxHeaderSize];
long infoHeaderStart = stream.Position;

// Read the header size.
this.stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize);
Expand Down Expand Up @@ -1271,12 +1273,38 @@ private void ReadInfoHeader()
infoHeaderType = BmpInfoHeaderType.Os2Version2;
this.infoHeader = BmpInfoHeader.ParseOs2Version2(buffer);
}
else if (headerSize >= BmpInfoHeader.SizeV4)
else if (headerSize == BmpInfoHeader.SizeV4)
{
// >= 108 bytes
infoHeaderType = headerSize == BmpInfoHeader.SizeV4 ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion5;
// == 108 bytes
infoHeaderType = BmpInfoHeaderType.WinVersion4;
this.infoHeader = BmpInfoHeader.ParseV4(buffer);
}
else if (headerSize > BmpInfoHeader.SizeV4)
{
// > 108 bytes
infoHeaderType = BmpInfoHeaderType.WinVersion5;
this.infoHeader = BmpInfoHeader.ParseV5(buffer);
if (this.infoHeader.ProfileData != 0 && this.infoHeader.ProfileSize != 0)
{
// Read color profile.
long streamPosition = this.stream.Position;
long profileStart = infoHeaderStart + this.infoHeader.ProfileData;

// Validate the external offset and size before allocating. Subtraction avoids
// overflowing an end offset calculated from the declared profile size.
if (this.infoHeader.ProfileData < 0 || this.infoHeader.ProfileSize <= 0 ||
profileStart > this.stream.Length || this.infoHeader.ProfileSize > this.stream.Length - profileStart)
{
BmpThrowHelper.ThrowInvalidImageContentException("Not enough data to read BMP ICC profile.");
}

byte[] iccProfileData = new byte[this.infoHeader.ProfileSize];
this.stream.Position = profileStart;
_ = this.stream.Read(iccProfileData);
this.metadata.IccProfile = new IccProfile(iccProfileData);
this.stream.Position = streamPosition;
}
}
else
{
BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. HeaderSize '{headerSize}'.");
Expand Down
39 changes: 34 additions & 5 deletions src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// <summary>
/// A bitmap v4 header will only be written, if the user explicitly wants support for transparency.
/// In this case the compression type BITFIELDS will be used.
/// If the image contains a color profile, a bitmap v5 header is written, which is needed to write this info.
/// Otherwise a bitmap v3 header will be written, which is supported by almost all decoders.
/// </summary>
private readonly bool writeV4Header;
private BmpInfoHeaderType infoHeaderType;

/// <summary>
/// The quantizer for reducing the color count for 8-Bit, 4-Bit and 1-Bit images.
Expand All @@ -97,7 +98,7 @@ public BmpEncoderCore(IBmpEncoderOptions options, MemoryAllocator memoryAllocato
{
this.memoryAllocator = memoryAllocator;
this.bitsPerPixel = options.BitsPerPixel;
this.writeV4Header = options.SupportTransparency;
this.infoHeaderType = options.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3;
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
}

Expand Down Expand Up @@ -154,7 +155,23 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
}
}

int infoHeaderSize = this.writeV4Header ? BmpInfoHeader.SizeV4 : BmpInfoHeader.SizeV3;
byte[]? iccProfileData = null;
int iccProfileSize = 0;
if (metadata.IccProfile != null)
{
this.infoHeaderType = BmpInfoHeaderType.WinVersion5;
iccProfileData = metadata.IccProfile.ToByteArray();
iccProfileSize = iccProfileData.Length;
}

int infoHeaderSize = this.infoHeaderType switch
{
BmpInfoHeaderType.WinVersion3 => BmpInfoHeader.SizeV3,
BmpInfoHeaderType.WinVersion4 => BmpInfoHeader.SizeV4,
BmpInfoHeaderType.WinVersion5 => BmpInfoHeader.SizeV5,
_ => BmpInfoHeader.SizeV3
};

var infoHeader = new BmpInfoHeader(
headerSize: infoHeaderSize,
height: image.Height,
Expand All @@ -167,7 +184,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
xPelsPerMeter: hResolution,
yPelsPerMeter: vResolution);

if (this.writeV4Header && this.bitsPerPixel == BmpBitsPerPixel.Pixel32)
if ((this.infoHeaderType is BmpInfoHeaderType.WinVersion4 or BmpInfoHeaderType.WinVersion5) && this.bitsPerPixel == BmpBitsPerPixel.Pixel32)
{
infoHeader.AlphaMask = Rgba32AlphaMask;
infoHeader.RedMask = Rgba32RedMask;
Expand All @@ -176,6 +193,14 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
infoHeader.Compression = BmpCompression.BitFields;
}

if (this.infoHeaderType is BmpInfoHeaderType.WinVersion5 && iccProfileData != null)
{
infoHeader.ProfileSize = iccProfileData.Length;
infoHeader.CsType = 1296188740;
infoHeader.Intent = BmpRenderingIntent.LCS_GM_IMAGES;
}


int colorPaletteSize = 0;
if (this.bitsPerPixel == BmpBitsPerPixel.Pixel8)
{
Expand All @@ -201,7 +226,11 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken

stream.Write(buffer, 0, BmpFileHeader.Size);

if (this.writeV4Header)
if (this.infoHeaderType == BmpInfoHeaderType.WinVersion5)
{
infoHeader.WriteV5Header(buffer);
}
else if (this.infoHeaderType == BmpInfoHeaderType.WinVersion4)
{
infoHeader.WriteV4Header(buffer);
}
Expand Down
Loading