Browse Source

Begin switching to generics

Former-commit-id: 88d16159717d23208e4e151e5434aca3a27ff3b2
Former-commit-id: 2a7d97bb9c9a42b3e6ce720bd0da36d0b3fea5d8
Former-commit-id: d6db3166d72eb48c18e2fc0c093ba42462e2a208
pull/1/head
James Jackson-South 10 years ago
parent
commit
ffaddb984c
  1. 188
      GenericImage/ImageBase.cs
  2. 18
      GenericImage/ImageRgba32.cs
  3. 4
      GenericImage/PackedVectors/Rgba64.cs
  4. 2
      GenericImage/PixelAccessorRgba32.cs
  5. 336
      src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs
  6. 40
      src/ImageProcessorCore/GenericImageFrame.cs
  7. 18
      src/ImageProcessorCore/IImageBase.cs
  8. 7
      src/ImageProcessorCore/IImageFrame.cs
  9. 33
      src/ImageProcessorCore/IPixelAccessor.cs
  10. 259
      src/ImageProcessorCore/Image.cs
  11. 87
      src/ImageProcessorCore/ImageBase.cs
  12. 34
      src/ImageProcessorCore/ImageFrame.cs
  13. 168
      src/ImageProcessorCore/PackedVector/Bgra32.cs
  14. 61
      src/ImageProcessorCore/PackedVector/IPackedVector.cs
  15. 19
      src/ImageProcessorCore/PixelAccessor.cs

188
GenericImage/ImageBase.cs

