Browse Source

Getting there....

Former-commit-id: 52d18a5b7db2755316ced3502b012186b6eebf96
Former-commit-id: c03f4bd0c6cf3701e9cd9244b43425c1d5e7c32b
Former-commit-id: 794deb40b750df545be1aacff66677ea4443190b
af/merge-core
James Jackson-South 10 years ago
parent
commit
abe4412c79
  1. 7
      src/ImageProcessorCore/Bootstrapper.cs
  2. 17
      src/ImageProcessorCore/Common/Helpers/Operator.cs
  3. 5
      src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs
  4. 39
      src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs
  5. 5
      src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs
  6. 22
      src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs
  7. 5
      src/ImageProcessorCore/Formats/IImageDecoder.cs
  8. 5
      src/ImageProcessorCore/Formats/IImageEncoder.cs
  9. 7
      src/ImageProcessorCore/IImageFrame.cs
  10. 284
      src/ImageProcessorCore/Image.cs
  11. 10
      src/ImageProcessorCore/Image/IImageBase.cs
  12. 8
      src/ImageProcessorCore/Image/IImageFrame.cs
  13. 10
      src/ImageProcessorCore/Image/IImageProcessor.cs
  14. 293
      src/ImageProcessorCore/Image/Image.cs
  15. 19
      src/ImageProcessorCore/Image/ImageBase.cs
  16. 39
      src/ImageProcessorCore/Image/ImageExtensions.cs
  17. 11
      src/ImageProcessorCore/Image/ImageFrame.cs
  18. 0
      src/ImageProcessorCore/Image/ImageProperty.cs
  19. 25
      src/ImageProcessorCore/ImageProcessor.cs
  20. 227
      src/ImageProcessorCore/PackedVector/Bgra32.cs
  21. 38
      src/ImageProcessorCore/PackedVector/IPackedVector.cs
  22. 5
      src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs
  23. 5
      src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs
  24. 30
      src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs
  25. 36
      src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs
  26. 25
      src/ImageProcessorCore/Samplers/Resize.cs
  27. 6
      tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs
  28. 3
      tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs

7
src/ImageProcessorCore/Bootstrapper.cs

@ -73,13 +73,14 @@ namespace ImageProcessorCore
/// <typeparam name="T">The type of pixel data.</typeparam>
/// <param name="image">The image</param>
/// <returns>The <see cref="IPixelAccessor"/></returns>
public IPixelAccessor<T> GetPixelAccessor<T>(IImageBase image)
where T : IPackedVector, new()
public IPixelAccessor<T, TP> GetPixelAccessor<T, TP>(IImageBase image)
where T : IPackedVector<TP>, new()
where TP : struct
{
Type packed = typeof(T);
if (this.pixelAccessors.ContainsKey(packed))
{
return (IPixelAccessor<T>)this.pixelAccessors[packed].Invoke(image);
return (IPixelAccessor<T, TP>)this.pixelAccessors[packed].Invoke(image);
}
throw new NotSupportedException($"PixelAccessor cannot be loaded for {packed}:");

17
src/ImageProcessorCore/Common/Helpers/Operator.cs

@ -260,6 +260,9 @@
}
private static readonly Func<TResult, TValue, TResult> add, subtract, multiply, divide;
private static readonly Func<TResult, float, TResult> multiplyF, divideF;
/// <summary>
/// Returns a delegate to evaluate binary addition (+) for the given types; this delegate will throw
/// an InvalidOperationException if the type T does not provide this operator, or for
@ -287,6 +290,10 @@
/// Nullable&lt;TInner&gt; if TInner does not provide this operator.
/// </summary>
public static Func<TResult, TValue, TResult> Divide => divide;
public static Func<TResult, float, TResult> MultiplyF => multiplyF;
public static Func<TResult, float, TResult> DivideF => divideF;
}
/// <summary>
@ -344,6 +351,8 @@
static readonly Func<T, T, T> add, subtract, multiply, divide;
static readonly Func<T, float, T> multiplyF, divideF;
/// <summary>
/// Returns a delegate to evaluate binary addition (+) for the given type; this delegate will throw
/// an InvalidOperationException if the type T does not provide this operator, or for
@ -372,8 +381,12 @@
/// </summary>
public static Func<T, T, T> Divide => divide;
public static Func<T, float, T> MultiplyF => multiplyF;
public static Func<T, float, T> DivideF => divideF;
static readonly Func<T, T, bool> equal, notEqual, greaterThan, lessThan, greaterThanOrEqual, lessThanOrEqual;
/// <summary>
/// Returns a delegate to evaluate binary equality (==) for the given type; this delegate will throw
/// an InvalidOperationException if the type T does not provide this operator, or for
@ -422,6 +435,8 @@
subtract = ExpressionUtil.CreateExpression<T, T, T>(Expression.Subtract);
divide = ExpressionUtil.CreateExpression<T, T, T>(Expression.Divide);
multiply = ExpressionUtil.CreateExpression<T, T, T>(Expression.Multiply);
multiplyF = ExpressionUtil.CreateExpression<T, float, T>(Expression.Multiply);
divideF = ExpressionUtil.CreateExpression<T, float, T>(Expression.Multiply);
greaterThan = ExpressionUtil.CreateExpression<T, T, bool>(Expression.GreaterThan);
greaterThanOrEqual = ExpressionUtil.CreateExpression<T, T, bool>(Expression.GreaterThanOrEqual);

5
src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs

@ -74,8 +74,9 @@ namespace ImageProcessorCore.Formats
/// </summary>
/// <param name="image">The <see cref="ImageBase{T}"/> to decode to.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
public void Decode<T>(Image<T> image, Stream stream)
where T : IPackedVector, new()
public void Decode<T,TP>(Image<T,TP> image, Stream stream)
where T : IPackedVector<TP>, new()
where TP : struct
{
new BmpDecoderCore().Decode(image, stream);
}

39
src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs

@ -59,8 +59,9 @@ namespace ImageProcessorCore.Formats
/// <para>- or -</para>
/// <para><paramref name="stream"/> is null.</para>
/// </exception>
public void Decode<T>(Image<T> image, Stream stream)
where T : IPackedVector, new()
public void Decode<T, TP>(Image<T, TP> image, Stream stream)
where T : IPackedVector<TP>, new()
where TP : struct
{
this.currentStream = stream;
@ -132,25 +133,19 @@ namespace ImageProcessorCore.Formats
if (this.infoHeader.BitsPerPixel == 32)
{
this.ReadRgb32(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted);
this.ReadRgb32<T, TP>(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted);
}
else if (this.infoHeader.BitsPerPixel == 24)
{
this.ReadRgb24(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted);
this.ReadRgb24<T, TP>(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted);
}
else if (this.infoHeader.BitsPerPixel == 16)
{
this.ReadRgb16(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted);
this.ReadRgb16<T, TP>(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);
this.ReadRgbPalette<T, TP>(imageData, palette, this.infoHeader.Width, this.infoHeader.Height, this.infoHeader.BitsPerPixel, inverted);
}
break;
@ -199,8 +194,9 @@ namespace ImageProcessorCore.Formats
/// <param name="height">The height of the bitmap.</param>
/// <param name="bits">The number of bits per pixel.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRgbPalette<T>(T[] imageData, byte[] colors, int width, int height, int bits, bool inverted)
where T : IPackedVector, new()
private void ReadRgbPalette<T, TP>(T[] imageData, byte[] colors, int width, int height, int bits, bool inverted)
where T : IPackedVector<TP>, new()
where TP : struct
{
// Pixels per byte (bits per pixel)
int ppb = 8 / bits;
@ -259,8 +255,9 @@ namespace ImageProcessorCore.Formats
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRgb16<T>(T[] imageData, int width, int height, bool inverted)
where T : IPackedVector, new()
private void ReadRgb16<T, TP>(T[] imageData, int width, int height, bool inverted)
where T : IPackedVector<TP>, new()
where TP : struct
{
// We divide here as we will store the colors in our floating point format.
const int ScaleR = 8; // 256/32
@ -307,8 +304,9 @@ namespace ImageProcessorCore.Formats
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRgb24<T>(T[] imageData, int width, int height, bool inverted)
where T : IPackedVector, new()
private void ReadRgb24<T, TP>(T[] imageData, int width, int height, bool inverted)
where T : IPackedVector<TP>, new()
where TP : struct
{
int alignment;
byte[] data = this.GetImageArray(width, height, 3, out alignment);
@ -345,8 +343,9 @@ namespace ImageProcessorCore.Formats
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRgb32<T>(T[] imageData, int width, int height, bool inverted)
where T : IPackedVector, new()
private void ReadRgb32<T, TP>(T[] imageData, int width, int height, bool inverted)
where T : IPackedVector<TP>, new()
where TP : struct
{
int alignment;
byte[] data = this.GetImageArray(width, height, 4, out alignment);

5
src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs

@ -43,8 +43,9 @@ namespace ImageProcessorCore.Formats
}
/// <inheritdoc/>
public void Encode<T>(ImageBase<T> image, Stream stream)
where T : IPackedVector, new()
public void Encode<T,TP>(ImageBase<T,TP> image, Stream stream)
where T : IPackedVector<TP>, new()
where TP : struct
{
BmpEncoderCore encoder = new BmpEncoderCore();
encoder.Encode(image, stream, this.BitsPerPixel);

22
src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs

@ -28,8 +28,9 @@ namespace ImageProcessorCore.Formats
/// <param name="image">The <see cref="ImageBase{T}"/> 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<T>(ImageBase<T> image, Stream stream, BmpBitsPerPixel bitsPerPixel)
where T : IPackedVector, new()
public void Encode<T,TP>(ImageBase<T,TP> image, Stream stream, BmpBitsPerPixel bitsPerPixel)
where T : IPackedVector<TP>, new()
where TP : struct
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
@ -127,8 +128,9 @@ namespace ImageProcessorCore.Formats
/// <param name="image">
/// The <see cref="ImageBase{T}"/> containing pixel data.
/// </param>
private void WriteImage<T>(EndianBinaryWriter writer, ImageBase<T> image)
where T : IPackedVector, new()
private void WriteImage<T,TP>(EndianBinaryWriter writer, ImageBase<T,TP> image)
where T : IPackedVector<TP>, new()
where TP : struct
{
// TODO: Add more compression formats.
int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4;
@ -137,7 +139,7 @@ namespace ImageProcessorCore.Formats
amount = 4 - amount;
}
using (IPixelAccessor<T> pixels = image.Lock())
using (IPixelAccessor<T,TP> pixels = image.Lock())
{
switch (this.bmpBitsPerPixel)
{
@ -159,8 +161,9 @@ namespace ImageProcessorCore.Formats
/// <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<T>(EndianBinaryWriter writer, IPixelAccessor<T> pixels, int amount)
where T : IPackedVector, new()
private void Write32bit<T,TP>(EndianBinaryWriter writer, IPixelAccessor<T,TP> pixels, int amount)
where T : IPackedVector<TP>, new()
where TP : struct
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
@ -185,8 +188,9 @@ namespace ImageProcessorCore.Formats
/// <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<T>(EndianBinaryWriter writer, IPixelAccessor<T> pixels, int amount)
where T : IPackedVector, new()
private void Write24bit<T,TP>(EndianBinaryWriter writer, IPixelAccessor<T,TP> pixels, int amount)
where T : IPackedVector<TP>, new()
where TP : struct
{
for (int y = pixels.Height - 1; y >= 0; y--)
{

5
src/ImageProcessorCore/Formats/IImageDecoder.cs

@ -44,7 +44,8 @@ namespace ImageProcessorCore.Formats
/// <typeparam name="T">The type of pixels contained within the image.</typeparam>
/// <param name="image">The <see cref="ImageBase{T}"/> to decode to.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
void Decode<T>(Image<T> image, Stream stream)
where T : IPackedVector, new();
void Decode<T, TP>(Image<T, TP> image, Stream stream)
where T : IPackedVector<TP>, new()
where TP : struct;
}
}

5
src/ImageProcessorCore/Formats/IImageEncoder.cs

@ -48,7 +48,8 @@ namespace ImageProcessorCore.Formats
/// <typeparam name="T">The type of pixels contained within the image.</typeparam>
/// <param name="image">The <see cref="ImageBase{T}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
void Encode<T>(ImageBase<T> image, Stream stream)
where T : IPackedVector, new();
void Encode<T, TP>(ImageBase<T, TP> image, Stream stream)
where T : IPackedVector<TP>,
new() where TP : struct;
}
}

7
src/ImageProcessorCore/IImageFrame.cs

@ -1,7 +0,0 @@
namespace ImageProcessorCore
{
public interface IImageFrame<T> : IImageBase<T>
where T : IPackedVector, new()
{
}
}

284
src/ImageProcessorCore/Image.cs

@ -1,292 +1,22 @@
// <copyright file="Image.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
using System.IO;
namespace ImageProcessorCore
{
using System.IO;
using System.Text;
using System;
using System.Collections.Generic;
using System.Linq;
using Formats;
/// <summary>
/// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes.
/// </summary>
/// <typeparam name="T">
/// The packed vector containing pixel information.
/// </typeparam>
public class Image<T> : ImageBase<T>
where T : IPackedVector, new()
public class Image : Image<Bgra32, uint>
{
/// <summary>
/// The default horizontal resolution value (dots per inch) in x direction.
/// <remarks>The default value is 96 dots per inch.</remarks>
/// </summary>
public const double DefaultHorizontalResolution = 96;
/// <summary>
/// The default vertical resolution value (dots per inch) in y direction.
/// <remarks>The default value is 96 dots per inch.</remarks>
/// </summary>
public const double DefaultVerticalResolution = 96;
/// <summary>
/// Initializes a new instance of the <see cref="Image{T}"/> class.
/// </summary>
public Image()
{
this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat));
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{T}"/> class
/// with the height and the width of the image.
/// </summary>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
// TODO Constructors.
public Image(int width, int height)
: base(width, height)
: base(width, height)
{
// TODO: Change to PNG
this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat));
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{T}"/> class.
/// </summary>
/// <param name="stream">
/// The stream containing image information.
/// </param>
/// <exception cref="ArgumentNullException">Thrown if the <paramref name="stream"/> is null.</exception>
public Image(Stream stream)
: base(stream)
{
Guard.NotNull(stream, nameof(stream));
this.Load(stream);
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{T}"/> class
/// by making a copy from another image.
/// </summary>
/// <param name="other">The other image, where the clone should be made from.</param>
/// <exception cref="ArgumentNullException"><paramref name="other"/> is null.</exception>
public Image(Image<T> other)
{
foreach (ImageFrame<T> frame in other.Frames)
{
if (frame != null)
{
this.Frames.Add(new ImageFrame<T>(frame));
}
}
this.RepeatCount = other.RepeatCount;
this.HorizontalResolution = other.HorizontalResolution;
this.VerticalResolution = other.VerticalResolution;
this.CurrentImageFormat = other.CurrentImageFormat;
}
/// <summary>
/// Gets a list of supported image formats.
/// </summary>
public IReadOnlyCollection<IImageFormat> Formats { get; } = Bootstrapper.Instance.ImageFormats;
/// <summary>
/// Gets or sets the resolution of the image in x- direction. It is defined as
/// number of dots per inch and should be an positive value.
/// </summary>
/// <value>The density of the image in x- direction.</value>
public double HorizontalResolution { get; set; } = DefaultHorizontalResolution;
/// <summary>
/// Gets or sets the resolution of the image in y- direction. It is defined as
/// number of dots per inch and should be an positive value.
/// </summary>
/// <value>The density of the image in y- direction.</value>
public double VerticalResolution { get; set; } = DefaultVerticalResolution;
/// <summary>
/// Gets the width of the image in inches. It is calculated as the width of the image
/// in pixels multiplied with the density. When the density is equals or less than zero
/// the default value is used.
/// </summary>
/// <value>The width of the image in inches.</value>
public double InchWidth
{
get
{
double resolution = this.HorizontalResolution;
if (resolution <= 0)
{
resolution = DefaultHorizontalResolution;
}
return this.Width / resolution;
}
}
/// <summary>
/// Gets the height of the image in inches. It is calculated as the height of the image
/// in pixels multiplied with the density. When the density is equals or less than zero
/// the default value is used.
/// </summary>
/// <value>The height of the image in inches.</value>
public double InchHeight
{
get
{
double resolution = this.VerticalResolution;
if (resolution <= 0)
{
resolution = DefaultVerticalResolution;
}
return this.Height / resolution;
}
}
/// <summary>
/// Gets a value indicating whether this image is animated.
/// </summary>
/// <value>
/// <c>True</c> if this image is animated; otherwise, <c>false</c>.
/// </value>
public bool IsAnimated => this.Frames.Count > 0;
/// <summary>
/// Gets or sets the number of times any animation is repeated.
/// <remarks>0 means to repeat indefinitely.</remarks>
/// </summary>
public ushort RepeatCount { get; set; }
/// <summary>
/// Gets the other frames for the animation.
/// </summary>
/// <value>The list of frame images.</value>
public IList<ImageFrame<T>> Frames { get; } = new List<ImageFrame<T>>();
/// <summary>
/// Gets the list of properties for storing meta information about this image.
/// </summary>
/// <value>A list of image properties.</value>
public IList<ImageProperty> Properties { get; } = new List<ImageProperty>();
/// <summary>
/// Gets the currently loaded image format.
/// </summary>
public IImageFormat CurrentImageFormat { get; internal set; }
/// <inheritdoc/>
public override IPixelAccessor<T> Lock()
{
return Bootstrapper.Instance.GetPixelAccessor<T>(this);
}
/// <summary>
/// Saves the image to the given stream using the currently loaded image format.
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
public void Save(Stream stream)
{
Guard.NotNull(stream, nameof(stream));
this.CurrentImageFormat.Encoder.Encode(this, stream);
}
/// <summary>
/// Saves the image to the given stream using the given image format.
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="format">The format to save the image as.</param>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
public void Save(Stream stream, IImageFormat format)
public Image(Image other)
: base(other)
{
Guard.NotNull(stream, nameof(stream));
format.Encoder.Encode(this, stream);
}
/// <summary>
/// Saves the image to the given stream using the given image encoder.
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
public void Save(Stream stream, IImageEncoder encoder)
{
Guard.NotNull(stream, nameof(stream));
encoder.Encode(this, stream);
}
/// <summary>
/// Returns a Base64 encoded string from the given image.
/// </summary>
/// <example>data:image/gif;base64,R0lGODlhAQABAIABAEdJRgAAACwAAAAAAQABAAACAkQBAA==</example>
/// <returns>The <see cref="string"/></returns>
public override string ToString()
{
using (MemoryStream stream = new MemoryStream())
{
this.Save(stream);
stream.Flush();
return $"data:{this.CurrentImageFormat.Encoder.MimeType};base64,{Convert.ToBase64String(stream.ToArray())}";
}
}
/// <summary>
/// Loads the image from the given stream.
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
private void Load(Stream stream)
{
if (!this.Formats.Any()) { return; }
if (!stream.CanRead)
{
throw new NotSupportedException("Cannot read from the stream.");
}
if (!stream.CanSeek)
{
throw new NotSupportedException("The stream does not support seeking.");
}
int maxHeaderSize = this.Formats.Max(x => x.Decoder.HeaderSize);
if (maxHeaderSize > 0)
{
byte[] header = new byte[maxHeaderSize];
stream.Position = 0;
stream.Read(header, 0, maxHeaderSize);
stream.Position = 0;
IImageFormat format = this.Formats.FirstOrDefault(x => x.Decoder.IsSupportedFileFormat(header));
if (format != null)
{
format.Decoder.Decode(this, stream);
this.CurrentImageFormat = format;
return;
}
}
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine("Image cannot be loaded. Available formats:");
foreach (IImageFormat format in this.Formats)
{
stringBuilder.AppendLine("-" + format);
}
throw new NotSupportedException(stringBuilder.ToString());
}
}
}

10
src/ImageProcessorCore/IImageBase.cs → src/ImageProcessorCore/Image/IImageBase.cs

@ -2,12 +2,16 @@
namespace ImageProcessorCore
{
public interface IImageBase<T> : IImageBase
where T : IPackedVector, new()
public interface IImageBase<T, TP> : IImageBase
where T : IPackedVector<TP>, new()
where TP : struct
{
T[] Pixels { get; }
void ClonePixels(int width, int height, T[] pixels);
IPixelAccessor<T> Lock();
IPixelAccessor<T, TP> Lock();
void SetPixels(int width, int height, T[] pixels);
}

8
src/ImageProcessorCore/Image/IImageFrame.cs

@ -0,0 +1,8 @@
namespace ImageProcessorCore
{
public interface IImageFrame<T,TP> : IImageBase<T,TP>
where T : IPackedVector<TP>, new()
where TP : struct
{
}
}

10
src/ImageProcessorCore/IImageProcessor.cs → src/ImageProcessorCore/Image/IImageProcessor.cs

@ -45,8 +45,9 @@ namespace ImageProcessorCore.Processors
/// <exception cref="System.ArgumentException">
/// <paramref name="sourceRectangle"/> doesnt fit the dimension of the image.
/// </exception>
void Apply<T>(ImageBase<T> target, ImageBase<T> source, Rectangle sourceRectangle)
where T : IPackedVector, new();
void Apply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle sourceRectangle)
where T : IPackedVector<TP>,
new() where TP : struct;
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageBase{T}"/> at the specified
@ -68,7 +69,8 @@ namespace ImageProcessorCore.Processors
/// The method keeps the source image unchanged and returns the
/// the result of image process as new image.
/// </remarks>
void Apply<T>(ImageBase<T> target, ImageBase<T> source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle)
where T : IPackedVector, new();
void Apply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle)
where T : IPackedVector<TP>, new()
where TP : struct;
}
}

293
src/ImageProcessorCore/Image/Image.cs

@ -0,0 +1,293 @@
// <copyright file="Image.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.IO;
using System.Text;
using System;
using System.Collections.Generic;
using System.Linq;
using Formats;
/// <summary>
/// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes.
/// </summary>
/// <typeparam name="T">
/// The packed vector containing pixel information.
/// </typeparam>
public class Image<T, TP> : ImageBase<T, TP>
where T : IPackedVector<TP>, new()
where TP : struct
{
/// <summary>
/// The default horizontal resolution value (dots per inch) in x direction.
/// <remarks>The default value is 96 dots per inch.</remarks>
/// </summary>
public const double DefaultHorizontalResolution = 96;
/// <summary>
/// The default vertical resolution value (dots per inch) in y direction.
/// <remarks>The default value is 96 dots per inch.</remarks>
/// </summary>
public const double DefaultVerticalResolution = 96;
/// <summary>
/// Initializes a new instance of the <see cref="Image{T}"/> class.
/// </summary>
public Image()
{
this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat));
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{T}"/> class
/// with the height and the width of the image.
/// </summary>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
public Image(int width, int height)
: base(width, height)
{
// TODO: Change to PNG
this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat));
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{T}"/> class.
/// </summary>
/// <param name="stream">
/// The stream containing image information.
/// </param>
/// <exception cref="ArgumentNullException">Thrown if the <paramref name="stream"/> is null.</exception>
public Image(Stream stream)
{
Guard.NotNull(stream, nameof(stream));
this.Load(stream);
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{T}"/> class
/// by making a copy from another image.
/// </summary>
/// <param name="other">The other image, where the clone should be made from.</param>
/// <exception cref="ArgumentNullException"><paramref name="other"/> is null.</exception>
public Image(Image<T, TP> other)
{
foreach (ImageFrame<T, TP> frame in other.Frames)
{
if (frame != null)
{
this.Frames.Add(new ImageFrame<T, TP>(frame));
}
}
this.RepeatCount = other.RepeatCount;
this.HorizontalResolution = other.HorizontalResolution;
this.VerticalResolution = other.VerticalResolution;
this.CurrentImageFormat = other.CurrentImageFormat;
}
/// <summary>
/// Gets a list of supported image formats.
/// </summary>
public IReadOnlyCollection<IImageFormat> Formats { get; } = Bootstrapper.Instance.ImageFormats;
/// <summary>
/// Gets or sets the resolution of the image in x- direction. It is defined as
/// number of dots per inch and should be an positive value.
/// </summary>
/// <value>The density of the image in x- direction.</value>
public double HorizontalResolution { get; set; } = DefaultHorizontalResolution;
/// <summary>
/// Gets or sets the resolution of the image in y- direction. It is defined as
/// number of dots per inch and should be an positive value.
/// </summary>
/// <value>The density of the image in y- direction.</value>
public double VerticalResolution { get; set; } = DefaultVerticalResolution;
/// <summary>
/// Gets the width of the image in inches. It is calculated as the width of the image
/// in pixels multiplied with the density. When the density is equals or less than zero
/// the default value is used.
/// </summary>
/// <value>The width of the image in inches.</value>
public double InchWidth
{
get
{
double resolution = this.HorizontalResolution;
if (resolution <= 0)
{
resolution = DefaultHorizontalResolution;
}
return this.Width / resolution;
}
}
/// <summary>
/// Gets the height of the image in inches. It is calculated as the height of the image
/// in pixels multiplied with the density. When the density is equals or less than zero
/// the default value is used.
/// </summary>
/// <value>The height of the image in inches.</value>
public double InchHeight
{
get
{
double resolution = this.VerticalResolution;
if (resolution <= 0)
{
resolution = DefaultVerticalResolution;
}
return this.Height / resolution;
}
}
/// <summary>
/// Gets a value indicating whether this image is animated.
/// </summary>
/// <value>
/// <c>True</c> if this image is animated; otherwise, <c>false</c>.
/// </value>
public bool IsAnimated => this.Frames.Count > 0;
/// <summary>
/// Gets or sets the number of times any animation is repeated.
/// <remarks>0 means to repeat indefinitely.</remarks>
/// </summary>
public ushort RepeatCount { get; set; }
/// <summary>
/// Gets the other frames for the animation.
/// </summary>
/// <value>The list of frame images.</value>
public IList<ImageFrame<T, TP>> Frames { get; } = new List<ImageFrame<T, TP>>();
/// <summary>
/// Gets the list of properties for storing meta information about this image.
/// </summary>
/// <value>A list of image properties.</value>
public IList<ImageProperty> Properties { get; } = new List<ImageProperty>();
/// <summary>
/// Gets the currently loaded image format.
/// </summary>
public IImageFormat CurrentImageFormat { get; internal set; }
/// <inheritdoc/>
public override IPixelAccessor<T, TP> Lock()
{
return Bootstrapper.Instance.GetPixelAccessor<T, TP>(this);
}
/// <summary>
/// Saves the image to the given stream using the currently loaded image format.
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
public void Save(Stream stream)
{
Guard.NotNull(stream, nameof(stream));
this.CurrentImageFormat.Encoder.Encode(this, stream);
}
/// <summary>
/// Saves the image to the given stream using the given image format.
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="format">The format to save the image as.</param>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
public void Save(Stream stream, IImageFormat format)
{
Guard.NotNull(stream, nameof(stream));
format.Encoder.Encode(this, stream);
}
/// <summary>
/// Saves the image to the given stream using the given image encoder.
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
public void Save(Stream stream, IImageEncoder encoder)
{
Guard.NotNull(stream, nameof(stream));
encoder.Encode(this, stream);
}
/// <summary>
/// Returns a Base64 encoded string from the given image.
/// </summary>
/// <example>data:image/gif;base64,R0lGODlhAQABAIABAEdJRgAAACwAAAAAAQABAAACAkQBAA==</example>
/// <returns>The <see cref="string"/></returns>
public override string ToString()
{
using (MemoryStream stream = new MemoryStream())
{
this.Save(stream);
stream.Flush();
return $"data:{this.CurrentImageFormat.Encoder.MimeType};base64,{Convert.ToBase64String(stream.ToArray())}";
}
}
/// <summary>
/// Loads the image from the given stream.
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
private void Load(Stream stream)
{
if (!this.Formats.Any()) { return; }
if (!stream.CanRead)
{
throw new NotSupportedException("Cannot read from the stream.");
}
if (!stream.CanSeek)
{
throw new NotSupportedException("The stream does not support seeking.");
}
int maxHeaderSize = this.Formats.Max(x => x.Decoder.HeaderSize);
if (maxHeaderSize > 0)
{
byte[] header = new byte[maxHeaderSize];
stream.Position = 0;
stream.Read(header, 0, maxHeaderSize);
stream.Position = 0;
IImageFormat format = this.Formats.FirstOrDefault(x => x.Decoder.IsSupportedFileFormat(header));
if (format != null)
{
format.Decoder.Decode(this, stream);
this.CurrentImageFormat = format;
return;
}
}
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine("Image cannot be loaded. Available formats:");
foreach (IImageFormat format in this.Formats)
{
stringBuilder.AppendLine("-" + format);
}
throw new NotSupportedException(stringBuilder.ToString());
}
}
}

19
src/ImageProcessorCore/ImageBase.cs → src/ImageProcessorCore/Image/ImageBase.cs

@ -14,18 +14,19 @@ namespace ImageProcessorCore
/// <typeparam name="T">
/// The packed vector pixels format.
/// </typeparam>
public abstract class ImageBase<T> : IImageBase<T>
where T : IPackedVector, new()
public abstract class ImageBase<T, TP> : IImageBase<T, TP>
where T : IPackedVector<TP>, new()
where TP : struct
{
/// <summary>
/// Initializes a new instance of the <see cref="ImageBase{T}"/> class.
/// Initializes a new instance of the <see cref="ImageBase{T,TP}"/> class.
/// </summary>
protected ImageBase()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageBase{T}"/> class.
/// Initializes a new instance of the <see cref="ImageBase{T,TP}"/> class.
/// </summary>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
@ -43,15 +44,15 @@ namespace ImageProcessorCore
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageBase{T}"/> class.
/// Initializes a new instance of the <see cref="ImageBase{T,TP}"/> class.
/// </summary>
/// <param name="other">
/// The other <see cref="ImageBase{T}"/> to create this instance from.
/// The other <see cref="ImageBase{T,TP}"/> to create this instance from.
/// </param>
/// <exception cref="ArgumentNullException">
/// Thrown if the given <see cref="ImageBase{T}"/> is null.
/// Thrown if the given <see cref="ImageBase{T,TP}"/> is null.
/// </exception>
protected ImageBase(ImageBase<T> other)
protected ImageBase(ImageBase<T, TP> other)
{
Guard.NotNull(other, nameof(other), "Other image cannot be null.");
@ -196,6 +197,6 @@ namespace ImageProcessorCore
/// </remarks>
/// </summary>
/// <returns>The <see cref="IPixelAccessor"/></returns>
public abstract IPixelAccessor<T> Lock();
public abstract IPixelAccessor<T, TP> Lock();
}
}

