Browse Source

Merge remote-tracking branch 'refs/remotes/origin/feature/colors' into V3

Former-commit-id: ba6b2de4807dd80b08a63cf61907023954eea676
Former-commit-id: dffed09b1486af8ffd105e22c29c4d5e9907a846
Former-commit-id: f32e945c059766c57c939fbb7de94c5e61b15d0a
pull/17/head
James Jackson-South 10 years ago
parent
commit
bccbbbb670
  1. 1
      .gitignore
  2. 397
      src/ImageProcessor/Colors/Bgra.cs
  3. 541
      src/ImageProcessor/Colors/Color.cs
  4. 204
      src/ImageProcessor/Colors/Formats/Bgra32.cs
  5. 160
      src/ImageProcessor/Colors/Formats/Cmyk.cs
  6. 132
      src/ImageProcessor/Colors/Formats/Hsv.cs
  7. 124
      src/ImageProcessor/Colors/Formats/YCbCr.cs
  8. 88
      src/ImageProcessor/Common/Extensions/EnumerableExtensions.cs
  9. 32
      src/ImageProcessor/Common/Helpers/ImageMaths.cs
  10. 142
      src/ImageProcessor/Common/Helpers/PixelOperations.cs
  11. 28
      src/ImageProcessor/Filters/Alpha.cs
  12. 64
      src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs
  13. 35
      src/ImageProcessor/Filters/ColorMatrix/Invert.cs
  14. 69
      src/ImageProcessor/Filters/Contrast.cs
  15. 31
      src/ImageProcessor/Filters/Invert.cs
  16. 5
      src/ImageProcessor/Formats/Bmp/BmpDecoder.cs
  17. 198
      src/ImageProcessor/Formats/Bmp/BmpDecoderCore.cs
  18. 10
      src/ImageProcessor/Formats/Bmp/BmpEncoder.cs
  19. 21
      src/ImageProcessor/Formats/Gif/GifDecoderCore.cs
  20. 4
      src/ImageProcessor/Formats/Gif/GifEncoder.cs
  21. 52
      src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs
  22. 8
      src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs
  23. 19
      src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs
  24. 68
      src/ImageProcessor/Formats/Jpg/JpegDecoder.cs
  25. 38
      src/ImageProcessor/Formats/Jpg/JpegEncoder.cs
  26. 41
      src/ImageProcessor/Formats/Png/GrayscaleReader.cs
  27. 16
      src/ImageProcessor/Formats/Png/IColorReader.cs
  28. 41
      src/ImageProcessor/Formats/Png/PaletteIndexReader.cs
  29. 9
      src/ImageProcessor/Formats/Png/PngDecoderCore.cs
  30. 25
      src/ImageProcessor/Formats/Png/PngEncoder.cs
  31. 39
      src/ImageProcessor/Formats/Png/TrueColorReader.cs
  32. 66
      src/ImageProcessor/Formats/Png/Zlib/InflaterInputBuffer.cs
  33. 10
      src/ImageProcessor/Formats/Png/Zlib/InflaterInputStream.cs
  34. 69
      src/ImageProcessor/Formats/Png/Zlib/ZipConstants.cs
  35. 8
      src/ImageProcessor/IImageBase.cs
  36. 55
      src/ImageProcessor/ImageBase.cs
  37. 17
      src/ImageProcessor/ImageProcessor.csproj
  38. 1
      src/ImageProcessor/ImageProcessor.csproj.DotSettings
  39. 2
      src/ImageProcessor/ParallelImageProcessor.cs
  40. 12
      src/ImageProcessor/Samplers/Resamplers/BicubicResampler.cs
  41. 4
      src/ImageProcessor/Samplers/Resamplers/BoxResampler.cs
  42. 8
      src/ImageProcessor/Samplers/Resamplers/CatmullRomResampler.cs
  43. 8
      src/ImageProcessor/Samplers/Resamplers/HermiteResampler.cs
  44. 15
      src/ImageProcessor/Samplers/Resamplers/IResampler.cs
  45. 4
      src/ImageProcessor/Samplers/Resamplers/Lanczos3Resampler.cs
  46. 4
      src/ImageProcessor/Samplers/Resamplers/Lanczos5Resampler.cs
  47. 4
      src/ImageProcessor/Samplers/Resamplers/Lanczos8Resampler.cs
  48. 8
      src/ImageProcessor/Samplers/Resamplers/MitchellNetravaliResampler.cs
  49. 8
      src/ImageProcessor/Samplers/Resamplers/RobidouxResampler.cs
  50. 8
      src/ImageProcessor/Samplers/Resamplers/RobidouxSharpResampler.cs
  51. 8
      src/ImageProcessor/Samplers/Resamplers/RobidouxSoftResampler.cs
  52. 8
      src/ImageProcessor/Samplers/Resamplers/SplineResampler.cs
  53. 4
      src/ImageProcessor/Samplers/Resamplers/TriangleResampler.cs
  54. 6
      src/ImageProcessor/Samplers/Resamplers/WelchResampler.cs
  55. 142
      src/ImageProcessor/Samplers/Resize.cs
  56. 13
      src/ImageProcessor/stylecop.json
  57. 206
      tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs
  58. 65
      tests/ImageProcessor.Tests/Colors/ColorTests.cs
  59. 8
      tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj
  60. 12
      tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
  61. 23
      tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs
  62. 7
      tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs
  63. 1
      tests/ImageProcessor.Tests/TestImages/Formats/Gif/ani.gif.REMOVED.git-id
  64. 1
      tests/ImageProcessor.Tests/TestImages/Formats/Png/splash.png.REMOVED.git-id
  65. 5
      tests/ImageProcessor.Tests/packages.config

1
.gitignore

@ -108,6 +108,7 @@ TestResults
*.Cache
ClientBin
stylecop.*
!stylecop.json
~$*
*.dbmdl
Generated_Code #added for RIA/Silverlight projects

397
src/ImageProcessor/Colors/Bgra.cs