@ -0,0 +1,188 @@
namespace GenericImage
{
using System;
using GenericImage.PackedVectors;
/// <summary>
/// Encapsulates the basic properties and methods required to manipulate images
/// in different pixel formats.
/// </summary>
/// <typeparam name="TPacked">
/// The packed vector pixels format.
/// </typeparam>
public abstract class ImageBase<TPacked>
where TPacked : IPackedVector
{
/// <summary>
/// Initializes a new instance of the <see cref="ImageBase{TPacked}"/> class.
/// </summary>
protected ImageBase()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageBase{TPacked}"/> class.
/// </summary>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if either <paramref name="width"/> or <paramref name="height"/> are less than or equal to 0.
/// </exception>
protected ImageBase(int width, int height)
{
// Guard.MustBeGreaterThan(width, 0, nameof(width));
// Guard.MustBeGreaterThan(height, 0, nameof(height));
this.Width = width;
this.Height = height;
this.Pixels = new TPacked[width * height];
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageBase{TPacked}"/> class.
/// </summary>
/// <param name="other">
/// The other <see cref="ImageBase{TPacked}"/> to create this instance from.
/// </param>
/// <exception cref="ArgumentNullException">
/// Thrown if the given <see cref="ImageBase{TPacked}"/> is null.
/// </exception>
protected ImageBase(ImageBase<TPacked> other)
{
// Guard.NotNull(other, nameof(other), "Other image cannot be null.");
this.Width = other.Width;
this.Height = other.Height;
this.Quality = other.Quality;
this.FrameDelay = other.FrameDelay;
// Copy the pixels.
this.Pixels = new TPacked[this.Width * this.Height];
Array.Copy(other.Pixels, this.Pixels, other.Pixels.Length);
}
/// <summary>
/// Gets the pixels as an array of the given packed pixel format.
/// </summary>
public TPacked[] Pixels { get; private set; }
/// <summary>
/// Gets the width in pixels.
/// </summary>
public int Width { get; private set; }
/// <summary>
/// Gets the height in pixels.
/// </summary>
public int Height { get; private set; }
/// <summary>
/// Gets the pixel ratio made up of the width and height.
/// </summary>
public double PixelRatio => (double)this.Width / this.Height;
/// <summary>
/// Gets the <see cref="Rectangle"/> representing the bounds of the image.
/// </summary>
// public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height);
/// <summary>
/// Gets or sets th quality of the image. This affects the output quality of lossy image formats.
/// </summary>
public int Quality { get; set; }
/// <summary>
/// Gets or sets the frame delay for animated images.
/// If not 0, this field specifies the number of hundredths (1/100) of a second to
/// wait before continuing with the processing of the Data Stream.
/// The clock starts ticking immediately after the graphic is rendered.
/// </summary>
public int FrameDelay { get; set; }
/// <summary>
/// Sets the pixel array of the image to the given value.
/// </summary>
/// <param name="width">The new width of the image. Must be greater than zero.</param>
/// <param name="height">The new height of the image. Must be greater than zero.</param>
/// <param name="pixels">
/// The array with colors. Must be a multiple of the width and height.
/// </param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if either <paramref name="width"/> or <paramref name="height"/> are less than or equal to 0.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown if the <paramref name="pixels"/> length is not equal to Width * Height.
/// </exception>
public void SetPixels(int width, int height, TPacked[] pixels)
{
if (width <= 0)
{
throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than or equals than zero.");
}
if (height <= 0)
{
throw new ArgumentOutOfRangeException(nameof(height), "Height must be greater than or equal than zero.");
}
if (pixels.Length != width * height)
{
throw new ArgumentException("Pixel array must have the length of Width * Height.");
}
this.Width = width;
this.Height = height;
this.Pixels = pixels;
}
/// <summary>
/// Sets the pixel array of the image to the given value, creating a copy of
/// the original pixels.
/// </summary>
/// <param name="width">The new width of the image. Must be greater than zero.</param>
/// <param name="height">The new height of the image. Must be greater than zero.</param>
/// <param name="pixels">
/// The array with colors. Must be a multiple of four times the width and height.
/// </param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if either <paramref name="width"/> or <paramref name="height"/> are less than or equal to 0.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown if the <paramref name="pixels"/> length is not equal to Width * Height.
/// </exception>
public void ClonePixels(int width, int height, TPacked[] pixels)
{
if (width <= 0)
{
throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than or equals than zero.");
}
if (height <= 0)
{
throw new ArgumentOutOfRangeException(nameof(height), "Height must be greater than or equal than zero.");
}
if (pixels.Length != width * height)
{
throw new ArgumentException("Pixel array must have the length of Width * Height.");
}
this.Width = width;
this.Height = height;
// Copy the pixels.
this.Pixels = new TPacked[pixels.Length];
Array.Copy(pixels, this.Pixels, pixels.Length);
}
/// <summary>
/// Locks the image providing access to the pixels.
/// <remarks>
/// It is imperative that the accessor is correctly disposed off after use.
/// </remarks>
/// </summary>
/// <returns>The <see cref="IPixelAccessor"/></returns>
public abstract IPixelAccessor Lock();
}
}

18
GenericImage/ImageRgba32.cs

@ -2,22 +2,10 @@
{
using GenericImage.PackedVectors;
public class ImageRgba32 : IImageBase<Rgba32>
public class ImageRgba32 : ImageBase<Rgba32>
{
public ImageRgba32(int width, int height)
{
this.Width = width;
this.Height = height;
this.Pixels = new Rgba32[width * height];
}
public Rgba32[] Pixels { get; }
public int Width { get; }
public int Height { get; }
public IPixelAccessor Lock()
/// <inheritdocs/>
public override IPixelAccessor Lock()
{
return new PixelAccessorRgba32(this);
}

4
GenericImage/PackedVectors/Rgba64.cs

@ -140,9 +140,9 @@
/// </returns>
private static ulong Pack(ref Vector4 vector)
{
return ((ulong)Math.Round(vector.Z) << 32) |
return ((ulong)Math.Round(vector.X) << 32) |
((ulong)Math.Round(vector.Y) << 16) |
(ulong)Math.Round(vector.X) |
(ulong)Math.Round(vector.Z) |
((ulong)Math.Round(vector.W) << 48);
}

2
GenericImage/PixelAccessorRgba32.cs

@ -39,7 +39,7 @@
/// <param name="image">
/// The image to provide pixel access for.
/// </param>
public PixelAccessorRgba32(IImageBase<Rgba32> image)
public PixelAccessorRgba32(ImageBase<Rgba32> image)
{
//Guard.NotNull(image, nameof(image));
//Guard.MustBeGreaterThan(image.Width, 0, "image width");

336
src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs

@ -1,168 +1,168 @@
// <copyright file="Bgra32.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.ComponentModel;
using System.Numerics;
/// <summary>
/// Represents an BGRA (blue, green, red, alpha) color.
/// </summary>
public struct Bgra32 : IEquatable<Bgra32>
{
/// <summary>
/// Represents a 32 bit <see cref="Bgra32"/> that has B, G, R, and A values set to zero.
/// </summary>
public static readonly Bgra32 Empty = default(Bgra32);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector4 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Bgra32"/> struct.
/// </summary>
/// <param name="b">The blue component of this <see cref="Bgra32"/>.</param>
/// <param name="g">The green component of this <see cref="Bgra32"/>.</param>
/// <param name="r">The red component of this <see cref="Bgra32"/>.</param>
/// <param name="a">The alpha component of this <see cref="Bgra32"/>.</param>
public Bgra32(byte b, byte g, byte r, byte a = 255)
: this()
{
this.backingVector = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, new Vector4(255));
}
/// <summary>
/// Gets the blue component of the color
/// </summary>
public byte B => (byte)this.backingVector.X;
/// <summary>
/// Gets the green component of the color
/// </summary>
public byte G => (byte)this.backingVector.Y;
/// <summary>
/// Gets the red component of the color
/// </summary>
public byte R => (byte)this.backingVector.Z;
/// <summary>
/// Gets the alpha component of the color
/// </summary>
public byte A => (byte)this.backingVector.W;
/// <summary>
/// Gets the <see cref="Bgra32"/> integer representation of the color.
/// </summary>
public int Bgra => (this.R << 16) | (this.G << 8) | (this.B << 0) | (this.A << 24);
/// <summary>
/// Gets a value indicating whether this <see cref="Bgra32"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a
/// <see cref="Bgra32"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Color"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Bgra32"/>.
/// </returns>
public static implicit operator Bgra32(Color color)
{
color = color.Limited * 255f;
return new Bgra32((byte)color.B, (byte)color.G, (byte)color.R, (byte)color.A);
}
/// <summary>
/// Compares two <see cref="Bgra32"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Bgra32"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Bgra32"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(Bgra32 left, Bgra32 right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="Bgra32"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Bgra32"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Bgra32"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(Bgra32 left, Bgra32 right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Bgra32)
{
Bgra32 color = (Bgra32)obj;
return this.backingVector == color.backingVector;
}
return false;
}
/// <inheritdoc/>
public override int GetHashCode()
{
return GetHashCode(this);
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "Bgra32 [ Empty ]";
}
return $"Bgra32 [ B={this.B}, G={this.G}, R={this.R}, A={this.A} ]";
}
/// <inheritdoc/>
public bool Equals(Bgra32 other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Cmyk"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private static int GetHashCode(Bgra32 color) => color.backingVector.GetHashCode();
}
}
//// <copyright file="Bgra32.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.ComponentModel;
// using System.Numerics;
// /// <summary>
// /// Represents an BGRA (blue, green, red, alpha) color.
// /// </summary>
// public struct Bgra32 : IEquatable<Bgra32>
// {
// /// <summary>
// /// Represents a 32 bit <see cref="Bgra32"/> that has B, G, R, and A values set to zero.
// /// </summary>
// public static readonly Bgra32 Empty = default(Bgra32);
// /// <summary>
// /// The backing vector for SIMD support.
// /// </summary>
// private Vector4 backingVector;
// /// <summary>
// /// Initializes a new instance of the <see cref="Bgra32"/> struct.
// /// </summary>
// /// <param name="b">The blue component of this <see cref="Bgra32"/>.</param>
// /// <param name="g">The green component of this <see cref="Bgra32"/>.</param>
// /// <param name="r">The red component of this <see cref="Bgra32"/>.</param>
// /// <param name="a">The alpha component of this <see cref="Bgra32"/>.</param>
// public Bgra32(byte b, byte g, byte r, byte a = 255)
// : this()
// {
// this.backingVector = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, new Vector4(255));
// }
// /// <summary>
// /// Gets the blue component of the color
// /// </summary>
// public byte B => (byte)this.backingVector.X;
// /// <summary>
// /// Gets the green component of the color
// /// </summary>
// public byte G => (byte)this.backingVector.Y;
// /// <summary>
// /// Gets the red component of the color
// /// </summary>
// public byte R => (byte)this.backingVector.Z;
// /// <summary>
// /// Gets the alpha component of the color
// /// </summary>
// public byte A => (byte)this.backingVector.W;
// /// <summary>
// /// Gets the <see cref="Bgra32"/> integer representation of the color.
// /// </summary>
// public int Bgra => (this.R << 16) | (this.G << 8) | (this.B << 0) | (this.A << 24);
// /// <summary>
// /// Gets a value indicating whether this <see cref="Bgra32"/> is empty.
// /// </summary>
// [EditorBrowsable(EditorBrowsableState.Never)]
// public bool IsEmpty => this.Equals(Empty);
// /// <summary>
// /// Allows the implicit conversion of an instance of <see cref="Color"/> to a
// /// <see cref="Bgra32"/>.
// /// </summary>
// /// <param name="color">
// /// The instance of <see cref="Color"/> to convert.
// /// </param>
// /// <returns>
// /// An instance of <see cref="Bgra32"/>.
// /// </returns>
// public static implicit operator Bgra32(Color color)
// {
// color = color.Limited * 255f;
// return new Bgra32((byte)color.B, (byte)color.G, (byte)color.R, (byte)color.A);
// }
// /// <summary>
// /// Compares two <see cref="Bgra32"/> objects for equality.
// /// </summary>
// /// <param name="left">
// /// The <see cref="Bgra32"/> on the left side of the operand.
// /// </param>
// /// <param name="right">
// /// The <see cref="Bgra32"/> on the right side of the operand.
// /// </param>
// /// <returns>
// /// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
// /// </returns>
// public static bool operator ==(Bgra32 left, Bgra32 right)
// {
// return left.Equals(right);
// }
// /// <summary>
// /// Compares two <see cref="Bgra32"/> objects for inequality.
// /// </summary>
// /// <param name="left">
// /// The <see cref="Bgra32"/> on the left side of the operand.
// /// </param>
// /// <param name="right">
// /// The <see cref="Bgra32"/> on the right side of the operand.
// /// </param>
// /// <returns>
// /// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
// /// </returns>
// public static bool operator !=(Bgra32 left, Bgra32 right)
// {
// return !left.Equals(right);
// }
// /// <inheritdoc/>
// public override bool Equals(object obj)
// {
// if (obj is Bgra32)
// {
// Bgra32 color = (Bgra32)obj;
// return this.backingVector == color.backingVector;
// }
// return false;
// }
// /// <inheritdoc/>
// public override int GetHashCode()
// {
// return GetHashCode(this);
// }
// /// <inheritdoc/>
// public override string ToString()
// {
// if (this.IsEmpty)
// {
// return "Bgra32 [ Empty ]";
// }
// return $"Bgra32 [ B={this.B}, G={this.G}, R={this.R}, A={this.A} ]";
// }
// /// <inheritdoc/>
// public bool Equals(Bgra32 other)
// {
// return this.backingVector.Equals(other.backingVector);
// }
// /// <summary>
// /// Returns the hash code for this instance.
// /// </summary>
// /// <param name="color">
// /// The instance of <see cref="Cmyk"/> to return the hash code for.
// /// </param>
// /// <returns>
// /// A 32-bit signed integer that is the hash code for this instance.
// /// </returns>
// private static int GetHashCode(Bgra32 color) => color.backingVector.GetHashCode();
// }
//}

40
src/ImageProcessorCore/GenericImageFrame.cs

@ -0,0 +1,40 @@
// <copyright file="GenericImageFrame.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>
/// Represents a single frame in a animation.
/// </summary>
/// <typeparam name="TPacked">
/// The packed vector pixels format.
/// </typeparam>
public abstract class GenericImageFrame<TPacked> : ImageBase<TPacked>, IImageFrame<TPacked>
where TPacked : IPackedVector
{
/// <summary>
/// Initializes a new instance of the <see cref="GenericImageFrame{TPacked}"/> class.
/// </summary>
protected GenericImageFrame()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GenericImageFrame{TPacked}"/> class.
/// </summary>
/// <param name="other">
/// The other <see cref="GenericImageFrame{TPacked}"/> to create this instance from.
/// </param>
/// <exception cref="ArgumentNullException">
/// Thrown if the given <see cref="GenericImageFrame{TPacked}"/> is null.
/// </exception>
protected GenericImageFrame(GenericImageFrame<TPacked> other)
: base(other)
{
}
}
}

18
src/ImageProcessorCore/IImageBase.cs

@ -0,0 +1,18 @@
namespace ImageProcessorCore
{
public interface IImageBase<TPacked>
where TPacked : IPackedVector
{
Rectangle Bounds { get; }
int FrameDelay { get; set; }
int Height { get; }
double PixelRatio { get; }
TPacked[] Pixels { get; }
int Quality { get; set; }
int Width { get; }
void ClonePixels(int width, int height, TPacked[] pixels);
IPixelAccessor Lock();
void SetPixels(int width, int height, TPacked[] pixels);
}
}

7
src/ImageProcessorCore/IImageFrame.cs

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

33
src/ImageProcessorCore/IPixelAccessor.cs

@ -0,0 +1,33 @@
// <copyright file="IPixelAccessor.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>
/// Encapsulates properties to provides per-pixel access to an images pixels.
/// </summary>
public interface IPixelAccessor : IDisposable
{
/// <summary>
/// Gets or sets the pixel at the specified position.
/// </summary>
/// <param name="x">
/// The x-coordinate of the pixel. Must be greater
/// than zero and smaller than the width of the pixel.
/// </param>
/// <param name="y">
/// The y-coordinate of the pixel. Must be greater
/// than zero and smaller than the width of the pixel.
/// </param>
/// <returns>The <see cref="IPackedVector"/> at the specified position.</returns>
IPackedVector this[int x, int y]
{
get;
set;
}
}
}

259
src/ImageProcessorCore/Image.cs

@ -1,61 +1,19 @@
// <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
namespace ImageProcessorCore
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using Formats;
/// <summary>
/// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes.
/// </summary>
/// <remarks>
/// The image data is always stored in RGBA format, where the red, green, blue, and
/// alpha values are floating point numbers.
/// 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>
[DebuggerDisplay("Image: {Width}x{Height}")]
public class Image : ImageBase
public class Image : GenericImage<Bgra32>
{
/// <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"/> class.
/// </summary>
public Image()
{
this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat));
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> 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)
{
this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat));
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class
/// by making a copy from another image.
@ -63,9 +21,9 @@ namespace ImageProcessorCore
/// <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 other)
: base(other)
{
foreach (ImageFrame frame in other.Frames)
// TODO: Check this. Not sure why I was getting a cast warning.
foreach (ImageFrame frame in other.Frames.Cast<ImageFrame>())
{
if (frame != null)
{
@ -79,209 +37,10 @@ namespace ImageProcessorCore
this.CurrentImageFormat = other.CurrentImageFormat;
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> 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>
/// 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> Frames { get; } = new List<ImageFrame>();
/// <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; }
/// <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)
/// <inheritdoc/>
public override IPixelAccessor Lock()
{
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());
return new PixelAccessor(this);
}
}
}

