mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Former-commit-id: 3cca13c49abcf0e9a5a394794153406084d2e19e Former-commit-id: 767a3db69cf4adecac58c58a84891b736ada79eb Former-commit-id: db1a5b695cc73951becdff9b31535b8f7b03e75bpull/1/head
260 changed files with 9198 additions and 2255 deletions
@ -0,0 +1,124 @@ |
|||||
|
// <copyright file="Bootstrapper.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageProcessorCore |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.ObjectModel; |
||||
|
using System.Text; |
||||
|
|
||||
|
using ImageProcessorCore.Formats; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Provides initialization code which allows extending the library.
|
||||
|
/// </summary>
|
||||
|
public class Bootstrapper |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A new instance Initializes a new instance of the <see cref="Bootstrapper"/> class.
|
||||
|
/// with lazy initialization.
|
||||
|
/// </summary>
|
||||
|
private static readonly Lazy<Bootstrapper> Lazy = new Lazy<Bootstrapper>(() => new Bootstrapper()); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The default list of supported <see cref="IImageFormat"/>
|
||||
|
/// </summary>
|
||||
|
private readonly List<IImageFormat> imageFormats; |
||||
|
|
||||
|
private readonly Dictionary<Type, Type> pixelAccessors; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Prevents a default instance of the <see cref="Bootstrapper"/> class from being created.
|
||||
|
/// </summary>
|
||||
|
private Bootstrapper() |
||||
|
{ |
||||
|
this.imageFormats = new List<IImageFormat> |
||||
|
{ |
||||
|
new BmpFormat(), |
||||
|
new JpegFormat(), |
||||
|
new PngFormat(), |
||||
|
new GifFormat() |
||||
|
}; |
||||
|
|
||||
|
this.pixelAccessors = new Dictionary<Type, Type> |
||||
|
{ |
||||
|
{ typeof(Bgra32), typeof(Bgra32PixelAccessor) } |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the current bootstrapper instance.
|
||||
|
/// </summary>
|
||||
|
public static Bootstrapper Instance = Lazy.Value; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the list of supported <see cref="IImageFormat"/>
|
||||
|
/// </summary>
|
||||
|
public IReadOnlyCollection<IImageFormat> ImageFormats => new ReadOnlyCollection<IImageFormat>(this.imageFormats); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds a new <see cref="IImageFormat"/> to the collection of supported image formats.
|
||||
|
/// </summary>
|
||||
|
/// <param name="format">The new format to add.</param>
|
||||
|
public void AddImageFormat(IImageFormat format) |
||||
|
{ |
||||
|
this.imageFormats.Add(format); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets an instance of the correct <see cref="IPixelAccessor"/> for the packed vector.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPackedVector">The type of pixel data.</typeparam>
|
||||
|
/// <param name="image">The image</param>
|
||||
|
/// <returns>The <see cref="IPixelAccessor"/></returns>
|
||||
|
public IPixelAccessor GetPixelAccessor<TPackedVector>(Image<TPackedVector> image) |
||||
|
where TPackedVector : IPackedVector |
||||
|
{ |
||||
|
Type packed = typeof(TPackedVector); |
||||
|
if (!this.pixelAccessors.ContainsKey(packed)) |
||||
|
{ |
||||
|
// TODO: Double check this. It should work...
|
||||
|
return (IPixelAccessor)Activator.CreateInstance(this.pixelAccessors[packed], image); |
||||
|
} |
||||
|
|
||||
|
StringBuilder stringBuilder = new StringBuilder(); |
||||
|
stringBuilder.AppendLine("PixelAccessor cannot be loaded. Available accessors:"); |
||||
|
|
||||
|
foreach (Type value in this.pixelAccessors.Values) |
||||
|
{ |
||||
|
stringBuilder.AppendLine("-" + value.Name); |
||||
|
} |
||||
|
|
||||
|
throw new NotSupportedException(stringBuilder.ToString()); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets an instance of the correct <see cref="IPixelAccessor"/> for the packed vector.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPackedVector">The type of pixel data.</typeparam>
|
||||
|
/// <param name="image">The image</param>
|
||||
|
/// <returns>The <see cref="IPixelAccessor"/></returns>
|
||||
|
public IPixelAccessor GetPixelAccessor<TPackedVector>(ImageFrame<TPackedVector> image) |
||||
|
where TPackedVector : IPackedVector |
||||
|
{ |
||||
|
Type packed = typeof(TPackedVector); |
||||
|
if (!this.pixelAccessors.ContainsKey(packed)) |
||||
|
{ |
||||
|
return (IPixelAccessor)Activator.CreateInstance(this.pixelAccessors[packed], image); |
||||
|
} |
||||
|
|
||||
|
StringBuilder stringBuilder = new StringBuilder(); |
||||
|
stringBuilder.AppendLine("PixelAccessor cannot be loaded. Available accessors:"); |
||||
|
|
||||
|
foreach (Type value in this.pixelAccessors.Values) |
||||
|
{ |
||||
|
stringBuilder.AppendLine("-" + value.Name); |
||||
|
} |
||||
|
|
||||
|
throw new NotSupportedException(stringBuilder.ToString()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
// <copyright file="ImageFormatException.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageProcessorCore |
||||
|
{ |
||||
|
using System; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The exception that is thrown when the library tries to load
|
||||
|
/// an image, which has an invalid format.
|
||||
|
/// </summary>
|
||||
|
public class ImageFormatException : Exception |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ImageFormatException"/> class.
|
||||
|
/// </summary>
|
||||
|
public ImageFormatException() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ImageFormatException"/> class with the name of the
|
||||
|
/// parameter that causes this exception.
|
||||
|
/// </summary>
|
||||
|
/// <param name="errorMessage">The error message that explains the reason for this exception.</param>
|
||||
|
public ImageFormatException(string errorMessage) |
||||
|
: base(errorMessage) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ImageFormatException"/> class with a specified
|
||||
|
/// error message and the exception that is the cause of this exception.
|
||||
|
/// </summary>
|
||||
|
/// <param name="errorMessage">The error message that explains the reason for this exception.</param>
|
||||
|
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic)
|
||||
|
/// if no inner exception is specified.</param>
|
||||
|
public ImageFormatException(string errorMessage, Exception innerException) |
||||
|
: base(errorMessage, innerException) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
// <copyright file="ImageProcessingException.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageProcessorCore |
||||
|
{ |
||||
|
using System; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The exception that is thrown when an error occurs when applying a process to an image.
|
||||
|
/// </summary>
|
||||
|
public class ImageProcessingException : Exception |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ImageProcessingException"/> class.
|
||||
|
/// </summary>
|
||||
|
public ImageProcessingException() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ImageProcessingException"/> class with the name of the
|
||||
|
/// parameter that causes this exception.
|
||||
|
/// </summary>
|
||||
|
/// <param name="errorMessage">The error message that explains the reason for this exception.</param>
|
||||
|
public ImageProcessingException(string errorMessage) |
||||
|
: base(errorMessage) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ImageProcessingException"/> class with a specified
|
||||
|
/// error message and the exception that is the cause of this exception.
|
||||
|
/// </summary>
|
||||
|
/// <param name="errorMessage">The error message that explains the reason for this exception.</param>
|
||||
|
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic)
|
||||
|
/// if no inner exception is specified.</param>
|
||||
|
public ImageProcessingException(string errorMessage, Exception innerException) |
||||
|
: base(errorMessage, innerException) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,60 @@ |
|||||
|
// <copyright file="ByteExtensions.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageProcessorCore |
||||
|
{ |
||||
|
using System; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Extension methods for the <see cref="byte"/> struct.
|
||||
|
/// </summary>
|
||||
|
internal static class ByteExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Converts a byte array to a new array where each value in the original array is represented
|
||||
|
/// by a the specified number of bits.
|
||||
|
/// </summary>
|
||||
|
/// <param name="bytes">The bytes to convert from. Cannot be null.</param>
|
||||
|
/// <param name="bits">The number of bits per value.</param>
|
||||
|
/// <returns>The resulting <see cref="T:byte[]"/> array. Is never null.</returns>
|
||||
|
/// <exception cref="ArgumentNullException"><paramref name="bytes"/> is null.</exception>
|
||||
|
/// <exception cref="ArgumentException"><paramref name="bits"/> is less than or equals than zero.</exception>
|
||||
|
public static byte[] ToArrayByBitsLength(this byte[] bytes, int bits) |
||||
|
{ |
||||
|
Guard.NotNull(bytes, "bytes"); |
||||
|
Guard.MustBeGreaterThan(bits, 0, "bits"); |
||||
|
|
||||
|
byte[] result; |
||||
|
|
||||
|
if (bits < 8) |
||||
|
{ |
||||
|
result = new byte[bytes.Length * 8 / bits]; |
||||
|
|
||||
|
// BUGFIX I dont think it should be there, but I am not sure if it breaks something else
|
||||
|
// int factor = (int)Math.Pow(2, bits) - 1;
|
||||
|
int mask = 0xFF >> (8 - bits); |
||||
|
int resultOffset = 0; |
||||
|
|
||||
|
foreach (byte b in bytes) |
||||
|
{ |
||||
|
for (int shift = 0; shift < 8; shift += bits) |
||||
|
{ |
||||
|
int colorIndex = (b >> (8 - bits - shift)) & mask; // * (255 / factor);
|
||||
|
|
||||
|
result[resultOffset] = (byte)colorIndex; |
||||
|
|
||||
|
resultOffset++; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
result = bytes; |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,145 @@ |
|||||
|
// <copyright file="ComparableExtensions.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageProcessorCore |
||||
|
{ |
||||
|
using System; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Extension methods for classes that implement <see cref="IComparable{T}"/>.
|
||||
|
/// </summary>
|
||||
|
internal static class ComparableExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Restricts a <see cref="byte"/> to be within a specified range.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The The value to clamp.</param>
|
||||
|
/// <param name="min">The minimum value. If value is less than min, min will be returned.</param>
|
||||
|
/// <param name="max">The maximum value. If value is greater than max, max will be returned.</param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="byte"/> representing the clamped value.
|
||||
|
/// </returns>
|
||||
|
public static byte Clamp(this byte value, byte min, byte max) |
||||
|
{ |
||||
|
// Order is important here as someone might set min to higher than max.
|
||||
|
if (value > max) |
||||
|
{ |
||||
|
return max; |
||||
|
} |
||||
|
|
||||
|
if (value < min) |
||||
|
{ |
||||
|
return min; |
||||
|
} |
||||
|
|
||||
|
return value; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Restricts a <see cref="int"/> to be within a specified range.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The The value to clamp.</param>
|
||||
|
/// <param name="min">The minimum value. If value is less than min, min will be returned.</param>
|
||||
|
/// <param name="max">The maximum value. If value is greater than max, max will be returned.</param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="int"/> representing the clamped value.
|
||||
|
/// </returns>
|
||||
|
public static int Clamp(this int value, int min, int max) |
||||
|
{ |
||||
|
if (value > max) |
||||
|
{ |
||||
|
return max; |
||||
|
} |
||||
|
|
||||
|
if (value < min) |
||||
|
{ |
||||
|
return min; |
||||
|
} |
||||
|
|
||||
|
return value; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Restricts a <see cref="float"/> to be within a specified range.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The The value to clamp.</param>
|
||||
|
/// <param name="min">The minimum value. If value is less than min, min will be returned.</param>
|
||||
|
/// <param name="max">The maximum value. If value is greater than max, max will be returned.</param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="float"/> representing the clamped value.
|
||||
|
/// </returns>
|
||||
|
public static float Clamp(this float value, float min, float max) |
||||
|
{ |
||||
|
if (value > max) |
||||
|
{ |
||||
|
return max; |
||||
|
} |
||||
|
|
||||
|
if (value < min) |
||||
|
{ |
||||
|
return min; |
||||
|
} |
||||
|
|
||||
|
return value; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Restricts a <see cref="double"/> to be within a specified range.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The The value to clamp.</param>
|
||||
|
/// <param name="min">The minimum value. If value is less than min, min will be returned.</param>
|
||||
|
/// <param name="max">The maximum value. If value is greater than max, max will be returned.</param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="double"/> representing the clamped value.
|
||||
|
/// </returns>
|
||||
|
public static double Clamp(this double value, double min, double max) |
||||
|
{ |
||||
|
if (value > max) |
||||
|
{ |
||||
|
return max; |
||||
|
} |
||||
|
|
||||
|
if (value < min) |
||||
|
{ |
||||
|
return min; |
||||
|
} |
||||
|
|
||||
|
return value; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts an <see cref="int"/> to a <see cref="byte"/> first restricting the value between the
|
||||
|
/// minimum and maximum allowable ranges.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The <see cref="int"/> this method extends.</param>
|
||||
|
/// <returns>The <see cref="byte"/></returns>
|
||||
|
public static byte ToByte(this int value) |
||||
|
{ |
||||
|
return (byte)value.Clamp(0, 255); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts an <see cref="float"/> to a <see cref="byte"/> first restricting the value between the
|
||||
|
/// minimum and maximum allowable ranges.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The <see cref="float"/> this method extends.</param>
|
||||
|
/// <returns>The <see cref="byte"/></returns>
|
||||
|
public static byte ToByte(this float value) |
||||
|
{ |
||||
|
return (byte)value.Clamp(0, 255); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts an <see cref="double"/> to a <see cref="byte"/> first restricting the value between the
|
||||
|
/// minimum and maximum allowable ranges.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The <see cref="double"/> this method extends.</param>
|
||||
|
/// <returns>The <see cref="byte"/></returns>
|
||||
|
public static byte ToByte(this double value) |
||||
|
{ |
||||
|
return (byte)value.Clamp(0, 255); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,88 @@ |
|||||
|
// <copyright file="EnumerableExtensions.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageProcessorCore |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Encapsulates a series of time saving extension methods to the <see cref="T:System.Collections.IEnumerable"/> interface.
|
||||
|
/// </summary>
|
||||
|
public static class EnumerableExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Generates a sequence of integral numbers within a specified range.
|
||||
|
/// </summary>
|
||||
|
/// <param name="fromInclusive">
|
||||
|
/// The start index, inclusive.
|
||||
|
/// </param>
|
||||
|
/// <param name="toExclusive">
|
||||
|
/// The end index, exclusive.
|
||||
|
/// </param>
|
||||
|
/// <param name="step">
|
||||
|
/// The incremental step.
|
||||
|
/// </param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="IEnumerable{Int32}"/> that contains a range of sequential integral numbers.
|
||||
|
/// </returns>
|
||||
|
public static IEnumerable<int> SteppedRange(int fromInclusive, int toExclusive, int step) |
||||
|
{ |
||||
|
// Borrowed from Enumerable.Range
|
||||
|
long num = (fromInclusive + toExclusive) - 1L; |
||||
|
if ((toExclusive < 0) || (num > 0x7fffffffL)) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException(nameof(toExclusive)); |
||||
|
} |
||||
|
|
||||
|
return RangeIterator(fromInclusive, i => i < toExclusive, step); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Generates a sequence of integral numbers within a specified range.
|
||||
|
/// </summary>
|
||||
|
/// <param name="fromInclusive">
|
||||
|
/// The start index, inclusive.
|
||||
|
/// </param>
|
||||
|
/// <param name="toDelegate">
|
||||
|
/// A method that has one parameter and returns a <see cref="bool"/> calculating the end index
|
||||
|
/// </param>
|
||||
|
/// <param name="step">
|
||||
|
/// The incremental step.
|
||||
|
/// </param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="IEnumerable{Int32}"/> that contains a range of sequential integral numbers.
|
||||
|
/// </returns>
|
||||
|
public static IEnumerable<int> SteppedRange(int fromInclusive, Func<int, bool> toDelegate, int step) |
||||
|
{ |
||||
|
return RangeIterator(fromInclusive, toDelegate, step); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Generates a sequence of integral numbers within a specified range.
|
||||
|
/// </summary>
|
||||
|
/// <param name="fromInclusive">
|
||||
|
/// The start index, inclusive.
|
||||
|
/// </param>
|
||||
|
/// <param name="toDelegate">
|
||||
|
/// A method that has one parameter and returns a <see cref="bool"/> calculating the end index
|
||||
|
/// </param>
|
||||
|
/// <param name="step">
|
||||
|
/// The incremental step.
|
||||
|
/// </param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="IEnumerable{Int32}"/> that contains a range of sequential integral numbers.
|
||||
|
/// </returns>
|
||||
|
private static IEnumerable<int> RangeIterator(int fromInclusive, Func<int, bool> toDelegate, int step) |
||||
|
{ |
||||
|
int i = fromInclusive; |
||||
|
while (toDelegate(i)) |
||||
|
{ |
||||
|
yield return i; |
||||
|
i += step; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,192 @@ |
|||||
|
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
// <copyright file="Guard.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
// <summary>
|
||||
|
// Provides methods to protect against invalid parameters.
|
||||
|
// </summary>
|
||||
|
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
[assembly: InternalsVisibleTo("ImageProcessorCore.Tests")] |
||||
|
namespace ImageProcessorCore |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Diagnostics; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Provides methods to protect against invalid parameters.
|
||||
|
/// </summary>
|
||||
|
[DebuggerStepThrough] |
||||
|
internal static class Guard |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Verifies, that the method parameter with specified object value is not null
|
||||
|
/// and throws an exception if it is found to be so.
|
||||
|
/// </summary>
|
||||
|
/// <param name="target">
|
||||
|
/// The target object, which cannot be null.
|
||||
|
/// </param>
|
||||
|
/// <param name="parameterName">
|
||||
|
/// The name of the parameter that is to be checked.
|
||||
|
/// </param>
|
||||
|
/// <param name="message">
|
||||
|
/// The error message, if any to add to the exception.
|
||||
|
/// </param>
|
||||
|
/// <exception cref="ArgumentNullException">
|
||||
|
/// <paramref name="target"/> is null
|
||||
|
/// </exception>
|
||||
|
public static void NotNull(object target, string parameterName, string message = "") |
||||
|
{ |
||||
|
if (target == null) |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(message)) |
||||
|
{ |
||||
|
throw new ArgumentNullException(parameterName, message); |
||||
|
} |
||||
|
|
||||
|
throw new ArgumentNullException(parameterName); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies, that the string method parameter with specified object value and message
|
||||
|
/// is not null, not empty and does not contain only blanks and throws an exception
|
||||
|
/// if the object is null.
|
||||
|
/// </summary>
|
||||
|
/// <param name="target">The target string, which should be checked against being null or empty.</param>
|
||||
|
/// <param name="parameterName">Name of the parameter.</param>
|
||||
|
/// <exception cref="ArgumentNullException">
|
||||
|
/// <paramref name="target"/> is null.
|
||||
|
/// </exception>
|
||||
|
/// <exception cref="ArgumentException">
|
||||
|
/// <paramref name="target"/> is
|
||||
|
/// empty or contains only blanks.
|
||||
|
/// </exception>
|
||||
|
public static void NotNullOrEmpty(string target, string parameterName) |
||||
|
{ |
||||
|
if (target == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(parameterName); |
||||
|
} |
||||
|
|
||||
|
if (string.IsNullOrWhiteSpace(target)) |
||||
|
{ |
||||
|
throw new ArgumentException("Value cannot be null or empty and cannot contain only blanks.", parameterName); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies that the specified value is less than a maximum value
|
||||
|
/// and throws an exception if it is not.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The target value, which should be validated.</param>
|
||||
|
/// <param name="max">The maximum value.</param>
|
||||
|
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
||||
|
/// <typeparam name="TValue">The type of the value.</typeparam>
|
||||
|
/// <exception cref="ArgumentException">
|
||||
|
/// <paramref name="value"/> is greater than the maximum value.
|
||||
|
/// </exception>
|
||||
|
public static void MustBeLessThan<TValue>(TValue value, TValue max, string parameterName) |
||||
|
where TValue : IComparable<TValue> |
||||
|
{ |
||||
|
if (value.CompareTo(max) >= 0) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException( |
||||
|
parameterName, |
||||
|
$"Value must be less than {max}."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies that the specified value is less than or equal to a maximum value
|
||||
|
/// and throws an exception if it is not.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The target value, which should be validated.</param>
|
||||
|
/// <param name="max">The maximum value.</param>
|
||||
|
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
||||
|
/// <typeparam name="TValue">The type of the value.</typeparam>
|
||||
|
/// <exception cref="ArgumentException">
|
||||
|
/// <paramref name="value"/> is greater than the maximum value.
|
||||
|
/// </exception>
|
||||
|
public static void MustBeLessThanOrEqualTo<TValue>(TValue value, TValue max, string parameterName) |
||||
|
where TValue : IComparable<TValue> |
||||
|
{ |
||||
|
if (value.CompareTo(max) > 0) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException( |
||||
|
parameterName, |
||||
|
$"Value must be less than or equal to {max}."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies that the specified value is greater than a minimum value
|
||||
|
/// and throws an exception if it is not.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The target value, which should be validated.</param>
|
||||
|
/// <param name="min">The minimum value.</param>
|
||||
|
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
||||
|
/// <typeparam name="TValue">The type of the value.</typeparam>
|
||||
|
/// <exception cref="ArgumentException">
|
||||
|
/// <paramref name="value"/> is less than the minimum value.
|
||||
|
/// </exception>
|
||||
|
public static void MustBeGreaterThan<TValue>(TValue value, TValue min, string parameterName) |
||||
|
where TValue : IComparable<TValue> |
||||
|
{ |
||||
|
if (value.CompareTo(min) <= 0) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException( |
||||
|
parameterName, |
||||
|
$"Value must be greater than {min}."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies that the specified value is greater than or equal to a minimum value
|
||||
|
/// and throws an exception if it is not.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The target value, which should be validated.</param>
|
||||
|
/// <param name="min">The minimum value.</param>
|
||||
|
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
||||
|
/// <typeparam name="TValue">The type of the value.</typeparam>
|
||||
|
/// <exception cref="ArgumentException">
|
||||
|
/// <paramref name="value"/> is less than the minimum value.
|
||||
|
/// </exception>
|
||||
|
public static void MustBeGreaterThanOrEqualTo<TValue>(TValue value, TValue min, string parameterName) |
||||
|
where TValue : IComparable<TValue> |
||||
|
{ |
||||
|
if (value.CompareTo(min) < 0) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException( |
||||
|
parameterName, |
||||
|
$"Value must be greater than or equal to {min}."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies that the specified value is greater than or equal to a minimum value and less than
|
||||
|
/// or equal to a maximum value and throws an exception if it is not.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The target value, which should be validated.</param>
|
||||
|
/// <param name="min">The minimum value.</param>
|
||||
|
/// <param name="max">The maximum value.</param>
|
||||
|
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
||||
|
/// <typeparam name="TValue">The type of the value.</typeparam>
|
||||
|
/// <exception cref="ArgumentException">
|
||||
|
/// <paramref name="value"/> is less than the minimum value of greater than the maximum value.
|
||||
|
/// </exception>
|
||||
|
public static void MustBeBetweenOrEqualTo<TValue>(TValue value, TValue min, TValue max, string parameterName) |
||||
|
where TValue : IComparable<TValue> |
||||
|
{ |
||||
|
if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException( |
||||
|
parameterName, |
||||
|
$"Value must be greater than or equal to {min} and less than or equal to {max}."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,289 @@ |
|||||
|
// <copyright file="ImageMaths.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageProcessorCore |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Numerics; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Provides common mathematical methods.
|
||||
|
/// </summary>
|
||||
|
internal static class ImageMaths |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Returns how many bits are required to store the specified number of colors.
|
||||
|
/// Performs a Log2() on the value.
|
||||
|
/// </summary>
|
||||
|
/// <param name="colors">The number of colors.</param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="int"/>
|
||||
|
/// </returns>
|
||||
|
public static int GetBitsNeededForColorDepth(int colors) |
||||
|
{ |
||||
|
return (int)Math.Ceiling(Math.Log(colors, 2)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Implementation of 1D Gaussian G(x) function
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The x provided to G(x).</param>
|
||||
|
/// <param name="sigma">The spread of the blur.</param>
|
||||
|
/// <returns>The Gaussian G(x)</returns>
|
||||
|
public static float Gaussian(float x, float sigma) |
||||
|
{ |
||||
|
const float Numerator = 1.0f; |
||||
|
float denominator = (float)(Math.Sqrt(2 * Math.PI) * sigma); |
||||
|
|
||||
|
float exponentNumerator = -x * x; |
||||
|
float exponentDenominator = (float)(2 * Math.Pow(sigma, 2)); |
||||
|
|
||||
|
float left = Numerator / denominator; |
||||
|
float right = (float)Math.Exp(exponentNumerator / exponentDenominator); |
||||
|
|
||||
|
return left * right; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns the result of a B-C filter against the given value.
|
||||
|
/// <see href="http://www.imagemagick.org/Usage/filter/#cubic_bc"/>
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The value to process.</param>
|
||||
|
/// <param name="b">The B-Spline curve variable.</param>
|
||||
|
/// <param name="c">The Cardinal curve variable.</param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="float"/>.
|
||||
|
/// </returns>
|
||||
|
public static float GetBcValue(float x, float b, float c) |
||||
|
{ |
||||
|
float temp; |
||||
|
|
||||
|
if (x < 0) |
||||
|
{ |
||||
|
x = -x; |
||||
|
} |
||||
|
|
||||
|
temp = x * x; |
||||
|
if (x < 1) |
||||
|
{ |
||||
|
x = ((12 - (9 * b) - (6 * c)) * (x * temp)) + ((-18 + (12 * b) + (6 * c)) * temp) + (6 - (2 * b)); |
||||
|
return x / 6; |
||||
|
} |
||||
|
|
||||
|
if (x < 2) |
||||
|
{ |
||||
|
x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c)); |
||||
|
return x / 6; |
||||
|
} |
||||
|
|
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the result of a sine cardinal function for the given value.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The value to calculate the result for.</param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="float"/>.
|
||||
|
/// </returns>
|
||||
|
public static float SinC(float x) |
||||
|
{ |
||||
|
const float Epsilon = .00001f; |
||||
|
|
||||
|
if (Math.Abs(x) > Epsilon) |
||||
|
{ |
||||
|
x *= (float)Math.PI; |
||||
|
return Clean((float)Math.Sin(x) / x); |
||||
|
} |
||||
|
|
||||
|
return 1.0f; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns the given degrees converted to radians.
|
||||
|
/// </summary>
|
||||
|
/// <param name="degrees">The angle in degrees.</param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="float"/> representing the degree as radians.
|
||||
|
/// </returns>
|
||||
|
public static float DegreesToRadians(float degrees) |
||||
|
{ |
||||
|
return degrees * (float)(Math.PI / 180); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the bounding <see cref="Rectangle"/> from the given points.
|
||||
|
/// </summary>
|
||||
|
/// <param name="topLeft">
|
||||
|
/// The <see cref="Point"/> designating the top left position.
|
||||
|
/// </param>
|
||||
|
/// <param name="bottomRight">
|
||||
|
/// The <see cref="Point"/> designating the bottom right position.
|
||||
|
/// </param>
|
||||
|
/// <returns>
|
||||
|
/// The bounding <see cref="Rectangle"/>.
|
||||
|
/// </returns>
|
||||
|
public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) |
||||
|
{ |
||||
|
return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the bounding <see cref="Rectangle"/> from the given matrix.
|
||||
|
/// </summary>
|
||||
|
/// <param name="rectangle">The source rectangle.</param>
|
||||
|
/// <param name="matrix">The transformation matrix.</param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="Rectangle"/>.
|
||||
|
/// </returns>
|
||||
|
public static Rectangle GetBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix) |
||||
|
{ |
||||
|
Vector2 leftTop = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix); |
||||
|
Vector2 rightTop = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix); |
||||
|
Vector2 leftBottom = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix); |
||||
|
Vector2 rightBottom = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); |
||||
|
|
||||
|
Vector2[] allCorners = { leftTop, rightTop, leftBottom, rightBottom }; |
||||
|
float extentX = allCorners.Select(v => v.X).Max() - allCorners.Select(v => v.X).Min(); |
||||
|
float extentY = allCorners.Select(v => v.Y).Max() - allCorners.Select(v => v.Y).Min(); |
||||
|
return new Rectangle(0, 0, (int)extentX, (int)extentY); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Finds the bounding rectangle based on the first instance of any color component other
|
||||
|
/// than the given one.
|
||||
|
/// </summary>
|
||||
|
/// <param name="bitmap">The <see cref="Image"/> to search within.</param>
|
||||
|
/// <param name="componentValue">The color component value to remove.</param>
|
||||
|
/// <param name="channel">The <see cref="RgbaComponent"/> channel to test against.</param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="Rectangle"/>.
|
||||
|
/// </returns>
|
||||
|
public static Rectangle GetFilteredBoundingRectangle(ImageBase bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) |
||||
|
{ |
||||
|
const float Epsilon = .00001f; |
||||
|
int width = bitmap.Width; |
||||
|
int height = bitmap.Height; |
||||
|
Point topLeft = new Point(); |
||||
|
Point bottomRight = new Point(); |
||||
|
|
||||
|
Func<PixelAccessor, int, int, float, bool> delegateFunc; |
||||
|
|
||||
|
// Determine which channel to check against
|
||||
|
switch (channel) |
||||
|
{ |
||||
|
case RgbaComponent.R: |
||||
|
delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].R - b) > Epsilon; |
||||
|
break; |
||||
|
|
||||
|
case RgbaComponent.G: |
||||
|
delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].G - b) > Epsilon; |
||||
|
break; |
||||
|
|
||||
|
case RgbaComponent.A: |
||||
|
delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].A - b) > Epsilon; |
||||
|
break; |
||||
|
|
||||
|
default: |
||||
|
delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].B - b) > Epsilon; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
Func<PixelAccessor, int> getMinY = pixels => |
||||
|
{ |
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
if (delegateFunc(pixels, x, y, componentValue)) |
||||
|
{ |
||||
|
return y; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return 0; |
||||
|
}; |
||||
|
|
||||
|
Func<PixelAccessor, int> getMaxY = pixels => |
||||
|
{ |
||||
|
for (int y = height - 1; y > -1; y--) |
||||
|
{ |
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
if (delegateFunc(pixels, x, y, componentValue)) |
||||
|
{ |
||||
|
return y; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return height; |
||||
|
}; |
||||
|
|
||||
|
Func<PixelAccessor, int> getMinX = pixels => |
||||
|
{ |
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
if (delegateFunc(pixels, x, y, componentValue)) |
||||
|
{ |
||||
|
return x; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return 0; |
||||
|
}; |
||||
|
|
||||
|
Func<PixelAccessor, int> getMaxX = pixels => |
||||
|
{ |
||||
|
for (int x = width - 1; x > -1; x--) |
||||
|
{ |
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
if (delegateFunc(pixels, x, y, componentValue)) |
||||
|
{ |
||||
|
return x; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return height; |
||||
|
}; |
||||
|
|
||||
|
using (PixelAccessor bitmapPixels = bitmap.Lock()) |
||||
|
{ |
||||
|
topLeft.Y = getMinY(bitmapPixels); |
||||
|
topLeft.X = getMinX(bitmapPixels); |
||||
|
bottomRight.Y = (getMaxY(bitmapPixels) + 1).Clamp(0, height); |
||||
|
bottomRight.X = (getMaxX(bitmapPixels) + 1).Clamp(0, width); |
||||
|
} |
||||
|
|
||||
|
return GetBoundingRectangle(topLeft, bottomRight); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Ensures that any passed double is correctly rounded to zero
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The value to clean.</param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="float"/>
|
||||
|
/// </returns>.
|
||||
|
private static float Clean(float x) |
||||
|
{ |
||||
|
const float Epsilon = .00001f; |
||||
|
|
||||
|
if (Math.Abs(x) < Epsilon) |
||||
|
{ |
||||
|
return 0f; |
||||
|
} |
||||
|
|
||||
|
return x; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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,63 @@ |
|||||
|
// <copyright file="BmpCompression.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>
|
||||
|
/// 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,82 @@ |
|||||
|
// <copyright file="BmpDecoder.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; |
||||
|
|
||||
|
/// <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 => 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,445 @@ |
|||||
|
// <copyright file="BmpDecoderCore.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 System.Threading.Tasks; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Performs the bmp decoding operation.
|
||||
|
/// </summary>
|
||||
|
internal sealed 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; |
||||
|
|
||||
|
/// <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; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Decodes the image from the specified this._stream and sets
|
||||
|
/// the data to image.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPackedVector">The type of pixels contained within the image.</typeparam>
|
||||
|
/// <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<TPackedVector>(Image<TPackedVector> image, Stream stream) |
||||
|
where TPackedVector : IPackedVector, new() |
||||
|
{ |
||||
|
this.currentStream = stream; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
this.ReadFileHeader(); |
||||
|
this.ReadInfoHeader(); |
||||
|
|
||||
|
// see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517
|
||||
|
// If the height is negative, then this is a Windows bitmap whose origin
|
||||
|
// is the upper-left corner and not the lower-left.The inverted flag
|
||||
|
// indicates a lower-left origin.Our code will be outputting an
|
||||
|
// upper-left origin pixel array.
|
||||
|
bool inverted = false; |
||||
|
if (this.infoHeader.Height < 0) |
||||
|
{ |
||||
|
inverted = true; |
||||
|
this.infoHeader.Height = -this.infoHeader.Height; |
||||
|
} |
||||
|
|
||||
|
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) |
||||
|
{ |
||||
|
// 255 * 4
|
||||
|
if (colorMapSize > 1020) |
||||
|
{ |
||||
|
throw new ImageFormatException($"Invalid bmp colormap size '{colorMapSize}'"); |
||||
|
} |
||||
|
|
||||
|
palette = new byte[colorMapSize]; |
||||
|
|
||||
|
this.currentStream.Read(palette, 0, colorMapSize); |
||||
|
} |
||||
|
|
||||
|
if (this.infoHeader.Width > image.MaxWidth || this.infoHeader.Height > image.MaxHeight) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException( |
||||
|
$"The input bitmap '{this.infoHeader.Width}x{this.infoHeader.Height}' is " |
||||
|
+ $"bigger then the max allowed size '{image.MaxWidth}x{image.MaxHeight}'"); |
||||
|
} |
||||
|
|
||||
|
TPackedVector[] imageData = new TPackedVector[this.infoHeader.Width * this.infoHeader.Height]; |
||||
|
|
||||
|
switch (this.infoHeader.Compression) |
||||
|
{ |
||||
|
case BmpCompression.RGB: |
||||
|
if (this.infoHeader.HeaderSize != 40) |
||||
|
{ |
||||
|
throw new ImageFormatException( |
||||
|
$"Header Size value '{this.infoHeader.HeaderSize}' is not valid."); |
||||
|
} |
||||
|
|
||||
|
if (this.infoHeader.BitsPerPixel == 32) |
||||
|
{ |
||||
|
this.ReadRgb32(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted); |
||||
|
} |
||||
|
else if (this.infoHeader.BitsPerPixel == 24) |
||||
|
{ |
||||
|
this.ReadRgb24(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted); |
||||
|
} |
||||
|
else if (this.infoHeader.BitsPerPixel == 16) |
||||
|
{ |
||||
|
this.ReadRgb16(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted); |
||||
|
} |
||||
|
else if (this.infoHeader.BitsPerPixel <= 8) |
||||
|
{ |
||||
|
this.ReadRgbPalette( |
||||
|
imageData, |
||||
|
palette, |
||||
|
this.infoHeader.Width, |
||||
|
this.infoHeader.Height, |
||||
|
this.infoHeader.BitsPerPixel, |
||||
|
inverted); |
||||
|
} |
||||
|
|
||||
|
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, bool inverted) |
||||
|
{ |
||||
|
int row; |
||||
|
|
||||
|
if (!inverted) |
||||
|
{ |
||||
|
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:float[]"/> 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(float[] imageData, byte[] colors, int width, int height, int bits, bool inverted) |
||||
|
{ |
||||
|
// 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; |
||||
|
} |
||||
|
|
||||
|
Parallel.For( |
||||
|
0, |
||||
|
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, inverted); |
||||
|
|
||||
|
int colOffset = x * ppb; |
||||
|
|
||||
|
for (int shift = 0; shift < ppb && (colOffset + shift) < width; shift++) |
||||
|
{ |
||||
|
int colorIndex = ((data[offset] >> (8 - bits - (shift * bits))) & mask) * 4; |
||||
|
int arrayOffset = ((row * width) + (colOffset + shift)) * 4; |
||||
|
|
||||
|
// We divide by 255 as we will store the colors in our floating point format.
|
||||
|
// Stored in r-> g-> b-> a order.
|
||||
|
imageData[arrayOffset] = colors[colorIndex + 2] / 255f; // r
|
||||
|
imageData[arrayOffset + 1] = colors[colorIndex + 1] / 255f; // g
|
||||
|
imageData[arrayOffset + 2] = colors[colorIndex] / 255f; // b
|
||||
|
imageData[arrayOffset + 3] = 1; // a
|
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reads the 16 bit color palette from the stream
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPackedVector">The type of pixels contained within the image.</typeparam>
|
||||
|
/// <param name="imageData">The <see cref="TPackedVector[]"/> 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<TPackedVector>(TPackedVector[] imageData, int width, int height, bool inverted) |
||||
|
where TPackedVector : IPackedVector, new() |
||||
|
{ |
||||
|
// We divide here as we will store the colors in our floating point format.
|
||||
|
const int ScaleR = 8; // 256/32
|
||||
|
const int ScaleG = 4; // 256/64
|
||||
|
|
||||
|
int alignment; |
||||
|
byte[] data = this.GetImageArray(width, height, 2, out alignment); |
||||
|
|
||||
|
Parallel.For( |
||||
|
0, |
||||
|
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, inverted); |
||||
|
|
||||
|
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); |
||||
|
|
||||
|
// Stored in b-> g-> r-> a order.
|
||||
|
TPackedVector packed = new TPackedVector(); |
||||
|
packed.PackBytes(b, g, r, 255); |
||||
|
imageData[arrayOffset] = packed; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reads the 24 bit color palette from the stream
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPackedVector">The type of pixels contained within the image.</typeparam>
|
||||
|
/// <param name="imageData">The <see cref="TPackedVector[]"/> 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<TPackedVector>(TPackedVector[] imageData, int width, int height, bool inverted) |
||||
|
where TPackedVector : IPackedVector, new() |
||||
|
{ |
||||
|
int alignment; |
||||
|
byte[] data = this.GetImageArray(width, height, 3, out alignment); |
||||
|
|
||||
|
Parallel.For( |
||||
|
0, |
||||
|
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, inverted); |
||||
|
|
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
int offset = rowOffset + (x * 3); |
||||
|
int arrayOffset = ((row * width) + x); |
||||
|
|
||||
|
// We divide by 255 as we will store the colors in our floating point format.
|
||||
|
// Stored in b-> g-> r-> a order.
|
||||
|
TPackedVector packed = new TPackedVector(); |
||||
|
packed.PackBytes(data[offset], data[offset + 1], data[offset + 2], 255); |
||||
|
imageData[arrayOffset] = packed; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reads the 32 bit color palette from the stream
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPackedVector">The type of pixels contained within the image.</typeparam>
|
||||
|
/// <param name="imageData">The <see cref="TPackedVector[]"/> 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<TPackedVector>(TPackedVector[] imageData, int width, int height, bool inverted) |
||||
|
where TPackedVector : IPackedVector, new() |
||||
|
{ |
||||
|
int alignment; |
||||
|
byte[] data = this.GetImageArray(width, height, 4, out alignment); |
||||
|
|
||||
|
Parallel.For( |
||||
|
0, |
||||
|
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, inverted); |
||||
|
|
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
int offset = rowOffset + (x * 4); |
||||
|
int arrayOffset = ((row * width) + x); |
||||
|
|
||||
|
// Stored in b-> g-> r-> a order.
|
||||
|
TPackedVector packed = new TPackedVector(); |
||||
|
packed.PackBytes(data[offset], data[offset + 1], data[offset + 2], data[offset + 3]); |
||||
|
imageData[arrayOffset] = packed; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/// <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) |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
// <copyright file="BmpEncoder.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; |
||||
|
|
||||
|
/// <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>Bitmap is a lossless format so this is not used in this encoder.</remarks>
|
||||
|
public int Quality { get; set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public string MimeType => "image/bmp"; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public string Extension => "bmp"; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the number of bits per pixel.
|
||||
|
/// </summary>
|
||||
|
public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public bool IsSupportedFileExtension(string extension) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(extension, nameof(extension)); |
||||
|
|
||||
|
extension = extension.StartsWith(".") ? extension.Substring(1) : extension; |
||||
|
|
||||
|
return extension.Equals(this.Extension, StringComparison.OrdinalIgnoreCase) |
||||
|
|| extension.Equals("dip", StringComparison.OrdinalIgnoreCase); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void Encode<TPackedVector>(ImageBase<TPackedVector> image, Stream stream) |
||||
|
where TPackedVector: IPackedVector |
||||
|
{ |
||||
|
BmpEncoderCore encoder = new BmpEncoderCore(); |
||||
|
encoder.Encode(image, stream, this.BitsPerPixel); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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 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{TPackedVector}"/>.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPackedVector">The type of pixels contained within the image.</typeparam>
|
||||
|
/// <param name="image">The <see cref="ImageBase{TPackedVector}"/> 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<TPackedVector>(ImageBase<TPackedVector> image, Stream stream, BmpBitsPerPixel bitsPerPixel) |
||||
|
where TPackedVector : IPackedVector |
||||
|
{ |
||||
|
Guard.NotNull(image, nameof(image)); |
||||
|
Guard.NotNull(stream, nameof(stream)); |
||||
|
|
||||
|
this.bmpBitsPerPixel = bitsPerPixel; |
||||
|
|
||||
|
int rowWidth = image.Width; |
||||
|
|
||||
|
// TODO: Check this for varying file formats.
|
||||
|
int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4; |
||||
|
if (amount != 0) |
||||
|
{ |
||||
|
rowWidth += 4 - amount; |
||||
|
} |
||||
|
|
||||
|
// Do not use IDisposable pattern here as we want to preserve the stream.
|
||||
|
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>
|
||||
|
/// <typeparam name="TPackedVector">The type of pixels contained within the image.</typeparam>
|
||||
|
/// <param name="writer">
|
||||
|
/// The <see cref="EndianBinaryWriter"/> containing the stream to write to.
|
||||
|
/// </param>
|
||||
|
/// <param name="image">
|
||||
|
/// The <see cref="ImageBase{TPackedVector}"/> containing pixel data.
|
||||
|
/// </param>
|
||||
|
private void WriteImage<TPackedVector>(EndianBinaryWriter writer, ImageBase<TPackedVector> image) |
||||
|
where TPackedVector : IPackedVector |
||||
|
{ |
||||
|
// TODO: Add more compression formats.
|
||||
|
int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4; |
||||
|
if (amount != 0) |
||||
|
{ |
||||
|
amount = 4 - amount; |
||||
|
} |
||||
|
|
||||
|
using (IPixelAccessor pixels = image.Lock()) |
||||
|
{ |
||||
|
switch (this.bmpBitsPerPixel) |
||||
|
{ |
||||
|
case BmpBitsPerPixel.Pixel32: |
||||
|
this.Write32bit(writer, pixels, amount); |
||||
|
break; |
||||
|
|
||||
|
case BmpBitsPerPixel.Pixel24: |
||||
|
this.Write24bit(writer, pixels, 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="pixels">The <see cref="IPixelAccessor"/> containing pixel data.</param>
|
||||
|
/// <param name="amount">The amount to pad each row by.</param>
|
||||
|
private void Write32bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) |
||||
|
{ |
||||
|
for (int y = pixels.Height - 1; y >= 0; y--) |
||||
|
{ |
||||
|
for (int x = 0; x < pixels.Width; x++) |
||||
|
{ |
||||
|
// Convert back to b-> g-> r-> a order.
|
||||
|
byte[] bytes = pixels[x, y].ToBytes(); |
||||
|
writer.Write(bytes); |
||||
|
} |
||||
|
|
||||
|
// 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="pixels">The <see cref="IPixelAccessor"/> containing pixel data.</param>
|
||||
|
/// <param name="amount">The amount to pad each row by.</param>
|
||||
|
private void Write24bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) |
||||
|
{ |
||||
|
for (int y = pixels.Height - 1; y >= 0; y--) |
||||
|
{ |
||||
|
for (int x = 0; x < pixels.Width; x++) |
||||
|
{ |
||||
|
// Convert back to b-> g-> r-> a order.
|
||||
|
byte[] bytes = pixels[x, y].ToBytes(); |
||||
|
writer.Write(new[] { bytes[0], bytes[1], bytes[2] }); |
||||
|
} |
||||
|
|
||||
|
// Pad
|
||||
|
for (int i = 0; i < amount; i++) |
||||
|
{ |
||||
|
writer.Write((byte)0); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
// <copyright file="BmpFileHeader.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>
|
||||
|
/// 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,19 @@ |
|||||
|
// <copyright file="BmpFormat.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>
|
||||
|
/// Encapsulates the means to encode and decode bitmap images.
|
||||
|
/// </summary>
|
||||
|
public class BmpFormat : IImageFormat |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public IImageDecoder Decoder => new BmpDecoder(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public IImageEncoder Encoder => new BmpEncoder(); |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue