mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Former-commit-id: 398e93818c9c9e901b125b7daa7a345fcc8d2787 Former-commit-id: 9aba9f2e190fc8a986e16d632a58ebc94fda2d5b Former-commit-id: fd99bf7129eb85360cae9484cccac3241af0681eaf/merge-core
16 changed files with 378 additions and 187 deletions
@ -0,0 +1,23 @@ |
|||||
|
// <copyright file="BmpBitsPerPixel.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageProcessorCore.Formats |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Enumerates the available bits per pixel for bitmap.
|
||||
|
/// </summary>
|
||||
|
public enum BmpBitsPerPixel |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 24 bits per pixel. Each pixel consists of 3 bytes.
|
||||
|
/// </summary>
|
||||
|
Pixel24 = 3, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 32 bits per pixel. Each pixel consists of 4 bytes.
|
||||
|
/// </summary>
|
||||
|
Pixel32 = 4, |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,205 @@ |
|||||
|
// <copyright file="BmpEncoderCore.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageProcessorCore.Formats |
||||
|
{ |
||||
|
using System; |
||||
|
using System.IO; |
||||
|
|
||||
|
using ImageProcessorCore.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>
|
||||
|
internal sealed class BmpEncoderCore |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The number of bits per pixel.
|
||||
|
/// </summary>
|
||||
|
private BmpBitsPerPixel bmpBitsPerPixel; |
||||
|
|
||||
|
/// <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>
|
||||
|
/// <param name="bitsPerPixel">The <see cref="BmpBitsPerPixel"/></param>
|
||||
|
public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) |
||||
|
{ |
||||
|
Guard.NotNull(image, nameof(image)); |
||||
|
Guard.NotNull(stream, nameof(stream)); |
||||
|
|
||||
|
this.bmpBitsPerPixel = bitsPerPixel; |
||||
|
|
||||
|
int rowWidth = image.Width; |
||||
|
|
||||
|
int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4; |
||||
|
if (amount != 0) |
||||
|
{ |
||||
|
rowWidth += 4 - amount; |
||||
|
} |
||||
|
|
||||
|
using (EndianBinaryWriter writer = new EndianBinaryWriter(EndianBitConverter.Little, stream)) |
||||
|
{ |
||||
|
int bpp = (int)this.bmpBitsPerPixel; |
||||
|
|
||||
|
BmpFileHeader fileHeader = new BmpFileHeader |
||||
|
{ |
||||
|
Type = 19778, // BM
|
||||
|
Offset = 54, |
||||
|
FileSize = 54 + (image.Height * rowWidth * bpp) |
||||
|
}; |
||||
|
|
||||
|
BmpInfoHeader infoHeader = new BmpInfoHeader |
||||
|
{ |
||||
|
HeaderSize = 40, |
||||
|
Height = image.Height, |
||||
|
Width = image.Width, |
||||
|
BitsPerPixel = (short)(8 * bpp), |
||||
|
Planes = 1, |
||||
|
ImageSize = image.Height * rowWidth * bpp, |
||||
|
ClrUsed = 0, |
||||
|
ClrImportant = 0 |
||||
|
}; |
||||
|
|
||||
|
WriteHeader(writer, fileHeader); |
||||
|
this.WriteInfo(writer, infoHeader); |
||||
|
this.WriteImage(writer, image); |
||||
|
|
||||
|
writer.Flush(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Writes the bitmap header data to the binary stream.
|
||||
|
/// </summary>
|
||||
|
/// <param name="writer">
|
||||
|
/// The <see cref="EndianBinaryWriter"/> containing the stream to write to.
|
||||
|
/// </param>
|
||||
|
/// <param name="fileHeader">
|
||||
|
/// The <see cref="BmpFileHeader"/> containing the header data.
|
||||
|
/// </param>
|
||||
|
private static void WriteHeader(EndianBinaryWriter 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="EndianBinaryWriter"/> containing the stream to write to.
|
||||
|
/// </param>
|
||||
|
/// <param name="infoHeader">
|
||||
|
/// The <see cref="BmpFileHeader"/> containing the detailed information about the image.
|
||||
|
/// </param>
|
||||
|
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); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Writes the pixel data to the binary stream.
|
||||
|
/// </summary>
|
||||
|
/// <param name="writer">
|
||||
|
/// The <see cref="EndianBinaryWriter"/> containing the stream to write to.
|
||||
|
/// </param>
|
||||
|
/// <param name="image">
|
||||
|
/// The <see cref="ImageBase"/> containing pixel data.
|
||||
|
/// </param>
|
||||
|
private void WriteImage(EndianBinaryWriter writer, ImageBase image) |
||||
|
{ |
||||
|
// TODO: Add more compression formats.
|
||||
|
int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4; |
||||
|
if (amount != 0) |
||||
|
{ |
||||
|
amount = 4 - amount; |
||||
|
} |
||||
|
|
||||
|
switch (this.bmpBitsPerPixel) |
||||
|
{ |
||||
|
case BmpBitsPerPixel.Pixel32: |
||||
|
this.Write32bit(writer, image, amount); |
||||
|
break; |
||||
|
|
||||
|
case BmpBitsPerPixel.Pixel24: |
||||
|
this.Write24bit(writer, image, amount); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Writes the 32bit color palette to the stream.
|
||||
|
/// </summary>
|
||||
|
/// <param name="writer">The <see cref="EndianBinaryWriter"/> containing the stream to write to.</param>
|
||||
|
/// <param name="image">The <see cref="ImageBase"/> containing pixel data.</param>
|
||||
|
/// <param name="amount">The amount to pad each row by.</param>
|
||||
|
private void Write32bit(EndianBinaryWriter writer, ImageBase image, int amount) |
||||
|
{ |
||||
|
for (int y = image.Height - 1; y >= 0; y--) |
||||
|
{ |
||||
|
for (int x = 0; x < image.Width; x++) |
||||
|
{ |
||||
|
// Limit the output range and multiply out from our floating point.
|
||||
|
// Convert back to b-> g-> r-> a order.
|
||||
|
// Convert to non-premultiplied color.
|
||||
|
Bgra32 color = Color.ToNonPremultiplied(image[x, y]); |
||||
|
|
||||
|
// We can take advantage of BGRA here
|
||||
|
writer.Write(color.Bgra); |
||||
|
} |
||||
|
|
||||
|
// Pad
|
||||
|
for (int i = 0; i < amount; i++) |
||||
|
{ |
||||
|
writer.Write((byte)0); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Writes the 24bit color palette to the stream.
|
||||
|
/// </summary>
|
||||
|
/// <param name="writer">The <see cref="EndianBinaryWriter"/> containing the stream to write to.</param>
|
||||
|
/// <param name="image">The <see cref="ImageBase"/> containing pixel data.</param>
|
||||
|
/// <param name="amount">The amount to pad each row by.</param>
|
||||
|
private void Write24bit(EndianBinaryWriter writer, ImageBase image, int amount) |
||||
|
{ |
||||
|
for (int y = image.Height - 1; y >= 0; y--) |
||||
|
{ |
||||
|
for (int x = 0; x < image.Width; x++) |
||||
|
{ |
||||
|
// Limit the output range and multiply out from our floating point.
|
||||
|
// Convert back to b-> g-> r-> a order.
|
||||
|
// Convert to non-premultiplied color.
|
||||
|
Bgra32 color = Color.ToNonPremultiplied(image[x, y]); |
||||
|
|
||||
|
// Allocate 1 array instead of allocating 3.
|
||||
|
writer.Write(new[] { color.B, color.G, color.R }); |
||||
|
} |
||||
|
|
||||
|
// Pad
|
||||
|
for (int i = 0; i < amount; i++) |
||||
|
{ |
||||
|
writer.Write((byte)0); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
// <copyright file="BitmapTests.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageProcessorCore.Tests |
||||
|
{ |
||||
|
using System.Diagnostics; |
||||
|
using System.IO; |
||||
|
|
||||
|
using Formats; |
||||
|
|
||||
|
using Xunit; |
||||
|
|
||||
|
public class BitmapTests : FileTestBase |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void BitmapCanEncodeDifferentBitRates() |
||||
|
{ |
||||
|
if (!Directory.Exists("TestOutput/Encode/Bitmap")) |
||||
|
{ |
||||
|
Directory.CreateDirectory("TestOutput/Encode/Bitmap"); |
||||
|
} |
||||
|
|
||||
|
foreach (string file in Files) |
||||
|
{ |
||||
|
using (FileStream stream = File.OpenRead(file)) |
||||
|
{ |
||||
|
Stopwatch watch = Stopwatch.StartNew(); |
||||
|
Image image = new Image(stream); |
||||
|
|
||||
|
string encodeFilename = "TestOutput/Encode/Bitmap/" + "24-" + Path.GetFileNameWithoutExtension(file) + ".bmp"; |
||||
|
|
||||
|
using (FileStream output = File.OpenWrite(encodeFilename)) |
||||
|
{ |
||||
|
image.Save(output, new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel24 }); |
||||
|
} |
||||
|
|
||||
|
encodeFilename = "TestOutput/Encode/Bitmap/" + "32-" + Path.GetFileNameWithoutExtension(file) + ".bmp"; |
||||
|
|
||||
|
using (FileStream output = File.OpenWrite(encodeFilename)) |
||||
|
{ |
||||
|
image.Save(output, new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel32 }); |
||||
|
} |
||||
|
|
||||
|
Trace.WriteLine($"{file} : {watch.ElapsedMilliseconds}ms"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
// <copyright file="PngTests.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageProcessorCore.Tests |
||||
|
{ |
||||
|
using System.IO; |
||||
|
|
||||
|
using Formats; |
||||
|
|
||||
|
using Xunit; |
||||
|
|
||||
|
public class PngTests : FileTestBase |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void ImageCanSaveIndexedPng() |
||||
|
{ |
||||
|
if (!Directory.Exists("TestOutput/Encode/Png")) |
||||
|
{ |
||||
|
Directory.CreateDirectory("TestOutput/Encode/Png"); |
||||
|
} |
||||
|
|
||||
|
foreach (string file in Files) |
||||
|
{ |
||||
|
using (FileStream stream = File.OpenRead(file)) |
||||
|
{ |
||||
|
Image image = new Image(stream); |
||||
|
|
||||
|
using (FileStream output = File.OpenWrite($"TestOutput/Encode/Png/{Path.GetFileNameWithoutExtension(file)}.png")) |
||||
|
{ |
||||
|
image.Quality = 256; |
||||
|
image.Save(output, new PngFormat()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue