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()
{
Span<byte> buffer = stackalloc byte[BmpInfoHeader.MaxHeaderSize];
var infoHeaderStart = this.stream.Position;
long infoHeaderStart = this.stream.Position;
// Resolution is stored in PPM.
this.metadata = new ImageMetadata

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

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
@ -79,9 +80,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <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.
@ -97,8 +99,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
this.memoryAllocator = memoryAllocator;
this.bitsPerPixel = options.BitsPerPixel;
this.writeV4Header = options.SupportTransparency;
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
this.infoHeaderType = options.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3;
}
/// <summary>
@ -123,7 +125,62 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32);
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 vResolution = 0;
@ -154,20 +211,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
}
int infoHeaderSize = this.writeV4Header ? BmpInfoHeader.SizeV4 : BmpInfoHeader.SizeV3;
var infoHeader = new BmpInfoHeader(
headerSize: infoHeaderSize,
height: image.Height,
width: image.Width,
height: height,
width: width,
bitsPerPixel: bpp,
planes: 1,
imageSize: image.Height * bytesPerLine,
imageSize: height * bytesPerLine,
clrUsed: 0,
clrImportant: 0,
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;
@ -176,45 +232,78 @@ namespace SixLabors.ImageSharp.Formats.Bmp
infoHeader.Compression = BmpCompression.BitFields;
}
int colorPaletteSize = 0;
if (this.bitsPerPixel == BmpBitsPerPixel.Pixel8)
if (this.infoHeaderType is BmpInfoHeaderType.WinVersion5 && metadata.IccProfile != null)
{
colorPaletteSize = ColorPaletteSize8Bit;
infoHeader.ProfileSize = iccProfileData.Length;
infoHeader.CsType = BmpColorSpace.PROFILE_EMBEDDED;
infoHeader.Intent = BmpRenderingIntent.LCS_GM_IMAGES;
}
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel4)
{
colorPaletteSize = ColorPaletteSize4Bit;
}
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel1)
return infoHeader;
}
/// <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(
type: BmpConstants.TypeMarkers.Bitmap,
fileSize: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize + infoHeader.ImageSize,
fileSize: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize + iccProfileSize + infoHeader.ImageSize,
reserved: 0,
offset: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize);
Span<byte> buffer = stackalloc byte[infoHeaderSize];
fileHeader.WriteTo(buffer);
stream.Write(buffer, 0, BmpFileHeader.Size);
}
if (this.writeV4Header)
{
infoHeader.WriteV4Header(buffer);
}
else
/// <summary>
/// Writes the bitmap information header.
/// </summary>
/// <param name="stream">The stream to write info header into.</param>
/// <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);
this.WriteImage(stream, image.Frames.RootFrame);
stream.Flush();
}
/// <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);
}
/// <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()
{
const int MaximumBmpDimension = 65535;

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

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
Invalid = 0,
/// <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>
LCS_GM_BUSINESS = 1,

Loading…
Cancel
Save