mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Former-commit-id: b068ba10fe3487ab7c2a50b3733f7b1d563451bf Former-commit-id: c7b54a166c121a834289325fa94b33501a625e7e Former-commit-id: 845101507ecbd543bd8de8ba0e0fdebb471444d8pull/17/head
13 changed files with 927 additions and 5 deletions
@ -0,0 +1,69 @@ |
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
// <copyright file="BmpCompression.cs" company="James South">
|
|||
// Copyright © James South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// <summary>
|
|||
// Defines how the compression type of the image data
|
|||
// in the bitmap file.
|
|||
// </summary>
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Formats |
|||
{ |
|||
/// <summary>
|
|||
/// Defines how the compression type of the image data
|
|||
/// in the bitmap file.
|
|||
/// </summary>
|
|||
internal enum BmpCompression |
|||
{ |
|||
/// <summary>
|
|||
/// Each image row has a multiple of four elements. If the
|
|||
/// row has less elements, zeros will be added at the right side.
|
|||
/// The format depends on the number of bits, stored in the info header.
|
|||
/// If the number of bits are one, four or eight each pixel data is
|
|||
/// a index to the palette. If the number of bits are sixteen,
|
|||
/// twenty-four or thirty-two each pixel contains a color.
|
|||
/// </summary>
|
|||
RGB = 0, |
|||
|
|||
/// <summary>
|
|||
/// Two bytes are one data record. If the first byte is not zero, the
|
|||
/// next two half bytes will be repeated as much as the value of the first byte.
|
|||
/// If the first byte is zero, the record has different meanings, depending
|
|||
/// on the second byte. If the second byte is zero, it is the end of the row,
|
|||
/// if it is one, it is the end of the image.
|
|||
/// Not supported at the moment.
|
|||
/// </summary>
|
|||
RLE8 = 1, |
|||
|
|||
/// <summary>
|
|||
/// Two bytes are one data record. If the first byte is not zero, the
|
|||
/// next byte will be repeated as much as the value of the first byte.
|
|||
/// If the first byte is zero, the record has different meanings, depending
|
|||
/// on the second byte. If the second byte is zero, it is the end of the row,
|
|||
/// if it is one, it is the end of the image.
|
|||
/// Not supported at the moment.
|
|||
/// </summary>
|
|||
RLE4 = 2, |
|||
|
|||
/// <summary>
|
|||
/// Each image row has a multiple of four elements. If the
|
|||
/// row has less elements, zeros will be added at the right side.
|
|||
/// Not supported at the moment.
|
|||
/// </summary>
|
|||
BitFields = 3, |
|||
|
|||
/// <summary>
|
|||
/// The bitmap contains a JPG image.
|
|||
/// Not supported at the moment.
|
|||
/// </summary>
|
|||
JPEG = 4, |
|||
|
|||
/// <summary>
|
|||
/// The bitmap contains a PNG image.
|
|||
/// Not supported at the moment.
|
|||
/// </summary>
|
|||
PNG = 5 |
|||
} |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
// <copyright file="BmpDecoder.cs" company="James South">
|
|||
// Copyright © James South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// <summary>
|
|||
// Image decoder for generating an image out of a Windows bitmap stream.
|
|||
// </summary>
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Formats |
|||
{ |
|||
using System; |
|||
using System.IO; |
|||
|
|||
/// <summary>
|
|||
/// Image decoder for generating an image out of a Windows bitmap stream.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Does not support the following formats at the moment:
|
|||
/// <list type="bullet">
|
|||
/// <item>JPG</item>
|
|||
/// <item>PNG</item>
|
|||
/// <item>RLE4</item>
|
|||
/// <item>RLE8</item>
|
|||
/// <item>BitFields</item>
|
|||
/// </list>
|
|||
/// Formats will be supported in a later releases. We advise always
|
|||
/// to use only 24 Bit Windows bitmaps.
|
|||
/// </remarks>
|
|||
public class BmpDecoder : IImageDecoder |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the size of the header for this image type.
|
|||
/// </summary>
|
|||
/// <value>The size of the header.</value>
|
|||
public int HeaderSize |
|||
{ |
|||
get |
|||
{ |
|||
return 2; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value indicating whether the <see cref="IImageDecoder"/> supports the specified
|
|||
/// file header.
|
|||
/// </summary>
|
|||
/// <param name="extension">The <see cref="string"/> containing the file extension.</param>
|
|||
/// <returns>
|
|||
/// True if the decoder supports the file extension; otherwise, false.
|
|||
/// </returns>
|
|||
public bool IsSupportedFileExtension(string extension) |
|||
{ |
|||
Guard.NotNullOrEmpty(extension, "extension"); |
|||
|
|||
extension = extension.StartsWith(".") ? extension.Substring(1) : extension; |
|||
|
|||
return extension.Equals("BMP", StringComparison.OrdinalIgnoreCase) |
|||
|| extension.Equals("DIP", StringComparison.OrdinalIgnoreCase); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value indicating whether the <see cref="IImageDecoder"/> supports the specified
|
|||
/// file header.
|
|||
/// </summary>
|
|||
/// <param name="header">The <see cref="T:byte[]"/> containing the file header.</param>
|
|||
/// <returns>
|
|||
/// True if the decoder supports the file header; otherwise, false.
|
|||
/// </returns>
|
|||
public bool IsSupportedFileFormat(byte[] header) |
|||
{ |
|||
bool isBmp = false; |
|||
if (header.Length >= 2) |
|||
{ |
|||
isBmp = |
|||
header[0] == 0x42 && // B
|
|||
header[1] == 0x4D; // M
|
|||
} |
|||
|
|||
return isBmp; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes the image from the specified stream to the <see cref="ImageBase"/>.
|
|||
/// </summary>
|
|||
/// <param name="image">The <see cref="ImageBase"/> to decode to.</param>
|
|||
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
|
|||
public void Decode(Image image, Stream stream) |
|||
{ |
|||
new BmpDecoderCore().Decode(image, stream); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,422 @@ |
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
// <copyright file="BmpDecoderCore.cs" company="James South">
|
|||
// Copyright © James South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// <summary>
|
|||
// Performs the bmp decoding operation.
|
|||
// </summary>
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Formats |
|||
{ |
|||
using System; |
|||
using System.IO; |
|||
|
|||
/// <summary>
|
|||
/// Performs the bmp decoding operation.
|
|||
/// </summary>
|
|||
internal class BmpDecoderCore |
|||
{ |
|||
/// <summary>
|
|||
/// The mask for the red part of the color for 16 bit rgb bitmaps.
|
|||
/// </summary>
|
|||
private const int Rgb16RMask = 0x00007C00; |
|||
|
|||
/// <summary>
|
|||
/// The mask for the green part of the color for 16 bit rgb bitmaps.
|
|||
/// </summary>
|
|||
private const int Rgb16GMask = 0x000003E0; |
|||
|
|||
/// <summary>
|
|||
/// The mask for the blue part of the color for 16 bit rgb bitmaps.
|
|||
/// </summary>
|
|||
private const int Rgb16BMask = 0x0000001F; |
|||
|
|||
#region Fields
|
|||
|
|||
/// <summary>
|
|||
/// The stream to decode from.
|
|||
/// </summary>
|
|||
private Stream currentStream; |
|||
|
|||
/// <summary>
|
|||
/// The file header containing general information.
|
|||
/// TODO: Why is this not used? We advance the stream but do not use the values parsed.
|
|||
/// </summary>
|
|||
private BmpFileHeader fileHeader; |
|||
|
|||
/// <summary>
|
|||
/// The info header containing detailed information about the bitmap.
|
|||
/// </summary>
|
|||
private BmpInfoHeader infoHeader; |
|||
|
|||
#endregion
|
|||
|
|||
#region IImageDecoder Members
|
|||
|
|||
/// <summary>
|
|||
/// Decodes the image from the specified this._stream and sets
|
|||
/// the data to image.
|
|||
/// </summary>
|
|||
/// <param name="image">The image, where the data should be set to.
|
|||
/// Cannot be null (Nothing in Visual Basic).</param>
|
|||
/// <param name="stream">The this._stream, where the image should be
|
|||
/// decoded from. Cannot be null (Nothing in Visual Basic).</param>
|
|||
/// <exception cref="ArgumentNullException">
|
|||
/// <para><paramref name="image"/> is null.</para>
|
|||
/// <para>- or -</para>
|
|||
/// <para><paramref name="stream"/> is null.</para>
|
|||
/// </exception>
|
|||
public void Decode(Image image, Stream stream) |
|||
{ |
|||
this.currentStream = stream; |
|||
|
|||
try |
|||
{ |
|||
this.ReadFileHeader(); |
|||
this.ReadInfoHeader(); |
|||
|
|||
int colorMapSize = -1; |
|||
|
|||
if (this.infoHeader.ClrUsed == 0) |
|||
{ |
|||
if (this.infoHeader.BitsPerPixel == 1 || |
|||
this.infoHeader.BitsPerPixel == 4 || |
|||
this.infoHeader.BitsPerPixel == 8) |
|||
{ |
|||
colorMapSize = (int)Math.Pow(2, this.infoHeader.BitsPerPixel) * 4; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
colorMapSize = this.infoHeader.ClrUsed * 4; |
|||
} |
|||
|
|||
byte[] palette = null; |
|||
|
|||
if (colorMapSize > 0) |
|||
{ |
|||
if (colorMapSize > 255 * 4) |
|||
{ |
|||
throw new ImageFormatException( |
|||
string.Format("Invalid bmp colormap size '{0}'", colorMapSize)); |
|||
} |
|||
|
|||
palette = new byte[colorMapSize]; |
|||
|
|||
this.currentStream.Read(palette, 0, colorMapSize); |
|||
} |
|||
|
|||
if (this.infoHeader.Width > ImageBase.MaxWidth || this.infoHeader.Height > ImageBase.MaxHeight) |
|||
{ |
|||
throw new ArgumentOutOfRangeException( |
|||
string.Format( |
|||
"The input bitmap '{0}x{1}' is bigger then the max allowed size '{2}x{3}'", |
|||
this.infoHeader.Width, |
|||
this.infoHeader.Height, |
|||
ImageBase.MaxWidth, |
|||
ImageBase.MaxHeight)); |
|||
} |
|||
|
|||
byte[] imageData = new byte[this.infoHeader.Width * this.infoHeader.Height * 4]; |
|||
|
|||
switch (this.infoHeader.Compression) |
|||
{ |
|||
case BmpCompression.RGB: |
|||
if (this.infoHeader.HeaderSize != 40) |
|||
{ |
|||
throw new ImageFormatException( |
|||
string.Format("Header Size value '{0}' is not valid.", this.infoHeader.HeaderSize)); |
|||
} |
|||
|
|||
if (this.infoHeader.BitsPerPixel == 32) |
|||
{ |
|||
this.ReadRgb32(imageData, this.infoHeader.Width, this.infoHeader.Height); |
|||
} |
|||
else if (this.infoHeader.BitsPerPixel == 24) |
|||
{ |
|||
this.ReadRgb24(imageData, this.infoHeader.Width, this.infoHeader.Height); |
|||
} |
|||
else if (this.infoHeader.BitsPerPixel == 16) |
|||
{ |
|||
this.ReadRgb16(imageData, this.infoHeader.Width, this.infoHeader.Height); |
|||
} |
|||
else if (this.infoHeader.BitsPerPixel <= 8) |
|||
{ |
|||
this.ReadRgbPalette(imageData, palette, |
|||
this.infoHeader.Width, |
|||
this.infoHeader.Height, |
|||
this.infoHeader.BitsPerPixel); |
|||
} |
|||
|
|||
break; |
|||
default: |
|||
throw new NotSupportedException("Does not support this kind of bitmap files."); |
|||
} |
|||
|
|||
image.SetPixels(this.infoHeader.Width, this.infoHeader.Height, imageData); |
|||
} |
|||
catch (IndexOutOfRangeException e) |
|||
{ |
|||
throw new ImageFormatException("Bitmap does not have a valid format.", e); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the y- value based on the given height.
|
|||
/// </summary>
|
|||
/// <param name="y">The y- value representing the current row.</param>
|
|||
/// <param name="height">The height of the bitmap.</param>
|
|||
/// <returns>The <see cref="int"/> representing the inverted value.</returns>
|
|||
private static int Invert(int y, int height) |
|||
{ |
|||
int row; |
|||
|
|||
if (height > 0) |
|||
{ |
|||
row = height - y - 1; |
|||
} |
|||
else |
|||
{ |
|||
row = y; |
|||
} |
|||
|
|||
return row; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the color palette from the stream.
|
|||
/// </summary>
|
|||
/// <param name="imageData">The <see cref="T:byte[]"/> image data to assign the palette to.</param>
|
|||
/// <param name="colors">The <see cref="T:byte[]"/> containing the colors.</param>
|
|||
/// <param name="width">The width of the bitmap.</param>
|
|||
/// <param name="height">The height of the bitmap.</param>
|
|||
/// <param name="bits">The number of bits per pixel.</param>
|
|||
private void ReadRgbPalette(byte[] imageData, byte[] colors, int width, int height, int bits) |
|||
{ |
|||
// Pixels per byte (bits per pixel)
|
|||
int ppb = 8 / bits; |
|||
|
|||
int arrayWidth = (width + ppb - 1) / ppb; |
|||
|
|||
// Bit mask
|
|||
int mask = 0xFF >> (8 - bits); |
|||
|
|||
byte[] data = new byte[(arrayWidth * height)]; |
|||
|
|||
this.currentStream.Read(data, 0, data.Length); |
|||
|
|||
// Rows are aligned on 4 byte boundaries
|
|||
int alignment = arrayWidth % 4; |
|||
if (alignment != 0) |
|||
{ |
|||
alignment = 4 - alignment; |
|||
} |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
int rowOffset = y * (arrayWidth + alignment); |
|||
|
|||
for (int x = 0; x < arrayWidth; x++) |
|||
{ |
|||
int offset = rowOffset + x; |
|||
|
|||
// Revert the y value, because bitmaps are saved from down to top
|
|||
int row = Invert(y, height); |
|||
|
|||
int colOffset = x * ppb; |
|||
|
|||
for (int shift = 0; shift < ppb && (colOffset + shift) < width; shift++) |
|||
{ |
|||
int colorIndex = (data[offset] >> (8 - bits - (shift * bits))) & mask; |
|||
|
|||
int arrayOffset = ((row * width) + (colOffset + shift)) * 4; |
|||
imageData[arrayOffset + 0] = colors[colorIndex * 4]; |
|||
imageData[arrayOffset + 1] = colors[(colorIndex * 4) + 1]; |
|||
imageData[arrayOffset + 2] = colors[(colorIndex * 4) + 2]; |
|||
imageData[arrayOffset + 3] = 255; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the 16 bit color palette from the stream
|
|||
/// </summary>
|
|||
/// <param name="imageData">The <see cref="T:byte[]"/> image data to assign the palette to.</param>
|
|||
/// <param name="width">The width of the bitmap.</param>
|
|||
/// <param name="height">The height of the bitmap.</param>
|
|||
private void ReadRgb16(byte[] imageData, int width, int height) |
|||
{ |
|||
const int ScaleR = 256 / 32; |
|||
const int ScaleG = 256 / 64; |
|||
|
|||
int alignment; |
|||
byte[] data = this.GetImageArray(width, height, 2, out alignment); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
int rowOffset = y * ((width * 2) + alignment); |
|||
|
|||
// Revert the y value, because bitmaps are saved from down to top
|
|||
int row = Invert(y, height); |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
int offset = rowOffset + (x * 2); |
|||
|
|||
short temp = BitConverter.ToInt16(data, offset); |
|||
|
|||
byte r = (byte)(((temp & Rgb16RMask) >> 11) * ScaleR); |
|||
byte g = (byte)(((temp & Rgb16GMask) >> 5) * ScaleG); |
|||
byte b = (byte)((temp & Rgb16BMask) * ScaleR); |
|||
|
|||
int arrayOffset = ((row * width) + x) * 4; |
|||
|
|||
imageData[arrayOffset + 0] = b; |
|||
imageData[arrayOffset + 1] = g; |
|||
imageData[arrayOffset + 2] = r; |
|||
imageData[arrayOffset + 3] = 255; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the 24 bit color palette from the stream
|
|||
/// </summary>
|
|||
/// <param name="imageData">The <see cref="T:byte[]"/> image data to assign the palette to.</param>
|
|||
/// <param name="width">The width of the bitmap.</param>
|
|||
/// <param name="height">The height of the bitmap.</param>
|
|||
private void ReadRgb24(byte[] imageData, int width, int height) |
|||
{ |
|||
int alignment; |
|||
byte[] data = this.GetImageArray(width, height, 3, out alignment); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
int rowOffset = y * ((width * 3) + alignment); |
|||
|
|||
// Revert the y value, because bitmaps are saved from down to top
|
|||
int row = Invert(y, height); |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
int offset = rowOffset + (x * 3); |
|||
int arrayOffset = ((row * width) + x) * 4; |
|||
|
|||
imageData[arrayOffset + 0] = data[offset + 0]; |
|||
imageData[arrayOffset + 1] = data[offset + 1]; |
|||
imageData[arrayOffset + 2] = data[offset + 2]; |
|||
imageData[arrayOffset + 3] = 255; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the 32 bit color palette from the stream
|
|||
/// </summary>
|
|||
/// <param name="imageData">The <see cref="T:byte[]"/> image data to assign the palette to.</param>
|
|||
/// <param name="width">The width of the bitmap.</param>
|
|||
/// <param name="height">The height of the bitmap.</param>
|
|||
private void ReadRgb32(byte[] imageData, int width, int height) |
|||
{ |
|||
int alignment; |
|||
byte[] data = this.GetImageArray(width, height, 4, out alignment); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
int rowOffset = y * ((width * 4) + alignment); |
|||
|
|||
// Revert the y value, because bitmaps are saved from down to top
|
|||
int row = Invert(y, height); |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
int offset = rowOffset + (x * 4); |
|||
|
|||
var arrayOffset = ((row * width) + x) * 4; |
|||
imageData[arrayOffset + 0] = data[offset + 0]; |
|||
imageData[arrayOffset + 1] = data[offset + 1]; |
|||
imageData[arrayOffset + 2] = data[offset + 2]; |
|||
imageData[arrayOffset + 3] = 255; // Can we get alpha here?
|
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a <see cref="T:byte[]"/> containing the pixels for the current bitmap.
|
|||
/// </summary>
|
|||
/// <param name="width">The width of the bitmap.</param>
|
|||
/// <param name="height">The height.</param>
|
|||
/// <param name="bytes">The number of bytes per pixel.</param>
|
|||
/// <param name="alignment">The alignment of the pixels.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="T:byte[]"/> containing the pixels.
|
|||
/// </returns>
|
|||
private byte[] GetImageArray(int width, int height, int bytes, out int alignment) |
|||
{ |
|||
int dataWidth = width; |
|||
|
|||
alignment = (width * bytes) % 4; |
|||
|
|||
if (alignment != 0) |
|||
{ |
|||
alignment = 4 - alignment; |
|||
} |
|||
|
|||
int size = ((dataWidth * bytes) + alignment) * height; |
|||
|
|||
byte[] data = new byte[size]; |
|||
|
|||
this.currentStream.Read(data, 0, size); |
|||
|
|||
return data; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the <see cref="BmpInfoHeader"/> from the stream.
|
|||
/// </summary>
|
|||
private void ReadInfoHeader() |
|||
{ |
|||
byte[] data = new byte[BmpInfoHeader.Size]; |
|||
|
|||
this.currentStream.Read(data, 0, BmpInfoHeader.Size); |
|||
|
|||
this.infoHeader = new BmpInfoHeader |
|||
{ |
|||
HeaderSize = BitConverter.ToInt32(data, 0), |
|||
Width = BitConverter.ToInt32(data, 4), |
|||
Height = BitConverter.ToInt32(data, 8), |
|||
Planes = BitConverter.ToInt16(data, 12), |
|||
BitsPerPixel = BitConverter.ToInt16(data, 14), |
|||
ImageSize = BitConverter.ToInt32(data, 20), |
|||
XPelsPerMeter = BitConverter.ToInt32(data, 24), |
|||
YPelsPerMeter = BitConverter.ToInt32(data, 28), |
|||
ClrUsed = BitConverter.ToInt32(data, 32), |
|||
ClrImportant = BitConverter.ToInt32(data, 36), |
|||
Compression = (BmpCompression)BitConverter.ToInt32(data, 16) |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the <see cref="BmpFileHeader"/> from the stream.
|
|||
/// </summary>
|
|||
private void ReadFileHeader() |
|||
{ |
|||
byte[] data = new byte[BmpFileHeader.Size]; |
|||
|
|||
this.currentStream.Read(data, 0, BmpFileHeader.Size); |
|||
|
|||
this.fileHeader = new BmpFileHeader |
|||
{ |
|||
Type = BitConverter.ToInt16(data, 0), |
|||
FileSize = BitConverter.ToInt32(data, 2), |
|||
Reserved = BitConverter.ToInt32(data, 6), |
|||
Offset = BitConverter.ToInt32(data, 10) |
|||
}; |
|||
} |
|||
|
|||
#endregion
|
|||
} |
|||
} |
|||
@ -0,0 +1,180 @@ |
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
// <copyright file="BmpEncoder.cs" company="James South">
|
|||
// Copyright © James South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// <summary>
|
|||
// Image encoder for writing an image to a stream as a Windows bitmap.
|
|||
// </summary>
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Formats |
|||
{ |
|||
using System; |
|||
using System.IO; |
|||
|
|||
/// <summary>
|
|||
/// Image encoder for writing an image to a stream as a Windows bitmap.
|
|||
/// </summary>
|
|||
/// <remarks>The encoder can currently only write 24-bit rgb images to streams.</remarks>
|
|||
public class BmpEncoder : IImageEncoder |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the quality of output for images.
|
|||
/// </summary>
|
|||
/// <remarks>Png is a lossless format so this is not used in this encoder.</remarks>
|
|||
public int Quality { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the default file extension for this encoder.
|
|||
/// </summary>
|
|||
public string Extension |
|||
{ |
|||
get { return "BMP"; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value indicating whether the <see cref="IImageEncoder"/> supports the specified
|
|||
/// file header.
|
|||
/// </summary>
|
|||
/// <param name="extension">The <see cref="string"/> containing the file extension.</param>
|
|||
/// <returns>
|
|||
/// True if the decoder supports the file extension; otherwise, false.
|
|||
/// </returns>
|
|||
public bool IsSupportedFileExtension(string extension) |
|||
{ |
|||
Guard.NotNullOrEmpty(extension, "extension"); |
|||
|
|||
extension = extension.StartsWith(".") ? extension.Substring(1) : extension; |
|||
|
|||
return extension.Equals("BMP", StringComparison.OrdinalIgnoreCase) |
|||
|| extension.Equals("DIP", StringComparison.OrdinalIgnoreCase); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes the image to the specified stream from the <see cref="ImageBase"/>.
|
|||
/// </summary>
|
|||
/// <param name="image">The <see cref="ImageBase"/> to encode from.</param>
|
|||
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
|
|||
public void Encode(ImageBase image, Stream stream) |
|||
{ |
|||
Guard.NotNull(image, "image"); |
|||
Guard.NotNull(stream, "stream"); |
|||
|
|||
int rowWidth = image.Width; |
|||
|
|||
int amount = (image.Width * 3) % 4; |
|||
if (amount != 0) |
|||
{ |
|||
rowWidth += 4 - amount; |
|||
} |
|||
|
|||
BinaryWriter writer = new BinaryWriter(stream); |
|||
|
|||
BmpFileHeader fileHeader = new BmpFileHeader |
|||
{ |
|||
Type = 19778, |
|||
Offset = 54, |
|||
FileSize = 54 + (image.Height * rowWidth * 3) |
|||
}; |
|||
WriteHeader(writer, fileHeader); |
|||
|
|||
BmpInfoHeader infoHeader = new BmpInfoHeader |
|||
{ |
|||
HeaderSize = 40, |
|||
Height = image.Height, |
|||
Width = image.Width, |
|||
BitsPerPixel = 24, |
|||
Planes = 1, |
|||
Compression = BmpCompression.RGB, |
|||
ImageSize = image.Height * rowWidth * 3, |
|||
ClrUsed = 0, |
|||
ClrImportant = 0 |
|||
}; |
|||
|
|||
WriteInfo(writer, infoHeader); |
|||
|
|||
WriteImage(writer, image); |
|||
|
|||
writer.Flush(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the pixel data to the binary stream.
|
|||
/// </summary>
|
|||
/// <param name="writer">
|
|||
/// The <see cref="BinaryWriter"/> containing the stream to write to.
|
|||
/// </param>
|
|||
/// <param name="image">
|
|||
/// The <see cref="ImageBase"/> containing pixel data.
|
|||
/// </param>
|
|||
private static void WriteImage(BinaryWriter writer, ImageBase image) |
|||
{ |
|||
int amount = (image.Width * 3) % 4; |
|||
if (amount != 0) |
|||
{ |
|||
amount = 4 - amount; |
|||
} |
|||
|
|||
byte[] data = image.Pixels; |
|||
|
|||
for (int y = image.Height - 1; y >= 0; y--) |
|||
{ |
|||
for (int x = 0; x < image.Width; x++) |
|||
{ |
|||
int offset = ((y * image.Width) + x) * 4; |
|||
|
|||
writer.Write(data[offset + 0]); |
|||
writer.Write(data[offset + 1]); |
|||
writer.Write(data[offset + 2]); |
|||
} |
|||
|
|||
for (int i = 0; i < amount; i++) |
|||
{ |
|||
writer.Write((byte)0); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the bitmap header data to the binary stream.
|
|||
/// </summary>
|
|||
/// <param name="writer">
|
|||
/// The <see cref="BinaryWriter"/> containing the stream to write to.
|
|||
/// </param>
|
|||
/// <param name="fileHeader">
|
|||
/// The <see cref="BmpFileHeader"/> containing the header data.
|
|||
/// </param>
|
|||
private static void WriteHeader(BinaryWriter writer, BmpFileHeader fileHeader) |
|||
{ |
|||
writer.Write(fileHeader.Type); |
|||
writer.Write(fileHeader.FileSize); |
|||
writer.Write(fileHeader.Reserved); |
|||
writer.Write(fileHeader.Offset); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the bitmap information to the binary stream.
|
|||
/// </summary>
|
|||
/// <param name="writer">
|
|||
/// The <see cref="BinaryWriter"/> containing the stream to write to.
|
|||
/// </param>
|
|||
/// <param name="infoHeader">
|
|||
/// The <see cref="BmpFileHeader"/> containing the detailed information about the image.
|
|||
/// </param>
|
|||
private static void WriteInfo(BinaryWriter 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); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
// <copyright file="BmpFileHeader.cs" company="James South">
|
|||
// Copyright © James South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// <summary>
|
|||
// Stores general information about the Bitmap file.
|
|||
// <see href="https://en.wikipedia.org/wiki/BMP_file_format" />
|
|||
// </summary>
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Formats |
|||
{ |
|||
/// <summary>
|
|||
/// Stores general information about the Bitmap file.
|
|||
/// <see href="https://en.wikipedia.org/wiki/BMP_file_format" />
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The first two bytes of the Bitmap file format
|
|||
/// (thus the Bitmap header) are stored in big-endian order.
|
|||
/// All of the other integer values are stored in little-endian format
|
|||
/// (i.e. least-significant byte first).
|
|||
/// </remarks>
|
|||
internal class BmpFileHeader |
|||
{ |
|||
/// <summary>
|
|||
/// Defines of the data structure in the bitmap file.
|
|||
/// </summary>
|
|||
public const int Size = 14; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the Bitmap identifier.
|
|||
/// The field used to identify the bitmap file: 0x42 0x4D
|
|||
/// (Hex code points for B and M)
|
|||
/// </summary>
|
|||
public short Type { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the size of the bitmap file in bytes.
|
|||
/// </summary>
|
|||
public int FileSize { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets any reserved data; actual value depends on the application
|
|||
/// that creates the image.
|
|||
/// </summary>
|
|||
public int Reserved { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the offset, i.e. starting address, of the byte where
|
|||
/// the bitmap data can be found.
|
|||
/// </summary>
|
|||
public int Offset { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
// <copyright file="BmpInfoHeader.cs" company="James South">
|
|||
// Copyright © James South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// <summary>
|
|||
// This block of bytes tells the application detailed information
|
|||
// about the image, which will be used to display the image on
|
|||
// the screen.
|
|||
// <see href="https://en.wikipedia.org/wiki/BMP_file_format" />
|
|||
// </summary>
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Formats |
|||
{ |
|||
/// <summary>
|
|||
/// This block of bytes tells the application detailed information
|
|||
/// about the image, which will be used to display the image on
|
|||
/// the screen.
|
|||
/// <see href="https://en.wikipedia.org/wiki/BMP_file_format"/>
|
|||
/// </summary>
|
|||
internal class BmpInfoHeader |
|||
{ |
|||
/// <summary>
|
|||
/// Defines of the data structure in the bitmap file.
|
|||
/// </summary>
|
|||
public const int Size = 40; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the size of this header (40 bytes)
|
|||
/// </summary>
|
|||
public int HeaderSize { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the bitmap width in pixels (signed integer).
|
|||
/// </summary>
|
|||
public int Width { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the bitmap height in pixels (signed integer).
|
|||
/// </summary>
|
|||
public int Height { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of color planes being used. Must be set to 1.
|
|||
/// </summary>
|
|||
public short Planes { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of bits per pixel, which is the color depth of the image.
|
|||
/// Typical values are 1, 4, 8, 16, 24 and 32.
|
|||
/// </summary>
|
|||
public short BitsPerPixel { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the compression method being used.
|
|||
/// See the next table for a list of possible values.
|
|||
/// </summary>
|
|||
public BmpCompression Compression { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the image size. This is the size of the raw bitmap data (see below),
|
|||
/// and should not be confused with the file size.
|
|||
/// </summary>
|
|||
public int ImageSize { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the horizontal resolution of the image.
|
|||
/// (pixel per meter, signed integer)
|
|||
/// </summary>
|
|||
public int XPelsPerMeter { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the vertical resolution of the image.
|
|||
/// (pixel per meter, signed integer)
|
|||
/// </summary>
|
|||
public int YPelsPerMeter { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of colors in the color palette,
|
|||
/// or 0 to default to 2^n.
|
|||
/// </summary>
|
|||
public int ClrUsed { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of important colors used,
|
|||
/// or 0 when every color is important{ get; set; } generally ignored.
|
|||
/// </summary>
|
|||
public int ClrImportant { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1 @@ |
|||
edd8ac1feb2c4649e6f5aa80af8d4cf33642a546 |
|||
Loading…
Reference in new issue