39
src/ImageProcessorCore/ImageExtensions.cs → src/ImageProcessorCore/Image/ImageExtensions.cs

@ -61,8 +61,9 @@ namespace ImageProcessorCore
/// <param name="source">The image this method extends.</param>
/// <param name="processor">The processor to apply to the image.</param>
/// <returns>The <see cref="Image{T}"/>.</returns>
public static Image<T> Process<T>(this Image<T> source, IImageProcessor processor)
where T : IPackedVector, new()
public static Image<T, TP> Process<T, TP>(this Image<T, TP> source, IImageProcessor processor)
where T : IPackedVector<TP>, new()
where TP : struct
{
return Process(source, source.Bounds, processor);
}
@ -78,8 +79,9 @@ namespace ImageProcessorCore
/// </param>
/// <param name="processor">The processors to apply to the image.</param>
/// <returns>The <see cref="Image{T}"/>.</returns>
public static Image<T> Process<T>(this Image<T> source, Rectangle sourceRectangle, IImageProcessor processor)
where T : IPackedVector, new()
public static Image<T, TP> Process<T, TP>(this Image<T, TP> source, Rectangle sourceRectangle, IImageProcessor processor)
where T : IPackedVector<TP>, new()
where TP : struct
{
return PerformAction(source, true, (sourceImage, targetImage) => processor.Apply(targetImage, sourceImage, sourceRectangle));
}
@ -96,8 +98,9 @@ namespace ImageProcessorCore
/// <param name="height">The target image height.</param>
/// <param name="sampler">The processor to apply to the image.</param>
/// <returns>The <see cref="Image{T}"/>.</returns>
public static Image<T> Process<T>(this Image<T> source, int width, int height, IImageSampler sampler)
where T : IPackedVector, new()
public static Image<T, TP> Process<T, TP>(this Image<T, TP> source, int width, int height, IImageSampler sampler)
where T : IPackedVector<TP>, new()
where TP : struct
{
return Process(source, width, height, source.Bounds, default(Rectangle), sampler);
}
@ -121,8 +124,9 @@ namespace ImageProcessorCore
/// </param>
/// <param name="sampler">The processor to apply to the image.</param>
/// <returns>The <see cref="Image{T}"/>.</returns>
public static Image<T> Process<T>(this Image<T> source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler)
where T : IPackedVector, new()
public static Image<T, TP> Process<T, TP>(this Image<T, TP> source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler)
where T : IPackedVector<TP>, new()
where TP : struct
{
return PerformAction(source, false, (sourceImage, targetImage) => sampler.Apply(targetImage, sourceImage, width, height, targetRectangle, sourceRectangle));
}
@ -135,12 +139,13 @@ namespace ImageProcessorCore
/// <param name="clone">Whether to clone the image.</param>
/// <param name="action">The <see cref="Action"/> to perform against the image.</param>
/// <returns>The <see cref="Image{T}"/>.</returns>
private static Image<T> PerformAction<T>(Image<T> source, bool clone, Action<ImageBase<T>, ImageBase<T>> action)
where T : IPackedVector, new()
private static Image<T, TP> PerformAction<T, TP>(Image<T, TP> source, bool clone, Action<ImageBase<T, TP>, ImageBase<T, TP>> action)
where T : IPackedVector<TP>, new()
where TP : struct
{
Image<T> transformedImage = clone
? new Image<T>(source)
: new Image<T>
Image<T, TP> transformedImage = clone
? new Image<T, TP>(source)
: new Image<T, TP>
{
// Several properties require copying
// TODO: Check why we need to set these?
@ -154,10 +159,10 @@ namespace ImageProcessorCore
for (int i = 0; i < source.Frames.Count; i++)
{
ImageFrame<T> sourceFrame = source.Frames[i];
ImageFrame<T> tranformedFrame = clone
? new ImageFrame<T>(sourceFrame)
: new ImageFrame<T> { FrameDelay = sourceFrame.FrameDelay };
ImageFrame<T, TP> sourceFrame = source.Frames[i];
ImageFrame<T, TP> tranformedFrame = clone
? new ImageFrame<T, TP>(sourceFrame)
: new ImageFrame<T, TP> { FrameDelay = sourceFrame.FrameDelay };
action(sourceFrame, tranformedFrame);

11
src/ImageProcessorCore/ImageFrame.cs → src/ImageProcessorCore/Image/ImageFrame.cs

@ -11,8 +11,9 @@ namespace ImageProcessorCore
/// <typeparam name="T">
/// The packed vector containing pixel information.
/// </typeparam>
public class ImageFrame<T> : ImageBase<T>
where T : IPackedVector, new()
public class ImageFrame<T, TP> : ImageBase<T, TP>
where T : IPackedVector<TP>, new()
where TP : struct
{
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{T}"/> class.
@ -27,15 +28,15 @@ namespace ImageProcessorCore
/// <param name="frame">
/// The frame to create the frame from.
/// </param>
public ImageFrame(ImageFrame<T> frame)
public ImageFrame(ImageFrame<T, TP> frame)
: base(frame)
{
}
/// <inheritdoc />
public override IPixelAccessor<T> Lock()
public override IPixelAccessor<T, TP> Lock()
{
return Bootstrapper.Instance.GetPixelAccessor<T>(this);
return Bootstrapper.Instance.GetPixelAccessor<T, TP>(this);
}
}
}

0
src/ImageProcessorCore/ImageProperty.cs → src/ImageProcessorCore/Image/ImageProperty.cs

25
src/ImageProcessorCore/ImageProcessor.cs

@ -27,8 +27,9 @@ namespace ImageProcessorCore.Processors
private int totalRows;
/// <inheritdoc/>
public void Apply<T>(ImageBase<T> target, ImageBase<T> source, Rectangle sourceRectangle)
where T : IPackedVector, new()
public void Apply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle sourceRectangle)
where T : IPackedVector<TP>, new()
where TP : struct
{
try
{
@ -49,8 +50,9 @@ namespace ImageProcessorCore.Processors
}
/// <inheritdoc/>
public void Apply<T>(ImageBase<T> target, ImageBase<T> source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle))
where T : IPackedVector, new()
public void Apply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle))
where T : IPackedVector<TP>, new()
where TP : struct
{
try
{
@ -95,8 +97,9 @@ namespace ImageProcessorCore.Processors
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
protected virtual void OnApply<T>(ImageBase<T> target, ImageBase<T> source, Rectangle targetRectangle, Rectangle sourceRectangle)
where T : IPackedVector, new()
protected virtual void OnApply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
where T : IPackedVector<TP>, new()
where TP : struct
{
}
@ -120,8 +123,9 @@ namespace ImageProcessorCore.Processors
/// The method keeps the source image unchanged and returns the
/// the result of image process as new image.
/// </remarks>
protected abstract void Apply<T>(ImageBase<T> target, ImageBase<T> source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
where T : IPackedVector, new();
protected abstract void Apply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
where T : IPackedVector<TP>, new()
where TP : struct;
/// <summary>
/// This method is called after the process is applied to prepare the processor.
@ -136,8 +140,9 @@ namespace ImageProcessorCore.Processors
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
protected virtual void AfterApply<T>(ImageBase<T> target, ImageBase<T> source, Rectangle targetRectangle, Rectangle sourceRectangle)
where T : IPackedVector, new()
protected virtual void AfterApply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
where T : IPackedVector<TP>, new()
where TP : struct
{
}

227
src/ImageProcessorCore/PackedVector/Bgra32.cs

@ -2,7 +2,6 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
@ -11,8 +10,19 @@ namespace ImageProcessorCore
/// <summary>
/// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 1.
/// </summary>
public struct Bgra32 : IPackedVector<uint>, IEquatable<Bgra32>
public unsafe struct Bgra32 : IPackedVector<uint>, IEquatable<Bgra32>
{
const uint B_MASK = 0x000000FF;
const uint G_MASK = 0x0000FF00;
const uint R_MASK = 0x00FF0000;
const uint A_MASK = 0xFF000000;
const int B_SHIFT = 0;
const int G_SHIFT = 8;
const int R_SHIFT = 16;
const int A_SHIFT = 24;
private uint packedValue;
/// <summary>
/// Initializes a new instance of the <see cref="Bgra32"/> struct.
/// </summary>
@ -23,7 +33,7 @@ namespace ImageProcessorCore
public Bgra32(float b, float g, float r, float a)
{
Vector4 clamped = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, Vector4.One) * 255f;
this.PackedValue = Pack(ref clamped);
this.packedValue = Pack(ref clamped);
}
/// <summary>
@ -35,42 +45,191 @@ namespace ImageProcessorCore
public Bgra32(Vector4 vector)
{
Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f;
this.PackedValue = Pack(ref clamped);
this.packedValue = Pack(ref clamped);
}
public byte B
{
get
{
return (byte)(this.packedValue & B_MASK);
}
set
{
this.packedValue = (this.packedValue & ~B_MASK) | value;
}
}
public byte G
{
get
{
return (byte)((this.packedValue & G_MASK) >> G_SHIFT);
}
set
{
this.packedValue = (this.packedValue & ~G_MASK) | (((uint)value) << G_SHIFT);
}
}
public byte R
{
get
{
return (byte)((this.packedValue & R_MASK) >> R_SHIFT);
}
set
{
this.packedValue = (this.packedValue & ~R_MASK) | (((uint)value) << R_SHIFT);
}
}
public byte A
{
get
{
return (byte)((this.packedValue & A_MASK) >> A_SHIFT);
}
set
{
this.packedValue = (this.packedValue & ~A_MASK) | (((uint)value) << A_SHIFT);
}
}
/// <inheritdoc/>
public uint PackedValue { get; set; }
public uint PackedValue()
{
return this.packedValue;
}
public void Add<TP>(TP value) where TP : IPackedVector<uint>
{
// this.PackVector(this.ToVector4() + value.ToVector4());
}
public void Subtract<TP>(TP value) where TP : IPackedVector<uint>
{
// this.PackVector(this.ToVector4() - value.ToVector4());
}
// The maths are wrong here I just wanted to test the performance to see if the
// issues are caused by the Vector transform or something else.
public void Add(IPackedVector value)
public void Multiply<TP>(TP value) where TP : IPackedVector<uint>
{
// this.PackVector(this.ToVector4() * value.ToVector4());
}
public void Multiply<TP>(float value) where TP : IPackedVector<uint>
{
this.B = (byte)(this.B * value);
this.G = (byte)(this.G * value);
this.R = (byte)(this.R * value);
this.A = (byte)(this.A * value);
}
public void Subtract(IPackedVector value)
public void Divide<TP>(TP value) where TP : IPackedVector<uint>
{
// this.PackVector(this.ToVector4() / value.ToVector4());
}
public void Divide<TP>(float value) where TP : IPackedVector<uint>
{
this.B = (byte)(this.B / value);
this.G = (byte)(this.G / value);
this.R = (byte)(this.R / value);
this.A = (byte)(this.A / value);
}
public void Multiply(IPackedVector value)
/// <summary>
/// Computes the product of multiplying a Bgra32 by a given factor.
/// </summary>
/// <param name="value">The Bgra32.</param>
/// <param name="factor">The multiplication factor.</param>
/// <returns>
/// The <see cref="Bgra32"/>
/// </returns>
public static Bgra32 operator *(Bgra32 value, float factor)
{
byte b = (byte)(value.B * factor);
byte g = (byte)(value.G * factor);
byte r = (byte)(value.R * factor);
byte a = (byte)(value.A * factor);
return new Bgra32(b, g, r, a);
}
public void Multiply(float value)
/// <summary>
/// Computes the product of multiplying a Bgra32 by a given factor.
/// </summary>
/// <param name="factor">The multiplication factor.</param>
/// <param name="value">The Bgra32.</param>
/// <returns>
/// The <see cref="Bgra32"/>
/// </returns>
public static Bgra32 operator *(float factor, Bgra32 value)
{
byte b = (byte)(value.B * factor);
byte g = (byte)(value.G * factor);
byte r = (byte)(value.R * factor);
byte a = (byte)(value.A * factor);
return new Bgra32(b, g, r, a);
}
public void Divide(IPackedVector value)
/// <summary>
/// Computes the product of multiplying two Bgra32s.
/// </summary>
/// <param name="left">The Bgra32 on the left hand of the operand.</param>
/// <param name="right">The Bgra32 on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Bgra32"/>
/// </returns>
public static Bgra32 operator *(Bgra32 left, Bgra32 right)
{
byte b = (byte)(left.B * right.B);
byte g = (byte)(left.G * right.G);
byte r = (byte)(left.R * right.R);
byte a = (byte)(left.A * right.A);
return new Bgra32(b, g, r, a);
}
/// <summary>
/// Computes the sum of adding two Bgra32s.
/// </summary>
/// <param name="left">The Bgra32 on the left hand of the operand.</param>
/// <param name="right">The Bgra32 on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Bgra32"/>
/// </returns>
public static Bgra32 operator +(Bgra32 left, Bgra32 right)
{
byte b = (byte)(left.B + right.B);
byte g = (byte)(left.G + right.G);
byte r = (byte)(left.R + right.R);
byte a = (byte)(left.A + right.A);
return new Bgra32(b, g, r, a);
}
public void Divide(float value)
/// <summary>
/// Computes the difference left by subtracting one Bgra32 from another.
/// </summary>
/// <param name="left">The Bgra32 on the left hand of the operand.</param>
/// <param name="right">The Bgra32 on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Bgra32"/>
/// </returns>
public static Bgra32 operator -(Bgra32 left, Bgra32 right)
{
byte b = (byte)(left.B - right.B);
byte g = (byte)(left.G - right.G);
byte r = (byte)(left.R - right.R);
byte a = (byte)(left.A - right.A);
return new Bgra32(b, g, r, a);
}
/// <summary>
@ -87,7 +246,7 @@ namespace ImageProcessorCore
/// </returns>
public static bool operator ==(Bgra32 left, Bgra32 right)
{
return left.PackedValue == right.PackedValue;
return left.packedValue == right.packedValue;
}
/// <summary>
@ -100,31 +259,30 @@ namespace ImageProcessorCore
/// </returns>
public static bool operator !=(Bgra32 left, Bgra32 right)
{
return left.PackedValue != right.PackedValue;
return left.packedValue != right.packedValue;
}
/// <inheritdoc/>
public void PackVector(Vector4 vector)
{
Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f;
this.PackedValue = Pack(ref clamped);
this.packedValue = Pack(ref clamped);
}
/// <inheritdoc/>
public void PackBytes(byte x, byte y, byte z, byte w)
{
Vector4 vector = new Vector4(x, y, z, w);
this.PackedValue = Pack(ref vector);
this.packedValue = Pack(ref x, ref y, ref z, ref w);
}
/// <inheritdoc/>
public Vector4 ToVector4()
{
return new Vector4(
this.PackedValue & 0xFF,
(this.PackedValue >> 8) & 0xFF,
(this.PackedValue >> 16) & 0xFF,
(this.PackedValue >> 24) & 0xFF) / 255f;
this.packedValue & 0xFF,
(this.packedValue >> 8) & 0xFF,
(this.packedValue >> 16) & 0xFF,
(this.packedValue >> 24) & 0xFF) / 255f;
}
/// <inheritdoc/>
@ -132,11 +290,11 @@ namespace ImageProcessorCore
{
return new[]
{
(byte)(this.PackedValue & 0xFF),
(byte)((this.PackedValue >> 8) & 0xFF),
(byte)((this.PackedValue >> 16) & 0xFF),
(byte)((this.PackedValue >> 24) & 0xFF)
};
(byte)(this.packedValue & 0xFF),
(byte)((this.packedValue >> 8) & 0xFF),
(byte)((this.packedValue >> 16) & 0xFF),
(byte)((this.packedValue >> 24) & 0xFF)
};
}
/// <inheritdoc/>
@ -148,7 +306,7 @@ namespace ImageProcessorCore
/// <inheritdoc/>
public bool Equals(Bgra32 other)
{
return this.PackedValue == other.PackedValue;
return this.packedValue == other.packedValue;
}
/// <summary>
@ -178,9 +336,14 @@ namespace ImageProcessorCore
private static uint Pack(ref Vector4 vector)
{
return (uint)Math.Round(vector.X) |
((uint)Math.Round(vector.Y) << 8) |
((uint)Math.Round(vector.Z) << 16) |
((uint)Math.Round(vector.W) << 24);
((uint)Math.Round(vector.Y) << 8) |
((uint)Math.Round(vector.Z) << 16) |
((uint)Math.Round(vector.W) << 24);
}
private static uint Pack(ref byte x, ref byte y, ref byte z, ref byte w)
{
return x | ((uint)y << 8) | ((uint)z << 16) | ((uint)w << 24);
}
/// <summary>
@ -194,7 +357,7 @@ namespace ImageProcessorCore
/// </returns>
private int GetHashCode(Bgra32 packed)
{
return packed.PackedValue.GetHashCode();
return packed.packedValue.GetHashCode();
}
}
}

