//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
namespace ImageSharp.Formats
{
using System;
using System.IO;
using IO;
///
/// Image encoder for writing an image to a stream as a Windows bitmap.
///
internal sealed class BmpEncoderCore
{
///
/// The options for the encoder.
///
private readonly IBmpEncoderOptions options;
///
/// The amount to pad each row by.
///
private int padding;
///
/// Initializes a new instance of the class.
///
/// The options for the encoder.
public BmpEncoderCore(IBmpEncoderOptions options)
{
this.options = options ?? new BmpEncoderOptions();
}
///
/// Encodes the image to the specified stream from the .
///
/// The pixel format.
/// The to encode from.
/// The to encode the image data to.
public void Encode(ImageBase image, Stream stream)
where TColor : struct, IPixel
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
// Cast to int will get the bytes per pixel
short bpp = (short)(8 * (int)this.options.BitsPerPixel);
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32);
this.padding = bytesPerLine - (image.Width * (int)this.options.BitsPerPixel);
// Do not use IDisposable pattern here as we want to preserve the stream.
EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream);
BmpInfoHeader infoHeader = new BmpInfoHeader
{
HeaderSize = BmpInfoHeader.Size,
Height = image.Height,
Width = image.Width,
BitsPerPixel = bpp,
Planes = 1,
ImageSize = image.Height * bytesPerLine,
ClrUsed = 0,
ClrImportant = 0
};
BmpFileHeader fileHeader = new BmpFileHeader
{
Type = 19778, // BM
Offset = 54,
FileSize = 54 + infoHeader.ImageSize
};
WriteHeader(writer, fileHeader);
this.WriteInfo(writer, infoHeader);
this.WriteImage(writer, image);
writer.Flush();
}
///
/// Writes the bitmap header data to the binary stream.
///
///
/// The containing the stream to write to.
///
///
/// The containing the header data.
///
private static void WriteHeader(EndianBinaryWriter writer, BmpFileHeader fileHeader)
{
writer.Write(fileHeader.Type);
writer.Write(fileHeader.FileSize);
writer.Write(fileHeader.Reserved);
writer.Write(fileHeader.Offset);
}
///
/// Writes the bitmap information to the binary stream.
///
///
/// The containing the stream to write to.
///
///
/// The containing the detailed information about the image.
///
private void WriteInfo(EndianBinaryWriter writer, BmpInfoHeader infoHeader)
{
writer.Write(infoHeader.HeaderSize);
writer.Write(infoHeader.Width);
writer.Write(infoHeader.Height);
writer.Write(infoHeader.Planes);
writer.Write(infoHeader.BitsPerPixel);
writer.Write((int)infoHeader.Compression);
writer.Write(infoHeader.ImageSize);
writer.Write(infoHeader.XPelsPerMeter);
writer.Write(infoHeader.YPelsPerMeter);
writer.Write(infoHeader.ClrUsed);
writer.Write(infoHeader.ClrImportant);
}
///
/// Writes the pixel data to the binary stream.
///
/// The pixel format.
/// The containing the stream to write to.
///
/// The containing pixel data.
///
private void WriteImage(EndianBinaryWriter writer, ImageBase image)
where TColor : struct, IPixel
{
using (PixelAccessor pixels = image.Lock())
{
switch (this.options.BitsPerPixel)
{
case BmpBitsPerPixel.Pixel32:
this.Write32Bit(writer, pixels);
break;
case BmpBitsPerPixel.Pixel24:
this.Write24Bit(writer, pixels);
break;
}
}
}
///
/// Writes the 32bit color palette to the stream.
///
/// The pixel format.
/// The containing the stream to write to.
/// The containing pixel data.
private void Write32Bit(EndianBinaryWriter writer, PixelAccessor pixels)
where TColor : struct, IPixel
{
using (PixelArea row = new PixelArea(pixels.Width, ComponentOrder.Zyxw, this.padding))
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
pixels.CopyTo(row, y);
writer.Write(row.Bytes, 0, row.Length);
}
}
}
///
/// Writes the 24bit color palette to the stream.
///
/// The pixel format.
/// The containing the stream to write to.
/// The containing pixel data.
private void Write24Bit(EndianBinaryWriter writer, PixelAccessor pixels)
where TColor : struct, IPixel
{
using (PixelArea row = new PixelArea(pixels.Width, ComponentOrder.Zyx, this.padding))
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
pixels.CopyTo(row, y);
writer.Write(row.Bytes, 0, row.Length);
}
}
}
}
}