@ -1,397 +0,0 @@
// <copyright file="Bgra.cs" company="James South">
// Copyright (c) James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor
{
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
/// <summary>
/// Represents an BGRA (blue, green, red, alpha) color.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct Bgra : IEquatable<Bgra>
{
/// <summary>
/// Represents a <see cref="Bgra"/> that has B, G, R, and A values set to zero.
/// </summary>
public static readonly Bgra Empty = default(Bgra);
/// <summary>
/// Represents a transparent <see cref="Bgra"/> that has B, G, R, and A values set to 255, 255, 255, 0.
/// </summary>
public static readonly Bgra Transparent = new Bgra(255, 255, 255, 0);
/// <summary>
/// Represents a black <see cref="Bgra"/> that has B, G, R, and A values set to 0, 0, 0, 0.
/// </summary>
public static readonly Bgra Black = new Bgra(0, 0, 0, 255);
/// <summary>
/// Represents a white <see cref="Bgra"/> that has B, G, R, and A values set to 255, 255, 255, 255.
/// </summary>
public static readonly Bgra White = new Bgra(255, 255, 255, 255);
/// <summary>
/// Holds the blue component of the color
/// </summary>
[FieldOffset(0)]
public readonly byte B;
/// <summary>
/// Holds the green component of the color
/// </summary>
[FieldOffset(1)]
public readonly byte G;
/// <summary>
/// Holds the red component of the color
/// </summary>
[FieldOffset(2)]
public readonly byte R;
/// <summary>
/// Holds the alpha component of the color
/// </summary>
[FieldOffset(3)]
public readonly byte A;
/// <summary>
/// Permits the <see cref="Bgra"/> to be treated as a 32 bit integer.
/// </summary>
[FieldOffset(0)]
public readonly int BGRA;
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.0001f;
/// <summary>
/// Initializes a new instance of the <see cref="Bgra"/> struct.
/// </summary>
/// <param name="b">
/// The blue component of this <see cref="Bgra"/>.
/// </param>
/// <param name="g">
/// The green component of this <see cref="Bgra"/>.
/// </param>
/// <param name="r">
/// The red component of this <see cref="Bgra"/>.
/// </param>
public Bgra(byte b, byte g, byte r)
: this()
{
this.B = b;
this.G = g;
this.R = r;
this.A = 255;
}
/// <summary>
/// Initializes a new instance of the <see cref="Bgra"/> struct.
/// </summary>
/// <param name="b">
/// The blue component of this <see cref="Bgra"/>.
/// </param>
/// <param name="g">
/// The green component of this <see cref="Bgra"/>.
/// </param>
/// <param name="r">
/// The red component of this <see cref="Bgra"/>.
/// </param>
/// <param name="a">
/// The alpha component of this <see cref="Bgra"/>.
/// </param>
public Bgra(byte b, byte g, byte r, byte a)
: this()
{
this.B = b;
this.G = g;
this.R = r;
this.A = a;
}
/// <summary>
/// Initializes a new instance of the <see cref="Bgra"/> struct.
/// </summary>
/// <param name="bgra">
/// The combined color components.
/// </param>
public Bgra(int bgra)
: this()
{
this.BGRA = bgra;
}
/// <summary>
/// Initializes a new instance of the <see cref="Bgra"/> struct.
/// </summary>
/// <param name="hex">
/// The hexadecimal representation of the combined color components arranged
/// in rgb, rrggbb, or aarrggbb format to match web syntax.
/// </param>
public Bgra(string hex)
: this()
{
// Hexadecimal representations are layed out AARRGGBB to we need to do some reordering.
hex = hex.StartsWith("#") ? hex.Substring(1) : hex;
if (hex.Length != 8 && hex.Length != 6 && hex.Length != 3)
{
throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex));
}
if (hex.Length == 8)
{
this.B = Convert.ToByte(hex.Substring(6, 2), 16);
this.G = Convert.ToByte(hex.Substring(4, 2), 16);
this.R = Convert.ToByte(hex.Substring(2, 2), 16);
this.A = Convert.ToByte(hex.Substring(0, 2), 16);
}
else if (hex.Length == 6)
{
this.B = Convert.ToByte(hex.Substring(4, 2), 16);
this.G = Convert.ToByte(hex.Substring(2, 2), 16);
this.R = Convert.ToByte(hex.Substring(0, 2), 16);
this.A = 255;
}
else
{
string b = char.ToString(hex[2]);
string g = char.ToString(hex[1]);
string r = char.ToString(hex[0]);
this.B = Convert.ToByte(b + b, 16);
this.G = Convert.ToByte(g + g, 16);
this.R = Convert.ToByte(r + r, 16);
this.A = 255;
}
}
/// <summary>
/// Gets a value indicating whether this <see cref="Bgra"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.B == 0 && this.G == 0 && this.R == 0 && this.A == 0;
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Hsv"/> to a
/// <see cref="Bgra"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Hsv"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Bgra"/>.
/// </returns>
public static implicit operator Bgra(Hsv color)
{
float s = color.S / 100;
float v = color.V / 100;
if (Math.Abs(s) < Epsilon)
{
byte component = (byte)(v * 255);
return new Bgra(component, component, component, 255);
}
float h = (Math.Abs(color.H - 360) < Epsilon) ? 0 : color.H / 60;
int i = (int)Math.Truncate(h);
float f = h - i;
float p = v * (1.0f - s);
float q = v * (1.0f - (s * f));
float t = v * (1.0f - (s * (1.0f - f)));
float r, g, b;
switch (i)
{
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
default:
r = v;
g = p;
b = q;
break;
}
return new Bgra((byte)Math.Round(b * 255), (byte)Math.Round(g * 255), (byte)Math.Round(r * 255));
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="YCbCr"/> to a
/// <see cref="Bgra"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="YCbCr"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Bgra"/>.
/// </returns>
public static implicit operator Bgra(YCbCr color)
{
float y = color.Y;
float cb = color.Cb - 128;
float cr = color.Cr - 128;
byte b = (y + (1.772 * cb)).ToByte();
byte g = (y - (0.34414 * cb) - (0.71414 * cr)).ToByte();
byte r = (y + (1.402 * cr)).ToByte();
return new Bgra(b, g, r, 255);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Cmyk"/> to a
/// <see cref="Bgra"/>.
/// </summary>
/// <param name="cmykColor">
/// The instance of <see cref="Cmyk"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Bgra"/>.
/// </returns>
public static implicit operator Bgra(Cmyk cmykColor)
{
int red = Convert.ToInt32((1 - (cmykColor.C / 100)) * (1 - (cmykColor.K / 100)) * 255.0);
int green = Convert.ToInt32((1 - (cmykColor.M / 100)) * (1 - (cmykColor.K / 100)) * 255.0);
int blue = Convert.ToInt32((1 - (cmykColor.Y / 100)) * (1 - (cmykColor.K / 100)) * 255.0);
return new Bgra(blue.ToByte(), green.ToByte(), red.ToByte());
}
/// <summary>
/// Compares two <see cref="Bgra"/> objects. The result specifies whether the values
/// of the <see cref="Bgra.B"/>, <see cref="Bgra.G"/>, <see cref="Bgra.R"/>, and <see cref="Bgra.A"/>
/// properties of the two <see cref="Bgra"/> objects are equal.
/// </summary>
/// <param name="left">
/// The <see cref="Bgra"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Bgra"/> 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 ==(Bgra left, Bgra right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="Bgra"/> objects. The result specifies whether the values
/// of the <see cref="Bgra.B"/>, <see cref="Bgra.G"/>, <see cref="Bgra.R"/>, and <see cref="Bgra.A"/>
/// properties of the two <see cref="Bgra"/> objects are unequal.
/// </summary>
/// <param name="left">
/// The <see cref="Bgra"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Bgra"/> 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 !=(Bgra left, Bgra right)
{
return !left.Equals(right);
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
/// <returns>
/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
/// </returns>
/// <param name="obj">Another object to compare to. </param>
public override bool Equals(object obj)
{
if (obj is Bgra)
{
Bgra color = (Bgra)obj;
return this.BGRA == color.BGRA;
}
return false;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
public override int GetHashCode()
{
unchecked
{
int hashCode = this.B.GetHashCode();
hashCode = (hashCode * 397) ^ this.G.GetHashCode();
hashCode = (hashCode * 397) ^ this.R.GetHashCode();
hashCode = (hashCode * 397) ^ this.A.GetHashCode();
return hashCode;
}
}
/// <summary>
/// Returns the fully qualified type name of this instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> containing a fully qualified type name.
/// </returns>
public override string ToString()
{
if (this.IsEmpty)
{
return "Color [ Empty ]";
}
return $"Color [ B={this.B}, G={this.G}, R={this.R}, A={this.A} ]";
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <returns>
/// True if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public bool Equals(Bgra other)
{
return this.BGRA == other.BGRA;
}
}
}

541
src/ImageProcessor/Colors/Color.cs

@ -0,0 +1,541 @@
// <copyright file="Color.cs" company="James South">
// Copyright (c) James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Represents a four-component color using red, green, blue, and alpha data.
/// </summary>
/// <remarks>
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
/// as it avoids the need to create new values for modification operations.
/// </remarks>
public struct Color : IEquatable<Color>
{
/// <summary>
/// Represents a <see cref="Color"/> that has R, G, B, and A values set to zero.
/// </summary>
public static readonly Color Empty = default(Color);
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.0001f;
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector4 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct with the alpha component set to 1.
/// </summary>
/// <param name="r">The red component of this <see cref="Color"/>.</param>
/// <param name="g">The green component of this <see cref="Color"/>.</param>
/// <param name="b">The blue component of this <see cref="Color"/>.</param>
public Color(float r, float g, float b)
: this(r, g, b, 1)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="r">The red component of this <see cref="Color"/>.</param>
/// <param name="g">The green component of this <see cref="Color"/>.</param>
/// <param name="b">The blue component of this <see cref="Color"/>.</param>
/// <param name="a">The alpha component of this <see cref="Color"/>.</param>
public Color(float r, float g, float b, float a)
: this()
{
this.backingVector.X = r;
this.backingVector.Y = g;
this.backingVector.Z = b;
this.backingVector.W = a;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="hex">
/// The hexadecimal representation of the combined color components arranged
/// in rgb, rrggbb, or aarrggbb format to match web syntax.
/// </param>
public Color(string hex)
: this()
{
// Hexadecimal representations are layed out AARRGGBB to we need to do some reordering.
hex = hex.StartsWith("#") ? hex.Substring(1) : hex;
if (hex.Length != 8 && hex.Length != 6 && hex.Length != 3)
{
throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex));
}
if (hex.Length == 8)
{
this.R = Convert.ToByte(hex.Substring(2, 2), 16) / 255f;
this.G = Convert.ToByte(hex.Substring(4, 2), 16) / 255f;
this.B = Convert.ToByte(hex.Substring(6, 2), 16) / 255f;
this.A = Convert.ToByte(hex.Substring(0, 2), 16) / 255f;
}
else if (hex.Length == 6)
{
this.R = Convert.ToByte(hex.Substring(0, 2), 16) / 255f;
this.G = Convert.ToByte(hex.Substring(2, 2), 16) / 255f;
this.B = Convert.ToByte(hex.Substring(4, 2), 16) / 255f;
this.A = 1;
}
else
{
string r = char.ToString(hex[0]);
string g = char.ToString(hex[1]);
string b = char.ToString(hex[2]);
this.B = Convert.ToByte(b + b, 16) / 255f;
this.G = Convert.ToByte(g + g, 16) / 255f;
this.R = Convert.ToByte(r + r, 16) / 255f;
this.A = 1;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="vector">
/// The vector.
/// </param>
public Color(Vector4 vector)
{
this.backingVector = vector;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="vector">
/// The vector representing the red, green, and blue componenets.
/// </param>
public Color(Vector3 vector)
{
this.backingVector = new Vector4(vector.X, vector.Y, vector.Z, 1);
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="vector">
/// The vector representing the red, green, and blue componenets.
/// </param>
/// <param name="alpha">The alpha component.</param>
public Color(Vector3 vector, float alpha)
{
this.backingVector = new Vector4(vector.X, vector.Y, vector.Z, alpha);
}
/// <summary>
/// Gets or sets the red component of the color.
/// </summary>
public float R
{
get
{
return this.backingVector.X;
}
set
{
this.backingVector.X = value;
}
}
/// <summary>
/// Gets or sets the green component of the color.
/// </summary>
public float G
{
get
{
return this.backingVector.Y;
}
set
{
this.backingVector.Y = value;
}
}
/// <summary>
/// Gets or sets the blue component of the color.
/// </summary>
public float B
{
get
{
return this.backingVector.Z;
}
set
{
this.backingVector.Z = value;
}
}
/// <summary>
/// Gets or sets the alpha component of the color.
/// </summary>
public float A
{
get
{
return this.backingVector.W;
}
set
{
this.backingVector.W = value;
}
}
/// <summary>
/// Gets a value indicating whether this <see cref="Color"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.backingVector.Equals(default(Vector4));
/// <summary>
/// Gets this color with the component values clamped from 0 to 1.
/// </summary>
public Color Limited
{
get
{
float r = this.R.Clamp(0, 1);
float g = this.G.Clamp(0, 1);
float b = this.B.Clamp(0, 1);
float a = this.A.Clamp(0, 1);
return new Color(r, g, b, a);
}
}
/// <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 Color(Bgra32 color)
{
return new Color(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Cmyk"/> to a
/// <see cref="Color"/>.
/// </summary>
/// <param name="cmykColor">The instance of <see cref="Cmyk"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>
public static implicit operator Color(Cmyk cmykColor)
{
float r = (1 - cmykColor.C) * (1 - cmykColor.K);
float g = (1 - cmykColor.M) * (1 - cmykColor.K);
float b = (1 - cmykColor.Y) * (1 - cmykColor.K);
return new Color(r, g, b);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="YCbCr"/> to a
/// <see cref="Color"/>.
/// </summary>
/// <param name="color">The instance of <see cref="YCbCr"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>
public static implicit operator Color(YCbCr color)
{
float y = color.Y;
float cb = color.Cb - 128;
float cr = color.Cr - 128;
float r = (float)(y + (1.402 * cr)) / 255f;
float g = (float)(y - (0.34414 * cb) - (0.71414 * cr)) / 255f;
float b = (float)(y + (1.772 * cb)) / 255f;
return new Color(r, g, b);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Hsv"/> to a
/// <see cref="Color"/>.
/// </summary>
/// <param name="color">The instance of <see cref="Hsv"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>
public static implicit operator Color(Hsv color)
{
float s = color.S;
float v = color.V;
if (Math.Abs(s) < Epsilon)
{
return new Color(v, v, v, 1);
}
float h = (Math.Abs(color.H - 360) < Epsilon) ? 0 : color.H / 60;
int i = (int)Math.Truncate(h);
float f = h - i;
float p = v * (1.0f - s);
float q = v * (1.0f - (s * f));
float t = v * (1.0f - (s * (1.0f - f)));
float r, g, b;
switch (i)
{
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
default:
r = v;
g = p;
b = q;
break;
}
return new Color(r, g, b);
}
/// <summary>
/// Computes the product of multiplying a color by a given factor.
/// </summary>
/// <param name="color">The color.</param>
/// <param name="factor">The multiplication factor.</param>
/// <returns>
/// The <see cref="Color"/>
/// </returns>
public static Color operator *(Color color, float factor)
{
return new Color(color.backingVector * factor);
}
/// <summary>
/// Computes the product of multiplying a color by a given factor.
/// </summary>
/// <param name="factor">The multiplication factor.</param>
/// <param name="color">The color.</param>
/// <returns>
/// The <see cref="Color"/>
/// </returns>
public static Color operator *(float factor, Color color)
{
return new Color(color.backingVector * factor);
}
/// <summary>
/// Computes the product of multiplying two colors.
/// </summary>
/// <param name="left">The color on the left hand of the operand.</param>
/// <param name="right">The color on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Color"/>
/// </returns>
public static Color operator *(Color left, Color right)
{
return new Color(left.backingVector * right.backingVector);
}
/// <summary>
/// Computes the sum of adding two colors.
/// </summary>
/// <param name="left">The color on the left hand of the operand.</param>
/// <param name="right">The color on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Color"/>
/// </returns>
public static Color operator +(Color left, Color right)
{
return new Color(left.R + right.R, left.G + right.G, left.B + right.B, left.A + right.A);
}
/// <summary>
/// Computes the difference left by subtracting one color from another.
/// </summary>
/// <param name="left">The color on the left hand of the operand.</param>
/// <param name="right">The color on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Color"/>
/// </returns>
public static Color operator -(Color left, Color right)
{
return new Color(left.R - right.R, left.G - right.G, left.B - right.B, left.A - right.A);
}
/// <summary>
/// Compares two <see cref="Color"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Color"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Color"/> 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 ==(Color left, Color right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="Hsv"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Color"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Color"/> 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 !=(Color left, Color right)
{
return !left.Equals(right);
}
/// <summary>
/// Returns a new color whose components are the average of the components of first and second.
/// </summary>
/// <param name="first">The first color.</param>
/// <param name="second">The second color.</param>
/// <returns>
/// The <see cref="Color"/>
/// </returns>
public static Color Average(Color first, Color second)
{
return new Color((first.backingVector + second.backingVector) * .5f);
}
/// <summary>
/// Linearly interpolates from one color to another based on the given amount.
/// </summary>
/// <param name="from">The first color value.</param>
/// <param name="to">The second color value.</param>
/// <param name="amount">
/// The weight value. At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
/// <returns>
/// The <see cref="Color"/>
/// </returns>
public static Color Lerp(Color from, Color to, float amount)
{
amount = amount.Clamp(0f, 1f);
return (from * (1 - amount)) + (to * amount);
}
/// <summary>
/// Gets a <see cref="Vector4"/> representation for this <see cref="Color"/>.
/// </summary>
/// <returns>A <see cref="Vector4"/> representation for this object.</returns>
public Vector4 ToVector4()
{
return new Vector4(this.R, this.G, this.B, this.A);
}
/// <summary>
/// Gets a <see cref="Vector3"/> representation for this <see cref="Color"/>.
/// </summary>
/// <returns>A <see cref="Vector3"/> representation for this object.</returns>
public Vector3 ToVector3()
{
return new Vector3(this.R, this.G, this.B);
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Color)
{
Color color = (Color)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 "Color [ Empty ]";
}
return $"Color [ R={this.R:#0.##}, G={this.G:#0.##}, B={this.B:#0.##}, A={this.A:#0.##} ]";
}
/// <inheritdoc/>
public bool Equals(Color other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Color"/> 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(Color color) => color.backingVector.GetHashCode();
}
}

204
src/ImageProcessor/Colors/Formats/Bgra32.cs

@ -0,0 +1,204 @@
// <copyright file="Bgra32.cs" company="James South">
// Copyright (c) James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor
{
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 <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>
public Bgra32(byte b, byte g, byte r)
: this(b, g, r, 255)
{
}
/// <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)
: this()
{
this.backingVector.X = b.Clamp(0, 255);
this.backingVector.Y = g.Clamp(0, 255);
this.backingVector.Z = r.Clamp(0, 255);
this.backingVector.W = a.Clamp(0, 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.backingVector.Equals(default(Vector4));
/// <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;
return new Bgra32((255f * color.B).ToByte(), (255f * color.G).ToByte(), (255f * color.R).ToByte(), (255f * color.A).ToByte());
}
/// <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);
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
/// <returns>
/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
/// </returns>
/// <param name="obj">Another object to compare to. </param>
public override bool Equals(object obj)
{
if (obj is Bgra32)
{
Bgra32 color = (Bgra32)obj;
return this.backingVector == color.backingVector;
}
return false;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
public override int GetHashCode()
{
return GetHashCode(this);
}
/// <summary>
/// Returns the fully qualified type name of this instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> containing a fully qualified type name.
/// </returns>
public override string ToString()
{
if (this.IsEmpty)
{
return "Color [ Empty ]";
}
return $"Color [ B={this.B}, G={this.G}, R={this.R}, A={this.A} ]";
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <returns>
/// True if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
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();
}
}

160
src/ImageProcessor/Colors/Cmyk.cs → src/ImageProcessor/Colors/Formats/Cmyk.cs

@ -7,6 +7,7 @@ namespace ImageProcessor
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Represents an CMYK (cyan, magenta, yellow, keyline) color.
@ -19,92 +20,95 @@ namespace ImageProcessor
public static readonly Cmyk Empty = default(Cmyk);
/// <summary>
/// Gets the cyan color component.
/// The epsilon for comparing floating point numbers.
/// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks>
public readonly float C;
private const float Epsilon = 0.0001f;
/// <summary>
/// Gets the magenta color component.
/// The backing vector for SIMD support.
/// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks>
public readonly float M;
private Vector4 backingVector;
/// <summary>
/// Gets the yellow color component.
/// Initializes a new instance of the <see cref="Cmyk"/> struct.
/// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks>
public readonly float Y;
/// <param name="cyan">The cyan component.</param>
/// <param name="magenta">The magenta component.</param>
/// <param name="yellow">The yellow component.</param>
/// <param name="keyline">The keyline black component.</param>
public Cmyk(float cyan, float magenta, float yellow, float keyline)
: this()
{
this.backingVector.X = Clamp(cyan);
this.backingVector.Y = Clamp(magenta);
this.backingVector.Z = Clamp(yellow);
this.backingVector.W = Clamp(keyline);
}
/// <summary>
/// Gets the keyline black color component.
/// Gets the cyan color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks>
public readonly float K;
public float C => this.backingVector.X;
/// <summary>
/// The epsilon for comparing floating point numbers.
/// Gets the magenta color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
private const float Epsilon = 0.0001f;
public float M => this.backingVector.Y;
/// <summary>
/// Initializes a new instance of the <see cref="Cmyk"/> struct.
/// Gets the yellow color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
/// <param name="cyan">The cyan component.</param>
/// <param name="magenta">The magenta component.</param>
/// <param name="yellow">The yellow component.</param>
/// <param name="keyline">The keyline black component.</param>
public Cmyk(float cyan, float magenta, float yellow, float keyline)
{
this.C = Clamp(cyan);
this.M = Clamp(magenta);
this.Y = Clamp(yellow);
this.K = Clamp(keyline);
}
public float Y => this.backingVector.Z;
/// <summary>
/// Gets the keyline black color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float K => this.backingVector.W;
/// <summary>
/// Gets a value indicating whether this <see cref="Cmyk"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => Math.Abs(this.C) < Epsilon
&& Math.Abs(this.M) < Epsilon
&& Math.Abs(this.Y) < Epsilon
&& Math.Abs(this.K) < Epsilon;
public bool IsEmpty => this.backingVector.Equals(default(Vector4));
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Bgra"/> to a
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a
/// <see cref="Cmyk"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Bgra"/> to convert.
/// The instance of <see cref="Bgra32"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Cmyk"/>.
/// </returns>
public static implicit operator Cmyk(Bgra color)
public static implicit operator Cmyk(Color color)
{
float c = (255f - color.R) / 255;
float m = (255f - color.G) / 255;
float y = (255f - color.B) / 255;
color = color.Limited;
float c = 1f - color.R;
float m = 1f - color.G;
float y = 1f - color.B;
float k = Math.Min(c, Math.Min(m, y));
if (Math.Abs(k - 1.0) <= Epsilon)
if (Math.Abs(k - 1.0f) <= Epsilon)
{
return new Cmyk(0, 0, 0, 100);
return new Cmyk(0, 0, 0, 1);
}
c = ((c - k) / (1 - k)) * 100;
m = ((m - k) / (1 - k)) * 100;
y = ((y - k) / (1 - k)) * 100;
c = (c - k) / (1 - k);
m = (m - k) / (1 - k);
y = (y - k) / (1 - k);
return new Cmyk(c, m, y, k * 100);
return new Cmyk(c, m, y, k);
}
/// <summary>
/// Compares two <see cref="Cmyk"/> objects. The result specifies whether the values
/// of the <see cref="Cmyk.C"/>, <see cref="Cmyk.M"/>, <see cref="Cmyk.Y"/>, and <see cref="Cmyk.K"/>
/// properties of the two <see cref="Cmyk"/> objects are equal.
/// Compares two <see cref="Cmyk"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Cmyk"/> on the left side of the operand.
@ -121,9 +125,7 @@ namespace ImageProcessor
}
/// <summary>
/// Compares two <see cref="Cmyk"/> objects. The result specifies whether the values
/// of the <see cref="Cmyk.C"/>, <see cref="Cmyk.M"/>, <see cref="Cmyk.Y"/>, and <see cref="Cmyk.K"/>
/// properties of the two <see cref="Cmyk"/> objects are unequal.
/// Compares two <see cref="Cmyk"/> objects for inequality
/// </summary>
/// <param name="left">
/// The <see cref="Cmyk"/> on the left side of the operand.
@ -139,52 +141,26 @@ namespace ImageProcessor
return !left.Equals(right);
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
/// <returns>
/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
/// </returns>
/// <param name="obj">Another object to compare to. </param>
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Cmyk)
{
Cmyk color = (Cmyk)obj;
return Math.Abs(this.C - color.C) < Epsilon
&& Math.Abs(this.M - color.M) < Epsilon
&& Math.Abs(this.Y - color.Y) < Epsilon
&& Math.Abs(this.K - color.K) < Epsilon;
return this.backingVector == color.backingVector;
}
return false;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
int hashCode = this.C.GetHashCode();
hashCode = (hashCode * 397) ^ this.M.GetHashCode();
hashCode = (hashCode * 397) ^ this.Y.GetHashCode();
hashCode = (hashCode * 397) ^ this.K.GetHashCode();
return hashCode;
}
return GetHashCode(this);
}
/// <summary>
/// Returns the fully qualified type name of this instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> containing a fully qualified type name.
/// </returns>
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
@ -195,19 +171,10 @@ namespace ImageProcessor
return $"Cmyk [ C={this.C:#0.##}, M={this.M:#0.##}, Y={this.Y:#0.##}, K={this.K:#0.##}]";
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <returns>
/// True if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
/// <inheritdoc/>
public bool Equals(Cmyk other)
{
return Math.Abs(this.C - other.C) < Epsilon
&& Math.Abs(this.M - other.M) < Epsilon
&& Math.Abs(this.Y - other.Y) < Epsilon
&& Math.Abs(this.K - other.Y) < Epsilon;
return this.backingVector.Equals(other.backingVector);
}
/// <summary>
@ -221,7 +188,18 @@ namespace ImageProcessor
/// </returns>
private static float Clamp(float value)
{
return value.Clamp(0, 100);
return value.Clamp(0, 1);
}
/// <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(Cmyk color) => color.backingVector.GetHashCode();
}
}

132
src/ImageProcessor/Colors/Hsv.cs → src/ImageProcessor/Colors/Formats/Hsv.cs

@ -7,6 +7,7 @@ namespace ImageProcessor
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness).
@ -19,27 +20,14 @@ namespace ImageProcessor
public static readonly Hsv Empty = default(Hsv);
/// <summary>
/// Gets the H hue component.
/// <remarks>A value ranging between 0 and 360.</remarks>
/// </summary>
public readonly float H;
/// <summary>
/// Gets the S saturation component.
/// <remarks>A value ranging between 0 and 100.</remarks>
/// </summary>
public readonly float S;
/// <summary>
/// Gets the V value (brightness) component.
/// <remarks>A value ranging between 0 and 100.</remarks>
/// The epsilon for comparing floating point numbers.
/// </summary>
public readonly float V;
private const float Epsilon = 0.0001f;
/// <summary>
/// The epsilon for comparing floating point numbers.
/// The backing vector for SIMD support.
/// </summary>
private const float Epsilon = 0.0001f;
private Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Hsv"/> struct.
@ -49,34 +37,49 @@ namespace ImageProcessor
/// <param name="v">The v value (brightness) component.</param>
public Hsv(float h, float s, float v)
{
this.H = h.Clamp(0, 360);
this.S = s.Clamp(0, 100);
this.V = v.Clamp(0, 100);
this.backingVector.X = h.Clamp(0, 360);
this.backingVector.Y = s.Clamp(0, 1);
this.backingVector.Z = v.Clamp(0, 1);
}
/// <summary>
/// Gets the hue component.
/// <remarks>A value ranging between 0 and 360.</remarks>
/// </summary>
public float H => this.backingVector.X;
/// <summary>
/// Gets the saturation component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float S => this.backingVector.Y;
/// <summary>
/// Gets the value (brightness) component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float V => this.backingVector.Z;
/// <summary>
/// Gets a value indicating whether this <see cref="Hsv"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => Math.Abs(this.H) < Epsilon
&& Math.Abs(this.S) < Epsilon
&& Math.Abs(this.V) < Epsilon;
public bool IsEmpty => this.backingVector.Equals(default(Vector3));
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Bgra"/> to a
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a
/// <see cref="Hsv"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Bgra"/> to convert.
/// </param>
/// <param name="color">The instance of <see cref="Color"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Hsv"/>.
/// </returns>
public static implicit operator Hsv(Bgra color)
public static implicit operator Hsv(Color color)
{
float r = color.R / 255f;
float g = color.G / 255f;
float b = color.B / 255f;
color = color.Limited;
float r = color.R;
float g = color.G;
float b = color.B;
float max = Math.Max(r, Math.Max(g, b));
float min = Math.Min(r, Math.Min(g, b));
@ -87,7 +90,7 @@ namespace ImageProcessor
if (Math.Abs(chroma) < Epsilon)
{
return new Hsv(0, s * 100, v * 100);
return new Hsv(0, s, v);
}
if (Math.Abs(chroma) < Epsilon)
@ -115,13 +118,11 @@ namespace ImageProcessor
s = chroma / v;
return new Hsv(h, s * 100, v * 100);
return new Hsv(h, s, v);
}
/// <summary>
/// Compares two <see cref="Hsv"/> objects. The result specifies whether the values
/// of the <see cref="Hsv.H"/>, <see cref="Hsv.S"/>, and <see cref="Hsv.V"/>
/// properties of the two <see cref="Hsv"/> objects are equal.
/// Compares two <see cref="Hsv"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Hsv"/> on the left side of the operand.
@ -138,9 +139,7 @@ namespace ImageProcessor
}
/// <summary>
/// Compares two <see cref="Hsv"/> objects. The result specifies whether the values
/// of the <see cref="Hsv.H"/>, <see cref="Hsv.S"/>, and <see cref="Hsv.V"/>
/// properties of the two <see cref="Hsv"/> objects are unequal.
/// Compares two <see cref="Hsv"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Hsv"/> on the left side of the operand.
@ -156,50 +155,26 @@ namespace ImageProcessor
return !left.Equals(right);
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
/// <returns>
/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
/// </returns>
/// <param name="obj">Another object to compare to. </param>
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Hsv)
{
Hsv color = (Hsv)obj;
return Math.Abs(this.H - color.H) < Epsilon
&& Math.Abs(this.S - color.S) < Epsilon
&& Math.Abs(this.V - color.V) < Epsilon;
return this.backingVector == color.backingVector;
}
return false;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
int hashCode = this.H.GetHashCode();
hashCode = (hashCode * 397) ^ this.S.GetHashCode();
hashCode = (hashCode * 397) ^ this.V.GetHashCode();
return hashCode;
}
return GetHashCode(this);
}
/// <summary>
/// Returns the fully qualified type name of this instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> containing a fully qualified type name.
/// </returns>
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
@ -210,18 +185,21 @@ namespace ImageProcessor
return $"Hsv [ H={this.H:#0.##}, S={this.S:#0.##}, V={this.V:#0.##} ]";
}
/// <inheritdoc/>
public bool Equals(Hsv other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// Returns the hash code for this instance.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Hsv"/> to return the hash code for.
/// </param>
/// <returns>
/// True if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public bool Equals(Hsv other)
{
return Math.Abs(this.H - other.H) < Epsilon
&& Math.Abs(this.S - other.S) < Epsilon
&& Math.Abs(this.V - other.V) < Epsilon;
}
private static int GetHashCode(Hsv color) => color.backingVector.GetHashCode();
}
}

124
src/ImageProcessor/Colors/YCbCr.cs → src/ImageProcessor/Colors/Formats/YCbCr.cs

@ -7,10 +7,11 @@ namespace ImageProcessor
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Represents an YCbCr (luminance, chroma, chroma) color conforming to the
/// ITU-R BT.601 standard used in digital imaging systems.
/// Full range standard used in digital imaging systems.
/// <see href="http://en.wikipedia.org/wiki/YCbCr"/>
/// </summary>
public struct YCbCr : IEquatable<YCbCr>
@ -21,64 +22,64 @@ namespace ImageProcessor
public static readonly YCbCr Empty = default(YCbCr);
/// <summary>
/// Gets the Y luminance component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// The backing vector for SIMD support.
/// </summary>
public readonly float Y;
private Vector3 backingVector;
/// <summary>
/// Gets the Cb chroma component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// Initializes a new instance of the <see cref="YCbCr"/> struct.
/// </summary>
public readonly float Cb;
/// <param name="y">The y luminance component.</param>
/// <param name="cb">The cb chroma component.</param>
/// <param name="cr">The cr chroma component.</param>
public YCbCr(float y, float cb, float cr)
: this()
{
this.backingVector.X = y.Clamp(0, 255);
this.backingVector.Y = cb.Clamp(0, 255);
this.backingVector.Z = cr.Clamp(0, 255);
}
/// <summary>
/// Gets the Cr chroma component.
/// Gets the Y luminance component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
public readonly float Cr;
public float Y => this.backingVector.X;
/// <summary>
/// The epsilon for comparing floating point numbers.
/// Gets the Cb chroma component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
private const float Epsilon = 0.0001f;
public float Cb => this.backingVector.Y;
/// <summary>
/// Initializes a new instance of the <see cref="YCbCr"/> struct.
/// Gets the Cr chroma component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
/// <param name="y">The y luminance component.</param>
/// <param name="cb">The cb chroma component.</param>
/// <param name="cr">The cr chroma component.</param>
public YCbCr(float y, float cb, float cr)
{
this.Y = y.ToByte();
this.Cb = cb.ToByte();
this.Cr = cr.ToByte();
}
public float Cr => this.backingVector.Z;
/// <summary>
/// Gets a value indicating whether this <see cref="YCbCr"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => Math.Abs(this.Y) < Epsilon
&& Math.Abs(this.Cb) < Epsilon
&& Math.Abs(this.Cr) < Epsilon;
public bool IsEmpty => this.backingVector.Equals(default(Vector3));
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Bgra"/> to a
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a
/// <see cref="YCbCr"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Bgra"/> to convert.
/// The instance of <see cref="Color"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="YCbCr"/>.
/// </returns>
public static implicit operator YCbCr(Bgra color)
public static implicit operator YCbCr(Color color)
{
byte b = color.B;
byte g = color.G;
byte r = color.R;
color = color.Limited;
float r = color.R * 255f;
float g = color.G * 255f;
float b = color.B * 255f;
float y = (float)((0.299 * r) + (0.587 * g) + (0.114 * b));
float cb = 128 + (float)((-0.168736 * r) - (0.331264 * g) + (0.5 * b));
@ -88,9 +89,7 @@ namespace ImageProcessor
}
/// <summary>
/// Compares two <see cref="YCbCr"/> objects. The result specifies whether the values
/// of the <see cref="YCbCr.Y"/>, <see cref="YCbCr.Cb"/>, and <see cref="YCbCr.Cr"/>
/// properties of the two <see cref="YCbCr"/> objects are equal.
/// Compares two <see cref="YCbCr"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="YCbCr"/> on the left side of the operand.
@ -107,9 +106,7 @@ namespace ImageProcessor
}
/// <summary>
/// Compares two <see cref="YCbCr"/> objects. The result specifies whether the values
/// of the <see cref="YCbCr.Y"/>, <see cref="YCbCr.Cb"/>, and <see cref="YCbCr.Cr"/>
/// properties of the two <see cref="YCbCr"/> objects are unequal.
/// Compares two <see cref="YCbCr"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="YCbCr"/> on the left side of the operand.
@ -125,50 +122,26 @@ namespace ImageProcessor
return !left.Equals(right);
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
/// <returns>
/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
/// </returns>
/// <param name="obj">Another object to compare to. </param>
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is YCbCr)
{
YCbCr color = (YCbCr)obj;
return Math.Abs(this.Y - color.Y) < Epsilon
&& Math.Abs(this.Cb - color.Cb) < Epsilon
&& Math.Abs(this.Cr - color.Cr) < Epsilon;
return this.backingVector == color.backingVector;
}
return false;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
int hashCode = this.Y.GetHashCode();
hashCode = (hashCode * 397) ^ this.Cb.GetHashCode();
hashCode = (hashCode * 397) ^ this.Cr.GetHashCode();
return hashCode;
}
return GetHashCode(this);
}
/// <summary>
/// Returns the fully qualified type name of this instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> containing a fully qualified type name.
/// </returns>
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
@ -179,18 +152,21 @@ namespace ImageProcessor
return $"YCbCr [ Y={this.Y:#0.##}, Cb={this.Cb:#0.##}, Cr={this.Cr:#0.##} ]";
}
/// <inheritdoc/>
public bool Equals(YCbCr other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// Returns the hash code for this instance.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Hsv"/> to return the hash code for.
/// </param>
/// <returns>
/// True if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public bool Equals(YCbCr other)
{
return Math.Abs(this.Y - other.Y) < Epsilon
&& Math.Abs(this.Cb - other.Cb) < Epsilon
&& Math.Abs(this.Cr - other.Cr) < Epsilon;
}
private static int GetHashCode(YCbCr color) => color.backingVector.GetHashCode();
}
}

88
src/ImageProcessor/Common/Extensions/EnumerableExtensions.cs

@ -0,0 +1,88 @@
// <copyright file="EnumerableExtensions.cs" company="James South">
// Copyright (c) James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor
{
using System;
using System.Collections.Generic;
/// <summary>
/// Encapsulates a series of time saving extension methods to the <see cref="T:System.Collections.IEnumerable"/> interface.
/// </summary>
public static class EnumerableExtensions
{
/// <summary>
/// Generates a sequence of integral numbers within a specified range.
/// </summary>
/// <param name="fromInclusive">
/// The start index, inclusive.
/// </param>
/// <param name="toExclusive">
/// The end index, exclusive.
/// </param>
/// <param name="step">
/// The incremental step.
/// </param>
/// <returns>
/// The <see cref="IEnumerable{Int32}"/> that contains a range of sequential integral numbers.
/// </returns>
public static IEnumerable<int> SteppedRange(int fromInclusive, int toExclusive, int step)
{
// Borrowed from Enumerable.Range
long num = (fromInclusive + toExclusive) - 1L;
if ((toExclusive < 0) || (num > 0x7fffffffL))
{
throw new ArgumentOutOfRangeException(nameof(toExclusive));
}
return RangeIterator(fromInclusive, i => i < toExclusive, step);
}
/// <summary>
/// Generates a sequence of integral numbers within a specified range.
/// </summary>
/// <param name="fromInclusive">
/// The start index, inclusive.
/// </param>
/// <param name="toDelegate">
/// A method that has one parameter and returns a <see cref="bool"/> calculating the end index
/// </param>
/// <param name="step">
/// The incremental step.
/// </param>
/// <returns>
/// The <see cref="IEnumerable{Int32}"/> that contains a range of sequential integral numbers.
/// </returns>
public static IEnumerable<int> SteppedRange(int fromInclusive, Func<int, bool> toDelegate, int step)
{
return RangeIterator(fromInclusive, toDelegate, step);
}
/// <summary>
/// Generates a sequence of integral numbers within a specified range.
/// </summary>
/// <param name="fromInclusive">
/// The start index, inclusive.
/// </param>
/// <param name="toDelegate">
/// A method that has one parameter and returns a <see cref="bool"/> calculating the end index
/// </param>
/// <param name="step">
/// The incremental step.
/// </param>
/// <returns>
/// The <see cref="IEnumerable{Int32}"/> that contains a range of sequential integral numbers.
/// </returns>
private static IEnumerable<int> RangeIterator(int fromInclusive, Func<int, bool> toDelegate, int step)
{
int i = fromInclusive;
while (toDelegate(i))
{
yield return i;
i += step;
}
}
}
}

32
src/ImageProcessor/Common/Helpers/ImageMaths.cs

@ -12,6 +12,12 @@ namespace ImageProcessor
/// </summary>
internal static class ImageMaths
{
/// <summary>
/// Represents PI, the ratio of a circle's circumference to its diameter.
/// </summary>
// ReSharper disable once InconsistentNaming
public const float PI = 3.1415926535897931f;
/// <summary>
/// Returns the result of a B-C filter against the given value.
/// <see href="http://www.imagemagick.org/Usage/filter/#cubic_bc"/>
@ -20,11 +26,11 @@ namespace ImageProcessor
/// <param name="b">The B-Spline curve variable.</param>
/// <param name="c">The Cardinal curve variable.</param>
/// <returns>
/// The <see cref="double"/>.
/// The <see cref="float"/>.
/// </returns>
public static double GetBcValue(double x, double b, double c)
public static float GetBcValue(float x, float b, float c)
{
double temp;
float temp;
if (x < 0)
{
@ -54,19 +60,19 @@ namespace ImageProcessor
/// The value to calculate the result for.
/// </param>
/// <returns>
/// The <see cref="double"/>.
/// The <see cref="float"/>.
/// </returns>
public static double SinC(double x)
public static float SinC(float x)
{
const double Epsilon = .0001;
const float Epsilon = .00001f;
if (Math.Abs(x) > Epsilon)
{
x *= Math.PI;
return Clean(Math.Sin(x) / x);
x *= PI;
return Clean((float)Math.Sin(x) / x);
}
return 1.0;
return 1.0f;
}
/// <summary>
@ -74,15 +80,15 @@ namespace ImageProcessor
/// </summary>
/// <param name="x">The value to clean.</param>
/// <returns>
/// The <see cref="double"/>
/// The <see cref="float"/>
/// </returns>.
private static double Clean(double x)
private static float Clean(float x)
{
const double Epsilon = .0001;
const float Epsilon = .00001f;
if (Math.Abs(x) < Epsilon)
{
return 0.0;
return 0f;
}
return x;

142
src/ImageProcessor/Common/Helpers/PixelOperations.cs

@ -12,102 +12,42 @@ namespace ImageProcessor
/// </summary>
public static class PixelOperations
{
/// <summary>
/// The array of bytes representing each possible value of color component
/// converted from sRGB to the linear color space.
/// </summary>
private static readonly Lazy<byte[]> LinearBytes = new Lazy<byte[]>(GetLinearBytes);
/// <summary>
/// The array of bytes representing each possible value of color component
/// converted from linear to the sRGB color space.
/// </summary>
private static readonly Lazy<byte[]> SrgbBytes = new Lazy<byte[]>(GetSrgbBytes);
/// <summary>
/// The array of bytes representing each possible value of color component
/// converted from gamma to the linear color space.
/// </summary>
private static readonly Lazy<byte[]> LinearGammaBytes = new Lazy<byte[]>(GetLinearGammaBytes);
/// <summary>
/// The array of bytes representing each possible value of color component
/// converted from linear to the gamma color space.
/// </summary>
private static readonly Lazy<byte[]> GammaLinearBytes = new Lazy<byte[]>(GetGammaLinearBytes);
/// <summary>
/// Converts an pixel from an sRGB color-space to the equivalent linear color-space.
/// </summary>
/// <param name="composite">
/// The <see cref="Bgra"/> to convert.
/// The <see cref="Bgra32"/> to convert.
/// </param>
/// <returns>
/// The <see cref="Bgra"/>.
/// The <see cref="Bgra32"/>.
/// </returns>
public static Bgra ToLinear(Bgra composite)
public static Color ToLinear(Color composite)
{
// Create only once and lazily.
// byte[] ramp = LinearGammaBytes.Value;
byte[] ramp = LinearBytes.Value;
// TODO: Figure out a way to either cache these values quickly or perform the calcuations together.
composite.R = SrgbToLinear(composite.R);
composite.G = SrgbToLinear(composite.G);
composite.B = SrgbToLinear(composite.B);
return new Bgra(ramp[composite.B], ramp[composite.G], ramp[composite.R], composite.A);
return composite;
}
/// <summary>
/// Converts a pixel from a linear color-space to the equivalent sRGB color-space.
/// </summary>
/// <param name="linear">
/// The <see cref="Bgra"/> to convert.
/// The <see cref="Bgra32"/> to convert.
/// </param>
/// <returns>
/// The <see cref="Bgra"/>.
/// </returns>
public static Bgra ToSrgb(Bgra linear)
{
// Create only once and lazily.
// byte[] ramp = GammaLinearBytes.Value;
byte[] ramp = SrgbBytes.Value;
return new Bgra(ramp[linear.B], ramp[linear.G], ramp[linear.R], linear.A);
}
/// <summary>
/// Gets an array of bytes representing each possible value of color component
/// converted from sRGB to the linear color space.
/// </summary>
/// <returns>
/// The <see cref="T:byte[]"/>.
/// </returns>
private static byte[] GetLinearBytes()
{
byte[] ramp = new byte[256];
for (int x = 0; x < 256; ++x)
{
byte val = (255f * SrgbToLinear(x / 255f)).ToByte();
ramp[x] = val;
}
return ramp;
}
/// <summary>
/// Gets an array of bytes representing each possible value of color component
/// converted from linear to the sRGB color space.
/// </summary>
/// <returns>
/// The <see cref="T:byte[]"/>.
/// The <see cref="Bgra32"/>.
/// </returns>
private static byte[] GetSrgbBytes()
public static Color ToSrgb(Color linear)
{
byte[] ramp = new byte[256];
for (int x = 0; x < 256; ++x)
{
byte val = (255f * LinearToSrgb(x / 255f)).ToByte();
ramp[x] = val;
}
// TODO: Figure out a way to either cache these values quickly or perform the calcuations together.
linear.R = LinearToSrgb(linear.R);
linear.G = LinearToSrgb(linear.G);
linear.B = LinearToSrgb(linear.B);
return ramp;
return linear;
}
/// <summary>
@ -121,14 +61,12 @@ namespace ImageProcessor
/// </returns>
private static float SrgbToLinear(float signal)
{
float a = 0.055f;
if (signal <= 0.04045)
if (signal <= 0.04045f)
{
return signal / 12.92f;
}
return (float)Math.Pow((signal + a) / (1 + a), 2.4);
return (float)Math.Pow((signal + 0.055f) / 1.055f, 2.4f);
}
/// <summary>
@ -142,52 +80,12 @@ namespace ImageProcessor
/// </returns>
private static float LinearToSrgb(float signal)
{
float a = 0.055f;
if (signal <= 0.0031308)
if (signal <= 0.0031308f)
{
return signal * 12.92f;
}
return ((float)((1 + a) * Math.Pow(signal, 1 / 2.4f))) - a;
}
/// <summary>
/// Gets an array of bytes representing each possible value of color component
/// converted from gamma to the linear color space.
/// </summary>
/// <returns>
/// The <see cref="T:byte[]"/>.
/// </returns>
private static byte[] GetLinearGammaBytes()
{
byte[] ramp = new byte[256];
for (int x = 0; x < 256; ++x)
{
byte val = (255f * Math.Pow(x / 255f, 2.2)).ToByte();
ramp[x] = val;
}
return ramp;
}
/// <summary>
/// Gets an array of bytes representing each possible value of color component
/// converted from linear to the gamma color space.
/// </summary>
/// <returns>
/// The <see cref="T:byte[]"/>.
/// </returns>
private static byte[] GetGammaLinearBytes()
{
byte[] ramp = new byte[256];
for (int x = 0; x < 256; ++x)
{
byte val = (255f * Math.Pow(x / 255f, 1 / 2.2)).ToByte();
ramp[x] = val;
}
return ramp;
return (1.055f * (float)Math.Pow(signal, 0.41666666f)) - 0.055f;
}
}
}

28
src/ImageProcessor/Filters/Alpha.cs

@ -6,6 +6,7 @@
namespace ImageProcessor.Filters
{
using System;
using System.Threading.Tasks;
/// <summary>
/// An <see cref="IImageProcessor"/> to change the Alpha of an <see cref="Image"/>.
@ -33,24 +34,27 @@ namespace ImageProcessor.Filters
/// <inheritdoc/>
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
double alpha = this.Value / 100.0;
float alpha = this.Value / 100f;
int sourceY = sourceRectangle.Y;
int sourceBottom = sourceRectangle.Bottom;
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
for (int y = startY; y < endY; y++)
{
if (y >= sourceY && y < sourceBottom)
{
for (int x = startX; x < endX; x++)
Parallel.For(
startY,
endY,
y =>
{
Bgra color = source[x, y];
double a = color.A * alpha;
target[x, y] = new Bgra(color.B, color.G, color.R, a.ToByte());
}
}
}
if (y >= sourceY && y < sourceBottom)
{
for (int x = startX; x < endX; x++)
{
Color color = source[x, y];
color.A = color.A * alpha;
target[x, y] = color;
}
}
});
}
}
}

64
src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs

@ -5,6 +5,8 @@
namespace ImageProcessor.Filters
{
using System.Threading.Tasks;
/// <summary>
/// The color matrix filter.
/// </summary>
@ -34,68 +36,56 @@ namespace ImageProcessor.Filters
/// <inheritdoc/>
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
bool gamma = this.GammaAdjust;
int sourceY = sourceRectangle.Y;
int sourceBottom = sourceRectangle.Bottom;
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
ColorMatrix matrix = this.Value;
Bgra previousColor = source[0, 0];
Bgra pixelValue = this.ApplyMatrix(previousColor, matrix);
for (int y = startY; y < endY; y++)
{
if (y >= sourceY && y < sourceBottom)
{
for (int x = startX; x < endX; x++)
Parallel.For(
startY,
endY,
y =>
{
Bgra sourceColor = source[x, y];
// Check if this is the same as the last pixel. If so use that value
// rather than calculating it again. This is an inexpensive optimization.
if (sourceColor != previousColor)
if (y >= sourceY && y < sourceBottom)
{
// Perform the operation on the pixel.
pixelValue = this.ApplyMatrix(sourceColor, matrix);
// And setup the previous pointer
previousColor = sourceColor;
for (int x = startX; x < endX; x++)
{
target[x, y] = ApplyMatrix(source[x, y], matrix, gamma);
}
}
target[x, y] = pixelValue;
}
}
}
});
}
/// <summary>
/// Applies the color matrix against the given color.
/// </summary>
/// <param name="sourceColor">The source color.</param>
/// <param name="color">The source color.</param>
/// <param name="matrix">The matrix.</param>
/// <param name="gamma">Whether to perform gamma adjustments.</param>
/// <returns>
/// The <see cref="Bgra"/>.
/// The <see cref="Color"/>.
/// </returns>
private Bgra ApplyMatrix(Bgra sourceColor, ColorMatrix matrix)
private static Color ApplyMatrix(Color color, ColorMatrix matrix, bool gamma)
{
bool gamma = this.GammaAdjust;
if (gamma)
{
sourceColor = PixelOperations.ToLinear(sourceColor);
color = PixelOperations.ToLinear(color);
}
int sr = sourceColor.R;
int sg = sourceColor.G;
int sb = sourceColor.B;
int sa = sourceColor.A;
float sr = color.R;
float sg = color.G;
float sb = color.B;
float sa = color.A;
// TODO: Investigate RGBAW
byte r = ((sr * matrix.Matrix00) + (sg * matrix.Matrix10) + (sb * matrix.Matrix20) + (sa * matrix.Matrix30) + (255f * matrix.Matrix40)).ToByte();
byte g = ((sr * matrix.Matrix01) + (sg * matrix.Matrix11) + (sb * matrix.Matrix21) + (sa * matrix.Matrix31) + (255f * matrix.Matrix41)).ToByte();
byte b = ((sr * matrix.Matrix02) + (sg * matrix.Matrix12) + (sb * matrix.Matrix22) + (sa * matrix.Matrix32) + (255f * matrix.Matrix42)).ToByte();
byte a = ((sr * matrix.Matrix03) + (sg * matrix.Matrix13) + (sb * matrix.Matrix23) + (sa * matrix.Matrix33) + (255f * matrix.Matrix43)).ToByte();
color.R = (sr * matrix.Matrix00) + (sg * matrix.Matrix10) + (sb * matrix.Matrix20) + (sa * matrix.Matrix30) + matrix.Matrix40;
color.G = (sr * matrix.Matrix01) + (sg * matrix.Matrix11) + (sb * matrix.Matrix21) + (sa * matrix.Matrix31) + matrix.Matrix41;
color.B = (sr * matrix.Matrix02) + (sg * matrix.Matrix12) + (sb * matrix.Matrix22) + (sa * matrix.Matrix32) + matrix.Matrix42;
color.A = (sr * matrix.Matrix03) + (sg * matrix.Matrix13) + (sb * matrix.Matrix23) + (sa * matrix.Matrix33) + matrix.Matrix43;
return gamma ? PixelOperations.ToSrgb(new Bgra(b, g, r, a)) : new Bgra(b, g, r, a);
return gamma ? PixelOperations.ToSrgb(color) : color;
}
}
}