87
src/ImageProcessorCore/ImageBase.cs

@ -8,25 +8,24 @@ namespace ImageProcessorCore
using System;
/// <summary>
/// The base class of all images. Encapsulates the basic properties and methods
/// required to manipulate images.
/// The base class of all images. Encapsulates the basic properties and methods required to manipulate images
/// in different pixel formats.
/// </summary>
public abstract class ImageBase
/// <typeparam name="TPacked">
/// The packed vector pixels format.
/// </typeparam>
public abstract class ImageBase<TPacked> : IImageBase<TPacked>
where TPacked : IPackedVector
{
/// <summary>
/// The array of pixels.
/// </summary>
private float[] pixelsArray;
/// <summary>
/// Initializes a new instance of the <see cref="ImageBase"/> class.
/// Initializes a new instance of the <see cref="ImageBase{TPacked}"/> class.
/// </summary>
protected ImageBase()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageBase"/> class.
/// Initializes a new instance of the <see cref="ImageBase{TPacked}"/> class.
/// </summary>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
@ -40,21 +39,19 @@ namespace ImageProcessorCore
this.Width = width;
this.Height = height;
// Assign the pointer and pixels.
this.pixelsArray = new float[width * height * 4];
this.Pixels = new TPacked[width * height];
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageBase"/> class.
/// Initializes a new instance of the <see cref="ImageBase{TPacked}"/> class.
/// </summary>
/// <param name="other">
/// The other <see cref="ImageBase"/> to create this instance from.
/// The other <see cref="ImageBase{TPacked}"/> to create this instance from.
/// </param>
/// <exception cref="ArgumentNullException">
/// Thrown if the given <see cref="ImageBase"/> is null.
/// Thrown if the given <see cref="ImageBase{TPacked}"/> is null.
/// </exception>
protected ImageBase(ImageBase other)
protected ImageBase(ImageBase<TPacked> other)
{
Guard.NotNull(other, nameof(other), "Other image cannot be null.");
@ -64,29 +61,14 @@ namespace ImageProcessorCore
this.FrameDelay = other.FrameDelay;
// Copy the pixels.
this.pixelsArray = new float[this.Width * this.Height * 4];
Array.Copy(other.pixelsArray, this.pixelsArray, other.pixelsArray.Length);
this.Pixels = new TPacked[this.Width * this.Height];
Array.Copy(other.Pixels, this.Pixels, other.Pixels.Length);
}
/// <summary>
/// Gets or sets the maximum allowable width in pixels.
/// </summary>
public static int MaxWidth { get; set; } = int.MaxValue;
/// <summary>
/// Gets or sets the maximum allowable height in pixels.
/// </summary>
public static int MaxHeight { get; set; } = int.MaxValue;
/// <summary>
/// Gets the image pixels as byte array.
/// Gets the pixels as an array of the given packed pixel format.
/// </summary>
/// <remarks>
/// The returned array has a length of Width * Height * 4 bytes
/// and stores the red, the green, the blue, and the alpha value for
/// each pixel in this order.
/// </remarks>
public float[] Pixels => this.pixelsArray;
public TPacked[] Pixels { get; private set; }
/// <summary>
/// Gets the width in pixels.
@ -127,15 +109,15 @@ namespace ImageProcessorCore
/// <param name="width">The new width of the image. Must be greater than zero.</param>
/// <param name="height">The new height of the image. Must be greater than zero.</param>
/// <param name="pixels">
/// The array with colors. Must be a multiple of four times the width and height.
/// The array with colors. Must be a multiple of the width and height.
/// </param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if either <paramref name="width"/> or <paramref name="height"/> are less than or equal to 0.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown if the <paramref name="pixels"/> length is not equal to Width * Height * 4.
/// Thrown if the <paramref name="pixels"/> length is not equal to Width * Height.
/// </exception>
public void SetPixels(int width, int height, float[] pixels)
public void SetPixels(int width, int height, TPacked[] pixels)
{
if (width <= 0)
{
@ -147,14 +129,14 @@ namespace ImageProcessorCore
throw new ArgumentOutOfRangeException(nameof(height), "Height must be greater than or equal than zero.");
}
if (pixels.Length != width * height * 4)
if (pixels.Length != width * height)
{
throw new ArgumentException("Pixel array must have the length of Width * Height * 4.");
throw new ArgumentException("Pixel array must have the length of Width * Height.");
}
this.Width = width;
this.Height = height;
this.pixelsArray = pixels;
this.Pixels = pixels;
}
/// <summary>
@ -170,11 +152,11 @@ namespace ImageProcessorCore
/// Thrown if either <paramref name="width"/> or <paramref name="height"/> are less than or equal to 0.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown if the <paramref name="pixels"/> length is not equal to Width * Height * 4.
/// Thrown if the <paramref name="pixels"/> length is not equal to Width * Height.
/// </exception>
public void ClonePixels(int width, int height, float[] pixels)
public void ClonePixels(int width, int height, TPacked[] pixels)
{
if (width <= 0)
if (width <= 0)
{
throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than or equals than zero.");
}
@ -184,17 +166,17 @@ namespace ImageProcessorCore
throw new ArgumentOutOfRangeException(nameof(height), "Height must be greater than or equal than zero.");
}
if (pixels.Length != width * height * 4)
if (pixels.Length != width * height)
{
throw new ArgumentException("Pixel array must have the length of Width * Height * 4.");
throw new ArgumentException("Pixel array must have the length of Width * Height.");
}
this.Width = width;
this.Height = height;
// Copy the pixels.
this.pixelsArray = new float[pixels.Length];
Array.Copy(pixels, this.pixelsArray, pixels.Length);
this.Pixels = new TPacked[pixels.Length];
Array.Copy(pixels, this.Pixels, pixels.Length);
}
/// <summary>
@ -203,10 +185,7 @@ namespace ImageProcessorCore
/// It is imperative that the accessor is correctly disposed off after use.
/// </remarks>
/// </summary>
/// <returns>The <see cref="PixelAccessor"/></returns>
public PixelAccessor Lock()
{
return new PixelAccessor(this);
}
/// <returns>The <see cref="IPixelAccessor"/></returns>
public abstract IPixelAccessor Lock();
}
}

34
src/ImageProcessorCore/ImageFrame.cs

@ -1,36 +1,16 @@
// <copyright file="ImageFrame.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
namespace ImageProcessorCore
{
using System;
/// <summary>
/// Represents a single frame in a animation.
/// </summary>
public class ImageFrame : ImageBase
public class ImageFrame : GenericImageFrame<Bgra32>
{
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame"/> class.
/// </summary>
public ImageFrame()
public ImageFrame(ImageFrame frame)
: base(frame)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame"/> class.
/// </summary>
/// <param name="other">
/// The other <see cref="ImageBase"/> to create this instance from.
/// </param>
/// <exception cref="ArgumentNullException">
/// Thrown if the given <see cref="ImageFrame"/> is null.
/// </exception>
public ImageFrame(ImageFrame other)
: base(other)
/// <inhertdoc />
public override IPixelAccessor Lock()
{
return new PixelAccessor(this);
}
}
}

168
src/ImageProcessorCore/PackedVector/Bgra32.cs

@ -0,0 +1,168 @@
// <copyright file="Bgra32.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.Numerics;
/// <summary>
/// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 1.
/// </summary>
public struct Bgra32 : IPackedVector<uint>, IEquatable<Bgra32>
{
/// <summary>
/// Initializes a new instance of the <see cref="Bgra32"/> struct.
/// </summary>
/// <param name="b">The blue component.</param>
/// <param name="g">The green component.</param>
/// <param name="r">The red component.</param>
/// <param name="a">The alpha component.</param>
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);
}
/// <summary>
/// Initializes a new instance of the <see cref="Bgra32"/> struct.
/// </summary>
/// <param name="vector">
/// The vector containing the components for the packed vector.
/// </param>
public Bgra32(Vector4 vector)
{
Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f;
this.PackedValue = Pack(ref clamped);
}
/// <inheritdoc/>
public uint PackedValue { get; set; }
/// <summary>
/// Compares two <see cref="Bgra32"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Bgra32"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Bgra32"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(Bgra32 left, Bgra32 right)
{
return left.PackedValue == right.PackedValue;
}
/// <summary>
/// Compares two <see cref="Bgra32"/> objects for equality.
/// </summary>
/// <param name="left">The <see cref="Bgra32"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="Bgra32"/> on the right side of the operand.</param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(Bgra32 left, Bgra32 right)
{
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);
}
/// <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);
}
/// <inheritdoc/>
public Vector4 ToVector4()
{
return new Vector4(
this.PackedValue & 0xFF,
(this.PackedValue >> 8) & 0xFF,
(this.PackedValue >> 16) & 0xFF,
(this.PackedValue >> 24) & 0xFF) / 255f;
}
/// <inheritdoc/>
public byte[] ToBytes()
{
return new[]
{
(byte)(this.PackedValue & 0xFF),
(byte)((this.PackedValue >> 8) & 0xFF),
(byte)((this.PackedValue >> 16) & 0xFF),
(byte)((this.PackedValue >> 24) & 0xFF)
};
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
return (obj is Bgra32) && this.Equals((Bgra32)obj);
}
/// <inheritdoc/>
public bool Equals(Bgra32 other)
{
return this.PackedValue == other.PackedValue;
}
/// <summary>
/// Gets a string representation of the packed vector.
/// </summary>
/// <returns>A string representation of the packed vector.</returns>
public override string ToString()
{
return this.ToVector4().ToString();
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
/// <summary>
/// Sets the packed representation from the given component values.
/// </summary>
/// <param name="vector">
/// The vector containing the components for the packed vector.
/// </param>
/// <returns>
/// The <see cref="uint"/>.
/// </returns>
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);
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="packed">
/// The instance of <see cref="Bgra32"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private int GetHashCode(Bgra32 packed)
{
return packed.PackedValue.GetHashCode();
}
}
}

