Browse Source

Write color profile, if present

pull/2108/head
Brian Popow 4 years ago
parent
commit
106f6c0a9b
  1. 2
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  2. 149
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  3. 11
      src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
  4. 2
      src/ImageSharp/Formats/Bmp/BmpRenderingIntent.cs

2
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -1200,7 +1200,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private void ReadInfoHeader() private void ReadInfoHeader()
{ {
Span<byte> buffer = stackalloc byte[BmpInfoHeader.MaxHeaderSize]; Span<byte> buffer = stackalloc byte[BmpInfoHeader.MaxHeaderSize];
var infoHeaderStart = this.stream.Position; long infoHeaderStart = this.stream.Position;
// Resolution is stored in PPM. // Resolution is stored in PPM.
this.metadata = new ImageMetadata this.metadata = new ImageMetadata

149
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
@ -79,9 +80,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary> /// <summary>
/// A bitmap v4 header will only be written, if the user explicitly wants support for transparency. /// 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. /// 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. /// Otherwise a bitmap v3 header will be written, which is supported by almost all decoders.
/// </summary> /// </summary>
private readonly bool writeV4Header; private BmpInfoHeaderType infoHeaderType;
/// <summary> /// <summary>
/// The quantizer for reducing the color count for 8-Bit, 4-Bit and 1-Bit images. /// The quantizer for reducing the color count for 8-Bit, 4-Bit and 1-Bit images.
@ -97,8 +99,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{ {
this.memoryAllocator = memoryAllocator; this.memoryAllocator = memoryAllocator;
this.bitsPerPixel = options.BitsPerPixel; this.bitsPerPixel = options.BitsPerPixel;
this.writeV4Header = options.SupportTransparency;
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
this.infoHeaderType = options.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3;
} }
/// <summary> /// <summary>
@ -123,7 +125,62 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32);
this.padding = bytesPerLine - (int)(image.Width * (bpp / 8F)); this.padding = bytesPerLine - (int)(image.Width * (bpp / 8F));
// Set Resolution. int colorPaletteSize = 0;
if (this.bitsPerPixel == BmpBitsPerPixel.Pixel8)
{
colorPaletteSize = ColorPaletteSize8Bit;
}
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel4)
{
colorPaletteSize = ColorPaletteSize4Bit;
}
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel1)
{
colorPaletteSize = ColorPaletteSize1Bit;
}
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
};
BmpInfoHeader infoHeader = this.CreateBmpInfoHeader(image.Width, image.Height, infoHeaderSize, bpp, bytesPerLine, metadata, iccProfileData);
Span<byte> buffer = stackalloc byte[infoHeaderSize];
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);
stream.Flush();
}
/// <summary>
/// Creates the bitmap information header.
/// </summary>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="infoHeaderSize">Size of the information header.</param>
/// <param name="bpp">The bits per pixel.</param>
/// <param name="bytesPerLine">The bytes per line.</param>
/// <param name="metadata">The metadata.</param>
/// <param name="iccProfileData">The icc profile data.</param>
/// <returns>The bitmap information header.</returns>
private BmpInfoHeader CreateBmpInfoHeader(int width, int height, int infoHeaderSize, short bpp, int bytesPerLine, ImageMetadata metadata, byte[] iccProfileData)
{
int hResolution = 0; int hResolution = 0;
int vResolution = 0; int vResolution = 0;
@ -154,20 +211,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
} }
} }
int infoHeaderSize = this.writeV4Header ? BmpInfoHeader.SizeV4 : BmpInfoHeader.SizeV3;
var infoHeader = new BmpInfoHeader( var infoHeader = new BmpInfoHeader(
headerSize: infoHeaderSize, headerSize: infoHeaderSize,
height: image.Height, height: height,
width: image.Width, width: width,
bitsPerPixel: bpp, bitsPerPixel: bpp,
planes: 1, planes: 1,
imageSize: image.Height * bytesPerLine, imageSize: height * bytesPerLine,
clrUsed: 0, clrUsed: 0,
clrImportant: 0, clrImportant: 0,
xPelsPerMeter: hResolution, xPelsPerMeter: hResolution,
yPelsPerMeter: vResolution); 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.AlphaMask = Rgba32AlphaMask;
infoHeader.RedMask = Rgba32RedMask; infoHeader.RedMask = Rgba32RedMask;
@ -176,45 +232,78 @@ namespace SixLabors.ImageSharp.Formats.Bmp
infoHeader.Compression = BmpCompression.BitFields; infoHeader.Compression = BmpCompression.BitFields;
} }
int colorPaletteSize = 0; if (this.infoHeaderType is BmpInfoHeaderType.WinVersion5 && metadata.IccProfile != null)
if (this.bitsPerPixel == BmpBitsPerPixel.Pixel8)
{ {
colorPaletteSize = ColorPaletteSize8Bit; infoHeader.ProfileSize = iccProfileData.Length;
infoHeader.CsType = BmpColorSpace.PROFILE_EMBEDDED;
infoHeader.Intent = BmpRenderingIntent.LCS_GM_IMAGES;
} }
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel4)
{ return infoHeader;
colorPaletteSize = ColorPaletteSize4Bit; }
}
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel1) /// <summary>
/// Writes the color profile to the stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="metadata">The metadata.</param>
/// <param name="buffer">The buffer.</param>
private void WriteColorProfile(Stream stream, ImageMetadata metadata, Span<byte> buffer)
{
if (metadata.IccProfile != null)
{ {
colorPaletteSize = ColorPaletteSize1Bit; int streamPositionAfterImageData = (int)stream.Position;
stream.Write(metadata.IccProfile.ToByteArray());
BinaryPrimitives.WriteInt32LittleEndian(buffer, streamPositionAfterImageData);
stream.Position = BmpFileHeader.Size + 112;
stream.Write(buffer.Slice(0, 4));
} }
}
/// <summary>
/// Writes the bitmap file header.
/// </summary>
/// <param name="stream">The stream to write the header to.</param>
/// <param name="infoHeaderSize">Size of the bitmap information header.</param>
/// <param name="colorPaletteSize">Size of the color palette.</param>
/// <param name="iccProfileSize">The size in bytes of the color profile.</param>
/// <param name="infoHeader">The information header to write.</param>
/// <param name="buffer">The buffer to write to.</param>
private void WriteBitmapFileHeader(Stream stream, int infoHeaderSize, int colorPaletteSize, int iccProfileSize, BmpInfoHeader infoHeader, Span<byte> buffer)
{
var fileHeader = new BmpFileHeader( var fileHeader = new BmpFileHeader(
type: BmpConstants.TypeMarkers.Bitmap, type: BmpConstants.TypeMarkers.Bitmap,
fileSize: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize + infoHeader.ImageSize, fileSize: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize + iccProfileSize + infoHeader.ImageSize,
reserved: 0, reserved: 0,
offset: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize); offset: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize);
Span<byte> buffer = stackalloc byte[infoHeaderSize];
fileHeader.WriteTo(buffer); fileHeader.WriteTo(buffer);
stream.Write(buffer, 0, BmpFileHeader.Size); stream.Write(buffer, 0, BmpFileHeader.Size);
}
if (this.writeV4Header) /// <summary>
{ /// Writes the bitmap information header.
infoHeader.WriteV4Header(buffer); /// </summary>
} /// <param name="stream">The stream to write info header into.</param>
else /// <param name="infoHeader">The information header.</param>
/// <param name="buffer">The buffer.</param>
/// <param name="infoHeaderSize">Size of the information header.</param>
private void WriteBitmapInfoHeader(Stream stream, BmpInfoHeader infoHeader, Span<byte> buffer, int infoHeaderSize)
{
switch (this.infoHeaderType)
{ {
infoHeader.WriteV3Header(buffer); case BmpInfoHeaderType.WinVersion3:
infoHeader.WriteV3Header(buffer);
break;
case BmpInfoHeaderType.WinVersion4:
infoHeader.WriteV4Header(buffer);
break;
case BmpInfoHeaderType.WinVersion5:
infoHeader.WriteV5Header(buffer);
break;
} }
stream.Write(buffer, 0, infoHeaderSize); stream.Write(buffer, 0, infoHeaderSize);
this.WriteImage(stream, image.Frames.RootFrame);
stream.Flush();
} }
/// <summary> /// <summary>

11
src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs

@ -532,6 +532,17 @@ namespace SixLabors.ImageSharp.Formats.Bmp
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(104, 4), this.GammaBlue); BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(104, 4), this.GammaBlue);
} }
/// <summary>
/// Writes a complete Bitmap V5 header to a buffer.
/// </summary>
/// <param name="buffer">The buffer to write to.</param>
public void WriteV5Header(Span<byte> buffer)
{
ref BmpInfoHeader dest = ref Unsafe.As<byte, BmpInfoHeader>(ref MemoryMarshal.GetReference(buffer));
dest = this;
}
internal void VerifyDimensions() internal void VerifyDimensions()
{ {
const int MaximumBmpDimension = 65535; const int MaximumBmpDimension = 65535;

2
src/ImageSharp/Formats/Bmp/BmpRenderingIntent.cs

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
Invalid = 0, Invalid = 0,
/// <summary> /// <summary>
/// TMaintains saturation. Used for business charts and other situations in which undithered colors are required. /// Maintains saturation. Used for business charts and other situations in which undithered colors are required.
/// </summary> /// </summary>
LCS_GM_BUSINESS = 1, LCS_GM_BUSINESS = 1,

Loading…
Cancel
Save