35
src/ImageProcessor/Filters/ColorMatrix/Invert.cs

@ -1,35 +0,0 @@
// <copyright file="Invert.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor.Filters
{
/// <summary>
/// Inverts the colors of the image.
/// </summary>
public class Invert : ColorMatrixFilter
{
/// <summary>
/// The inversion matrix.
/// TODO: With gamma adjustment enabled this leaves the image too bright.
/// </summary>
private static readonly ColorMatrix Matrix = new ColorMatrix(
new[]
{
new float[] { -1, 0, 0, 0, 0 },
new float[] { 0, -1, 0, 0, 0 },
new float[] { 0, 0, -1, 0, 0 },
new float[] { 0, 0, 0, 1, 0 },
new float[] { 1, 1, 1, 0, 1 }
});
/// <summary>
/// Initializes a new instance of the <see cref="Invert"/> class.
/// </summary>
public Invert()
: base(Matrix, false)
{
}
}
}

69
src/ImageProcessor/Filters/Contrast.cs

@ -6,6 +6,7 @@
namespace ImageProcessor.Filters
{
using System;
using System.Threading.Tasks;
/// <summary>
/// An <see cref="IImageProcessor"/> to change the contrast of an <see cref="Image"/>.
@ -33,48 +34,52 @@ namespace ImageProcessor.Filters
/// <inheritdoc/>
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
double contrast = (100.0 + this.Value) / 100.0;
float contrast = (100f + this.Value) / 100f;
int sourceY = sourceRectangle.Y;
int sourceBottom = sourceRectangle.Bottom;
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
for (int y = startY; y < endY; y++)
{
if (y >= sourceY && y < sourceBottom)
{
for (int x = startX; x < endX; x++)
Parallel.For(
startY,
endY,
y =>
{
Bgra sourceColor = source[x, y];
sourceColor = PixelOperations.ToLinear(sourceColor);
if (y >= sourceY && y < sourceBottom)
{
for (int x = startX; x < endX; x++)
{
target[x, y] = AdjustContrast(source[x, y], contrast);
}
}
});
}
/// <summary>
/// Returns a <see cref="Color"/> with the contrast adjusted.
/// </summary>
/// <param name="color">The source color.</param>
/// <param name="contrast">The contrast adjustment factor.</param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
private static Color AdjustContrast(Color color, float contrast)
{
color = PixelOperations.ToLinear(color);
double r = sourceColor.R / 255.0;
r -= 0.5;
r *= contrast;
r += 0.5;
r *= 255;
r = r.ToByte();
color.R -= 0.5f;
color.R *= contrast;
color.R += 0.5f;
double g = sourceColor.G / 255.0;
g -= 0.5;
g *= contrast;
g += 0.5;
g *= 255;
g = g.ToByte();
color.G -= 0.5f;
color.G *= contrast;
color.G += 0.5f;
double b = sourceColor.B / 255.0;
b -= 0.5;
b *= contrast;
b += 0.5;
b *= 255;
b = b.ToByte();
color.B -= 0.5f;
color.B *= contrast;
color.B += 0.5f;
Bgra destinationColor = new Bgra(b.ToByte(), g.ToByte(), r.ToByte(), sourceColor.A);
destinationColor = PixelOperations.ToSrgb(destinationColor);
target[x, y] = destinationColor;
}
}
}
return PixelOperations.ToSrgb(color);
}
}
}

31
src/ImageProcessor/Filters/Invert.cs

@ -5,6 +5,8 @@
namespace ImageProcessor.Filters
{
using System.Threading.Tasks;
/// <summary>
/// An <see cref="IImageProcessor"/> to invert the colors of an <see cref="Image"/>.
/// </summary>
@ -18,19 +20,24 @@ namespace ImageProcessor.Filters
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
for (int y = startY; y < endY; y++)
{
if (y >= sourceY && y < sourceBottom)
{
for (int x = startX; x < endX; x++)
Parallel.For(
startY,
endY,
y =>
{
// TODO: This doesn't work for gamma test images.
Bgra color = source[x, y];
Bgra targetColor = new Bgra((255 - color.B).ToByte(), (255 - color.G).ToByte(), (255 - color.R).ToByte(), color.A);
target[x, y] = targetColor;
}
}
}
if (y >= sourceY && y < sourceBottom)
{
for (int x = startX; x < endX; x++)
{
// TODO: This doesn't work for gamma test images.
Color color = source[x, y];
color.R = 1 - color.R;
color.G = 1 - color.G;
color.B = 1 - color.B;
target[x, y] = color;
}
}
});
}
}
}

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

@ -67,9 +67,8 @@ namespace ImageProcessor.Formats
bool isBmp = false;
if (header.Length >= 2)
{
isBmp =
header[0] == 0x42 && // B
header[1] == 0x4D; // M
isBmp = header[0] == 0x42 && // B
header[1] == 0x4D; // M
}
return isBmp;

198
src/ImageProcessor/Formats/Bmp/BmpDecoderCore.cs

@ -1,17 +1,13 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="BmpDecoderCore.cs" company="James South">
// Copyright (c) James South and contributors.
// Licensed under the Apache License, Version 2.0.
// <copyright file="BmpDecoderCore.cs" company="James South">
// Copyright (c) James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Performs the bmp decoding operation.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
using System;
using System.IO;
using System.Threading.Tasks;
/// <summary>
/// Performs the bmp decoding operation.
@ -108,7 +104,7 @@ namespace ImageProcessor.Formats
+ $"bigger then the max allowed size '{ImageBase.MaxWidth}x{ImageBase.MaxHeight}'");
}
byte[] imageData = new byte[this.infoHeader.Width * this.infoHeader.Height * 4];
float[] imageData = new float[this.infoHeader.Width * this.infoHeader.Height * 4];
switch (this.infoHeader.Compression)
{
@ -179,12 +175,12 @@ namespace ImageProcessor.Formats
/// <summary>
/// Reads the color palette from the stream.
/// </summary>
/// <param name="imageData">The <see cref="T:byte[]"/> image data to assign the palette to.</param>
/// <param name="imageData">The <see cref="T:float[]"/> image data to assign the palette to.</param>
/// <param name="colors">The <see cref="T:byte[]"/> containing the colors.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="bits">The number of bits per pixel.</param>
private void ReadRgbPalette(byte[] imageData, byte[] colors, int width, int height, int bits)
private void ReadRgbPalette(float[] imageData, byte[] colors, int width, int height, int bits)
{
// Pixels per byte (bits per pixel)
int ppb = 8 / bits;
@ -194,7 +190,7 @@ namespace ImageProcessor.Formats
// Bit mask
int mask = 0xFF >> (8 - bits);
byte[] data = new byte[(arrayWidth * height)];
byte[] data = new byte[arrayWidth * height];
this.currentStream.Read(data, 0, data.Length);
@ -205,134 +201,154 @@ namespace ImageProcessor.Formats
alignment = 4 - alignment;
}
for (int y = 0; y < height; y++)
{
int rowOffset = y * (arrayWidth + alignment);
Parallel.For(
0,
height,
y =>
{
int rowOffset = y * (arrayWidth + alignment);
for (int x = 0; x < arrayWidth; x++)
{
int offset = rowOffset + x;
for (int x = 0; x < arrayWidth; x++)
{
int offset = rowOffset + x;
// Revert the y value, because bitmaps are saved from down to top
int row = Invert(y, height);
// Revert the y value, because bitmaps are saved from down to top
int row = Invert(y, height);
int colOffset = x * ppb;
int colOffset = x * ppb;
for (int shift = 0; shift < ppb && (colOffset + shift) < width; shift++)
{
int colorIndex = (data[offset] >> (8 - bits - (shift * bits))) & mask;
for (int shift = 0; shift < ppb && (colOffset + shift) < width; shift++)
{
int colorIndex = ((data[offset] >> (8 - bits - (shift * bits))) & mask) * 4;
int arrayOffset = ((row * width) + (colOffset + shift)) * 4;
int arrayOffset = ((row * width) + (colOffset + shift)) * 4;
imageData[arrayOffset + 0] = colors[colorIndex * 4];
imageData[arrayOffset + 1] = colors[(colorIndex * 4) + 1];
imageData[arrayOffset + 2] = colors[(colorIndex * 4) + 2];
imageData[arrayOffset + 3] = 255;
}
}
}
// We divide by 255 as we will store the colors in our floating point format.
// Stored in r-> g-> b-> a order.
imageData[arrayOffset] = colors[colorIndex + 2] / 255f; // r
imageData[arrayOffset + 1] = colors[colorIndex + 1] / 255f; // g
imageData[arrayOffset + 2] = colors[colorIndex] / 255f; // b
imageData[arrayOffset + 3] = 1; // a
}
}
});
}
/// <summary>
/// Reads the 16 bit color palette from the stream
/// </summary>
/// <param name="imageData">The <see cref="T:byte[]"/> image data to assign the palette to.</param>
/// <param name="imageData">The <see cref="T:float[]"/> image data to assign the palette to.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
private void ReadRgb16(byte[] imageData, int width, int height)
private void ReadRgb16(float[] imageData, int width, int height)
{
const int ScaleR = 256 / 32;
const int ScaleG = 256 / 64;
// We divide here as we will store the colors in our floating point format.
const int ScaleR = (256 / 32) / 32;
const int ScaleG = (256 / 64) / 64;
int alignment;
byte[] data = this.GetImageArray(width, height, 2, out alignment);
for (int y = 0; y < height; y++)
{
int rowOffset = y * ((width * 2) + alignment);
Parallel.For(
0,
height,
y =>
{
int rowOffset = y * ((width * 2) + alignment);
// Revert the y value, because bitmaps are saved from down to top
int row = Invert(y, height);
// Revert the y value, because bitmaps are saved from down to top
int row = Invert(y, height);
for (int x = 0; x < width; x++)
{
int offset = rowOffset + (x * 2);
for (int x = 0; x < width; x++)
{
int offset = rowOffset + (x * 2);
short temp = BitConverter.ToInt16(data, offset);
short temp = BitConverter.ToInt16(data, offset);
byte r = (byte)(((temp & Rgb16RMask) >> 11) * ScaleR);
byte g = (byte)(((temp & Rgb16GMask) >> 5) * ScaleG);
byte b = (byte)((temp & Rgb16BMask) * ScaleR);
float r = ((temp & Rgb16RMask) >> 11) * ScaleR;
float g = ((temp & Rgb16GMask) >> 5) * ScaleG;
float b = (temp & Rgb16BMask) * ScaleR;
int arrayOffset = ((row * width) + x) * 4;
int arrayOffset = ((row * width) + x) * 4;
imageData[arrayOffset + 0] = b;
imageData[arrayOffset + 1] = g;
imageData[arrayOffset + 2] = r;
imageData[arrayOffset + 3] = 255;
}
}
// Stored in r-> g-> b-> a order.
imageData[arrayOffset] = r;
imageData[arrayOffset + 1] = g;
imageData[arrayOffset + 2] = b;
imageData[arrayOffset + 3] = 1;
}
});
}
/// <summary>
/// Reads the 24 bit color palette from the stream
/// </summary>
/// <param name="imageData">The <see cref="T:byte[]"/> image data to assign the palette to.</param>
/// <param name="imageData">The <see cref="T:float[]"/> image data to assign the palette to.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
private void ReadRgb24(byte[] imageData, int width, int height)
private void ReadRgb24(float[] imageData, int width, int height)
{
int alignment;
byte[] data = this.GetImageArray(width, height, 3, out alignment);
for (int y = 0; y < height; y++)
{
int rowOffset = y * ((width * 3) + alignment);
// Revert the y value, because bitmaps are saved from down to top
int row = Invert(y, height);
Parallel.For(
0,
height,
y =>
{
int rowOffset = y * ((width * 3) + alignment);
for (int x = 0; x < width; x++)
{
int offset = rowOffset + (x * 3);
int arrayOffset = ((row * width) + x) * 4;
// Revert the y value, because bitmaps are saved from down to top
int row = Invert(y, height);
imageData[arrayOffset + 0] = data[offset + 0];
imageData[arrayOffset + 1] = data[offset + 1];
imageData[arrayOffset + 2] = data[offset + 2];
imageData[arrayOffset + 3] = 255;
}
}
for (int x = 0; x < width; x++)
{
int offset = rowOffset + (x * 3);
int arrayOffset = ((row * width) + x) * 4;
// We divide by 255 as we will store the colors in our floating point format.
// Stored in r-> g-> b-> a order.
imageData[arrayOffset] = data[offset + 2] / 255f;
imageData[arrayOffset + 1] = data[offset + 1] / 255f;
imageData[arrayOffset + 2] = data[offset] / 255f;
imageData[arrayOffset + 3] = 1;
}
});
}
/// <summary>
/// Reads the 32 bit color palette from the stream
/// </summary>
/// <param name="imageData">The <see cref="T:byte[]"/> image data to assign the palette to.</param>
/// <param name="imageData">The <see cref="T:float[]"/> image data to assign the palette to.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
private void ReadRgb32(byte[] imageData, int width, int height)
private void ReadRgb32(float[] imageData, int width, int height)
{
int alignment;
byte[] data = this.GetImageArray(width, height, 4, out alignment);
for (int y = 0; y < height; y++)
{
int rowOffset = y * ((width * 4) + alignment);
// Revert the y value, because bitmaps are saved from down to top
int row = Invert(y, height);
Parallel.For(
0,
height,
y =>
{
int rowOffset = y * ((width * 4) + alignment);
for (int x = 0; x < width; x++)
{
int offset = rowOffset + (x * 4);
// Revert the y value, because bitmaps are saved from down to top
int row = Invert(y, height);
var arrayOffset = ((row * width) + x) * 4;
imageData[arrayOffset + 0] = data[offset + 0];
imageData[arrayOffset + 1] = data[offset + 1];
imageData[arrayOffset + 2] = data[offset + 2];
imageData[arrayOffset + 3] = 255; // Can we get alpha here?
}
}
for (int x = 0; x < width; x++)
{
int offset = rowOffset + (x * 4);
int arrayOffset = ((row * width) + x) * 4;
// We divide by 255 as we will store the colors in our floating point format.
// Stored in r-> g-> b-> a order.
imageData[arrayOffset] = data[offset + 2] / 255f;
imageData[arrayOffset + 1] = data[offset + 1] / 255f;
imageData[arrayOffset + 2] = data[offset] / 255f;
imageData[arrayOffset + 3] = 1; // TODO: Can we use our real alpha here?
}
});
}
/// <summary>

10
src/ImageProcessor/Formats/Bmp/BmpEncoder.cs