61
src/ImageProcessorCore/PackedVector/IPackedVector.cs

@ -0,0 +1,61 @@
// <copyright file="IPackedVector.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.Numerics;
/// <summary>
/// An interface that converts packed vector types to and from <see cref="Vector4"/> values,
/// allowing multiple encodings to be manipulated in a generic way.
/// </summary>
/// <typeparam name="TPacked">
/// The type of object representing the packed value.
/// </typeparam>
public interface IPackedVector<TPacked> : IPackedVector
where TPacked : struct
{
/// <summary>
/// Gets or sets the packed representation of the value.
/// Typically packed in least to greatest significance order.
/// </summary>
TPacked PackedValue { get; set; }
}
/// <summary>
/// An interface that converts packed vector types to and from <see cref="Vector4"/> values.
/// </summary>
public interface IPackedVector
{
/// <summary>
/// Sets the packed representation from a <see cref="Vector4"/>.
/// </summary>
/// <param name="vector">The vector to pack.</param>
void PackVector(Vector4 vector);
/// <summary>
/// Sets the packed representation from a <see cref="Vector4"/>.
/// </summary>
/// <param name="x">The x-component.</param>
/// <param name="y">The y-component.</param>
/// <param name="z">The z-component.</param>
/// <param name="w">The w-component.</param>
void PackBytes(byte x, byte y, byte z, byte w);
/// <summary>
/// Expands the packed representation into a <see cref="Vector4"/>.
/// The vector components are typically expanded in least to greatest significance order.
/// </summary>
/// <returns>The <see cref="Vector4"/>.</returns>
Vector4 ToVector4();
/// <summary>
/// Expands the packed representation into a <see cref="T:byte[]"/>.
/// The bytes are typically expanded in least to greatest significance order.
/// </summary>
/// <returns>The <see cref="Vector4"/>.</returns>
byte[] ToBytes();
}
}