38
src/ImageProcessorCore/PackedVector/IPackedVector.cs

@ -18,12 +18,22 @@ namespace ImageProcessorCore
where T : struct
{
/// <summary>
/// Gets or sets the packed representation of the value.
/// Gets the packed representation of the value.
/// Typically packed in least to greatest significance order.
/// </summary>
T PackedValue { get; set; }
T PackedValue();
void Add<TP>(TP value) where TP : IPackedVector<T>;
void Subtract<TP>(TP value) where TP : IPackedVector<T>;
void Multiply<TP>(TP value) where TP : IPackedVector<T>;
void Multiply<TP>(float value) where TP : IPackedVector<T>;
void Divide<TP>(TP value) where TP : IPackedVector<T>;
void Divide<TP>(float value) where TP : IPackedVector<T>;
}
/// <summary>
@ -31,30 +41,6 @@ namespace ImageProcessorCore
/// </summary>
public interface IPackedVector
{
//void Add<U>(U value);
//void Subtract<U>(U value);
//void Multiply<U>(U value);
//void Multiply(float value);
//void Divide<U>(U value);
//void Divide(float value);
void Add(IPackedVector value);
void Subtract(IPackedVector value);
void Multiply(IPackedVector value);
void Multiply(float value);
void Divide(IPackedVector value);
void Divide(float value);
/// <summary>
/// Sets the packed representation from a <see cref="Vector4"/>.
/// </summary>

5
src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs

@ -15,8 +15,7 @@ namespace ImageProcessorCore
/// The image data is always stored in <see cref="Bgra32"/> format, where the blue, green, red, and
/// alpha values are 8 bit unsigned bytes.
/// </remarks>
public sealed unsafe class Bgra32PixelAccessor : IPixelAccessor<Bgra32>
public sealed unsafe class Bgra32PixelAccessor : IPixelAccessor<Bgra32, uint>
{
/// <summary>
/// The position of the first pixel in the bitmap.
@ -56,7 +55,7 @@ namespace ImageProcessorCore
this.Width = image.Width;
this.Height = image.Height;
this.pixelsHandle = GCHandle.Alloc(((ImageBase<Bgra32>)image).Pixels, GCHandleType.Pinned);
this.pixelsHandle = GCHandle.Alloc(((ImageBase<Bgra32, uint>)image).Pixels, GCHandleType.Pinned);
this.pixelsBase = (Bgra32*)this.pixelsHandle.AddrOfPinnedObject().ToPointer();
}

5
src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs

@ -10,8 +10,9 @@ namespace ImageProcessorCore
/// <summary>
/// Encapsulates properties to provides per-pixel access to an images pixels.
/// </summary>
public interface IPixelAccessor<T> : IPixelAccessor
where T : IPackedVector, new()
public interface IPixelAccessor<T, TP> : IPixelAccessor
where T : IPackedVector<TP>, new()
where TP : struct
{
/// <summary>
/// Gets or sets the pixel at the specified position.

30
src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs

@ -23,8 +23,9 @@ namespace ImageProcessorCore
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
public static Rectangle CalculateTargetLocationAndBounds<T>(ImageBase<T> source, ResizeOptions options)
where T : IPackedVector, new()
public static Rectangle CalculateTargetLocationAndBounds<T, TP>(ImageBase<T, TP> source, ResizeOptions options)
where T : IPackedVector<TP>, new()
where TP : struct
{
switch (options.Mode)
{
@ -54,8 +55,9 @@ namespace ImageProcessorCore
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
private static Rectangle CalculateCropRectangle<T>(ImageBase<T> source, ResizeOptions options)
where T : IPackedVector, new()
private static Rectangle CalculateCropRectangle<T, TP>(ImageBase<T, TP> source, ResizeOptions options)
where T : IPackedVector<TP>, new()
where TP : struct
{
int width = options.Size.Width;
int height = options.Size.Height;
@ -173,8 +175,9 @@ namespace ImageProcessorCore
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
private static Rectangle CalculatePadRectangle<T>(ImageBase<T> source, ResizeOptions options)
where T : IPackedVector, new()
private static Rectangle CalculatePadRectangle<T, TP>(ImageBase<T, TP> source, ResizeOptions options)
where T : IPackedVector<TP>, new()
where TP : struct
{
int width = options.Size.Width;
int height = options.Size.Height;
@ -254,8 +257,9 @@ namespace ImageProcessorCore
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
private static Rectangle CalculateBoxPadRectangle<T>(ImageBase<T> source, ResizeOptions options)
where T : IPackedVector, new()
private static Rectangle CalculateBoxPadRectangle<T, TP>(ImageBase<T, TP> source, ResizeOptions options)
where T : IPackedVector<TP>, new()
where TP : struct
{
int width = options.Size.Width;
int height = options.Size.Height;
@ -341,8 +345,9 @@ namespace ImageProcessorCore
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
private static Rectangle CalculateMaxRectangle<T>(ImageBase<T> source, ResizeOptions options)
where T : IPackedVector, new()
private static Rectangle CalculateMaxRectangle<T, TP>(ImageBase<T, TP> source, ResizeOptions options)
where T : IPackedVector<TP>, new()
where TP : struct
{
int width = options.Size.Width;
int height = options.Size.Height;
@ -382,8 +387,9 @@ namespace ImageProcessorCore
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
private static Rectangle CalculateMinRectangle<T>(ImageBase<T> source, ResizeOptions options)
where T : IPackedVector, new()
private static Rectangle CalculateMinRectangle<T, TP>(ImageBase<T, TP> source, ResizeOptions options)
where T : IPackedVector<TP>, new()
where TP : struct
{
int width = options.Size.Width;
int height = options.Size.Height;

36
src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs

@ -9,6 +9,8 @@ namespace ImageProcessorCore.Processors
using System.Numerics;
using System.Threading.Tasks;
using ImageProcessorCore.Helpers;
/// <summary>
/// Provides methods that allow the resizing of images using various algorithms.
/// </summary>
@ -43,7 +45,7 @@ namespace ImageProcessorCore.Processors
protected Weights[] VerticalWeights { get; set; }
/// <inheritdoc/>
protected override void OnApply<T>(ImageBase<T> target, ImageBase<T> source, Rectangle targetRectangle, Rectangle sourceRectangle)
protected override void OnApply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
if (!(this.Sampler is NearestNeighborResampler))
{
@ -53,7 +55,7 @@ namespace ImageProcessorCore.Processors
}
/// <inheritdoc/>
protected override void Apply<T>(ImageBase<T> target, ImageBase<T> source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
protected override void Apply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
// Jump out, we'll deal with that later.
if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle)
@ -78,8 +80,8 @@ namespace ImageProcessorCore.Processors
float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width;
float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height;
using (IPixelAccessor<T> sourcePixels = source.Lock())
using (IPixelAccessor<T> targetPixels = target.Lock())
using (IPixelAccessor<T, TP> sourcePixels = source.Lock())
using (IPixelAccessor<T, TP> targetPixels = target.Lock())
{
Parallel.For(
startY,
@ -114,10 +116,10 @@ namespace ImageProcessorCore.Processors
// A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm
// First process the columns. Since we are not using multiple threads startY and endY
// are the upper and lower bounds of the source rectangle.
Image<T> firstPass = new Image<T>(target.Width, source.Height);
using (IPixelAccessor<T> sourcePixels = source.Lock())
using (IPixelAccessor<T> firstPassPixels = firstPass.Lock())
using (IPixelAccessor<T> targetPixels = target.Lock())
Image<T, TP> firstPass = new Image<T, TP>(target.Width, source.Height);
using (IPixelAccessor<T, TP> sourcePixels = source.Lock())
using (IPixelAccessor<T, TP> firstPassPixels = firstPass.Lock())
using (IPixelAccessor<T, TP> targetPixels = target.Lock())
{
Parallel.For(
0,
@ -153,14 +155,13 @@ namespace ImageProcessorCore.Processors
//}
//firstPassPixels[x, y] = destination;
T sourceColor;
T destination = default(T);
for (int i = 0; i < sum; i++)
{
Weight xw = horizontalValues[i];
int originX = xw.Index;
sourceColor = sourcePixels[originX, y];
T sourceColor = sourcePixels[originX, y];
//Color sourceColor = compand
// ? Color.Expand(sourcePixels[originX, y])
// : sourcePixels[originX, y];
@ -168,8 +169,8 @@ namespace ImageProcessorCore.Processors
//destination.Add(sourceColor);
//destination += sourceColor * xw.Value;
sourceColor.Multiply(xw.Value);
destination.Add(sourceColor);
//sourceColor.Multiply<T>(xw.Value);
destination.Add(Operator<T>.Add(destination, Operator<T>.MultiplyF(sourceColor, xw.Value)));
}
//if (compand)
@ -200,22 +201,23 @@ namespace ImageProcessorCore.Processors
for (int x = 0; x < width; x++)
{
// Destination color components
T sourceColor;
T destination = default(T);
for (int i = 0; i < sum; i++)
{
Weight yw = verticalValues[i];
int originY = yw.Index;
sourceColor = firstPassPixels[x, originY];
T sourceColor = firstPassPixels[x, originY];
//Color sourceColor = compand
// ? Color.Expand(firstPassPixels[x, originY])
// : firstPassPixels[x, originY];
//Vector4 sourceColor = firstPassPixels[x, originY].ToVector4();
//destination += sourceColor * yw.Value;
sourceColor.Multiply(yw.Value);
destination.Add(sourceColor);
//sourceColor.Multiply<T>(yw.Value);
//destination.Add(sourceColor);
destination.Add(Operator<T>.Add(destination, Operator<T>.MultiplyF(sourceColor, yw.Value)));
}
//if (compand)
@ -237,7 +239,7 @@ namespace ImageProcessorCore.Processors
}
/// <inheritdoc/>
protected override void AfterApply<T>(ImageBase<T> target, ImageBase<T> source, Rectangle targetRectangle, Rectangle sourceRectangle)
protected override void AfterApply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
// Copy the pixels over.
if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle)

25
src/ImageProcessorCore/Samplers/Resize.cs

@ -21,8 +21,9 @@ namespace ImageProcessorCore
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image{T}"/></returns>
/// <remarks>Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image</remarks>
public static Image<T> Resize<T>(this Image<T> source, ResizeOptions options, ProgressEventHandler progressHandler = null)
where T : IPackedVector, new()
public static Image<T,TP> Resize<T,TP>(this Image<T,TP> source, ResizeOptions options, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>, new()
where TP : struct
{
// Ensure size is populated across both dimensions.
if (options.Size.Width == 0 && options.Size.Height > 0)
@ -50,8 +51,9 @@ namespace ImageProcessorCore
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image{T}"/></returns>
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
public static Image<T> Resize<T>(this Image<T> source, int width, int height, ProgressEventHandler progressHandler = null)
where T : IPackedVector, new()
public static Image<T,TP> Resize<T,TP>(this Image<T,TP> source, int width, int height, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>, new()
where TP : struct
{
return Resize(source, width, height, new BicubicResampler(), false, progressHandler);
}
@ -67,8 +69,9 @@ namespace ImageProcessorCore
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image{T}"/></returns>
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
public static Image<T> Resize<T>(this Image<T> source, int width, int height, bool compand, ProgressEventHandler progressHandler = null)
where T : IPackedVector, new()
public static Image<T,TP> Resize<T,TP>(this Image<T,TP> source, int width, int height, bool compand, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>, new()
where TP : struct
{
return Resize(source, width, height, new BicubicResampler(), compand, progressHandler);
}
@ -85,8 +88,9 @@ namespace ImageProcessorCore
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image{T}"/></returns>
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
public static Image<T> Resize<T>(this Image<T> source, int width, int height, IResampler sampler, bool compand, ProgressEventHandler progressHandler = null)
where T : IPackedVector, new()
public static Image<T,TP> Resize<T,TP>(this Image<T,TP> source, int width, int height, IResampler sampler, bool compand, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>, new()
where TP : struct
{
return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height), compand, progressHandler);
}
@ -110,8 +114,9 @@ namespace ImageProcessorCore
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image{T}"/></returns>
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
public static Image<T> Resize<T>(this Image<T> source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false, ProgressEventHandler progressHandler = null)
where T : IPackedVector, new()
public static Image<T,TP> Resize<T,TP>(this Image<T,TP> source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>, new()
where TP : struct
{
if (width == 0 && height > 0)
{

6
tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs

@ -23,11 +23,11 @@
[Benchmark(Description = "ImageProcessorCore GetSet Pixel")]
public Bgra32 ResizeCore()
{
Image<Bgra32> image = new Image<Bgra32>(400, 400);
using (IPixelAccessor<Bgra32> imagePixels = image.Lock())
Image<Bgra32, uint> image = new Image<Bgra32, uint>(400, 400);
using (IPixelAccessor<Bgra32, uint> imagePixels = image.Lock())
{
imagePixels[200, 200] = new Bgra32(1, 1, 1, 1);
return (Bgra32)imagePixels[200, 200];
return imagePixels[200, 200];
}
}
}

3
tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs

@ -5,6 +5,7 @@
using BenchmarkDotNet.Attributes;
using CoreSize = ImageProcessorCore.Size;
using CoreImage = ImageProcessorCore.Image;
public class Resize
{
@ -31,7 +32,7 @@
[Benchmark(Description = "ImageProcessorCore Resize")]
public CoreSize ResizeCore()
{
Image<Bgra32> image = new Image<Bgra32>(2000, 2000);
CoreImage image = new CoreImage(2000, 2000);
image.Resize(400, 400);
return new CoreSize(image.Width, image.Height);
}

Loading…
Cancel
Save