@ -100,7 +100,7 @@ namespace ImageProcessor.Formats
amount = 4 - amount;
}
byte[] data = image.Pixels;
float[] data = image.Pixels;
for (int y = image.Height - 1; y >= 0; y--)
{
@ -108,9 +108,11 @@ namespace ImageProcessor.Formats
{
int offset = ((y * image.Width) + x) * 4;
writer.Write(data[offset + 0]);
writer.Write(data[offset + 1]);
writer.Write(data[offset + 2]);
// Limit the output range and multiply out from our floating point.
// Convert back to b-> g-> r-> a order.
writer.Write((byte)(data[offset + 2].Clamp(0, 1) * 255));
writer.Write((byte)(data[offset + 1].Clamp(0, 1) * 255));
writer.Write((byte)(data[offset].Clamp(0, 1) * 255));
}
for (int i = 0; i < amount; i++)

21
src/ImageProcessor/Formats/Gif/GifDecoderCore.cs

@ -31,7 +31,7 @@ namespace ImageProcessor.Formats
/// <summary>
/// The current frame.
/// </summary>
private byte[] currentFrame;
private float[] currentFrame;
/// <summary>
/// The logical screen descriptor.
@ -288,15 +288,15 @@ namespace ImageProcessor.Formats
if (this.currentFrame == null)
{
this.currentFrame = new byte[imageWidth * imageHeight * 4];
this.currentFrame = new float[imageWidth * imageHeight * 4];
}
byte[] lastFrame = null;
float[] lastFrame = null;
if (this.graphicsControlExtension != null &&
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
{
lastFrame = new byte[imageWidth * imageHeight * 4];
lastFrame = new float[imageWidth * imageHeight * 4];
Array.Copy(this.currentFrame, lastFrame, lastFrame.Length);
}
@ -352,18 +352,20 @@ namespace ImageProcessor.Formats
this.graphicsControlExtension.TransparencyFlag == false ||
this.graphicsControlExtension.TransparencyIndex != index)
{
// We divide by 255 as we will store the colors in our floating point format.
// Stored in r-> g-> b-> a order.
int indexOffset = index * 3;
this.currentFrame[offset + 0] = colorTable[indexOffset + 2];
this.currentFrame[offset + 1] = colorTable[indexOffset + 1];
this.currentFrame[offset + 2] = colorTable[indexOffset + 0];
this.currentFrame[offset + 3] = 255;
this.currentFrame[offset + 0] = colorTable[indexOffset] / 255f; // r
this.currentFrame[offset + 1] = colorTable[indexOffset + 1] / 255f; // g
this.currentFrame[offset + 2] = colorTable[indexOffset + 2] / 255f; // b
this.currentFrame[offset + 3] = 1; // a
}
i++;
}
}
byte[] pixels = new byte[imageWidth * imageHeight * 4];
float[] pixels = new float[imageWidth * imageHeight * 4];
Array.Copy(this.currentFrame, pixels, pixels.Length);
@ -406,6 +408,7 @@ namespace ImageProcessor.Formats
{
offset = ((y * imageWidth) + x) * 4;
// Stored in r-> g-> b-> a order.
this.currentFrame[offset + 0] = 0;
this.currentFrame[offset + 1] = 0;
this.currentFrame[offset + 2] = 0;

4
src/ImageProcessor/Formats/Gif/GifEncoder.cs

@ -127,7 +127,7 @@ namespace ImageProcessor.Formats
QuantizedImage quantizedImage = quantizer.Quantize(image);
// Grab the pallete and write it to the stream.
Bgra[] pallete = quantizedImage.Palette;
Bgra32[] pallete = quantizedImage.Palette;
int pixelCount = pallete.Length;
// Get max colors for bit depth.
@ -137,7 +137,7 @@ namespace ImageProcessor.Formats
for (int i = 0; i < pixelCount; i++)
{
int offset = i * 3;
Bgra color = pallete[i];
Bgra32 color = pallete[i];
colorTable[offset + 2] = color.B;
colorTable[offset + 1] = color.G;
colorTable[offset + 0] = color.R;

52
src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs

@ -70,7 +70,7 @@ namespace ImageProcessor.Formats
/// This function need only be overridden if your quantize algorithm needs two passes,
/// such as an Octree quantizer.
/// </remarks>
protected override void InitialQuantizePixel(Bgra pixel)
protected override void InitialQuantizePixel(Bgra32 pixel)
{
// Add the color to the Octree
this.octree.AddColor(pixel);
@ -85,7 +85,7 @@ namespace ImageProcessor.Formats
/// <returns>
/// The quantized value
/// </returns>
protected override byte QuantizePixel(Bgra pixel)
protected override byte QuantizePixel(Bgra32 pixel)
{
// The color at [maxColors] is set to transparent
byte paletteIndex = (byte)this.maxColors;
@ -105,13 +105,13 @@ namespace ImageProcessor.Formats
/// <returns>
/// The new color palette
/// </returns>
protected override List<Bgra> GetPalette()
protected override List<Bgra32> GetPalette()
{
// First off convert the Octree to maxColors colors
List<Bgra> palette = this.octree.Palletize(Math.Max(this.maxColors - 1, 1));
List<Bgra32> palette = this.octree.Palletize(Math.Max(this.maxColors - 1, 1));
// Add empty color for transparency
palette.Add(Bgra.Empty);
palette.Add(Bgra32.Empty);
return palette;
}
@ -190,18 +190,18 @@ namespace ImageProcessor.Formats
/// Add a given color value to the Octree
/// </summary>
/// <param name="pixel">
/// The <see cref="Bgra"/>containing color information to add.
/// The <see cref="Bgra32"/>containing color information to add.
/// </param>
public void AddColor(Bgra pixel)
public void AddColor(Bgra32 pixel)
{
// Check if this request is for the same color as the last
if (this.previousColor == pixel.BGRA)
if (this.previousColor == pixel.Bgra)
{
// If so, check if I have a previous node setup. This will only occur if the first color in the image
// happens to be black, with an alpha component of zero.
if (null == this.previousNode)
if (this.previousNode == null)
{
this.previousColor = pixel.BGRA;
this.previousColor = pixel.Bgra;
this.root.AddColor(pixel, this.maxColorBits, 0, this);
}
else
@ -212,7 +212,7 @@ namespace ImageProcessor.Formats
}
else
{
this.previousColor = pixel.BGRA;
this.previousColor = pixel.Bgra;
this.root.AddColor(pixel, this.maxColorBits, 0, this);
}
}
@ -226,7 +226,7 @@ namespace ImageProcessor.Formats
/// <returns>
/// An <see cref="List{Bgra}"/> with the palletized colors
/// </returns>
public List<Bgra> Palletize(int colorCount)
public List<Bgra32> Palletize(int colorCount)
{
while (this.Leaves > colorCount)
{
@ -234,7 +234,7 @@ namespace ImageProcessor.Formats
}
// Now palletize the nodes
List<Bgra> palette = new List<Bgra>(this.Leaves);
List<Bgra32> palette = new List<Bgra32>(this.Leaves);
int paletteIndex = 0;
this.root.ConstructPalette(palette, ref paletteIndex);
@ -246,12 +246,12 @@ namespace ImageProcessor.Formats
/// Get the palette index for the passed color
/// </summary>
/// <param name="pixel">
/// The <see cref="Bgra"/> containing the pixel data.
/// The <see cref="Bgra32"/> containing the pixel data.
/// </param>
/// <returns>
/// The index of the given structure.
/// </returns>
public int GetPaletteIndex(Bgra pixel)
public int GetPaletteIndex(Bgra32 pixel)
{
return this.root.GetPaletteIndex(pixel, 0);
}
@ -274,7 +274,7 @@ namespace ImageProcessor.Formats
{
// Find the deepest level containing at least one reducible node
int index = this.maxColorBits - 1;
while ((index > 0) && (null == this.reducibleNodes[index]))
while ((index > 0) && (this.reducibleNodes[index] == null))
{
index--;
}
@ -387,7 +387,7 @@ namespace ImageProcessor.Formats
/// <param name="octree">
/// The tree to which this node belongs
/// </param>
public void AddColor(Bgra pixel, int colorBits, int level, Octree octree)
public void AddColor(Bgra32 pixel, int colorBits, int level, Octree octree)
{
// Update the color information if this is a leaf
if (this.leaf)
@ -407,7 +407,7 @@ namespace ImageProcessor.Formats
OctreeNode child = this.children[index];
if (null == child)
if (child == null)
{
// Create a new child node and store it in the array
child = new OctreeNode(level + 1, colorBits, octree);
@ -431,7 +431,7 @@ namespace ImageProcessor.Formats
// Loop through all children and add their information to this node
for (int index = 0; index < 8; index++)
{
if (null != this.children[index])
if (this.children[index] != null)
{
this.red += this.children[index].red;
this.green += this.children[index].green;
@ -458,7 +458,7 @@ namespace ImageProcessor.Formats
/// <param name="index">
/// The current palette index
/// </param>
public void ConstructPalette(List<Bgra> palette, ref int index)
public void ConstructPalette(List<Bgra32> palette, ref int index)
{
if (this.leaf)
{
@ -470,14 +470,14 @@ namespace ImageProcessor.Formats
byte b = (this.blue / this.pixelCount).ToByte();
// And set the color of the palette entry
palette.Add(new Bgra(b, g, r));
palette.Add(new Bgra32(b, g, r));
}
else
{
// Loop through children looking for leaves
for (int i = 0; i < 8; i++)
{
if (null != this.children[i])
if (this.children[i] != null)
{
this.children[i].ConstructPalette(palette, ref index);
}
@ -489,7 +489,7 @@ namespace ImageProcessor.Formats
/// Return the palette index for the passed color
/// </summary>
/// <param name="pixel">
/// The <see cref="Bgra"/> representing the pixel.
/// The <see cref="Bgra32"/> representing the pixel.
/// </param>
/// <param name="level">
/// The level.
@ -497,7 +497,7 @@ namespace ImageProcessor.Formats
/// <returns>
/// The <see cref="int"/> representing the index of the pixel in the palette.
/// </returns>
public int GetPaletteIndex(Bgra pixel, int level)
public int GetPaletteIndex(Bgra32 pixel, int level)
{
int index = this.paletteIndex;
@ -508,7 +508,7 @@ namespace ImageProcessor.Formats
((pixel.G & Mask[level]) >> (shift - 1)) |
((pixel.B & Mask[level]) >> shift);
if (null != this.children[pixelIndex])
if (this.children[pixelIndex] != null)
{
index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1);
}
@ -527,7 +527,7 @@ namespace ImageProcessor.Formats
/// <param name="pixel">
/// The pixel to add.
/// </param>
public void Increment(Bgra pixel)
public void Increment(Bgra32 pixel)
{
this.pixelCount++;
this.red += pixel.R;

8
src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs

@ -19,7 +19,7 @@ namespace ImageProcessor.Formats
/// <param name="height">The image height.</param>
/// <param name="palette">The color palette.</param>
/// <param name="pixels">The quantized pixels.</param>
public QuantizedImage(int width, int height, Bgra[] palette, byte[] pixels)
public QuantizedImage(int width, int height, Bgra32[] palette, byte[] pixels)
{
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
@ -51,7 +51,7 @@ namespace ImageProcessor.Formats
/// <summary>
/// Gets the color palette of this <see cref="T:QuantizedImage"/>.
/// </summary>
public Bgra[] Palette { get; }
public Bgra32[] Palette { get; }
/// <summary>
/// Gets the pixels of this <see cref="T:QuantizedImage"/>.
@ -69,12 +69,12 @@ namespace ImageProcessor.Formats
Image image = new Image();
int pixelCount = this.Pixels.Length;
byte[] bgraPixels = new byte[pixelCount * 4];
float[] bgraPixels = new float[pixelCount * 4];
for (int i = 0; i < pixelCount; i++)
{
int offset = i * 4;
Bgra color = this.Palette[this.Pixels[i]];
Bgra32 color = this.Palette[this.Pixels[i]];
bgraPixels[offset + 0] = color.B;
bgraPixels[offset + 1] = color.G;
bgraPixels[offset + 2] = color.R;

19
src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs

@ -18,7 +18,7 @@ namespace ImageProcessor.Formats
private readonly bool singlePass;
/// <summary>
/// Initializes a new instance of the <see cref="Quantizer"/> class.
/// Initializes a new instance of the <see cref="Quantizer"/> class.
/// </summary>
/// <param name="singlePass">
/// If true, the quantization only needs to loop through the source pixels once
@ -56,7 +56,8 @@ namespace ImageProcessor.Formats
byte[] quantizedPixels = new byte[width * height];
List<Bgra> palette = this.GetPalette();
// Get the pallete
List<Bgra32> palette = this.GetPalette();
this.SecondPass(imageBase, quantizedPixels, width, height);
@ -94,8 +95,9 @@ namespace ImageProcessor.Formats
{
int i = 0;
// Convert the first pixel, so that I have values going into the loop
Bgra previousPixel = source[0, 0];
// Convert the first pixel, so that I have values going into the loop.
// Implicit cast here from Color.
Bgra32 previousPixel = source[0, 0];
byte pixelValue = this.QuantizePixel(previousPixel);
output[0] = pixelValue;
@ -104,7 +106,8 @@ namespace ImageProcessor.Formats
{
for (int x = 0; x < width; x++)
{
Bgra sourcePixel = source[x, y];
// Implicit cast here from Color.
Bgra32 sourcePixel = source[x, y];
// Check if this is the same as the last pixel. If so use that value
// rather than calculating it again. This is an inexpensive optimization.
@ -132,7 +135,7 @@ namespace ImageProcessor.Formats
/// This function need only be overridden if your quantize algorithm needs two passes,
/// such as an Octree quantizer.
/// </remarks>
protected virtual void InitialQuantizePixel(Bgra pixel)
protected virtual void InitialQuantizePixel(Bgra32 pixel)
{
}
@ -145,7 +148,7 @@ namespace ImageProcessor.Formats
/// <returns>
/// The quantized value
/// </returns>
protected abstract byte QuantizePixel(Bgra pixel);
protected abstract byte QuantizePixel(Bgra32 pixel);
/// <summary>
/// Retrieve the palette for the quantized image
@ -153,6 +156,6 @@ namespace ImageProcessor.Formats
/// <returns>
/// The new color palette
/// </returns>
protected abstract List<Bgra> GetPalette();
protected abstract List<Bgra32> GetPalette();
}
}

68
src/ImageProcessor/Formats/Jpg/JpegDecoder.cs

@ -7,6 +7,8 @@ namespace ImageProcessor.Formats
{
using System;
using System.IO;
using System.Threading.Tasks;
using BitMiracle.LibJpeg;
/// <summary>
@ -97,50 +99,41 @@ namespace ImageProcessor.Formats
int pixelWidth = jpg.Width;
int pixelHeight = jpg.Height;
byte[] pixels = new byte[pixelWidth * pixelHeight * 4];
float[] pixels = new float[pixelWidth * pixelHeight * 4];
if (!(jpg.Colorspace == Colorspace.RGB && jpg.BitsPerComponent == 8))
{
throw new NotSupportedException("JpegDecoder only support RGB color space.");
}
for (int y = 0; y < pixelHeight; y++)
{
SampleRow row = jpg.GetRow(y);
Parallel.For(
0,
pixelHeight,
y =>
{
SampleRow row = jpg.GetRow(y);
for (int x = 0; x < pixelWidth; x++)
{
Sample sample = row.GetAt(x);
for (int x = 0; x < pixelWidth; x++)
{
Sample sample = row.GetAt(x);
int offset = ((y * pixelWidth) + x) * 4;
int offset = ((y * pixelWidth) + x) * 4;
pixels[offset + 0] = (byte)sample[2];
pixels[offset + 1] = (byte)sample[1];
pixels[offset + 2] = (byte)sample[0];
pixels[offset + 3] = 255;
}
}
pixels[offset + 0] = sample[0] / 255f;
pixels[offset + 1] = sample[1] / 255f;
pixels[offset + 2] = sample[2] / 255f;
pixels[offset + 3] = 1;
}
});
image.SetPixels(pixelWidth, pixelHeight, pixels);
}
/// <summary>
///
/// Returns a value indicating whether the given bytes identify Jpeg data.
/// </summary>
/// <param name="header"></param>
/// <returns></returns>
private bool IsExif(byte[] header)
{
bool isExif =
header[6] == 0x45 && // E
header[7] == 0x78 && // x
header[8] == 0x69 && // i
header[9] == 0x66 && // f
header[10] == 0x00;
return isExif;
}
/// <param name="header">The bytes representing the file header.</param>
/// <returns>The <see cref="bool"/></returns>
private static bool IsJpeg(byte[] header)
{
bool isJpg =
@ -152,5 +145,22 @@ namespace ImageProcessor.Formats
return isJpg;
}
/// <summary>
/// Returns a value indicating whether the given bytes identify EXIF data.
/// </summary>
/// <param name="header">The bytes representing the file header.</param>
/// <returns>The <see cref="bool"/></returns>
private bool IsExif(byte[] header)
{
bool isExif =
header[6] == 0x45 && // E
header[7] == 0x78 && // x
header[8] == 0x69 && // i
header[9] == 0x66 && // f
header[10] == 0x00;
return isExif;
}
}
}

38
src/ImageProcessor/Formats/Jpg/JpegEncoder.cs

@ -7,6 +7,7 @@ namespace ImageProcessor.Formats
{
using System;
using System.IO;
using System.Threading.Tasks;
using BitMiracle.LibJpeg;
@ -88,26 +89,29 @@ namespace ImageProcessor.Formats
int pixelWidth = image.Width;
int pixelHeight = image.Height;
byte[] sourcePixels = image.Pixels;
float[] sourcePixels = image.Pixels;
SampleRow[] rows = new SampleRow[pixelHeight];
for (int y = 0; y < pixelHeight; y++)
{
byte[] samples = new byte[pixelWidth * 3];
for (int x = 0; x < pixelWidth; x++)
{
int start = x * 3;
int source = ((y * pixelWidth) + x) * 4;
samples[start] = sourcePixels[source + 2];
samples[start + 1] = sourcePixels[source + 1];
samples[start + 2] = sourcePixels[source];
}
rows[y] = new SampleRow(samples, pixelWidth, 8, 3);
}
Parallel.For(
0,
pixelHeight,
y =>
{
byte[] samples = new byte[pixelWidth * 3];
for (int x = 0; x < pixelWidth; x++)
{
int start = x * 3;
int source = ((y * pixelWidth) + x) * 4;
samples[start] = (byte)(sourcePixels[source].Clamp(0, 1) * 255);
samples[start + 1] = (byte)(sourcePixels[source + 1].Clamp(0, 1) * 255);
samples[start + 2] = (byte)(sourcePixels[source + 2].Clamp(0, 1) * 255);
}
rows[y] = new SampleRow(samples, pixelWidth, 8, 3);
});
JpegImage jpg = new JpegImage(rows, Colorspace.RGB);
jpg.WriteJpeg(stream, new CompressionParameters { Quality = this.Quality });

41
src/ImageProcessor/Formats/Png/GrayscaleReader.cs

@ -1,12 +1,7 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="GrayscaleReader.cs" company="James South">
// Copyright (c) James South and contributors.
// Licensed under the Apache License, Version 2.0.
// <copyright file="GrayscaleReader.cs" company="James South">
// Copyright (c) James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Color reader for reading grayscale colors from a png file.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
@ -37,31 +32,25 @@ namespace ImageProcessor.Formats
this.useAlpha = useAlpha;
}
/// <summary>
/// Reads the specified scanline.
/// </summary>
/// <param name="scanline">The scanline.</param>
/// <param name="pixels">The pixels, where the colors should be stored in BGRA format.</param>
/// <param name="header">
/// The header, which contains information about the png file, like
/// the width of the image and the height.
/// </param>
public void ReadScanline(byte[] scanline, byte[] pixels, PngHeader header)
/// <inheritdoc/>
public void ReadScanline(byte[] scanline, float[] pixels, PngHeader header)
{
int offset;
byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth);
// We divide by 255 as we will store the colors in our floating point format.
// Stored in r-> g-> b-> a order.
if (this.useAlpha)
{
for (int x = 0; x < header.Width / 2; x++)
{
offset = ((this.row * header.Width) + x) * 4;
pixels[offset + 0] = newScanline[x * 2];
pixels[offset + 1] = newScanline[x * 2];
pixels[offset + 2] = newScanline[x * 2];
pixels[offset + 3] = newScanline[(x * 2) + 1];
pixels[offset] = newScanline[x * 2] / 255f;
pixels[offset + 1] = newScanline[x * 2] / 255f;
pixels[offset + 2] = newScanline[x * 2] / 255f;
pixels[offset + 3] = newScanline[(x * 2) + 1] / 255f;
}
}
else
@ -70,10 +59,10 @@ namespace ImageProcessor.Formats
{
offset = ((this.row * header.Width) + x) * 4;
pixels[offset + 0] = newScanline[x];
pixels[offset + 1] = newScanline[x];
pixels[offset + 2] = newScanline[x];
pixels[offset + 3] = 255;
pixels[offset] = newScanline[x] / 255f;
pixels[offset + 1] = newScanline[x] / 255f;
pixels[offset + 2] = newScanline[x] / 255f;
pixels[offset + 3] = 1;
}
}

16
src/ImageProcessor/Formats/Png/IColorReader.cs

@ -1,18 +1,12 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IColorReader.cs" company="James South">
// Copyright (c) James South and contributors.
// Licensed under the Apache License, Version 2.0.
// <copyright file="IColorReader.cs" company="James South">
// Copyright (c) James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Encapsulates methods for color readers, which are responsible for reading
// different color formats from a png file.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
/// <summary>
/// Encapsulates methods for color readers, which are responsible for reading
/// Encapsulates methods for color readers, which are responsible for reading
/// different color formats from a png file.
/// </summary>
public interface IColorReader
@ -26,6 +20,6 @@ namespace ImageProcessor.Formats
/// The header, which contains information about the png file, like
/// the width of the image and the height.
/// </param>
void ReadScanline(byte[] scanline, byte[] pixels, PngHeader header);
void ReadScanline(byte[] scanline, float[] pixels, PngHeader header);
}
}

41
src/ImageProcessor/Formats/Png/PaletteIndexReader.cs

@ -1,12 +1,7 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PaletteIndexReader.cs" company="James South">
// Copyright (c) James South and contributors.
// Licensed under the Apache License, Version 2.0.
// <copyright file="PaletteIndexReader.cs" company="James South">
// Copyright (c) James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// A color reader for reading palette indices from the png file.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
@ -43,14 +38,8 @@ namespace ImageProcessor.Formats
this.paletteAlpha = paletteAlpha;
}
/// <summary>
/// Reads the specified scanline.
/// </summary>
/// <param name="scanline">The scanline.</param>
/// <param name="pixels">The pixels, where the colors should be stored in BGRA format.</param>
/// <param name="header">The header, which contains information about the png file, like
/// the width of the image and the height.</param>
public void ReadScanline(byte[] scanline, byte[] pixels, PngHeader header)
/// <inheritdoc/>
public void ReadScanline(byte[] scanline, float[] pixels, PngHeader header)
{
byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth);
int offset, index;
@ -65,13 +54,14 @@ namespace ImageProcessor.Formats
index = newScanline[i];
offset = ((this.row * header.Width) + i) * 4;
int pixelOffset = index * 3;
pixels[offset + 0] = this.palette[(index * 3) + 2];
pixels[offset + 1] = this.palette[(index * 3) + 1];
pixels[offset + 2] = this.palette[(index * 3) + 0];
pixels[offset] = this.palette[pixelOffset] / 255f;
pixels[offset + 1] = this.palette[pixelOffset + 1] / 255f;
pixels[offset + 2] = this.palette[pixelOffset + 2] / 255f;
pixels[offset + 3] = this.paletteAlpha.Length > index
? this.paletteAlpha[index]
: (byte)255;
? this.paletteAlpha[index] / 255f
: 1;
}
}
else
@ -81,11 +71,12 @@ namespace ImageProcessor.Formats
index = newScanline[i];
offset = ((this.row * header.Width) + i) * 4;
int pixelOffset = index * 3;
pixels[offset + 0] = this.palette[(index * 3) + 2];
pixels[offset + 1] = this.palette[(index * 3) + 1];
pixels[offset + 2] = this.palette[(index * 3) + 0];
pixels[offset + 3] = 255;
pixels[offset] = this.palette[pixelOffset] / 255f;
pixels[offset + 1] = this.palette[pixelOffset + 1] / 255f;
pixels[offset + 2] = this.palette[pixelOffset + 2] / 255f;
pixels[offset + 3] = 1;
}
}

9
src/ImageProcessor/Formats/Png/PngDecoderCore.cs

@ -16,9 +16,6 @@ namespace ImageProcessor.Formats
using System.Linq;
using System.Text;
//using ICSharpCode.SharpZipLib.Checksums;
//using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
/// <summary>
/// Performs the png decoding operation.
/// </summary>
@ -145,7 +142,7 @@ namespace ImageProcessor.Formats
+ $"max allowed size '{ImageBase.MaxWidth}x{ImageBase.MaxHeight}'");
}
byte[] pixels = new byte[this.header.Width * this.header.Height * 4];
float[] pixels = new float[this.header.Width * this.header.Height * 4];
PngColorTypeInformation colorTypeInformation = ColorTypes[this.header.ColorType];
@ -248,10 +245,10 @@ namespace ImageProcessor.Formats
/// </summary>
/// <param name="dataStream">The <see cref="MemoryStream"/> containing data.</param>
/// <param name="pixels">
/// The <see cref="T:byte[]"/> containing pixel data.</param>
/// The <see cref="T:float[]"/> containing pixel data.</param>
/// <param name="colorReader">The color reader.</param>
/// <param name="colorTypeInformation">The color type information.</param>
private void ReadScanlines(MemoryStream dataStream, byte[] pixels, IColorReader colorReader, PngColorTypeInformation colorTypeInformation)
private void ReadScanlines(MemoryStream dataStream, float[] pixels, IColorReader colorReader, PngColorTypeInformation colorTypeInformation)
{
dataStream.Position = 0;

25
src/ImageProcessor/Formats/Png/PngEncoder.cs

@ -8,9 +8,6 @@ namespace ImageProcessor.Formats
using System;
using System.IO;
//using ICSharpCode.SharpZipLib.Checksums;
//using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
/// <summary>
/// Image encoder for writing image data to a stream in png format.
/// </summary>
@ -36,7 +33,7 @@ namespace ImageProcessor.Formats
public int Quality { get; set; }
/// <inheritdoc/>
public string MimeType => "image/jpepngg";
public string MimeType => "image/png";
/// <inheritdoc/>
public string Extension => "png";
@ -225,7 +222,7 @@ namespace ImageProcessor.Formats
/// <param name="imageBase">The image base.</param>
private void WriteDataChunksFast(Stream stream, ImageBase imageBase)
{
byte[] pixels = imageBase.Pixels;
float[] pixels = imageBase.Pixels;
// Convert the pixel array to a new array for adding
// the filter byte.
@ -297,7 +294,7 @@ namespace ImageProcessor.Formats
/// <param name="imageBase">The image base.</param>
private void WriteDataChunks(Stream stream, ImageBase imageBase)
{
byte[] pixels = imageBase.Pixels;
float[] pixels = imageBase.Pixels;
byte[] data = new byte[(imageBase.Width * imageBase.Height * 4) + imageBase.Height];
@ -321,19 +318,19 @@ namespace ImageProcessor.Formats
// Calculate the offset for the original pixel array.
int pixelOffset = ((y * imageBase.Width) + x) * 4;
data[dataOffset + 0] = pixels[pixelOffset + 2];
data[dataOffset + 1] = pixels[pixelOffset + 1];
data[dataOffset + 2] = pixels[pixelOffset + 0];
data[dataOffset + 3] = pixels[pixelOffset + 3];
data[dataOffset] = (byte)(pixels[pixelOffset].Clamp(0, 1) * 255);
data[dataOffset + 1] = (byte)(pixels[pixelOffset + 1].Clamp(0, 1) * 255);
data[dataOffset + 2] = (byte)(pixels[pixelOffset + 2].Clamp(0, 1) * 255);
data[dataOffset + 3] = (byte)(pixels[pixelOffset + 3].Clamp(0, 1) * 255);
if (y > 0)
{
int lastOffset = (((y - 1) * imageBase.Width) + x) * 4;
data[dataOffset + 0] -= pixels[lastOffset + 2];
data[dataOffset + 1] -= pixels[lastOffset + 1];
data[dataOffset + 2] -= pixels[lastOffset + 0];
data[dataOffset + 3] -= pixels[lastOffset + 3];
data[dataOffset] -= (byte)(pixels[lastOffset].Clamp(0, 1) * 255);
data[dataOffset + 1] -= (byte)(pixels[lastOffset + 1].Clamp(0, 1) * 255);
data[dataOffset + 2] -= (byte)(pixels[lastOffset + 2].Clamp(0, 1) * 255);
data[dataOffset + 3] -= (byte)(pixels[lastOffset + 3].Clamp(0, 1) * 255);
}
}
}

39
src/ImageProcessor/Formats/Png/TrueColorReader.cs

@ -1,13 +1,7 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TrueColorReader.cs" company="James South">
// Copyright (c) James South and contributors.
// Licensed under the Apache License, Version 2.0.
// <copyright file="TrueColorReader.cs" company="James South">
// Copyright (c) James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Color reader for reading true colors from a png file. Only colors
// with 24 or 32 bit (3 or 4 bytes) per pixel are supported at the moment.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
@ -37,14 +31,8 @@ namespace ImageProcessor.Formats
this.useAlpha = useAlpha;
}
/// <summary>
/// Reads the specified scanline.
/// </summary>
/// <param name="scanline">The scanline.</param>
/// <param name="pixels">The pixels, where the colors should be stored in BGRA format.</param>
/// <param name="header">The header, which contains information about the png file, like
/// the width of the image and the height.</param>
public void ReadScanline(byte[] scanline, byte[] pixels, PngHeader header)
/// <inheritdoc/>
public void ReadScanline(byte[] scanline, float[] pixels, PngHeader header)
{
int offset;
@ -56,10 +44,10 @@ namespace ImageProcessor.Formats
{
offset = ((this.row * header.Width) + (x >> 2)) * 4;
pixels[offset + 0] = newScanline[x + 2];
pixels[offset + 1] = newScanline[x + 1];
pixels[offset + 2] = newScanline[x + 0];
pixels[offset + 3] = newScanline[x + 3];
pixels[offset + 0] = newScanline[x] / 255f;
pixels[offset + 1] = newScanline[x + 1] / 255f;
pixels[offset + 2] = newScanline[x + 2] / 255f;
pixels[offset + 3] = newScanline[x + 3] / 255f;
}
}
else
@ -67,11 +55,12 @@ namespace ImageProcessor.Formats
for (int x = 0; x < newScanline.Length / 3; x++)
{
offset = ((this.row * header.Width) + x) * 4;
int pixelOffset = x * 3;
pixels[offset + 0] = newScanline[(x * 3) + 2];
pixels[offset + 1] = newScanline[(x * 3) + 1];
pixels[offset + 2] = newScanline[(x * 3) + 0];
pixels[offset + 3] = 255;
pixels[offset + 0] = newScanline[pixelOffset] / 255f;
pixels[offset + 1] = newScanline[pixelOffset + 1] / 255f;
pixels[offset + 2] = newScanline[pixelOffset + 2] / 255f;
pixels[offset + 3] = 1;
}
}

66
src/ImageProcessor/Formats/Png/Zlib/InflaterInputBuffer.cs

@ -3,9 +3,6 @@
using System;
using System.IO;
//using ICSharpCode.SharpZipLib.Zip;
//using ICSharpCode.SharpZipLib.Zip.Compression;
/// <summary>
/// An input buffer customised for use by <see cref="InflaterInputStream"/>
/// </summary>
@ -14,7 +11,6 @@
/// </remarks>
public class InflaterInputBuffer
{
#region Constructors
/// <summary>
/// Initialise a new instance of <see cref="InflaterInputBuffer"/> with a default buffer size
/// </summary>
@ -39,7 +35,6 @@
rawData = new byte[bufferSize];
clearText = rawData;
}
#endregion
/// <summary>
/// Get the length of bytes bytes in the <see cref="RawData"/>
@ -127,17 +122,7 @@
toRead -= count;
}
#if !NETCF_1_0 && !NOCRYPTO
if (cryptoTransform != null)
{
clearTextLength = cryptoTransform.TransformBlock(rawData, 0, rawLength, clearText, 0);
}
else
#endif
{
clearTextLength = rawLength;
}
clearTextLength = rawLength;
available = clearTextLength;
}
@ -178,12 +163,14 @@
return 0;
}
}
int toCopy = Math.Min(currentLength, available);
System.Array.Copy(rawData, rawLength - (int)available, outBuffer, currentOffset, toCopy);
Array.Copy(rawData, rawLength - (int)available, outBuffer, currentOffset, toCopy);
currentOffset += toCopy;
currentLength -= toCopy;
available -= toCopy;
}
return length;
}
@ -270,57 +257,12 @@
return (uint)ReadLeInt() | ((long)ReadLeInt() << 32);
}
#if !NETCF_1_0 && !NOCRYPTO
/// <summary>
/// Get/set the <see cref="ICryptoTransform"/> to apply to any data.
/// </summary>
/// <remarks>Set this value to null to have no transform applied.</remarks>
public ICryptoTransform CryptoTransform
{
set
{
cryptoTransform = value;
if (cryptoTransform != null)
{
if (rawData == clearText)
{
if (internalClearText == null)
{
internalClearText = new byte[rawData.Length];
}
clearText = internalClearText;
}
clearTextLength = rawLength;
if (available > 0)
{
cryptoTransform.TransformBlock(rawData, rawLength - available, available, clearText, rawLength - available);
}
}
else
{
clearText = rawData;
clearTextLength = rawLength;
}
}
}
#endif
#region Instance Fields
int rawLength;
byte[] rawData;
int clearTextLength;
byte[] clearText;
#if !NETCF_1_0 && !NOCRYPTO
byte[] internalClearText;
#endif
int available;
#if !NETCF_1_0 && !NOCRYPTO
ICryptoTransform cryptoTransform;
#endif
Stream inputStream;
#endregion
}
}

