diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 32025f69fc..247ed78117 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -163,7 +163,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.WriteBitmapFileHeader(stream, infoHeaderSize, colorPaletteSize, iccProfileSize, infoHeader, buffer); this.WriteBitmapInfoHeader(stream, infoHeader, buffer, infoHeaderSize); this.WriteImage(stream, image.Frames.RootFrame); - this.WriteColorProfile(stream, metadata, buffer); + this.WriteColorProfile(stream, iccProfileData, buffer); stream.Flush(); } @@ -246,14 +246,15 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// Writes the color profile to the stream. /// /// The stream to write to. - /// The metadata. + /// The color profile data. /// The buffer. - private void WriteColorProfile(Stream stream, ImageMetadata metadata, Span buffer) + private void WriteColorProfile(Stream stream, byte[] iccProfileData, Span buffer) { - if (metadata.IccProfile != null) + if (iccProfileData != null) { - int streamPositionAfterImageData = (int)stream.Position; - stream.Write(metadata.IccProfile.ToByteArray()); + // The offset, in bytes, from the beginning of the BITMAPV5HEADER structure to the start of the profile data. + int streamPositionAfterImageData = (int)stream.Position - BmpFileHeader.Size; + stream.Write(iccProfileData); BinaryPrimitives.WriteInt32LittleEndian(buffer, streamPositionAfterImageData); stream.Position = BmpFileHeader.Size + 112; stream.Write(buffer.Slice(0, 4)); diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 43ec45a34f..b42569a232 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -29,10 +29,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public static readonly string[] BitfieldsBmpFiles = BitFields; - private static BmpDecoder BmpDecoder => new BmpDecoder(); + private static BmpDecoder BmpDecoder => new(); public static readonly TheoryData RatioFiles = - new TheoryData + new() { { Car, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, { V5Header, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 073cf5fcf2..2215d5f0a5 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -301,6 +301,33 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void Encode_PreservesAlpha(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + [Theory] + [WithFile(IccProfile, PixelTypes.Rgba32)] + public void Encode_PreservesColorProfile(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image input = provider.GetImage(new BmpDecoder())) + { + ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile; + byte[] expectedProfileBytes = expectedProfile.ToByteArray(); + + using (var memStream = new MemoryStream()) + { + input.Save(memStream, new BmpEncoder()); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile; + byte[] actualProfileBytes = actualProfile.ToByteArray(); + + Assert.NotNull(actualProfile); + Assert.Equal(expectedProfileBytes, actualProfileBytes); + } + } + } + } + [Theory] [WithFile(Car, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] [WithFile(V5Header, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs index 8931c242ef..6a582e0199 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs @@ -3,7 +3,7 @@ using System.IO; using SixLabors.ImageSharp.Formats.Bmp; - +using SixLabors.ImageSharp.PixelFormats; using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Bmp; @@ -47,5 +47,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp Assert.Equal(expectedInfoHeaderType, bitmapMetadata.InfoHeaderType); } } + + [Theory] + [WithFile(IccProfile, PixelTypes.Rgba32)] + public void Decoder_CanReadColorProfile(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + ImageSharp.Metadata.ImageMetadata metaData = image.Metadata; + Assert.NotNull(metaData); + Assert.NotNull(metaData.IccProfile); + Assert.Equal(16, metaData.IccProfile.Entries.Length); + } + } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index fa51fb2254..d36cec630d 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -379,6 +379,7 @@ namespace SixLabors.ImageSharp.Tests public const string Rgb24jpeg = "Bmp/rgb24jpeg.bmp"; public const string Rgb24png = "Bmp/rgb24png.bmp"; public const string Rgba32v4 = "Bmp/rgba32v4.bmp"; + public const string IccProfile = "Bmp/BMP_v5_with_ICC_2.bmp"; // Bitmap images with compression type BITFIELDS. public const string Rgb32bfdef = "Bmp/rgb32bfdef.bmp"; diff --git a/tests/Images/Input/Bmp/BMP_v5_with_ICC_2.bmp b/tests/Images/Input/Bmp/BMP_v5_with_ICC_2.bmp new file mode 100644 index 0000000000..d6328d429f --- /dev/null +++ b/tests/Images/Input/Bmp/BMP_v5_with_ICC_2.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5b483e9a9d3f3ebdeada2eff70800002c27c046bf971206af0ecc73fa1416e6 +size 27782