19
src/ImageProcessorCore/PixelAccessor.cs

@ -9,14 +9,14 @@ namespace ImageProcessorCore
using System.Runtime.InteropServices;
/// <summary>
/// Provides per-pixel access to an images pixels.
/// Provides per-pixel access to an images pixels as 8 bit unsigned components.
/// </summary>
public sealed unsafe class PixelAccessor : IDisposable
public sealed unsafe class PixelAccessor : IPixelAccessor
{
/// <summary>
/// The position of the first pixel in the bitmap.
/// </summary>
private Color* pixelsBase;
private Bgra32* pixelsBase;
/// <summary>
/// Provides a way to access the pixels from unmanaged memory.
@ -42,7 +42,7 @@ namespace ImageProcessorCore
/// <param name="image">
/// The image to provide pixel access for.
/// </param>
public PixelAccessor(ImageBase image)
public PixelAccessor(ImageBase<Bgra32> image)
{
Guard.NotNull(image, nameof(image));
Guard.MustBeGreaterThan(image.Width, 0, "image width");
@ -51,9 +51,8 @@ namespace ImageProcessorCore
this.Width = image.Width;
this.Height = image.Height;
// Assign the pointer.
this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned);
this.pixelsBase = (Color*)this.pixelsHandle.AddrOfPinnedObject().ToPointer();
this.pixelsBase = (Bgra32*)this.pixelsHandle.AddrOfPinnedObject().ToPointer();
}
/// <summary>
@ -85,8 +84,8 @@ namespace ImageProcessorCore
/// The y-coordinate of the pixel. Must be greater
/// than zero and smaller than the width of the pixel.
/// </param>
/// <returns>The <see cref="Color"/> at the specified position.</returns>
public Color this[int x, int y]
/// <returns>The <see cref="IPackedVector"/> at the specified position.</returns>
public IPackedVector this[int x, int y]
{
get
{
@ -117,7 +116,7 @@ namespace ImageProcessorCore
throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height.");
}
#endif
*(this.pixelsBase + ((y * this.Width) + x)) = value;
*(this.pixelsBase + ((y * this.Width) + x)) = (Bgra32)value;
}
}
@ -149,4 +148,4 @@ namespace ImageProcessorCore
GC.SuppressFinalize(this);
}
}
}
}

Loading…
Cancel
Save