10
src/ImageProcessor/Formats/Png/Zlib/InflaterInputStream.cs

@ -149,16 +149,6 @@
}
}
/// <summary>
/// Clear any cryptographic state.
/// </summary>
protected void StopDecrypting()
{
#if !NETCF_1_0 && !NOCRYPTO
inputBuffer.CryptoTransform = null;
#endif
}
/// <summary>
/// Returns 0 once the end of the stream (EOF) has been reached.
/// Otherwise returns 1.

69
src/ImageProcessor/Formats/Png/Zlib/ZipConstants.cs

@ -7,7 +7,6 @@
/// </summary>
public static class ZipConstants
{
#region Versions
/// <summary>
/// The version made by field for entries in the central header when created by this library
/// </summary>
@ -30,9 +29,7 @@
/// The version required for Zip64 extensions (4.5 or higher)
/// </summary>
public const int VersionZip64 = 45;
#endregion
#region Header Sizes
/// <summary>
/// Size of local entry header (excluding variable length fields at end)
/// </summary>
@ -62,9 +59,7 @@
/// Size of 'classic' cryptographic header stored before any entry data
/// </summary>
public const int CryptoHeaderSize = 12;
#endregion
#region Header Signatures
/// <summary>
/// Signature for local entry header
@ -121,52 +116,8 @@
/// End of central directory record signature
/// </summary>
public const int EndOfCentralDirectorySignature = 'P' | ('K' << 8) | (5 << 16) | (6 << 24);
#endregion
#if NETCF_1_0 || NETCF_2_0
// This isnt so great but is better than nothing.
// Trying to work out an appropriate OEM code page would be good.
// 850 is a good default for english speakers particularly in Europe.
static int defaultCodePage = CultureInfo.CurrentCulture.TextInfo.ANSICodePage;
#elif PCL
static Encoding defaultEncoding = Encoding.UTF8;
#else
/// <remarks>
/// Get OEM codepage from NetFX, which parses the NLP file with culture info table etc etc.
/// But sometimes it yields the special value of 1 which is nicknamed <c>CodePageNoOEM</c> in <see cref="Encoding"/> sources (might also mean <c>CP_OEMCP</c>, but Encoding puts it so).
/// This was observed on Ukranian and Hindu systems.
/// Given this value, <see cref="Encoding.GetEncoding(int)"/> throws an <see cref="ArgumentException"/>.
/// So replace it with some fallback, e.g. 437 which is the default cpcp in a console in a default Windows installation.
/// </remarks>
static int defaultCodePage =
// these values cause ArgumentException in subsequent calls to Encoding::GetEncoding()
((Thread.CurrentThread.CurrentCulture.TextInfo.OEMCodePage == 1) || (Thread.CurrentThread.CurrentCulture.TextInfo.OEMCodePage == 2) || (Thread.CurrentThread.CurrentCulture.TextInfo.OEMCodePage == 3) || (Thread.CurrentThread.CurrentCulture.TextInfo.OEMCodePage == 42))
? 437 // The default OEM encoding in a console in a default Windows installation, as a fallback.
: Thread.CurrentThread.CurrentCulture.TextInfo.OEMCodePage;
#endif
#if !PCL
/// <summary>
/// Default encoding used for string conversion. 0 gives the default system OEM code page.
/// Dont use unicode encodings if you want to be Zip compatible!
/// Using the default code page isnt the full solution neccessarily
/// there are many variable factors, codepage 850 is often a good choice for
/// European users, however be careful about compatability.
/// </summary>
public static int DefaultCodePage {
get {
return defaultCodePage;
}
set {
if ((value < 0) || (value > 65535) ||
(value == 1) || (value == 2) || (value == 3) || (value == 42)) {
throw new ArgumentOutOfRangeException("value");
}
defaultCodePage = value;
}
}
#else
/// <summary>
/// PCL don't support CodePage so we used Encoding instead of
/// </summary>
@ -176,12 +127,12 @@
{
return defaultEncoding;
}
set
{
defaultEncoding = value;
}
}
#endif
/// <summary>
/// Convert a portion of a byte array to a string.
@ -201,11 +152,8 @@
{
return string.Empty;
}
#if !PCL
return Encoding.GetEncoding(DefaultCodePage).GetString(data, 0, count);
#else
return DefaultEncoding.GetString(data, 0, count);
#endif
}
/// <summary>
@ -294,11 +242,8 @@
{
return new byte[0];
}
#if !PCL
return Encoding.GetEncoding(DefaultCodePage).GetBytes(str);
#else
return DefaultEncoding.GetBytes(str);
#endif
}
/// <summary>
@ -320,10 +265,8 @@
{
return Encoding.UTF8.GetBytes(str);
}
else
{
return ConvertToArray(str);
}
return ConvertToArray(str);
}
}
}

8
src/ImageProcessor/IImageBase.cs

@ -20,7 +20,7 @@ namespace ImageProcessor
/// and stores the blue, the green, the red and the alpha value for
/// each pixel in this order.
/// </remarks>
byte[] Pixels { get; }
float[] Pixels { get; }
/// <summary>
/// Gets the width in pixels.
@ -66,8 +66,8 @@ namespace ImageProcessor
/// The y-coordinate of the pixel. Must be greater
/// than zero and smaller than the width of the pixel.
/// </param>
/// <returns>The <see cref="Bgra"/> at the specified position.</returns>
Bgra this[int x, int y] { get; set; }
/// <returns>The <see cref="Color"/> at the specified position.</returns>
Color this[int x, int y] { get; set; }
/// <summary>
/// Sets the pixel array of the image.
@ -85,6 +85,6 @@ namespace ImageProcessor
/// <exception cref="ArgumentException">
/// Thrown if the <paramref name="pixels"/> length is not equal to Width * Height * 4.
/// </exception>
void SetPixels(int width, int height, byte[] pixels);
void SetPixels(int width, int height, float[] pixels);
}
}

55
src/ImageProcessor/ImageBase.cs

@ -40,7 +40,7 @@ namespace ImageProcessor
this.Width = width;
this.Height = height;
this.Pixels = new byte[width * height * 4];
this.Pixels = new float[width * height * 4];
}
/// <summary>
@ -56,13 +56,13 @@ namespace ImageProcessor
{
Guard.NotNull(other, nameof(other), "Other image cannot be null.");
byte[] pixels = other.Pixels;
float[] pixels = other.Pixels;
this.Width = other.Width;
this.Height = other.Height;
this.Quality = other.Quality;
this.FrameDelay = other.FrameDelay;
this.Pixels = new byte[pixels.Length];
this.Pixels = new float[pixels.Length];
Array.Copy(pixels, this.Pixels, pixels.Length);
}
@ -84,7 +84,7 @@ namespace ImageProcessor
/// and stores the blue, the green, the red and the alpha value for
/// each pixel in this order.
/// </remarks>
public byte[] Pixels { get; private set; }
public float[] Pixels { get; private set; }
/// <summary>
/// Gets the width in pixels.
@ -106,9 +106,7 @@ namespace ImageProcessor
/// </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>
/// <inheritdoc/>
public int Quality { get; set; }
/// <summary>
@ -119,19 +117,8 @@ namespace ImageProcessor
/// </summary>
public int FrameDelay { get; set; }
/// <summary>
/// Gets or sets the color of a 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="Bgra"/> at the specified position.</returns>
public Bgra this[int x, int y]
/// <inheritdoc/>
public Color this[int x, int y]
{
get
{
@ -148,7 +135,7 @@ namespace ImageProcessor
#endif
int start = ((y * this.Width) + x) * 4;
return new Bgra(this.Pixels[start], this.Pixels[start + 1], this.Pixels[start + 2], this.Pixels[start + 3]);
return new Color(this.Pixels[start], this.Pixels[start + 1], this.Pixels[start + 2], this.Pixels[start + 3]);
}
set
@ -166,31 +153,17 @@ namespace ImageProcessor
#endif
int start = ((y * this.Width) + x) * 4;
this.Pixels[start + 0] = value.B;
this.Pixels[start + 0] = value.R;
this.Pixels[start + 1] = value.G;
this.Pixels[start + 2] = value.R;
this.Pixels[start + 2] = value.B;
this.Pixels[start + 3] = value.A;
}
}
/// <summary>
/// Sets the pixel array of the image.
/// </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, 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.
/// </exception>
public void SetPixels(int width, int height, byte[] pixels)
/// <inheritdoc/>
public void SetPixels(int width, int height, float[] pixels)
{
#if DEBUG
if (width <= 0)
{
throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than or equals than zero.");
@ -205,7 +178,7 @@ namespace ImageProcessor
{
throw new ArgumentException("Pixel array must have the length of Width * Height * 4.");
}
#endif
this.Width = width;
this.Height = height;
this.Pixels = pixels;

17
src/ImageProcessor/ImageProcessor.csproj

@ -39,9 +39,13 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\ImageProcessor.XML</DocumentationFile>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="Colors\Cmyk.cs" />
<Compile Include="Colors\Formats\Cmyk.cs" />
<Compile Include="Colors\Color.cs" />
<Compile Include="Common\Extensions\EnumerableExtensions.cs" />
<Compile Include="Common\Helpers\ImageMaths.cs" />
<Compile Include="Common\Helpers\PixelOperations.cs" />
<Compile Include="Filters\Invert.cs" />
@ -80,8 +84,8 @@
<Compile Include="Numerics\Rectangle.cs" />
<Compile Include="ParallelImageProcessor.cs" />
<Compile Include="IImageProcessor.cs" />
<Compile Include="Colors\Hsv.cs" />
<Compile Include="Colors\YCbCr.cs" />
<Compile Include="Colors\Formats\Hsv.cs" />
<Compile Include="Colors\Formats\YCbCr.cs" />
<Compile Include="Common\Extensions\ByteExtensions.cs" />
<Compile Include="Common\Extensions\ComparableExtensions.cs" />
<Compile Include="Formats\Bmp\BmpCompression.cs" />
@ -196,7 +200,7 @@
<Compile Include="IImageBase.cs" />
<Compile Include="ImageProperty.cs" />
<Compile Include="ImageFrame.cs" />
<Compile Include="Colors\Bgra.cs" />
<Compile Include="Colors\Formats\Bgra32.cs" />
<Compile Include="Common\Exceptions\ImageFormatException.cs" />
<Compile Include="Common\Helpers\Guard.cs" />
<Compile Include="Formats\Gif\GifDecoder.cs" />
@ -235,13 +239,14 @@
<None Include="Formats\Jpg\README.md" />
<None Include="Formats\Png\Zlib\README.md" />
<None Include="project.json" />
<None Include="stylecop.json" />
</ItemGroup>
<ItemGroup>
<None Include="Formats\Bmp\README.md" />
<None Include="Formats\Png\README.md" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<AdditionalFiles Include="stylecop.json" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

1
src/ImageProcessor/ImageProcessor.csproj.DotSettings

@ -1,6 +1,7 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp60</s:String>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=colors/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=colors_005Cformats/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=common/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=common_005Cexceptions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=common_005Cextensions/@EntryIndexedValue">True</s:Boolean>

2
src/ImageProcessor/ParallelImageProcessor.cs

@ -53,7 +53,7 @@ namespace ImageProcessor
/// <inheritdoc/>
public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle))
{
byte[] pixels = new byte[width * height * 4];
float[] pixels = new float[width * height * 4];
target.SetPixels(width, height, pixels);
if (targetRectangle == Rectangle.Empty)

12
src/ImageProcessor/Samplers/Resamplers/BicubicResampler.cs

@ -12,28 +12,28 @@ namespace ImageProcessor.Samplers
public class BicubicResampler : IResampler
{
/// <inheritdoc/>
public double Radius => 2;
public float Radius => 2;
/// <inheritdoc/>
public double GetValue(double x)
public float GetValue(float x)
{
// The coefficient.
double a = -0.5;
float a = -0.5f;
if (x < 0)
{
x = -x;
}
double result = 0;
float result = 0;
if (x <= 1)
{
result = (((1.5 * x) - 2.5) * x * x) + 1;
result = (((1.5f * x) - 2.5f) * x * x) + 1;
}
else if (x < 2)
{
result = (((((a * x) + 2.5) * x) - 4) * x) + 2;
result = (((((a * x) + 2.5f) * x) - 4) * x) + 2;
}
return result;

4
src/ImageProcessor/Samplers/Resamplers/BoxResampler.cs

@ -11,10 +11,10 @@ namespace ImageProcessor.Samplers
public class BoxResampler : IResampler
{
/// <inheritdoc/>
public double Radius => 0.5;
public float Radius => 0.5f;
/// <inheritdoc/>
public double GetValue(double x)
public float GetValue(float x)
{
if (x < 0)
{

8
src/ImageProcessor/Samplers/Resamplers/CatmullRomResampler.cs

@ -12,13 +12,13 @@ namespace ImageProcessor.Samplers
public class CatmullRomResampler : IResampler
{
/// <inheritdoc/>
public double Radius => 2;
public float Radius => 2;
/// <inheritdoc/>
public double GetValue(double x)
public float GetValue(float x)
{
const double B = 0;
const double C = 1 / 2d;
const float B = 0;
const float C = 1 / 2f;
return ImageMaths.GetBcValue(x, B, C);
}

8
src/ImageProcessor/Samplers/Resamplers/HermiteResampler.cs

@ -12,13 +12,13 @@ namespace ImageProcessor.Samplers
public class HermiteResampler : IResampler
{
/// <inheritdoc/>
public double Radius => 2;
public float Radius => 2;
/// <inheritdoc/>
public double GetValue(double x)
public float GetValue(float x)
{
const double B = 0;
const double C = 0;
const float B = 0;
const float C = 0;
return ImageMaths.GetBcValue(x, B, C);
}

15
src/ImageProcessor/Samplers/Resamplers/IResampler.cs

@ -1,22 +1,27 @@
namespace ImageProcessor.Samplers
// <copyright file="IResampler.cs" company="James South">
// Copyright (c) James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor.Samplers
{
/// <summary>
/// Encasulates an interpolation algorithm for resampling images.
/// Encapsulates an interpolation algorithm for resampling images.
/// </summary>
public interface IResampler
{
/// <summary>
/// Gets the radius in which to sample pixels.
/// </summary>
double Radius { get; }
float Radius { get; }
/// <summary>
/// Gets the result of the interpolation algorithm.
/// </summary>
/// <param name="x">The value to process.</param>
/// <returns>
/// The <see cref="double"/>
/// The <see cref="float"/>
/// </returns>
double GetValue(double x);
float GetValue(float x);
}
}

4
src/ImageProcessor/Samplers/Resamplers/Lanczos3Resampler.cs

@ -12,10 +12,10 @@ namespace ImageProcessor.Samplers
public class Lanczos3Resampler : IResampler
{
/// <inheritdoc/>
public double Radius => 3;
public float Radius => 3;
/// <inheritdoc/>
public double GetValue(double x)
public float GetValue(float x)
{
if (x < 0)
{

4
src/ImageProcessor/Samplers/Resamplers/Lanczos5Resampler.cs

@ -12,10 +12,10 @@ namespace ImageProcessor.Samplers
public class Lanczos5Resampler : IResampler
{
/// <inheritdoc/>
public double Radius => 5;
public float Radius => 5;
/// <inheritdoc/>
public double GetValue(double x)
public float GetValue(float x)
{
if (x < 0)
{

4
src/ImageProcessor/Samplers/Resamplers/Lanczos8Resampler.cs

@ -12,10 +12,10 @@ namespace ImageProcessor.Samplers
public class Lanczos8Resampler : IResampler
{
/// <inheritdoc/>
public double Radius => 8;
public float Radius => 8;
/// <inheritdoc/>
public double GetValue(double x)
public float GetValue(float x)
{
if (x < 0)
{

8
src/ImageProcessor/Samplers/Resamplers/MitchellNetravaliResampler.cs

@ -12,13 +12,13 @@ namespace ImageProcessor.Samplers
public class MitchellNetravaliResampler : IResampler
{
/// <inheritdoc/>
public double Radius => 2;
public float Radius => 2;
/// <inheritdoc/>
public double GetValue(double x)
public float GetValue(float x)
{
const double B = 1 / 3d;
const double C = 1 / 3d;
const float B = 1 / 3f;
const float C = 1 / 3f;
return ImageMaths.GetBcValue(x, B, C);
}

8
src/ImageProcessor/Samplers/Resamplers/RobidouxResampler.cs

@ -12,13 +12,13 @@ namespace ImageProcessor.Samplers
public class RobidouxResampler : IResampler
{
/// <inheritdoc/>
public double Radius => 2;
public float Radius => 2;
/// <inheritdoc/>
public double GetValue(double x)
public float GetValue(float x)
{
const double B = 0.3782;
const double C = 0.3109;
const float B = 0.3782f;
const float C = 0.3109f;
return ImageMaths.GetBcValue(x, B, C);
}

8
src/ImageProcessor/Samplers/Resamplers/RobidouxSharpResampler.cs

@ -12,13 +12,13 @@ namespace ImageProcessor.Samplers
public class RobidouxSharpResampler : IResampler
{
/// <inheritdoc/>
public double Radius => 2;
public float Radius => 2;
/// <inheritdoc/>
public double GetValue(double x)
public float GetValue(float x)
{
const double B = 0.2620;
const double C = 0.3690;
const float B = 0.2620f;
const float C = 0.3690f;
return ImageMaths.GetBcValue(x, B, C);
}

8
src/ImageProcessor/Samplers/Resamplers/RobidouxSoftResampler.cs

@ -12,13 +12,13 @@ namespace ImageProcessor.Samplers
public class RobidouxSoftResampler : IResampler
{
/// <inheritdoc/>
public double Radius => 2;
public float Radius => 2;
/// <inheritdoc/>
public double GetValue(double x)
public float GetValue(float x)
{
const double B = 0.6796;
const double C = 0.1602;
const float B = 0.6796f;
const float C = 0.1602f;
return ImageMaths.GetBcValue(x, B, C);
}

8
src/ImageProcessor/Samplers/Resamplers/SplineResampler.cs

@ -12,13 +12,13 @@ namespace ImageProcessor.Samplers
public class SplineResampler : IResampler
{
/// <inheritdoc/>
public double Radius => 2;
public float Radius => 2;
/// <inheritdoc/>
public double GetValue(double x)
public float GetValue(float x)
{
const double B = 1;
const double C = 0;
const float B = 1;
const float C = 0;
return ImageMaths.GetBcValue(x, B, C);
}

4
src/ImageProcessor/Samplers/Resamplers/TriangleResampler.cs

@ -11,10 +11,10 @@ namespace ImageProcessor.Samplers
public class TriangleResampler : IResampler
{
/// <inheritdoc/>
public double Radius => 1;
public float Radius => 1;
/// <inheritdoc/>
public double GetValue(double x)
public float GetValue(float x)
{
if (x < 0)
{

6
src/ImageProcessor/Samplers/Resamplers/WelchResampler.cs

@ -12,10 +12,10 @@ namespace ImageProcessor.Samplers
public class WelchResampler : IResampler
{
/// <inheritdoc/>
public double Radius => 3;
public float Radius => 3;
/// <inheritdoc/>
public double GetValue(double x)
public float GetValue(float x)
{
if (x < 0)
{
@ -24,7 +24,7 @@ namespace ImageProcessor.Samplers
if (x < 3)
{
return ImageMaths.SinC(x) * (1.0 - (x * x / 9.0));
return ImageMaths.SinC(x) * (1.0f - (x * x / 9.0f));
}
return 0;

142
src/ImageProcessor/Samplers/Resize.cs

@ -7,6 +7,7 @@ namespace ImageProcessor.Samplers
{
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
/// <summary>
/// Provides methods that allow the resizing of images using various resampling algorithms.
@ -16,7 +17,7 @@ namespace ImageProcessor.Samplers
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.0001f;
private const float Epsilon = 0.0000001f;
/// <summary>
/// The horizontal weights.
@ -61,57 +62,57 @@ namespace ImageProcessor.Samplers
int startX = targetRectangle.X;
int endX = targetRectangle.Right;
for (int y = startY; y < endY; y++)
{
if (y >= targetY && y < targetBottom)
Parallel.For(
startY,
endY,
y =>
{
List<Weight> verticalValues = this.verticalWeights[y].Values;
double verticalSum = this.verticalWeights[y].Sum;
for (int x = startX; x < endX; x++)
if (y >= targetY && y < targetBottom)
{
List<Weight> horizontalValues = this.horizontalWeights[x].Values;
double horizontalSum = this.horizontalWeights[x].Sum;
List<Weight> verticalValues = this.verticalWeights[y].Values;
float verticalSum = this.verticalWeights[y].Sum;
// Destination color components
double r = 0;
double g = 0;
double b = 0;
double a = 0;
foreach (Weight yw in verticalValues)
for (int x = startX; x < endX; x++)
{
if (Math.Abs(yw.Value) < Epsilon)
{
continue;
}
List<Weight> horizontalValues = this.horizontalWeights[x].Values;
float horizontalSum = this.horizontalWeights[x].Sum;
int originY = yw.Index;
// Destination color components
Color destination = new Color(0, 0, 0, 0);
foreach (Weight xw in horizontalValues)
foreach (Weight yw in verticalValues)
{
if (Math.Abs(xw.Value) < Epsilon)
if (Math.Abs(yw.Value) < Epsilon)
{
continue;
}
int originX = xw.Index;
Bgra sourceColor = source[originX, originY];
sourceColor = PixelOperations.ToLinear(sourceColor);
int originY = yw.Index;
foreach (Weight xw in horizontalValues)
{
if (Math.Abs(xw.Value) < Epsilon)
{
continue;
}
int originX = xw.Index;
Color sourceColor = PixelOperations.ToLinear(source[originX, originY]);
r += sourceColor.R * (yw.Value / verticalSum) * (xw.Value / horizontalSum);
g += sourceColor.G * (yw.Value / verticalSum) * (xw.Value / horizontalSum);
b += sourceColor.B * (yw.Value / verticalSum) * (xw.Value / horizontalSum);
a += sourceColor.A * (yw.Value / verticalSum) * (xw.Value / horizontalSum);
float weight = (yw.Value / verticalSum) * (xw.Value / horizontalSum);
destination.R += sourceColor.R * weight;
destination.G += sourceColor.G * weight;
destination.B += sourceColor.B * weight;
destination.A += sourceColor.A * weight;
}
}
}
Bgra destinationColor = new Bgra(b.ToByte(), g.ToByte(), r.ToByte(), a.ToByte());
destinationColor = PixelOperations.ToSrgb(destinationColor);
target[x, y] = destinationColor;
destination = PixelOperations.ToSrgb(destination);
target[x, y] = destination;
}
}
}
}
});
}
/// <summary>
@ -125,50 +126,53 @@ namespace ImageProcessor.Samplers
private Weights[] PrecomputeWeights(int destinationSize, int sourceSize)
{
IResampler sampler = this.Sampler;
double du = sourceSize / (double)destinationSize;
double scale = du;
float du = sourceSize / (float)destinationSize;
float scale = du;
if (scale < 1)
{
scale = 1;
}
double ru = Math.Ceiling(scale * sampler.Radius);
float ru = (float)Math.Ceiling(scale * sampler.Radius);
Weights[] result = new Weights[destinationSize];
for (int i = 0; i < destinationSize; i++)
{
double fu = ((i + .5) * du) - 0.5;
int startU = (int)Math.Ceiling(fu - ru);
Parallel.For(
0,
destinationSize,
i =>
{
float fu = ((i + .5f) * du) - 0.5f;
int startU = (int)Math.Ceiling(fu - ru);
if (startU < 0)
{
startU = 0;
}
if (startU < 0)
{
startU = 0;
}
int endU = (int)Math.Floor(fu + ru);
int endU = (int)Math.Floor(fu + ru);
if (endU > sourceSize - 1)
{
endU = sourceSize - 1;
}
if (endU > sourceSize - 1)
{
endU = sourceSize - 1;
}
double sum = 0;
result[i] = new Weights();
float sum = 0;
result[i] = new Weights();
for (int a = startU; a <= endU; a++)
{
double w = 255 * sampler.GetValue((a - fu) / scale);
for (int a = startU; a <= endU; a++)
{
float w = sampler.GetValue((a - fu) / scale);
if (Math.Abs(w) > Epsilon)
{
sum += w;
result[i].Values.Add(new Weight(a, w));
}
}
if (Math.Abs(w) > Epsilon)
{
sum += w;
result[i].Values.Add(new Weight(a, w));
}
}
result[i].Sum = sum;
}
result[i].Sum = sum;
});
return result;
}
@ -186,14 +190,14 @@ namespace ImageProcessor.Samplers
/// <summary>
/// The result of the interpolation algorithm.
/// </summary>
public readonly double Value;
public readonly float Value;
/// <summary>
/// Initializes a new instance of the <see cref="Weight"/> struct.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="value">The value.</param>
public Weight(int index, double value)
public Weight(int index, float value)
{
this.Index = index;
this.Value = value;
@ -221,7 +225,7 @@ namespace ImageProcessor.Samplers
/// <summary>
/// Gets or sets the sum.
/// </summary>
public double Sum { get; set; }
public float Sum { get; set; }
}
}
}

13
src/ImageProcessor/stylecop.json

@ -0,0 +1,13 @@
{
"$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
"settings": {
"documentationRules": {
"companyName": "James South",
"copyrightText": "Copyright (c) {companyName} and contributors.\nLicensed under the {licenseName}.",
"variables": {
"licenseName": "Apache License, Version 2.0"
},
"documentPrivateFields": true
}
}
}

206
tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs

@ -17,19 +17,23 @@ namespace ImageProcessor.Tests
/// <summary>
/// Test conversion between the various color structs.
/// </summary>
/// </summary>
/// <remarks>
/// Output values have been compared with <see cref="http://colormine.org/color-converter"/>
/// and <see cref="http://www.colorhexa.com/"/> for accuracy.
/// </remarks>
public class ColorConversionTests
{
/// <summary>
/// Tests the implicit conversion from <see cref="Bgra"/> to <see cref="YCbCr"/>.
/// Tests the implicit conversion from <see cref="Color"/> to <see cref="YCbCr"/>.
/// </summary>
[Fact]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "Reviewed. Suppression is OK here.")]
public void BgrToYCbCr()
public void ColorToYCbCr()
{
// White
Bgra color = new Bgra(255, 255, 255, 255);
Color color = new Color(1, 1, 1);
YCbCr yCbCr = color;
Assert.Equal(255, yCbCr.Y);
@ -37,14 +41,14 @@ namespace ImageProcessor.Tests
Assert.Equal(128, yCbCr.Cr);
// Black
Bgra color2 = new Bgra(0, 0, 0, 255);
Color color2 = new Color(0, 0, 0);
YCbCr yCbCr2 = color2;
Assert.Equal(0, yCbCr2.Y);
Assert.Equal(128, yCbCr2.Cb);
Assert.Equal(128, yCbCr2.Cr);
// Grey
Bgra color3 = new Bgra(128, 128, 128, 255);
Color color3 = new Color(.5f, .5f, .5f);
YCbCr yCbCr3 = color3;
Assert.Equal(128, yCbCr3.Y);
Assert.Equal(128, yCbCr3.Cb);
@ -52,201 +56,201 @@ namespace ImageProcessor.Tests
}
/// <summary>
/// Tests the implicit conversion from <see cref="YCbCr"/> to <see cref="Bgra"/>.
/// Tests the implicit conversion from <see cref="YCbCr"/> to <see cref="Color"/>.
/// </summary>
[Fact]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "Reviewed. Suppression is OK here.")]
public void YCbCrToBgr()
public void YCbCrToColor()
{
// White
YCbCr yCbCr = new YCbCr(255, 128, 128);
Bgra color = yCbCr;
Color color = yCbCr;
Assert.Equal(255, color.B);
Assert.Equal(255, color.G);
Assert.Equal(255, color.R);
Assert.Equal(255, color.A);
Assert.Equal(1f, color.R, 1);
Assert.Equal(1f, color.G, 1);
Assert.Equal(1f, color.B, 1);
Assert.Equal(1f, color.A, 1);
// Black
YCbCr yCbCr2 = new YCbCr(0, 128, 128);
Bgra color2 = yCbCr2;
Color color2 = yCbCr2;
Assert.Equal(0, color2.B);
Assert.Equal(0, color2.G);
Assert.Equal(0, color2.R);
Assert.Equal(255, color2.A);
Assert.Equal(0, color2.G);
Assert.Equal(0, color2.B);
Assert.Equal(1, color2.A);
// Grey
YCbCr yCbCr3 = new YCbCr(128, 128, 128);
Bgra color3 = yCbCr3;
Color color3 = yCbCr3;
Assert.Equal(128, color3.B);
Assert.Equal(128, color3.G);
Assert.Equal(128, color3.R);
Assert.Equal(255, color3.A);
Assert.Equal(.5f, color3.R, 1);
Assert.Equal(.5f, color3.G, 1);
Assert.Equal(.5f, color3.B, 1);
Assert.Equal(1f, color3.A, 1);
}
/// <summary>
/// Tests the implicit conversion from <see cref="Bgra"/> to <see cref="Hsv"/>.
/// Tests the implicit conversion from <see cref="Color"/> to <see cref="Hsv"/>.
/// </summary>
[Fact]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "Reviewed. Suppression is OK here.")]
public void BgrToHsv()
public void ColorToHsv()
{
// Black
Bgra b = new Bgra(0, 0, 0, 255);
Color b = new Color(0, 0, 0);
Hsv h = b;
Assert.Equal(0, h.H);
Assert.Equal(0, h.S);
Assert.Equal(0, h.V);
Assert.Equal(0, h.H, 1);
Assert.Equal(0, h.S, 1);
Assert.Equal(0, h.V, 1);
// White
Bgra color = new Bgra(255, 255, 255, 255);
Color color = new Color(1, 1, 1);
Hsv hsv = color;
Assert.Equal(0, hsv.H);
Assert.Equal(0, hsv.S);
Assert.Equal(100, hsv.V);
Assert.Equal(0f, hsv.H, 1);
Assert.Equal(0f, hsv.S, 1);
Assert.Equal(1f, hsv.V, 1);
// Dark moderate pink.
Bgra color2 = new Bgra(106, 64, 128, 255);
Color color2 = new Color(128 / 255f, 64 / 255f, 106 / 255f);
Hsv hsv2 = color2;
Assert.Equal(320.6, hsv2.H, 1);
Assert.Equal(50, hsv2.S, 1);
Assert.Equal(50.2, hsv2.V, 1);
Assert.Equal(320.6f, hsv2.H, 1);
Assert.Equal(0.5f, hsv2.S, 1);
Assert.Equal(0.502f, hsv2.V, 2);
// Ochre.
Bgra color3 = new Bgra(34, 119, 204, 255);
Color color3 = new Color(204 / 255f, 119 / 255f, 34 / 255f);
Hsv hsv3 = color3;
Assert.Equal(30, hsv3.H, 1);
Assert.Equal(83.3, hsv3.S, 1);
Assert.Equal(80, hsv3.V, 1);
Assert.Equal(30f, hsv3.H, 1);
Assert.Equal(0.833f, hsv3.S, 3);
Assert.Equal(0.8f, hsv3.V, 1);
}
/// <summary>
/// Tests the implicit conversion from <see cref="Hsv"/> to <see cref="Bgra"/>.
/// Tests the implicit conversion from <see cref="Hsv"/> to <see cref="Color"/>.
/// </summary>
[Fact]
public void HsvToBgr()
public void HsvToColor()
{
// Dark moderate pink.
Hsv hsv = new Hsv(320.6f, 50, 50.2f);
Bgra bgra = hsv;
Hsv hsv = new Hsv(320.6f, 0.5f, 0.502f);
Color color = hsv;
Assert.Equal(bgra.B, 106);
Assert.Equal(bgra.G, 64);
Assert.Equal(bgra.R, 128);
Assert.Equal(color.B, 106 / 255f, 1);
Assert.Equal(color.G, 64 / 255f, 1);
Assert.Equal(color.R, 128 / 255f, 1);
// Ochre
Hsv hsv2 = new Hsv(30, 83.3f, 80);
Bgra bgra2 = hsv2;
Hsv hsv2 = new Hsv(30, 0.833f, 0.8f);
Color color2 = hsv2;
Assert.Equal(bgra2.B, 34);
Assert.Equal(bgra2.G, 119);
Assert.Equal(bgra2.R, 204);
Assert.Equal(color2.B, 34 / 255f, 1);
Assert.Equal(color2.G, 119 / 255f, 1);
Assert.Equal(color2.R, 204 / 255f, 1);
// White
Hsv hsv3 = new Hsv(0, 0, 100);
Bgra bgra3 = hsv3;
Hsv hsv3 = new Hsv(0, 0, 1);
Color color3 = hsv3;
Assert.Equal(bgra3.B, 255);
Assert.Equal(bgra3.G, 255);
Assert.Equal(bgra3.R, 255);
Assert.Equal(color3.B, 1, 1);
Assert.Equal(color3.G, 1, 1);
Assert.Equal(color3.R, 1, 1);
// Check others.
Random random = new Random(0);
for (int i = 0; i < 1000; i++)
{
Bgra bgra4 = new Bgra((byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255));
Hsv hsb4 = bgra4;
Assert.Equal(bgra4, (Bgra)hsb4);
Color color4 = new Color(random.Next(1), random.Next(1), random.Next(1));
Hsv hsv4 = color4;
Assert.Equal(color4, (Color)hsv4);
}
}
/// <summary>
/// Tests the implicit conversion from <see cref="Bgra"/> to <see cref="Cmyk"/>.
/// Tests the implicit conversion from <see cref="Color"/> to <see cref="Cmyk"/>.
/// </summary>
[Fact]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "Reviewed. Suppression is OK here.")]
public void BgrToCmyk()
public void ColorToCmyk()
{
// White
Bgra color = new Bgra(255, 255, 255, 255);
Color color = new Color(1, 1, 1);
Cmyk cmyk = color;
Assert.Equal(0, cmyk.C);
Assert.Equal(0, cmyk.M);
Assert.Equal(0, cmyk.Y);
Assert.Equal(0, cmyk.K);
Assert.Equal(0, cmyk.C, 1);
Assert.Equal(0, cmyk.M, 1);
Assert.Equal(0, cmyk.Y, 1);
Assert.Equal(0, cmyk.K, 1);
// Black
Bgra color2 = new Bgra(0, 0, 0, 255);
Color color2 = new Color(0, 0, 0);
Cmyk cmyk2 = color2;
Assert.Equal(0, cmyk2.C);
Assert.Equal(0, cmyk2.M);
Assert.Equal(0, cmyk2.Y);
Assert.Equal(100, cmyk2.K);
Assert.Equal(0, cmyk2.C, 1);
Assert.Equal(0, cmyk2.M, 1);
Assert.Equal(0, cmyk2.Y, 1);
Assert.Equal(1, cmyk2.K, 1);
// Grey
Bgra color3 = new Bgra(128, 128, 128, 255);
Color color3 = new Color(128 / 255f, 128 / 255f, 128 / 255f);
Cmyk cmyk3 = color3;
Assert.Equal(0, cmyk3.C);
Assert.Equal(0, cmyk3.M);
Assert.Equal(0, cmyk3.Y);
Assert.Equal(49.8, cmyk3.K, 1); // Checked with other tools.
Assert.Equal(0f, cmyk3.C, 1);
Assert.Equal(0f, cmyk3.M, 1);
Assert.Equal(0f, cmyk3.Y, 1);
Assert.Equal(0.498, cmyk3.K, 2); // Checked with other online converters.
// Cyan
Bgra color4 = new Bgra(255, 255, 0, 255);
Color color4 = new Color(0, 1, 1);
Cmyk cmyk4 = color4;
Assert.Equal(100, cmyk4.C);
Assert.Equal(0, cmyk4.M);
Assert.Equal(0, cmyk4.Y);
Assert.Equal(0, cmyk4.K);
Assert.Equal(1, cmyk4.C, 1);
Assert.Equal(0f, cmyk4.M, 1);
Assert.Equal(0f, cmyk4.Y, 1);
Assert.Equal(0f, cmyk4.K, 1);
}
/// <summary>
/// Tests the implicit conversion from <see cref="Hsv"/> to <see cref="Bgra"/>.
/// Tests the implicit conversion from <see cref="Cmyk"/> to <see cref="Color"/>.
/// </summary>
[Fact]
public void CmykToBgr()
public void CmykToColor()
{
// Dark moderate pink.
Cmyk cmyk = new Cmyk(49.8f, 74.9f, 58.4f, 0);
Bgra bgra = cmyk;
Cmyk cmyk = new Cmyk(0f, .5f, .171f, .498f);
Color color = cmyk;
Assert.Equal(bgra.B, 106);
Assert.Equal(bgra.G, 64);
Assert.Equal(bgra.R, 128);
Assert.Equal(color.R, 128 / 255f, 1);
Assert.Equal(color.G, 64 / 255f, 1);
Assert.Equal(color.B, 106 / 255f, 1);
// Ochre
Cmyk cmyk2 = new Cmyk(20, 53.3f, 86.7f, 0);
Bgra bgra2 = cmyk2;
Cmyk cmyk2 = new Cmyk(0, .416f, .833f, .199f);
Color color2 = cmyk2;
Assert.Equal(bgra2.B, 34);
Assert.Equal(bgra2.G, 119);
Assert.Equal(bgra2.R, 204);
Assert.Equal(color2.R, 204 / 255f, 1);
Assert.Equal(color2.G, 119 / 255f, 1);
Assert.Equal(color2.B, 34 / 255f, 1);
// White
Cmyk cmyk3 = new Cmyk(0, 0, 0, 0);
Bgra bgra3 = cmyk3;
Color color3 = cmyk3;
Assert.Equal(bgra3.B, 255);
Assert.Equal(bgra3.G, 255);
Assert.Equal(bgra3.R, 255);
Assert.Equal(color3.R, 1f, 1);
Assert.Equal(color3.G, 1f, 1);
Assert.Equal(color3.B, 1f, 1);
// Check others.
Random random = new Random(0);
for (int i = 0; i < 1000; i++)
{
Bgra bgra4 = new Bgra((byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255));
Cmyk cmyk4 = bgra4;
Assert.Equal(bgra4, (Bgra)cmyk4);
Color color4 = new Color(random.Next(1), random.Next(1), random.Next(1));
Cmyk cmyk4 = color4;
Assert.Equal(color4, (Color)cmyk4);
}
}
}

65
tests/ImageProcessor.Tests/Colors/ColorTests.cs

@ -13,7 +13,7 @@ namespace ImageProcessor.Tests
using Xunit;
/// <summary>
/// Tests the <see cref="Bgra"/> struct.
/// Tests the <see cref="Color"/> struct.
/// </summary>
public class ColorTests
{
@ -23,18 +23,16 @@ namespace ImageProcessor.Tests
[Fact]
public void AreEqual()
{
Bgra color1 = new Bgra(0, 0, 0, 255);
Bgra color2 = new Bgra(0, 0, 0, 255);
Bgra color3 = new Bgra("#000");
Bgra color4 = new Bgra("#000000");
Bgra color5 = new Bgra("#FF000000");
Bgra color6 = new Bgra(-16777216);
Color color1 = new Color(0, 0, 0);
Color color2 = new Color(0, 0, 0, 1);
Color color3 = new Color("#000");
Color color4 = new Color("#000000");
Color color5 = new Color("#FF000000");
Assert.Equal(color1, color2);
Assert.Equal(color1, color3);
Assert.Equal(color1, color4);
Assert.Equal(color1, color5);
Assert.Equal(color1, color6);
}
/// <summary>
@ -43,18 +41,16 @@ namespace ImageProcessor.Tests
[Fact]
public void AreNotEqual()
{
Bgra color1 = new Bgra(255, 0, 0, 255);
Bgra color2 = new Bgra(0, 0, 0, 255);
Bgra color3 = new Bgra("#000");
Bgra color4 = new Bgra("#000000");
Bgra color5 = new Bgra("#FF000000");
Bgra color6 = new Bgra(-16777216);
Color color1 = new Color(255, 0, 0, 255);
Color color2 = new Color(0, 0, 0, 255);
Color color3 = new Color("#000");
Color color4 = new Color("#000000");
Color color5 = new Color("#FF000000");
Assert.NotEqual(color1, color2);
Assert.NotEqual(color1, color3);
Assert.NotEqual(color1, color4);
Assert.NotEqual(color1, color5);
Assert.NotEqual(color1, color6);
}
/// <summary>
@ -63,29 +59,23 @@ namespace ImageProcessor.Tests
[Fact]
public void ConstructorAssignsProperties()
{
Bgra color1 = new Bgra(255, 10, 34, 220);
Assert.Equal(255, color1.B);
Assert.Equal(10, color1.G);
Assert.Equal(34, color1.R);
Assert.Equal(220, color1.A);
Color color1 = new Color(1, .1f, .133f, .864f);
Assert.Equal(1, color1.R, 1);
Assert.Equal(.1f, color1.G, 1);
Assert.Equal(.133f, color1.B, 3);
Assert.Equal(.864f, color1.A, 3);
Bgra color2 = new Bgra(255, 10, 34);
Assert.Equal(255, color2.B);
Assert.Equal(10, color2.G);
Assert.Equal(34, color2.R);
Assert.Equal(255, color2.A);
Color color2 = new Color(1, .1f, .133f);
Assert.Equal(1, color2.R, 1);
Assert.Equal(.1f, color2.G, 1);
Assert.Equal(.133f, color2.B, 3);
Assert.Equal(1, color2.A, 1);
Bgra color3 = new Bgra(-1);
Assert.Equal(255, color3.B);
Assert.Equal(255, color3.G);
Assert.Equal(255, color3.R);
Assert.Equal(255, color3.A);
Bgra color4 = new Bgra("#FF0000");
Assert.Equal(0, color4.B);
Assert.Equal(0, color4.G);
Assert.Equal(255, color4.R);
Assert.Equal(255, color4.A);
Color color3 = new Color("#FF0000");
Assert.Equal(1, color3.R, 1);
Assert.Equal(0, color3.G, 1);
Assert.Equal(0, color3.B, 3);
Assert.Equal(1, color3.A, 1);
}
/// <summary>
@ -95,7 +85,8 @@ namespace ImageProcessor.Tests
public void ConvertHex()
{
const string First = "FF000000";
string second = new Bgra(0, 0, 0, 255).BGRA.ToString("X");
Bgra32 bgra = new Color(0, 0, 0, 1);
string second = bgra.Bgra.ToString("X");
Assert.Equal(First, second);
}
}

8
tests/ImageProcessor.Tests/ImageProcessor.Tests.csproj

@ -10,13 +10,14 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ImageProcessor.Tests</RootNamespace>
<AssemblyName>ImageProcessor.Tests</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
<RestorePackages>true</RestorePackages>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -37,6 +38,11 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Numerics.Vectors.4.1.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
<Private>True</Private>

12
tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs

@ -15,13 +15,13 @@ namespace ImageProcessor.Tests
//{ "Contrast-50", new Contrast(50) },
//{ "Contrast--50", new Contrast(-50) },
//{ "Alpha--50", new Alpha(50) },
//{ "Invert", new Invert() },
//{ "Sepia", new Sepia() },
//{ "BlackWhite", new BlackWhite() },
//{ "Lomograph", new Lomograph() },
//{ "Polaroid", new Polaroid() },
{ "Invert", new Invert() },
{ "Sepia", new Sepia() },
{ "BlackWhite", new BlackWhite() },
{ "Lomograph", new Lomograph() },
{ "Polaroid", new Polaroid() },
{ "GreyscaleBt709", new GreyscaleBt709() },
//{ "GreyscaleBt601", new GreyscaleBt601() },
{ "GreyscaleBt601", new GreyscaleBt601() },
};
[Theory]

23
tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs

@ -19,19 +19,18 @@ namespace ImageProcessor.Tests
/// </summary>
public static readonly List<string> Files = new List<string>
{
//"../../TestImages/Formats/Jpg/Backdrop.jpg",
//"../../TestImages/Formats/Jpg/Calliphora.jpg",
//"../../TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg",
"../../TestImages/Formats/Jpg/Backdrop.jpg",
"../../TestImages/Formats/Jpg/Calliphora.jpg",
"../../TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg",
"../../TestImages/Formats/Jpg/greyscale.jpg",
//"../../TestImages/Formats/Bmp/Car.bmp",
//"../../TestImages/Formats/Png/cmyk.png",
//"../../TestImages/Formats/Png/gamma-1.0-or-2.2.png",
//"../../TestImages/Formats/Gif/leaf.gif",
//"../../TestImages/Formats/Gif/rings.gif"
// { "../../TestImages/Formats/Gif/ani.gif" },
// { "../../TestImages/Formats/Gif/ani2.gif" },
// { "../../TestImages/Formats/Gif/giphy.gif" },
"../../TestImages/Formats/Bmp/Car.bmp",
"../../TestImages/Formats/Png/cmyk.png",
"../../TestImages/Formats/Png/gamma-1.0-or-2.2.png",
"../../TestImages/Formats/Png/splash.png",
"../../TestImages/Formats/Gif/leaf.gif",
"../../TestImages/Formats/Gif/rings.gif",
//"../../TestImages/Formats/Gif/ani2.gif",
//"../../TestImages/Formats/Gif/giphy.gif"
};
}
}

7
tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs

@ -1,6 +1,7 @@

namespace ImageProcessor.Tests
{
using System;
using System.Diagnostics;
using System.IO;
@ -47,7 +48,7 @@ namespace ImageProcessor.Tests
using (FileStream output = File.OpenWrite($"Resized/{filename}"))
{
//image.Resize(image.Width / 2, image.Height / 2, sampler).Save(output);
image.Resize(500, 750, sampler).Save(output);
image.Resize(image.Width / 2, image.Height / 2, sampler).Save(output);
}
Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");
@ -62,10 +63,10 @@ namespace ImageProcessor.Tests
[InlineData(1, 0)]
[InlineData(2, 0)]
[InlineData(2, 0)]
public static void Lanczos3WindowOscillatesCorrectly(double x, double expected)
public static void Lanczos3WindowOscillatesCorrectly(float x, float expected)
{
Lanczos3Resampler sampler = new Lanczos3Resampler();
double result = sampler.GetValue(x);
float result = sampler.GetValue(x);
Assert.Equal(result, expected);
}

1
tests/ImageProcessor.Tests/TestImages/Formats/Gif/ani.gif.REMOVED.git-id

@ -1 +0,0 @@
a0cc93222effb5feec0d1a1dc45efd0c5af77450

1
tests/ImageProcessor.Tests/TestImages/Formats/Png/splash.png.REMOVED.git-id

@ -0,0 +1 @@
e37d569208ff9490b77b4131330feb323e367fd3

5
tests/ImageProcessor.Tests/packages.config

@ -1,5 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="System.Globalization" version="4.0.10" targetFramework="net46" />
<package id="System.Numerics.Vectors" version="4.1.0" targetFramework="net46" />
<package id="System.Resources.ResourceManager" version="4.0.0" targetFramework="net46" />
<package id="System.Runtime" version="4.0.20" targetFramework="net46" />
<package id="System.Runtime.Extensions" version="4.0.10" targetFramework="net46" />
<package id="xunit" version="2.0.0" targetFramework="net45" />
<package id="xunit.abstractions" version="2.0.0" targetFramework="net45" />
<package id="xunit.assert" version="2.0.0" targetFramework="net45" />

Loading…
Cancel
Save