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 11 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 *.Cache
ClientBin ClientBin
stylecop.* stylecop.*
!stylecop.json
~$* ~$*
*.dbmdl *.dbmdl
Generated_Code #added for RIA/Silverlight projects 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;
using System.ComponentModel; using System.ComponentModel;
using System.Numerics;
/// <summary> /// <summary>
/// Represents an CMYK (cyan, magenta, yellow, keyline) color. /// Represents an CMYK (cyan, magenta, yellow, keyline) color.
@ -19,92 +20,95 @@ namespace ImageProcessor
public static readonly Cmyk Empty = default(Cmyk); public static readonly Cmyk Empty = default(Cmyk);
/// <summary> /// <summary>
/// Gets the cyan color component. /// The epsilon for comparing floating point numbers.
/// </summary> /// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks> private const float Epsilon = 0.0001f;
public readonly float C;
/// <summary> /// <summary>
/// Gets the magenta color component. /// The backing vector for SIMD support.
/// </summary> /// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks> private Vector4 backingVector;
public readonly float M;
/// <summary> /// <summary>
/// Gets the yellow color component. /// Initializes a new instance of the <see cref="Cmyk"/> struct.
/// </summary> /// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks> /// <param name="cyan">The cyan component.</param>
public readonly float Y; /// <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> /// <summary>
/// Gets the keyline black color component. /// Gets the cyan color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary> /// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks> public float C => this.backingVector.X;
public readonly float K;
/// <summary> /// <summary>
/// The epsilon for comparing floating point numbers. /// Gets the magenta color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary> /// </summary>
private const float Epsilon = 0.0001f; public float M => this.backingVector.Y;
/// <summary> /// <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> /// </summary>
/// <param name="cyan">The cyan component.</param> public float Y => this.backingVector.Z;
/// <param name="magenta">The magenta component.</param>
/// <param name="yellow">The yellow component.</param> /// <summary>
/// <param name="keyline">The keyline black component.</param> /// Gets the keyline black color component.
public Cmyk(float cyan, float magenta, float yellow, float keyline) /// <remarks>A value ranging between 0 and 1.</remarks>
{ /// </summary>
this.C = Clamp(cyan); public float K => this.backingVector.W;
this.M = Clamp(magenta);
this.Y = Clamp(yellow);
this.K = Clamp(keyline);
}
/// <summary> /// <summary>
/// Gets a value indicating whether this <see cref="Cmyk"/> is empty. /// Gets a value indicating whether this <see cref="Cmyk"/> is empty.
/// </summary> /// </summary>
[EditorBrowsable(EditorBrowsableState.Never)] [EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => Math.Abs(this.C) < Epsilon public bool IsEmpty => this.backingVector.Equals(default(Vector4));
&& Math.Abs(this.M) < Epsilon
&& Math.Abs(this.Y) < Epsilon
&& Math.Abs(this.K) < Epsilon;
/// <summary> /// <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"/>. /// <see cref="Cmyk"/>.
/// </summary> /// </summary>
/// <param name="color"> /// <param name="color">
/// The instance of <see cref="Bgra"/> to convert. /// The instance of <see cref="Bgra32"/> to convert.
/// </param> /// </param>
/// <returns> /// <returns>
/// An instance of <see cref="Cmyk"/>. /// An instance of <see cref="Cmyk"/>.
/// </returns> /// </returns>
public static implicit operator Cmyk(Bgra color) public static implicit operator Cmyk(Color color)
{ {
float c = (255f - color.R) / 255; color = color.Limited;
float m = (255f - color.G) / 255;
float y = (255f - color.B) / 255; float c = 1f - color.R;
float m = 1f - color.G;
float y = 1f - color.B;
float k = Math.Min(c, Math.Min(m, y)); 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; c = (c - k) / (1 - k);
m = ((m - k) / (1 - k)) * 100; m = (m - k) / (1 - k);
y = ((y - k) / (1 - k)) * 100; y = (y - k) / (1 - k);
return new Cmyk(c, m, y, k * 100); return new Cmyk(c, m, y, k);
} }
/// <summary> /// <summary>
/// Compares two <see cref="Cmyk"/> objects. The result specifies whether the values /// Compares two <see cref="Cmyk"/> objects for equality.
/// 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.
/// </summary> /// </summary>
/// <param name="left"> /// <param name="left">
/// The <see cref="Cmyk"/> on the left side of the operand. /// The <see cref="Cmyk"/> on the left side of the operand.
@ -121,9 +125,7 @@ namespace ImageProcessor
} }
/// <summary> /// <summary>
/// Compares two <see cref="Cmyk"/> objects. The result specifies whether the values /// Compares two <see cref="Cmyk"/> objects for inequality
/// 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.
/// </summary> /// </summary>
/// <param name="left"> /// <param name="left">
/// The <see cref="Cmyk"/> on the left side of the operand. /// The <see cref="Cmyk"/> on the left side of the operand.
@ -139,52 +141,26 @@ namespace ImageProcessor
return !left.Equals(right); return !left.Equals(right);
} }
/// <summary> /// <inheritdoc/>
/// 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) public override bool Equals(object obj)
{ {
if (obj is Cmyk) if (obj is Cmyk)
{ {
Cmyk color = (Cmyk)obj; Cmyk color = (Cmyk)obj;
return Math.Abs(this.C - color.C) < Epsilon return this.backingVector == color.backingVector;
&& Math.Abs(this.M - color.M) < Epsilon
&& Math.Abs(this.Y - color.Y) < Epsilon
&& Math.Abs(this.K - color.K) < Epsilon;
} }
return false; return false;
} }
/// <summary> /// <inheritdoc/>
/// 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() public override int GetHashCode()
{ {
unchecked return GetHashCode(this);
{
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;
}
} }
/// <summary> /// <inheritdoc/>
/// 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() public override string ToString()
{ {
if (this.IsEmpty) 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.##}]"; return $"Cmyk [ C={this.C:#0.##}, M={this.M:#0.##}, Y={this.Y:#0.##}, K={this.K:#0.##}]";
} }
/// <summary> /// <inheritdoc/>
/// 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(Cmyk other) public bool Equals(Cmyk other)
{ {
return Math.Abs(this.C - other.C) < Epsilon return this.backingVector.Equals(other.backingVector);
&& Math.Abs(this.M - other.M) < Epsilon
&& Math.Abs(this.Y - other.Y) < Epsilon
&& Math.Abs(this.K - other.Y) < Epsilon;
} }
/// <summary> /// <summary>
@ -221,7 +188,18 @@ namespace ImageProcessor
/// </returns> /// </returns>
private static float Clamp(float value) 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;
using System.ComponentModel; using System.ComponentModel;
using System.Numerics;
/// <summary> /// <summary>
/// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness). /// 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); public static readonly Hsv Empty = default(Hsv);
/// <summary> /// <summary>
/// Gets the H hue component. /// The epsilon for comparing floating point numbers.
/// <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>
/// </summary> /// </summary>
public readonly float V; private const float Epsilon = 0.0001f;
/// <summary> /// <summary>
/// The epsilon for comparing floating point numbers. /// The backing vector for SIMD support.
/// </summary> /// </summary>
private const float Epsilon = 0.0001f; private Vector3 backingVector;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Hsv"/> struct. /// 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> /// <param name="v">The v value (brightness) component.</param>
public Hsv(float h, float s, float v) public Hsv(float h, float s, float v)
{ {
this.H = h.Clamp(0, 360); this.backingVector.X = h.Clamp(0, 360);
this.S = s.Clamp(0, 100); this.backingVector.Y = s.Clamp(0, 1);
this.V = v.Clamp(0, 100); 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> /// <summary>
/// Gets a value indicating whether this <see cref="Hsv"/> is empty. /// Gets a value indicating whether this <see cref="Hsv"/> is empty.
/// </summary> /// </summary>
[EditorBrowsable(EditorBrowsableState.Never)] [EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => Math.Abs(this.H) < Epsilon public bool IsEmpty => this.backingVector.Equals(default(Vector3));
&& Math.Abs(this.S) < Epsilon
&& Math.Abs(this.V) < Epsilon;
/// <summary> /// <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"/>. /// <see cref="Hsv"/>.
/// </summary> /// </summary>
/// <param name="color"> /// <param name="color">The instance of <see cref="Color"/> to convert.</param>
/// The instance of <see cref="Bgra"/> to convert.
/// </param>
/// <returns> /// <returns>
/// An instance of <see cref="Hsv"/>. /// An instance of <see cref="Hsv"/>.
/// </returns> /// </returns>
public static implicit operator Hsv(Bgra color) public static implicit operator Hsv(Color color)
{ {
float r = color.R / 255f; color = color.Limited;
float g = color.G / 255f; float r = color.R;
float b = color.B / 255f; float g = color.G;
float b = color.B;
float max = Math.Max(r, Math.Max(g, b)); float max = Math.Max(r, Math.Max(g, b));
float min = Math.Min(r, Math.Min(g, b)); float min = Math.Min(r, Math.Min(g, b));
@ -87,7 +90,7 @@ namespace ImageProcessor
if (Math.Abs(chroma) < Epsilon) if (Math.Abs(chroma) < Epsilon)
{ {
return new Hsv(0, s * 100, v * 100); return new Hsv(0, s, v);
} }
if (Math.Abs(chroma) < Epsilon) if (Math.Abs(chroma) < Epsilon)
@ -115,13 +118,11 @@ namespace ImageProcessor
s = chroma / v; s = chroma / v;
return new Hsv(h, s * 100, v * 100); return new Hsv(h, s, v);
} }
/// <summary> /// <summary>
/// Compares two <see cref="Hsv"/> objects. The result specifies whether the values /// Compares two <see cref="Hsv"/> objects for equality.
/// 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.
/// </summary> /// </summary>
/// <param name="left"> /// <param name="left">
/// The <see cref="Hsv"/> on the left side of the operand. /// The <see cref="Hsv"/> on the left side of the operand.
@ -138,9 +139,7 @@ namespace ImageProcessor
} }
/// <summary> /// <summary>
/// Compares two <see cref="Hsv"/> objects. The result specifies whether the values /// Compares two <see cref="Hsv"/> objects for inequality.
/// 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.
/// </summary> /// </summary>
/// <param name="left"> /// <param name="left">
/// The <see cref="Hsv"/> on the left side of the operand. /// The <see cref="Hsv"/> on the left side of the operand.
@ -156,50 +155,26 @@ namespace ImageProcessor
return !left.Equals(right); return !left.Equals(right);
} }
/// <summary> /// <inheritdoc/>
/// 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) public override bool Equals(object obj)
{ {
if (obj is Hsv) if (obj is Hsv)
{ {
Hsv color = (Hsv)obj; Hsv color = (Hsv)obj;
return Math.Abs(this.H - color.H) < Epsilon return this.backingVector == color.backingVector;
&& Math.Abs(this.S - color.S) < Epsilon
&& Math.Abs(this.V - color.V) < Epsilon;
} }
return false; return false;
} }
/// <summary> /// <inheritdoc/>
/// 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() public override int GetHashCode()
{ {
unchecked return GetHashCode(this);
{
int hashCode = this.H.GetHashCode();
hashCode = (hashCode * 397) ^ this.S.GetHashCode();
hashCode = (hashCode * 397) ^ this.V.GetHashCode();
return hashCode;
}
} }
/// <summary> /// <inheritdoc/>
/// 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() public override string ToString()
{ {
if (this.IsEmpty) if (this.IsEmpty)
@ -210,18 +185,21 @@ namespace ImageProcessor
return $"Hsv [ H={this.H:#0.##}, S={this.S:#0.##}, V={this.V:#0.##} ]"; 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> /// <summary>
/// Indicates whether the current object is equal to another object of the same type. /// Returns the hash code for this instance.
/// </summary> /// </summary>
/// <param name="color">
/// The instance of <see cref="Hsv"/> to return the hash code for.
/// </param>
/// <returns> /// <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> /// </returns>
/// <param name="other">An object to compare with this object.</param> private static int GetHashCode(Hsv color) => color.backingVector.GetHashCode();
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;
}
} }
} }

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

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

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> /// </summary>
internal static class ImageMaths 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> /// <summary>
/// Returns the result of a B-C filter against the given value. /// Returns the result of a B-C filter against the given value.
/// <see href="http://www.imagemagick.org/Usage/filter/#cubic_bc"/> /// <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="b">The B-Spline curve variable.</param>
/// <param name="c">The Cardinal curve variable.</param> /// <param name="c">The Cardinal curve variable.</param>
/// <returns> /// <returns>
/// The <see cref="double"/>. /// The <see cref="float"/>.
/// </returns> /// </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) if (x < 0)
{ {
@ -54,19 +60,19 @@ namespace ImageProcessor
/// The value to calculate the result for. /// The value to calculate the result for.
/// </param> /// </param>
/// <returns> /// <returns>
/// The <see cref="double"/>. /// The <see cref="float"/>.
/// </returns> /// </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) if (Math.Abs(x) > Epsilon)
{ {
x *= Math.PI; x *= PI;
return Clean(Math.Sin(x) / x); return Clean((float)Math.Sin(x) / x);
} }
return 1.0; return 1.0f;
} }
/// <summary> /// <summary>
@ -74,15 +80,15 @@ namespace ImageProcessor
/// </summary> /// </summary>
/// <param name="x">The value to clean.</param> /// <param name="x">The value to clean.</param>
/// <returns> /// <returns>
/// The <see cref="double"/> /// The <see cref="float"/>
/// </returns>. /// </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) if (Math.Abs(x) < Epsilon)
{ {
return 0.0; return 0f;
} }
return x; return x;

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

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

28
src/ImageProcessor/Filters/Alpha.cs

@ -6,6 +6,7 @@
namespace ImageProcessor.Filters namespace ImageProcessor.Filters
{ {
using System; using System;
using System.Threading.Tasks;
/// <summary> /// <summary>
/// An <see cref="IImageProcessor"/> to change the Alpha of an <see cref="Image"/>. /// An <see cref="IImageProcessor"/> to change the Alpha of an <see cref="Image"/>.
@ -33,24 +34,27 @@ namespace ImageProcessor.Filters
/// <inheritdoc/> /// <inheritdoc/>
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) 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 sourceY = sourceRectangle.Y;
int sourceBottom = sourceRectangle.Bottom; int sourceBottom = sourceRectangle.Bottom;
int startX = sourceRectangle.X; int startX = sourceRectangle.X;
int endX = sourceRectangle.Right; int endX = sourceRectangle.Right;
for (int y = startY; y < endY; y++) Parallel.For(
{ startY,
if (y >= sourceY && y < sourceBottom) endY,
{ y =>
for (int x = startX; x < endX; x++)
{ {
Bgra color = source[x, y]; if (y >= sourceY && y < sourceBottom)
double a = color.A * alpha; {
target[x, y] = new Bgra(color.B, color.G, color.R, a.ToByte()); 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 namespace ImageProcessor.Filters
{ {
using System.Threading.Tasks;
/// <summary> /// <summary>
/// The color matrix filter. /// The color matrix filter.
/// </summary> /// </summary>
@ -34,68 +36,56 @@ namespace ImageProcessor.Filters
/// <inheritdoc/> /// <inheritdoc/>
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) 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 sourceY = sourceRectangle.Y;
int sourceBottom = sourceRectangle.Bottom; int sourceBottom = sourceRectangle.Bottom;
int startX = sourceRectangle.X; int startX = sourceRectangle.X;
int endX = sourceRectangle.Right; int endX = sourceRectangle.Right;
ColorMatrix matrix = this.Value; ColorMatrix matrix = this.Value;
Bgra previousColor = source[0, 0];
Bgra pixelValue = this.ApplyMatrix(previousColor, matrix);
for (int y = startY; y < endY; y++) Parallel.For(
{ startY,
if (y >= sourceY && y < sourceBottom) endY,
{ y =>
for (int x = startX; x < endX; x++)
{ {
Bgra sourceColor = source[x, y]; if (y >= sourceY && y < sourceBottom)
// 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)
{ {
// Perform the operation on the pixel. for (int x = startX; x < endX; x++)
pixelValue = this.ApplyMatrix(sourceColor, matrix); {
target[x, y] = ApplyMatrix(source[x, y], matrix, gamma);
// And setup the previous pointer }
previousColor = sourceColor;
} }
});
target[x, y] = pixelValue;
}
}
}
} }
/// <summary> /// <summary>
/// Applies the color matrix against the given color. /// Applies the color matrix against the given color.
/// </summary> /// </summary>
/// <param name="sourceColor">The source color.</param> /// <param name="color">The source color.</param>
/// <param name="matrix">The matrix.</param> /// <param name="matrix">The matrix.</param>
/// <param name="gamma">Whether to perform gamma adjustments.</param>
/// <returns> /// <returns>
/// The <see cref="Bgra"/>. /// The <see cref="Color"/>.
/// </returns> /// </returns>
private Bgra ApplyMatrix(Bgra sourceColor, ColorMatrix matrix) private static Color ApplyMatrix(Color color, ColorMatrix matrix, bool gamma)
{ {
bool gamma = this.GammaAdjust;
if (gamma) if (gamma)
{ {
sourceColor = PixelOperations.ToLinear(sourceColor); color = PixelOperations.ToLinear(color);
} }
int sr = sourceColor.R; float sr = color.R;
int sg = sourceColor.G; float sg = color.G;
int sb = sourceColor.B; float sb = color.B;
int sa = sourceColor.A; float sa = color.A;
// TODO: Investigate RGBAW // TODO: Investigate RGBAW
byte r = ((sr * matrix.Matrix00) + (sg * matrix.Matrix10) + (sb * matrix.Matrix20) + (sa * matrix.Matrix30) + (255f * matrix.Matrix40)).ToByte(); color.R = (sr * matrix.Matrix00) + (sg * matrix.Matrix10) + (sb * matrix.Matrix20) + (sa * matrix.Matrix30) + matrix.Matrix40;
byte g = ((sr * matrix.Matrix01) + (sg * matrix.Matrix11) + (sb * matrix.Matrix21) + (sa * matrix.Matrix31) + (255f * matrix.Matrix41)).ToByte(); color.G = (sr * matrix.Matrix01) + (sg * matrix.Matrix11) + (sb * matrix.Matrix21) + (sa * matrix.Matrix31) + matrix.Matrix41;
byte b = ((sr * matrix.Matrix02) + (sg * matrix.Matrix12) + (sb * matrix.Matrix22) + (sa * matrix.Matrix32) + (255f * matrix.Matrix42)).ToByte(); color.B = (sr * matrix.Matrix02) + (sg * matrix.Matrix12) + (sb * matrix.Matrix22) + (sa * matrix.Matrix32) + matrix.Matrix42;
byte a = ((sr * matrix.Matrix03) + (sg * matrix.Matrix13) + (sb * matrix.Matrix23) + (sa * matrix.Matrix33) + (255f * matrix.Matrix43)).ToByte(); 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 namespace ImageProcessor.Filters
{ {
using System; using System;
using System.Threading.Tasks;
/// <summary> /// <summary>
/// An <see cref="IImageProcessor"/> to change the contrast of an <see cref="Image"/>. /// An <see cref="IImageProcessor"/> to change the contrast of an <see cref="Image"/>.
@ -33,48 +34,52 @@ namespace ImageProcessor.Filters
/// <inheritdoc/> /// <inheritdoc/>
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) 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 sourceY = sourceRectangle.Y;
int sourceBottom = sourceRectangle.Bottom; int sourceBottom = sourceRectangle.Bottom;
int startX = sourceRectangle.X; int startX = sourceRectangle.X;
int endX = sourceRectangle.Right; int endX = sourceRectangle.Right;
for (int y = startY; y < endY; y++) Parallel.For(
{ startY,
if (y >= sourceY && y < sourceBottom) endY,
{ y =>
for (int x = startX; x < endX; x++)
{ {
Bgra sourceColor = source[x, y]; if (y >= sourceY && y < sourceBottom)
sourceColor = PixelOperations.ToLinear(sourceColor); {
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; color.R -= 0.5f;
r -= 0.5; color.R *= contrast;
r *= contrast; color.R += 0.5f;
r += 0.5;
r *= 255;
r = r.ToByte();
double g = sourceColor.G / 255.0; color.G -= 0.5f;
g -= 0.5; color.G *= contrast;
g *= contrast; color.G += 0.5f;
g += 0.5;
g *= 255;
g = g.ToByte();
double b = sourceColor.B / 255.0; color.B -= 0.5f;
b -= 0.5; color.B *= contrast;
b *= contrast; color.B += 0.5f;
b += 0.5;
b *= 255;
b = b.ToByte();
Bgra destinationColor = new Bgra(b.ToByte(), g.ToByte(), r.ToByte(), sourceColor.A); return PixelOperations.ToSrgb(color);
destinationColor = PixelOperations.ToSrgb(destinationColor);
target[x, y] = destinationColor;
}
}
}
} }
} }
} }

31
src/ImageProcessor/Filters/Invert.cs

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

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

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

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

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

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

@ -31,7 +31,7 @@ namespace ImageProcessor.Formats
/// <summary> /// <summary>
/// The current frame. /// The current frame.
/// </summary> /// </summary>
private byte[] currentFrame; private float[] currentFrame;
/// <summary> /// <summary>
/// The logical screen descriptor. /// The logical screen descriptor.
@ -288,15 +288,15 @@ namespace ImageProcessor.Formats
if (this.currentFrame == null) 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 && if (this.graphicsControlExtension != null &&
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious) this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
{ {
lastFrame = new byte[imageWidth * imageHeight * 4]; lastFrame = new float[imageWidth * imageHeight * 4];
Array.Copy(this.currentFrame, lastFrame, lastFrame.Length); Array.Copy(this.currentFrame, lastFrame, lastFrame.Length);
} }
@ -352,18 +352,20 @@ namespace ImageProcessor.Formats
this.graphicsControlExtension.TransparencyFlag == false || this.graphicsControlExtension.TransparencyFlag == false ||
this.graphicsControlExtension.TransparencyIndex != index) 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; int indexOffset = index * 3;
this.currentFrame[offset + 0] = colorTable[indexOffset + 2]; this.currentFrame[offset + 0] = colorTable[indexOffset] / 255f; // r
this.currentFrame[offset + 1] = colorTable[indexOffset + 1]; this.currentFrame[offset + 1] = colorTable[indexOffset + 1] / 255f; // g
this.currentFrame[offset + 2] = colorTable[indexOffset + 0]; this.currentFrame[offset + 2] = colorTable[indexOffset + 2] / 255f; // b
this.currentFrame[offset + 3] = 255; this.currentFrame[offset + 3] = 1; // a
} }
i++; i++;
} }
} }
byte[] pixels = new byte[imageWidth * imageHeight * 4]; float[] pixels = new float[imageWidth * imageHeight * 4];
Array.Copy(this.currentFrame, pixels, pixels.Length); Array.Copy(this.currentFrame, pixels, pixels.Length);
@ -406,6 +408,7 @@ namespace ImageProcessor.Formats
{ {
offset = ((y * imageWidth) + x) * 4; offset = ((y * imageWidth) + x) * 4;
// Stored in r-> g-> b-> a order.
this.currentFrame[offset + 0] = 0; this.currentFrame[offset + 0] = 0;
this.currentFrame[offset + 1] = 0; this.currentFrame[offset + 1] = 0;
this.currentFrame[offset + 2] = 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); QuantizedImage quantizedImage = quantizer.Quantize(image);
// Grab the pallete and write it to the stream. // Grab the pallete and write it to the stream.
Bgra[] pallete = quantizedImage.Palette; Bgra32[] pallete = quantizedImage.Palette;
int pixelCount = pallete.Length; int pixelCount = pallete.Length;
// Get max colors for bit depth. // Get max colors for bit depth.
@ -137,7 +137,7 @@ namespace ImageProcessor.Formats
for (int i = 0; i < pixelCount; i++) for (int i = 0; i < pixelCount; i++)
{ {
int offset = i * 3; int offset = i * 3;
Bgra color = pallete[i]; Bgra32 color = pallete[i];
colorTable[offset + 2] = color.B; colorTable[offset + 2] = color.B;
colorTable[offset + 1] = color.G; colorTable[offset + 1] = color.G;
colorTable[offset + 0] = color.R; 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, /// This function need only be overridden if your quantize algorithm needs two passes,
/// such as an Octree quantizer. /// such as an Octree quantizer.
/// </remarks> /// </remarks>
protected override void InitialQuantizePixel(Bgra pixel) protected override void InitialQuantizePixel(Bgra32 pixel)
{ {
// Add the color to the Octree // Add the color to the Octree
this.octree.AddColor(pixel); this.octree.AddColor(pixel);
@ -85,7 +85,7 @@ namespace ImageProcessor.Formats
/// <returns> /// <returns>
/// The quantized value /// The quantized value
/// </returns> /// </returns>
protected override byte QuantizePixel(Bgra pixel) protected override byte QuantizePixel(Bgra32 pixel)
{ {
// The color at [maxColors] is set to transparent // The color at [maxColors] is set to transparent
byte paletteIndex = (byte)this.maxColors; byte paletteIndex = (byte)this.maxColors;
@ -105,13 +105,13 @@ namespace ImageProcessor.Formats
/// <returns> /// <returns>
/// The new color palette /// The new color palette
/// </returns> /// </returns>
protected override List<Bgra> GetPalette() protected override List<Bgra32> GetPalette()
{ {
// First off convert the Octree to maxColors colors // 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 // Add empty color for transparency
palette.Add(Bgra.Empty); palette.Add(Bgra32.Empty);
return palette; return palette;
} }
@ -190,18 +190,18 @@ namespace ImageProcessor.Formats
/// Add a given color value to the Octree /// Add a given color value to the Octree
/// </summary> /// </summary>
/// <param name="pixel"> /// <param name="pixel">
/// The <see cref="Bgra"/>containing color information to add. /// The <see cref="Bgra32"/>containing color information to add.
/// </param> /// </param>
public void AddColor(Bgra pixel) public void AddColor(Bgra32 pixel)
{ {
// Check if this request is for the same color as the last // 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 // 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. // 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); this.root.AddColor(pixel, this.maxColorBits, 0, this);
} }
else else
@ -212,7 +212,7 @@ namespace ImageProcessor.Formats
} }
else else
{ {
this.previousColor = pixel.BGRA; this.previousColor = pixel.Bgra;
this.root.AddColor(pixel, this.maxColorBits, 0, this); this.root.AddColor(pixel, this.maxColorBits, 0, this);
} }
} }
@ -226,7 +226,7 @@ namespace ImageProcessor.Formats
/// <returns> /// <returns>
/// An <see cref="List{Bgra}"/> with the palletized colors /// An <see cref="List{Bgra}"/> with the palletized colors
/// </returns> /// </returns>
public List<Bgra> Palletize(int colorCount) public List<Bgra32> Palletize(int colorCount)
{ {
while (this.Leaves > colorCount) while (this.Leaves > colorCount)
{ {
@ -234,7 +234,7 @@ namespace ImageProcessor.Formats
} }
// Now palletize the nodes // Now palletize the nodes
List<Bgra> palette = new List<Bgra>(this.Leaves); List<Bgra32> palette = new List<Bgra32>(this.Leaves);
int paletteIndex = 0; int paletteIndex = 0;
this.root.ConstructPalette(palette, ref paletteIndex); this.root.ConstructPalette(palette, ref paletteIndex);
@ -246,12 +246,12 @@ namespace ImageProcessor.Formats
/// Get the palette index for the passed color /// Get the palette index for the passed color
/// </summary> /// </summary>
/// <param name="pixel"> /// <param name="pixel">
/// The <see cref="Bgra"/> containing the pixel data. /// The <see cref="Bgra32"/> containing the pixel data.
/// </param> /// </param>
/// <returns> /// <returns>
/// The index of the given structure. /// The index of the given structure.
/// </returns> /// </returns>
public int GetPaletteIndex(Bgra pixel) public int GetPaletteIndex(Bgra32 pixel)
{ {
return this.root.GetPaletteIndex(pixel, 0); return this.root.GetPaletteIndex(pixel, 0);
} }
@ -274,7 +274,7 @@ namespace ImageProcessor.Formats
{ {
// Find the deepest level containing at least one reducible node // Find the deepest level containing at least one reducible node
int index = this.maxColorBits - 1; int index = this.maxColorBits - 1;
while ((index > 0) && (null == this.reducibleNodes[index])) while ((index > 0) && (this.reducibleNodes[index] == null))
{ {
index--; index--;
} }
@ -387,7 +387,7 @@ namespace ImageProcessor.Formats
/// <param name="octree"> /// <param name="octree">
/// The tree to which this node belongs /// The tree to which this node belongs
/// </param> /// </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 // Update the color information if this is a leaf
if (this.leaf) if (this.leaf)
@ -407,7 +407,7 @@ namespace ImageProcessor.Formats
OctreeNode child = this.children[index]; OctreeNode child = this.children[index];
if (null == child) if (child == null)
{ {
// Create a new child node and store it in the array // Create a new child node and store it in the array
child = new OctreeNode(level + 1, colorBits, octree); 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 // Loop through all children and add their information to this node
for (int index = 0; index < 8; index++) for (int index = 0; index < 8; index++)
{ {
if (null != this.children[index]) if (this.children[index] != null)
{ {
this.red += this.children[index].red; this.red += this.children[index].red;
this.green += this.children[index].green; this.green += this.children[index].green;
@ -458,7 +458,7 @@ namespace ImageProcessor.Formats
/// <param name="index"> /// <param name="index">
/// The current palette index /// The current palette index
/// </param> /// </param>
public void ConstructPalette(List<Bgra> palette, ref int index) public void ConstructPalette(List<Bgra32> palette, ref int index)
{ {
if (this.leaf) if (this.leaf)
{ {
@ -470,14 +470,14 @@ namespace ImageProcessor.Formats
byte b = (this.blue / this.pixelCount).ToByte(); byte b = (this.blue / this.pixelCount).ToByte();
// And set the color of the palette entry // And set the color of the palette entry
palette.Add(new Bgra(b, g, r)); palette.Add(new Bgra32(b, g, r));
} }
else else
{ {
// Loop through children looking for leaves // Loop through children looking for leaves
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
{ {
if (null != this.children[i]) if (this.children[i] != null)
{ {
this.children[i].ConstructPalette(palette, ref index); this.children[i].ConstructPalette(palette, ref index);
} }
@ -489,7 +489,7 @@ namespace ImageProcessor.Formats
/// Return the palette index for the passed color /// Return the palette index for the passed color
/// </summary> /// </summary>
/// <param name="pixel"> /// <param name="pixel">
/// The <see cref="Bgra"/> representing the pixel. /// The <see cref="Bgra32"/> representing the pixel.
/// </param> /// </param>
/// <param name="level"> /// <param name="level">
/// The level. /// The level.
@ -497,7 +497,7 @@ namespace ImageProcessor.Formats
/// <returns> /// <returns>
/// The <see cref="int"/> representing the index of the pixel in the palette. /// The <see cref="int"/> representing the index of the pixel in the palette.
/// </returns> /// </returns>
public int GetPaletteIndex(Bgra pixel, int level) public int GetPaletteIndex(Bgra32 pixel, int level)
{ {
int index = this.paletteIndex; int index = this.paletteIndex;
@ -508,7 +508,7 @@ namespace ImageProcessor.Formats
((pixel.G & Mask[level]) >> (shift - 1)) | ((pixel.G & Mask[level]) >> (shift - 1)) |
((pixel.B & Mask[level]) >> shift); ((pixel.B & Mask[level]) >> shift);
if (null != this.children[pixelIndex]) if (this.children[pixelIndex] != null)
{ {
index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1); index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1);
} }
@ -527,7 +527,7 @@ namespace ImageProcessor.Formats
/// <param name="pixel"> /// <param name="pixel">
/// The pixel to add. /// The pixel to add.
/// </param> /// </param>
public void Increment(Bgra pixel) public void Increment(Bgra32 pixel)
{ {
this.pixelCount++; this.pixelCount++;
this.red += pixel.R; 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="height">The image height.</param>
/// <param name="palette">The color palette.</param> /// <param name="palette">The color palette.</param>
/// <param name="pixels">The quantized pixels.</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(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height)); Guard.MustBeGreaterThan(height, 0, nameof(height));
@ -51,7 +51,7 @@ namespace ImageProcessor.Formats
/// <summary> /// <summary>
/// Gets the color palette of this <see cref="T:QuantizedImage"/>. /// Gets the color palette of this <see cref="T:QuantizedImage"/>.
/// </summary> /// </summary>
public Bgra[] Palette { get; } public Bgra32[] Palette { get; }
/// <summary> /// <summary>
/// Gets the pixels of this <see cref="T:QuantizedImage"/>. /// Gets the pixels of this <see cref="T:QuantizedImage"/>.
@ -69,12 +69,12 @@ namespace ImageProcessor.Formats
Image image = new Image(); Image image = new Image();
int pixelCount = this.Pixels.Length; int pixelCount = this.Pixels.Length;
byte[] bgraPixels = new byte[pixelCount * 4]; float[] bgraPixels = new float[pixelCount * 4];
for (int i = 0; i < pixelCount; i++) for (int i = 0; i < pixelCount; i++)
{ {
int offset = i * 4; 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 + 0] = color.B;
bgraPixels[offset + 1] = color.G; bgraPixels[offset + 1] = color.G;
bgraPixels[offset + 2] = color.R; bgraPixels[offset + 2] = color.R;

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

@ -18,7 +18,7 @@ namespace ImageProcessor.Formats
private readonly bool singlePass; private readonly bool singlePass;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Quantizer"/> class. /// Initializes a new instance of the <see cref="Quantizer"/> class.
/// </summary> /// </summary>
/// <param name="singlePass"> /// <param name="singlePass">
/// If true, the quantization only needs to loop through the source pixels once /// 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]; 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); this.SecondPass(imageBase, quantizedPixels, width, height);
@ -94,8 +95,9 @@ namespace ImageProcessor.Formats
{ {
int i = 0; int i = 0;
// Convert the first pixel, so that I have values going into the loop // Convert the first pixel, so that I have values going into the loop.
Bgra previousPixel = source[0, 0]; // Implicit cast here from Color.
Bgra32 previousPixel = source[0, 0];
byte pixelValue = this.QuantizePixel(previousPixel); byte pixelValue = this.QuantizePixel(previousPixel);
output[0] = pixelValue; output[0] = pixelValue;
@ -104,7 +106,8 @@ namespace ImageProcessor.Formats
{ {
for (int x = 0; x < width; x++) 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 // 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. // 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, /// This function need only be overridden if your quantize algorithm needs two passes,
/// such as an Octree quantizer. /// such as an Octree quantizer.
/// </remarks> /// </remarks>
protected virtual void InitialQuantizePixel(Bgra pixel) protected virtual void InitialQuantizePixel(Bgra32 pixel)
{ {
} }
@ -145,7 +148,7 @@ namespace ImageProcessor.Formats
/// <returns> /// <returns>
/// The quantized value /// The quantized value
/// </returns> /// </returns>
protected abstract byte QuantizePixel(Bgra pixel); protected abstract byte QuantizePixel(Bgra32 pixel);
/// <summary> /// <summary>
/// Retrieve the palette for the quantized image /// Retrieve the palette for the quantized image
@ -153,6 +156,6 @@ namespace ImageProcessor.Formats
/// <returns> /// <returns>
/// The new color palette /// The new color palette
/// </returns> /// </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;
using System.IO; using System.IO;
using System.Threading.Tasks;
using BitMiracle.LibJpeg; using BitMiracle.LibJpeg;
/// <summary> /// <summary>
@ -97,50 +99,41 @@ namespace ImageProcessor.Formats
int pixelWidth = jpg.Width; int pixelWidth = jpg.Width;
int pixelHeight = jpg.Height; 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)) if (!(jpg.Colorspace == Colorspace.RGB && jpg.BitsPerComponent == 8))
{ {
throw new NotSupportedException("JpegDecoder only support RGB color space."); throw new NotSupportedException("JpegDecoder only support RGB color space.");
} }
for (int y = 0; y < pixelHeight; y++) Parallel.For(
{ 0,
SampleRow row = jpg.GetRow(y); pixelHeight,
y =>
{
SampleRow row = jpg.GetRow(y);
for (int x = 0; x < pixelWidth; x++) for (int x = 0; x < pixelWidth; x++)
{ {
Sample sample = row.GetAt(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 + 0] = sample[0] / 255f;
pixels[offset + 1] = (byte)sample[1]; pixels[offset + 1] = sample[1] / 255f;
pixels[offset + 2] = (byte)sample[0]; pixels[offset + 2] = sample[2] / 255f;
pixels[offset + 3] = 255; pixels[offset + 3] = 1;
} }
} });
image.SetPixels(pixelWidth, pixelHeight, pixels); image.SetPixels(pixelWidth, pixelHeight, pixels);
} }
/// <summary> /// <summary>
/// /// Returns a value indicating whether the given bytes identify Jpeg data.
/// </summary> /// </summary>
/// <param name="header"></param> /// <param name="header">The bytes representing the file header.</param>
/// <returns></returns> /// <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;
}
private static bool IsJpeg(byte[] header) private static bool IsJpeg(byte[] header)
{ {
bool isJpg = bool isJpg =
@ -152,5 +145,22 @@ namespace ImageProcessor.Formats
return isJpg; 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;
using System.IO; using System.IO;
using System.Threading.Tasks;
using BitMiracle.LibJpeg; using BitMiracle.LibJpeg;
@ -88,26 +89,29 @@ namespace ImageProcessor.Formats
int pixelWidth = image.Width; int pixelWidth = image.Width;
int pixelHeight = image.Height; int pixelHeight = image.Height;
byte[] sourcePixels = image.Pixels; float[] sourcePixels = image.Pixels;
SampleRow[] rows = new SampleRow[pixelHeight]; SampleRow[] rows = new SampleRow[pixelHeight];
for (int y = 0; y < pixelHeight; y++) Parallel.For(
{ 0,
byte[] samples = new byte[pixelWidth * 3]; pixelHeight,
y =>
for (int x = 0; x < pixelWidth; x++) {
{ byte[] samples = new byte[pixelWidth * 3];
int start = x * 3;
int source = ((y * pixelWidth) + x) * 4; for (int x = 0; x < pixelWidth; x++)
{
samples[start] = sourcePixels[source + 2]; int start = x * 3;
samples[start + 1] = sourcePixels[source + 1]; int source = ((y * pixelWidth) + x) * 4;
samples[start + 2] = sourcePixels[source];
} samples[start] = (byte)(sourcePixels[source].Clamp(0, 1) * 255);
samples[start + 1] = (byte)(sourcePixels[source + 1].Clamp(0, 1) * 255);
rows[y] = new SampleRow(samples, pixelWidth, 8, 3); 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); JpegImage jpg = new JpegImage(rows, Colorspace.RGB);
jpg.WriteJpeg(stream, new CompressionParameters { Quality = this.Quality }); 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 file="GrayscaleReader.cs" company="James South"> // Copyright (c) James South and contributors.
// Copyright (c) James South and contributors. // Licensed under the Apache License, Version 2.0.
// Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
// <summary>
// Color reader for reading grayscale colors from a png file.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats namespace ImageProcessor.Formats
{ {
@ -37,31 +32,25 @@ namespace ImageProcessor.Formats
this.useAlpha = useAlpha; this.useAlpha = useAlpha;
} }
/// <summary> /// <inheritdoc/>
/// Reads the specified scanline. public void ReadScanline(byte[] scanline, float[] pixels, PngHeader header)
/// </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)
{ {
int offset; int offset;
byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth); 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) if (this.useAlpha)
{ {
for (int x = 0; x < header.Width / 2; x++) for (int x = 0; x < header.Width / 2; x++)
{ {
offset = ((this.row * header.Width) + x) * 4; offset = ((this.row * header.Width) + x) * 4;
pixels[offset + 0] = newScanline[x * 2]; pixels[offset] = newScanline[x * 2] / 255f;
pixels[offset + 1] = newScanline[x * 2]; pixels[offset + 1] = newScanline[x * 2] / 255f;
pixels[offset + 2] = newScanline[x * 2]; pixels[offset + 2] = newScanline[x * 2] / 255f;
pixels[offset + 3] = newScanline[(x * 2) + 1]; pixels[offset + 3] = newScanline[(x * 2) + 1] / 255f;
} }
} }
else else
@ -70,10 +59,10 @@ namespace ImageProcessor.Formats
{ {
offset = ((this.row * header.Width) + x) * 4; offset = ((this.row * header.Width) + x) * 4;
pixels[offset + 0] = newScanline[x]; pixels[offset] = newScanline[x] / 255f;
pixels[offset + 1] = newScanline[x]; pixels[offset + 1] = newScanline[x] / 255f;
pixels[offset + 2] = newScanline[x]; pixels[offset + 2] = newScanline[x] / 255f;
pixels[offset + 3] = 255; pixels[offset + 3] = 1;
} }
} }

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

@ -1,18 +1,12 @@
// -------------------------------------------------------------------------------------------------------------------- // <copyright file="IColorReader.cs" company="James South">
// <copyright file="IColorReader.cs" company="James South"> // Copyright (c) James South and contributors.
// Copyright (c) James South and contributors. // Licensed under the Apache License, Version 2.0.
// Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
// <summary>
// Encapsulates methods for color readers, which are responsible for reading
// different color formats from a png file.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats namespace ImageProcessor.Formats
{ {
/// <summary> /// <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. /// different color formats from a png file.
/// </summary> /// </summary>
public interface IColorReader public interface IColorReader
@ -26,6 +20,6 @@ namespace ImageProcessor.Formats
/// The header, which contains information about the png file, like /// The header, which contains information about the png file, like
/// the width of the image and the height. /// the width of the image and the height.
/// </param> /// </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 file="PaletteIndexReader.cs" company="James South"> // Copyright (c) James South and contributors.
// Copyright (c) James South and contributors. // Licensed under the Apache License, Version 2.0.
// Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
// <summary>
// A color reader for reading palette indices from the png file.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats namespace ImageProcessor.Formats
{ {
@ -43,14 +38,8 @@ namespace ImageProcessor.Formats
this.paletteAlpha = paletteAlpha; this.paletteAlpha = paletteAlpha;
} }
/// <summary> /// <inheritdoc/>
/// Reads the specified scanline. public void ReadScanline(byte[] scanline, float[] pixels, PngHeader header)
/// </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)
{ {
byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth); byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth);
int offset, index; int offset, index;
@ -65,13 +54,14 @@ namespace ImageProcessor.Formats
index = newScanline[i]; index = newScanline[i];
offset = ((this.row * header.Width) + i) * 4; offset = ((this.row * header.Width) + i) * 4;
int pixelOffset = index * 3;
pixels[offset + 0] = this.palette[(index * 3) + 2]; pixels[offset] = this.palette[pixelOffset] / 255f;
pixels[offset + 1] = this.palette[(index * 3) + 1]; pixels[offset + 1] = this.palette[pixelOffset + 1] / 255f;
pixels[offset + 2] = this.palette[(index * 3) + 0]; pixels[offset + 2] = this.palette[pixelOffset + 2] / 255f;
pixels[offset + 3] = this.paletteAlpha.Length > index pixels[offset + 3] = this.paletteAlpha.Length > index
? this.paletteAlpha[index] ? this.paletteAlpha[index] / 255f
: (byte)255; : 1;
} }
} }
else else
@ -81,11 +71,12 @@ namespace ImageProcessor.Formats
index = newScanline[i]; index = newScanline[i];
offset = ((this.row * header.Width) + i) * 4; offset = ((this.row * header.Width) + i) * 4;
int pixelOffset = index * 3;
pixels[offset + 0] = this.palette[(index * 3) + 2]; pixels[offset] = this.palette[pixelOffset] / 255f;
pixels[offset + 1] = this.palette[(index * 3) + 1]; pixels[offset + 1] = this.palette[pixelOffset + 1] / 255f;
pixels[offset + 2] = this.palette[(index * 3) + 0]; pixels[offset + 2] = this.palette[pixelOffset + 2] / 255f;
pixels[offset + 3] = 255; pixels[offset + 3] = 1;
} }
} }

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

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

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

@ -8,9 +8,6 @@ namespace ImageProcessor.Formats
using System; using System;
using System.IO; using System.IO;
//using ICSharpCode.SharpZipLib.Checksums;
//using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
/// <summary> /// <summary>
/// Image encoder for writing image data to a stream in png format. /// Image encoder for writing image data to a stream in png format.
/// </summary> /// </summary>
@ -36,7 +33,7 @@ namespace ImageProcessor.Formats
public int Quality { get; set; } public int Quality { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public string MimeType => "image/jpepngg"; public string MimeType => "image/png";
/// <inheritdoc/> /// <inheritdoc/>
public string Extension => "png"; public string Extension => "png";
@ -225,7 +222,7 @@ namespace ImageProcessor.Formats
/// <param name="imageBase">The image base.</param> /// <param name="imageBase">The image base.</param>
private void WriteDataChunksFast(Stream stream, ImageBase imageBase) 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 // Convert the pixel array to a new array for adding
// the filter byte. // the filter byte.
@ -297,7 +294,7 @@ namespace ImageProcessor.Formats
/// <param name="imageBase">The image base.</param> /// <param name="imageBase">The image base.</param>
private void WriteDataChunks(Stream stream, ImageBase imageBase) 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]; 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. // Calculate the offset for the original pixel array.
int pixelOffset = ((y * imageBase.Width) + x) * 4; int pixelOffset = ((y * imageBase.Width) + x) * 4;
data[dataOffset + 0] = pixels[pixelOffset + 2]; data[dataOffset] = (byte)(pixels[pixelOffset].Clamp(0, 1) * 255);
data[dataOffset + 1] = pixels[pixelOffset + 1]; data[dataOffset + 1] = (byte)(pixels[pixelOffset + 1].Clamp(0, 1) * 255);
data[dataOffset + 2] = pixels[pixelOffset + 0]; data[dataOffset + 2] = (byte)(pixels[pixelOffset + 2].Clamp(0, 1) * 255);
data[dataOffset + 3] = pixels[pixelOffset + 3]; data[dataOffset + 3] = (byte)(pixels[pixelOffset + 3].Clamp(0, 1) * 255);
if (y > 0) if (y > 0)
{ {
int lastOffset = (((y - 1) * imageBase.Width) + x) * 4; int lastOffset = (((y - 1) * imageBase.Width) + x) * 4;
data[dataOffset + 0] -= pixels[lastOffset + 2]; data[dataOffset] -= (byte)(pixels[lastOffset].Clamp(0, 1) * 255);
data[dataOffset + 1] -= pixels[lastOffset + 1]; data[dataOffset + 1] -= (byte)(pixels[lastOffset + 1].Clamp(0, 1) * 255);
data[dataOffset + 2] -= pixels[lastOffset + 0]; data[dataOffset + 2] -= (byte)(pixels[lastOffset + 2].Clamp(0, 1) * 255);
data[dataOffset + 3] -= pixels[lastOffset + 3]; 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 file="TrueColorReader.cs" company="James South"> // Copyright (c) James South and contributors.
// Copyright (c) James South and contributors. // Licensed under the Apache License, Version 2.0.
// Licensed under the Apache License, Version 2.0.
// </copyright> // </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 namespace ImageProcessor.Formats
{ {
@ -37,14 +31,8 @@ namespace ImageProcessor.Formats
this.useAlpha = useAlpha; this.useAlpha = useAlpha;
} }
/// <summary> /// <inheritdoc/>
/// Reads the specified scanline. public void ReadScanline(byte[] scanline, float[] pixels, PngHeader header)
/// </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)
{ {
int offset; int offset;
@ -56,10 +44,10 @@ namespace ImageProcessor.Formats
{ {
offset = ((this.row * header.Width) + (x >> 2)) * 4; offset = ((this.row * header.Width) + (x >> 2)) * 4;
pixels[offset + 0] = newScanline[x + 2]; pixels[offset + 0] = newScanline[x] / 255f;
pixels[offset + 1] = newScanline[x + 1]; pixels[offset + 1] = newScanline[x + 1] / 255f;
pixels[offset + 2] = newScanline[x + 0]; pixels[offset + 2] = newScanline[x + 2] / 255f;
pixels[offset + 3] = newScanline[x + 3]; pixels[offset + 3] = newScanline[x + 3] / 255f;
} }
} }
else else
@ -67,11 +55,12 @@ namespace ImageProcessor.Formats
for (int x = 0; x < newScanline.Length / 3; x++) for (int x = 0; x < newScanline.Length / 3; x++)
{ {
offset = ((this.row * header.Width) + x) * 4; offset = ((this.row * header.Width) + x) * 4;
int pixelOffset = x * 3;
pixels[offset + 0] = newScanline[(x * 3) + 2]; pixels[offset + 0] = newScanline[pixelOffset] / 255f;
pixels[offset + 1] = newScanline[(x * 3) + 1]; pixels[offset + 1] = newScanline[pixelOffset + 1] / 255f;
pixels[offset + 2] = newScanline[(x * 3) + 0]; pixels[offset + 2] = newScanline[pixelOffset + 2] / 255f;
pixels[offset + 3] = 255; pixels[offset + 3] = 1;
} }
} }

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

@ -3,9 +3,6 @@
using System; using System;
using System.IO; using System.IO;
//using ICSharpCode.SharpZipLib.Zip;
//using ICSharpCode.SharpZipLib.Zip.Compression;
/// <summary> /// <summary>
/// An input buffer customised for use by <see cref="InflaterInputStream"/> /// An input buffer customised for use by <see cref="InflaterInputStream"/>
/// </summary> /// </summary>
@ -14,7 +11,6 @@
/// </remarks> /// </remarks>
public class InflaterInputBuffer public class InflaterInputBuffer
{ {
#region Constructors
/// <summary> /// <summary>
/// Initialise a new instance of <see cref="InflaterInputBuffer"/> with a default buffer size /// Initialise a new instance of <see cref="InflaterInputBuffer"/> with a default buffer size
/// </summary> /// </summary>
@ -39,7 +35,6 @@
rawData = new byte[bufferSize]; rawData = new byte[bufferSize];
clearText = rawData; clearText = rawData;
} }
#endregion
/// <summary> /// <summary>
/// Get the length of bytes bytes in the <see cref="RawData"/> /// Get the length of bytes bytes in the <see cref="RawData"/>
@ -127,17 +122,7 @@
toRead -= count; toRead -= count;
} }
#if !NETCF_1_0 && !NOCRYPTO clearTextLength = rawLength;
if (cryptoTransform != null)
{
clearTextLength = cryptoTransform.TransformBlock(rawData, 0, rawLength, clearText, 0);
}
else
#endif
{
clearTextLength = rawLength;
}
available = clearTextLength; available = clearTextLength;
} }
@ -178,12 +163,14 @@
return 0; return 0;
} }
} }
int toCopy = Math.Min(currentLength, available); 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; currentOffset += toCopy;
currentLength -= toCopy; currentLength -= toCopy;
available -= toCopy; available -= toCopy;
} }
return length; return length;
} }
@ -270,57 +257,12 @@
return (uint)ReadLeInt() | ((long)ReadLeInt() << 32); 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; int rawLength;
byte[] rawData; byte[] rawData;
int clearTextLength; int clearTextLength;
byte[] clearText; byte[] clearText;
#if !NETCF_1_0 && !NOCRYPTO
byte[] internalClearText;
#endif
int available; int available;
#if !NETCF_1_0 && !NOCRYPTO
ICryptoTransform cryptoTransform;
#endif
Stream inputStream; 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> /// <summary>
/// Returns 0 once the end of the stream (EOF) has been reached. /// Returns 0 once the end of the stream (EOF) has been reached.
/// Otherwise returns 1. /// Otherwise returns 1.

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

@ -7,7 +7,6 @@
/// </summary> /// </summary>
public static class ZipConstants public static class ZipConstants
{ {
#region Versions
/// <summary> /// <summary>
/// The version made by field for entries in the central header when created by this library /// The version made by field for entries in the central header when created by this library
/// </summary> /// </summary>
@ -30,9 +29,7 @@
/// The version required for Zip64 extensions (4.5 or higher) /// The version required for Zip64 extensions (4.5 or higher)
/// </summary> /// </summary>
public const int VersionZip64 = 45; public const int VersionZip64 = 45;
#endregion
#region Header Sizes
/// <summary> /// <summary>
/// Size of local entry header (excluding variable length fields at end) /// Size of local entry header (excluding variable length fields at end)
/// </summary> /// </summary>
@ -62,9 +59,7 @@
/// Size of 'classic' cryptographic header stored before any entry data /// Size of 'classic' cryptographic header stored before any entry data
/// </summary> /// </summary>
public const int CryptoHeaderSize = 12; public const int CryptoHeaderSize = 12;
#endregion
#region Header Signatures
/// <summary> /// <summary>
/// Signature for local entry header /// Signature for local entry header
@ -121,52 +116,8 @@
/// End of central directory record signature /// End of central directory record signature
/// </summary> /// </summary>
public const int EndOfCentralDirectorySignature = 'P' | ('K' << 8) | (5 << 16) | (6 << 24); 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; 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> /// <summary>
/// PCL don't support CodePage so we used Encoding instead of /// PCL don't support CodePage so we used Encoding instead of
/// </summary> /// </summary>
@ -176,12 +127,12 @@
{ {
return defaultEncoding; return defaultEncoding;
} }
set set
{ {
defaultEncoding = value; defaultEncoding = value;
} }
} }
#endif
/// <summary> /// <summary>
/// Convert a portion of a byte array to a string. /// Convert a portion of a byte array to a string.
@ -201,11 +152,8 @@
{ {
return string.Empty; return string.Empty;
} }
#if !PCL
return Encoding.GetEncoding(DefaultCodePage).GetString(data, 0, count);
#else
return DefaultEncoding.GetString(data, 0, count); return DefaultEncoding.GetString(data, 0, count);
#endif
} }
/// <summary> /// <summary>
@ -294,11 +242,8 @@
{ {
return new byte[0]; return new byte[0];
} }
#if !PCL
return Encoding.GetEncoding(DefaultCodePage).GetBytes(str);
#else
return DefaultEncoding.GetBytes(str); return DefaultEncoding.GetBytes(str);
#endif
} }
/// <summary> /// <summary>
@ -320,10 +265,8 @@
{ {
return Encoding.UTF8.GetBytes(str); 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 /// and stores the blue, the green, the red and the alpha value for
/// each pixel in this order. /// each pixel in this order.
/// </remarks> /// </remarks>
byte[] Pixels { get; } float[] Pixels { get; }
/// <summary> /// <summary>
/// Gets the width in pixels. /// Gets the width in pixels.
@ -66,8 +66,8 @@ namespace ImageProcessor
/// The y-coordinate of the pixel. Must be greater /// The y-coordinate of the pixel. Must be greater
/// than zero and smaller than the width of the pixel. /// than zero and smaller than the width of the pixel.
/// </param> /// </param>
/// <returns>The <see cref="Bgra"/> at the specified position.</returns> /// <returns>The <see cref="Color"/> at the specified position.</returns>
Bgra this[int x, int y] { get; set; } Color this[int x, int y] { get; set; }
/// <summary> /// <summary>
/// Sets the pixel array of the image. /// Sets the pixel array of the image.
@ -85,6 +85,6 @@ namespace ImageProcessor
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// Thrown if the <paramref name="pixels"/> length is not equal to Width * Height * 4. /// Thrown if the <paramref name="pixels"/> length is not equal to Width * Height * 4.
/// </exception> /// </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.Width = width;
this.Height = height; this.Height = height;
this.Pixels = new byte[width * height * 4]; this.Pixels = new float[width * height * 4];
} }
/// <summary> /// <summary>
@ -56,13 +56,13 @@ namespace ImageProcessor
{ {
Guard.NotNull(other, nameof(other), "Other image cannot be null."); Guard.NotNull(other, nameof(other), "Other image cannot be null.");
byte[] pixels = other.Pixels; float[] pixels = other.Pixels;
this.Width = other.Width; this.Width = other.Width;
this.Height = other.Height; this.Height = other.Height;
this.Quality = other.Quality; this.Quality = other.Quality;
this.FrameDelay = other.FrameDelay; this.FrameDelay = other.FrameDelay;
this.Pixels = new byte[pixels.Length]; this.Pixels = new float[pixels.Length];
Array.Copy(pixels, this.Pixels, 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 /// and stores the blue, the green, the red and the alpha value for
/// each pixel in this order. /// each pixel in this order.
/// </remarks> /// </remarks>
public byte[] Pixels { get; private set; } public float[] Pixels { get; private set; }
/// <summary> /// <summary>
/// Gets the width in pixels. /// Gets the width in pixels.
@ -106,9 +106,7 @@ namespace ImageProcessor
/// </summary> /// </summary>
public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height); public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height);
/// <summary> /// <inheritdoc/>
/// Gets or sets th quality of the image. This affects the output quality of lossy image formats.
/// </summary>
public int Quality { get; set; } public int Quality { get; set; }
/// <summary> /// <summary>
@ -119,19 +117,8 @@ namespace ImageProcessor
/// </summary> /// </summary>
public int FrameDelay { get; set; } public int FrameDelay { get; set; }
/// <summary> /// <inheritdoc/>
/// Gets or sets the color of a pixel at the specified position. public Color this[int x, int y]
/// </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]
{ {
get get
{ {
@ -148,7 +135,7 @@ namespace ImageProcessor
#endif #endif
int start = ((y * this.Width) + x) * 4; 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 set
@ -166,31 +153,17 @@ namespace ImageProcessor
#endif #endif
int start = ((y * this.Width) + x) * 4; 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 + 1] = value.G;
this.Pixels[start + 2] = value.R; this.Pixels[start + 2] = value.B;
this.Pixels[start + 3] = value.A; this.Pixels[start + 3] = value.A;
} }
} }
/// <summary> /// <inheritdoc/>
/// Sets the pixel array of the image. public void SetPixels(int width, int height, float[] pixels)
/// </summary>
/// <param name="width">
/// The new width of the image. Must be greater than zero.</param>
/// <param name="height">The new height of the image. Must be greater than zero.</param>
/// <param name="pixels">
/// The array with colors. Must be a multiple
/// of four, 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)
{ {
#if DEBUG
if (width <= 0) if (width <= 0)
{ {
throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than or equals than zero."); 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."); throw new ArgumentException("Pixel array must have the length of Width * Height * 4.");
} }
#endif
this.Width = width; this.Width = width;
this.Height = height; this.Height = height;
this.Pixels = pixels; this.Pixels = pixels;

17
src/ImageProcessor/ImageProcessor.csproj

@ -39,9 +39,13 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\ImageProcessor.XML</DocumentationFile>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <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\ImageMaths.cs" />
<Compile Include="Common\Helpers\PixelOperations.cs" /> <Compile Include="Common\Helpers\PixelOperations.cs" />
<Compile Include="Filters\Invert.cs" /> <Compile Include="Filters\Invert.cs" />
@ -80,8 +84,8 @@
<Compile Include="Numerics\Rectangle.cs" /> <Compile Include="Numerics\Rectangle.cs" />
<Compile Include="ParallelImageProcessor.cs" /> <Compile Include="ParallelImageProcessor.cs" />
<Compile Include="IImageProcessor.cs" /> <Compile Include="IImageProcessor.cs" />
<Compile Include="Colors\Hsv.cs" /> <Compile Include="Colors\Formats\Hsv.cs" />
<Compile Include="Colors\YCbCr.cs" /> <Compile Include="Colors\Formats\YCbCr.cs" />
<Compile Include="Common\Extensions\ByteExtensions.cs" /> <Compile Include="Common\Extensions\ByteExtensions.cs" />
<Compile Include="Common\Extensions\ComparableExtensions.cs" /> <Compile Include="Common\Extensions\ComparableExtensions.cs" />
<Compile Include="Formats\Bmp\BmpCompression.cs" /> <Compile Include="Formats\Bmp\BmpCompression.cs" />
@ -196,7 +200,7 @@
<Compile Include="IImageBase.cs" /> <Compile Include="IImageBase.cs" />
<Compile Include="ImageProperty.cs" /> <Compile Include="ImageProperty.cs" />
<Compile Include="ImageFrame.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\Exceptions\ImageFormatException.cs" />
<Compile Include="Common\Helpers\Guard.cs" /> <Compile Include="Common\Helpers\Guard.cs" />
<Compile Include="Formats\Gif\GifDecoder.cs" /> <Compile Include="Formats\Gif\GifDecoder.cs" />
@ -235,13 +239,14 @@
<None Include="Formats\Jpg\README.md" /> <None Include="Formats\Jpg\README.md" />
<None Include="Formats\Png\Zlib\README.md" /> <None Include="Formats\Png\Zlib\README.md" />
<None Include="project.json" /> <None Include="project.json" />
<None Include="stylecop.json" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Formats\Bmp\README.md" /> <None Include="Formats\Bmp\README.md" />
<None Include="Formats\Png\README.md" /> <None Include="Formats\Png\README.md" />
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup>
<AdditionalFiles Include="stylecop.json" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" /> <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. <!-- 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. 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"> <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: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/@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/@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_005Cexceptions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=common_005Cextensions/@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/> /// <inheritdoc/>
public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) 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); target.SetPixels(width, height, pixels);
if (targetRectangle == Rectangle.Empty) if (targetRectangle == Rectangle.Empty)

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

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

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

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

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

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

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

@ -12,13 +12,13 @@ namespace ImageProcessor.Samplers
public class HermiteResampler : IResampler public class HermiteResampler : IResampler
{ {
/// <inheritdoc/> /// <inheritdoc/>
public double Radius => 2; public float Radius => 2;
/// <inheritdoc/> /// <inheritdoc/>
public double GetValue(double x) public float GetValue(float x)
{ {
const double B = 0; const float B = 0;
const double C = 0; const float C = 0;
return ImageMaths.GetBcValue(x, B, C); 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> /// <summary>
/// Encasulates an interpolation algorithm for resampling images. /// Encapsulates an interpolation algorithm for resampling images.
/// </summary> /// </summary>
public interface IResampler public interface IResampler
{ {
/// <summary> /// <summary>
/// Gets the radius in which to sample pixels. /// Gets the radius in which to sample pixels.
/// </summary> /// </summary>
double Radius { get; } float Radius { get; }
/// <summary> /// <summary>
/// Gets the result of the interpolation algorithm. /// Gets the result of the interpolation algorithm.
/// </summary> /// </summary>
/// <param name="x">The value to process.</param> /// <param name="x">The value to process.</param>
/// <returns> /// <returns>
/// The <see cref="double"/> /// The <see cref="float"/>
/// </returns> /// </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 public class Lanczos3Resampler : IResampler
{ {
/// <inheritdoc/> /// <inheritdoc/>
public double Radius => 3; public float Radius => 3;
/// <inheritdoc/> /// <inheritdoc/>
public double GetValue(double x) public float GetValue(float x)
{ {
if (x < 0) if (x < 0)
{ {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -12,10 +12,10 @@ namespace ImageProcessor.Samplers
public class WelchResampler : IResampler public class WelchResampler : IResampler
{ {
/// <inheritdoc/> /// <inheritdoc/>
public double Radius => 3; public float Radius => 3;
/// <inheritdoc/> /// <inheritdoc/>
public double GetValue(double x) public float GetValue(float x)
{ {
if (x < 0) if (x < 0)
{ {
@ -24,7 +24,7 @@ namespace ImageProcessor.Samplers
if (x < 3) 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; return 0;

142
src/ImageProcessor/Samplers/Resize.cs

@ -7,6 +7,7 @@ namespace ImageProcessor.Samplers
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
/// <summary> /// <summary>
/// Provides methods that allow the resizing of images using various resampling algorithms. /// Provides methods that allow the resizing of images using various resampling algorithms.
@ -16,7 +17,7 @@ namespace ImageProcessor.Samplers
/// <summary> /// <summary>
/// The epsilon for comparing floating point numbers. /// The epsilon for comparing floating point numbers.
/// </summary> /// </summary>
private const float Epsilon = 0.0001f; private const float Epsilon = 0.0000001f;
/// <summary> /// <summary>
/// The horizontal weights. /// The horizontal weights.
@ -61,57 +62,57 @@ namespace ImageProcessor.Samplers
int startX = targetRectangle.X; int startX = targetRectangle.X;
int endX = targetRectangle.Right; int endX = targetRectangle.Right;
for (int y = startY; y < endY; y++) Parallel.For(
{ startY,
if (y >= targetY && y < targetBottom) endY,
y =>
{ {
List<Weight> verticalValues = this.verticalWeights[y].Values; if (y >= targetY && y < targetBottom)
double verticalSum = this.verticalWeights[y].Sum;
for (int x = startX; x < endX; x++)
{ {
List<Weight> horizontalValues = this.horizontalWeights[x].Values; List<Weight> verticalValues = this.verticalWeights[y].Values;
double horizontalSum = this.horizontalWeights[x].Sum; float verticalSum = this.verticalWeights[y].Sum;
// Destination color components for (int x = startX; x < endX; x++)
double r = 0;
double g = 0;
double b = 0;
double a = 0;
foreach (Weight yw in verticalValues)
{ {
if (Math.Abs(yw.Value) < Epsilon) List<Weight> horizontalValues = this.horizontalWeights[x].Values;
{ float horizontalSum = this.horizontalWeights[x].Sum;
continue;
}
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; continue;
} }
int originX = xw.Index; int originY = yw.Index;
Bgra sourceColor = source[originX, originY];
sourceColor = PixelOperations.ToLinear(sourceColor); 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); float weight = (yw.Value / verticalSum) * (xw.Value / horizontalSum);
g += sourceColor.G * (yw.Value / verticalSum) * (xw.Value / horizontalSum);
b += sourceColor.B * (yw.Value / verticalSum) * (xw.Value / horizontalSum); destination.R += sourceColor.R * weight;
a += sourceColor.A * (yw.Value / verticalSum) * (xw.Value / horizontalSum); 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()); destination = PixelOperations.ToSrgb(destination);
destinationColor = PixelOperations.ToSrgb(destinationColor); target[x, y] = destination;
target[x, y] = destinationColor; }
} }
} });
}
} }
/// <summary> /// <summary>
@ -125,50 +126,53 @@ namespace ImageProcessor.Samplers
private Weights[] PrecomputeWeights(int destinationSize, int sourceSize) private Weights[] PrecomputeWeights(int destinationSize, int sourceSize)
{ {
IResampler sampler = this.Sampler; IResampler sampler = this.Sampler;
double du = sourceSize / (double)destinationSize; float du = sourceSize / (float)destinationSize;
double scale = du; float scale = du;
if (scale < 1) if (scale < 1)
{ {
scale = 1; scale = 1;
} }
double ru = Math.Ceiling(scale * sampler.Radius); float ru = (float)Math.Ceiling(scale * sampler.Radius);
Weights[] result = new Weights[destinationSize]; Weights[] result = new Weights[destinationSize];
for (int i = 0; i < destinationSize; i++) Parallel.For(
{ 0,
double fu = ((i + .5) * du) - 0.5; destinationSize,
int startU = (int)Math.Ceiling(fu - ru); i =>
{
float fu = ((i + .5f) * du) - 0.5f;
int startU = (int)Math.Ceiling(fu - ru);
if (startU < 0) if (startU < 0)
{ {
startU = 0; startU = 0;
} }
int endU = (int)Math.Floor(fu + ru); int endU = (int)Math.Floor(fu + ru);
if (endU > sourceSize - 1) if (endU > sourceSize - 1)
{ {
endU = sourceSize - 1; endU = sourceSize - 1;
} }
double sum = 0; float sum = 0;
result[i] = new Weights(); result[i] = new Weights();
for (int a = startU; a <= endU; a++) for (int a = startU; a <= endU; a++)
{ {
double w = 255 * sampler.GetValue((a - fu) / scale); float w = sampler.GetValue((a - fu) / scale);
if (Math.Abs(w) > Epsilon) if (Math.Abs(w) > Epsilon)
{ {
sum += w; sum += w;
result[i].Values.Add(new Weight(a, w)); result[i].Values.Add(new Weight(a, w));
} }
} }
result[i].Sum = sum; result[i].Sum = sum;
} });
return result; return result;
} }
@ -186,14 +190,14 @@ namespace ImageProcessor.Samplers
/// <summary> /// <summary>
/// The result of the interpolation algorithm. /// The result of the interpolation algorithm.
/// </summary> /// </summary>
public readonly double Value; public readonly float Value;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Weight"/> struct. /// Initializes a new instance of the <see cref="Weight"/> struct.
/// </summary> /// </summary>
/// <param name="index">The index.</param> /// <param name="index">The index.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
public Weight(int index, double value) public Weight(int index, float value)
{ {
this.Index = index; this.Index = index;
this.Value = value; this.Value = value;
@ -221,7 +225,7 @@ namespace ImageProcessor.Samplers
/// <summary> /// <summary>
/// Gets or sets the sum. /// Gets or sets the sum.
/// </summary> /// </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> /// <summary>
/// Test conversion between the various color structs. /// 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 public class ColorConversionTests
{ {
/// <summary> /// <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> /// </summary>
[Fact] [Fact]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "Reviewed. Suppression is OK here.")] Justification = "Reviewed. Suppression is OK here.")]
public void BgrToYCbCr() public void ColorToYCbCr()
{ {
// White // White
Bgra color = new Bgra(255, 255, 255, 255); Color color = new Color(1, 1, 1);
YCbCr yCbCr = color; YCbCr yCbCr = color;
Assert.Equal(255, yCbCr.Y); Assert.Equal(255, yCbCr.Y);
@ -37,14 +41,14 @@ namespace ImageProcessor.Tests
Assert.Equal(128, yCbCr.Cr); Assert.Equal(128, yCbCr.Cr);
// Black // Black
Bgra color2 = new Bgra(0, 0, 0, 255); Color color2 = new Color(0, 0, 0);
YCbCr yCbCr2 = color2; YCbCr yCbCr2 = color2;
Assert.Equal(0, yCbCr2.Y); Assert.Equal(0, yCbCr2.Y);
Assert.Equal(128, yCbCr2.Cb); Assert.Equal(128, yCbCr2.Cb);
Assert.Equal(128, yCbCr2.Cr); Assert.Equal(128, yCbCr2.Cr);
// Grey // Grey
Bgra color3 = new Bgra(128, 128, 128, 255); Color color3 = new Color(.5f, .5f, .5f);
YCbCr yCbCr3 = color3; YCbCr yCbCr3 = color3;
Assert.Equal(128, yCbCr3.Y); Assert.Equal(128, yCbCr3.Y);
Assert.Equal(128, yCbCr3.Cb); Assert.Equal(128, yCbCr3.Cb);
@ -52,201 +56,201 @@ namespace ImageProcessor.Tests
} }
/// <summary> /// <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> /// </summary>
[Fact] [Fact]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "Reviewed. Suppression is OK here.")] Justification = "Reviewed. Suppression is OK here.")]
public void YCbCrToBgr() public void YCbCrToColor()
{ {
// White // White
YCbCr yCbCr = new YCbCr(255, 128, 128); YCbCr yCbCr = new YCbCr(255, 128, 128);
Bgra color = yCbCr; Color color = yCbCr;
Assert.Equal(255, color.B); Assert.Equal(1f, color.R, 1);
Assert.Equal(255, color.G); Assert.Equal(1f, color.G, 1);
Assert.Equal(255, color.R); Assert.Equal(1f, color.B, 1);
Assert.Equal(255, color.A); Assert.Equal(1f, color.A, 1);
// Black // Black
YCbCr yCbCr2 = new YCbCr(0, 128, 128); 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(0, color2.R);
Assert.Equal(255, color2.A); Assert.Equal(0, color2.G);
Assert.Equal(0, color2.B);
Assert.Equal(1, color2.A);
// Grey // Grey
YCbCr yCbCr3 = new YCbCr(128, 128, 128); YCbCr yCbCr3 = new YCbCr(128, 128, 128);
Bgra color3 = yCbCr3; Color color3 = yCbCr3;
Assert.Equal(128, color3.B); Assert.Equal(.5f, color3.R, 1);
Assert.Equal(128, color3.G); Assert.Equal(.5f, color3.G, 1);
Assert.Equal(128, color3.R); Assert.Equal(.5f, color3.B, 1);
Assert.Equal(255, color3.A); Assert.Equal(1f, color3.A, 1);
} }
/// <summary> /// <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> /// </summary>
[Fact] [Fact]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "Reviewed. Suppression is OK here.")] Justification = "Reviewed. Suppression is OK here.")]
public void BgrToHsv() public void ColorToHsv()
{ {
// Black // Black
Bgra b = new Bgra(0, 0, 0, 255); Color b = new Color(0, 0, 0);
Hsv h = b; Hsv h = b;
Assert.Equal(0, h.H); Assert.Equal(0, h.H, 1);
Assert.Equal(0, h.S); Assert.Equal(0, h.S, 1);
Assert.Equal(0, h.V); Assert.Equal(0, h.V, 1);
// White // White
Bgra color = new Bgra(255, 255, 255, 255); Color color = new Color(1, 1, 1);
Hsv hsv = color; Hsv hsv = color;
Assert.Equal(0, hsv.H); Assert.Equal(0f, hsv.H, 1);
Assert.Equal(0, hsv.S); Assert.Equal(0f, hsv.S, 1);
Assert.Equal(100, hsv.V); Assert.Equal(1f, hsv.V, 1);
// Dark moderate pink. // Dark moderate pink.
Bgra color2 = new Bgra(106, 64, 128, 255); Color color2 = new Color(128 / 255f, 64 / 255f, 106 / 255f);
Hsv hsv2 = color2; Hsv hsv2 = color2;
Assert.Equal(320.6, hsv2.H, 1); Assert.Equal(320.6f, hsv2.H, 1);
Assert.Equal(50, hsv2.S, 1); Assert.Equal(0.5f, hsv2.S, 1);
Assert.Equal(50.2, hsv2.V, 1); Assert.Equal(0.502f, hsv2.V, 2);
// Ochre. // Ochre.
Bgra color3 = new Bgra(34, 119, 204, 255); Color color3 = new Color(204 / 255f, 119 / 255f, 34 / 255f);
Hsv hsv3 = color3; Hsv hsv3 = color3;
Assert.Equal(30, hsv3.H, 1); Assert.Equal(30f, hsv3.H, 1);
Assert.Equal(83.3, hsv3.S, 1); Assert.Equal(0.833f, hsv3.S, 3);
Assert.Equal(80, hsv3.V, 1); Assert.Equal(0.8f, hsv3.V, 1);
} }
/// <summary> /// <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> /// </summary>
[Fact] [Fact]
public void HsvToBgr() public void HsvToColor()
{ {
// Dark moderate pink. // Dark moderate pink.
Hsv hsv = new Hsv(320.6f, 50, 50.2f); Hsv hsv = new Hsv(320.6f, 0.5f, 0.502f);
Bgra bgra = hsv; Color color = hsv;
Assert.Equal(bgra.B, 106); Assert.Equal(color.B, 106 / 255f, 1);
Assert.Equal(bgra.G, 64); Assert.Equal(color.G, 64 / 255f, 1);
Assert.Equal(bgra.R, 128); Assert.Equal(color.R, 128 / 255f, 1);
// Ochre // Ochre
Hsv hsv2 = new Hsv(30, 83.3f, 80); Hsv hsv2 = new Hsv(30, 0.833f, 0.8f);
Bgra bgra2 = hsv2; Color color2 = hsv2;
Assert.Equal(bgra2.B, 34); Assert.Equal(color2.B, 34 / 255f, 1);
Assert.Equal(bgra2.G, 119); Assert.Equal(color2.G, 119 / 255f, 1);
Assert.Equal(bgra2.R, 204); Assert.Equal(color2.R, 204 / 255f, 1);
// White // White
Hsv hsv3 = new Hsv(0, 0, 100); Hsv hsv3 = new Hsv(0, 0, 1);
Bgra bgra3 = hsv3; Color color3 = hsv3;
Assert.Equal(bgra3.B, 255); Assert.Equal(color3.B, 1, 1);
Assert.Equal(bgra3.G, 255); Assert.Equal(color3.G, 1, 1);
Assert.Equal(bgra3.R, 255); Assert.Equal(color3.R, 1, 1);
// Check others. // Check others.
Random random = new Random(0); Random random = new Random(0);
for (int i = 0; i < 1000; i++) for (int i = 0; i < 1000; i++)
{ {
Bgra bgra4 = new Bgra((byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255)); Color color4 = new Color(random.Next(1), random.Next(1), random.Next(1));
Hsv hsb4 = bgra4; Hsv hsv4 = color4;
Assert.Equal(bgra4, (Bgra)hsb4); Assert.Equal(color4, (Color)hsv4);
} }
} }
/// <summary> /// <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> /// </summary>
[Fact] [Fact]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "Reviewed. Suppression is OK here.")] Justification = "Reviewed. Suppression is OK here.")]
public void BgrToCmyk() public void ColorToCmyk()
{ {
// White // White
Bgra color = new Bgra(255, 255, 255, 255); Color color = new Color(1, 1, 1);
Cmyk cmyk = color; Cmyk cmyk = color;
Assert.Equal(0, cmyk.C); Assert.Equal(0, cmyk.C, 1);
Assert.Equal(0, cmyk.M); Assert.Equal(0, cmyk.M, 1);
Assert.Equal(0, cmyk.Y); Assert.Equal(0, cmyk.Y, 1);
Assert.Equal(0, cmyk.K); Assert.Equal(0, cmyk.K, 1);
// Black // Black
Bgra color2 = new Bgra(0, 0, 0, 255); Color color2 = new Color(0, 0, 0);
Cmyk cmyk2 = color2; Cmyk cmyk2 = color2;
Assert.Equal(0, cmyk2.C); Assert.Equal(0, cmyk2.C, 1);
Assert.Equal(0, cmyk2.M); Assert.Equal(0, cmyk2.M, 1);
Assert.Equal(0, cmyk2.Y); Assert.Equal(0, cmyk2.Y, 1);
Assert.Equal(100, cmyk2.K); Assert.Equal(1, cmyk2.K, 1);
// Grey // Grey
Bgra color3 = new Bgra(128, 128, 128, 255); Color color3 = new Color(128 / 255f, 128 / 255f, 128 / 255f);
Cmyk cmyk3 = color3; Cmyk cmyk3 = color3;
Assert.Equal(0, cmyk3.C); Assert.Equal(0f, cmyk3.C, 1);
Assert.Equal(0, cmyk3.M); Assert.Equal(0f, cmyk3.M, 1);
Assert.Equal(0, cmyk3.Y); Assert.Equal(0f, cmyk3.Y, 1);
Assert.Equal(49.8, cmyk3.K, 1); // Checked with other tools. Assert.Equal(0.498, cmyk3.K, 2); // Checked with other online converters.
// Cyan // Cyan
Bgra color4 = new Bgra(255, 255, 0, 255); Color color4 = new Color(0, 1, 1);
Cmyk cmyk4 = color4; Cmyk cmyk4 = color4;
Assert.Equal(100, cmyk4.C); Assert.Equal(1, cmyk4.C, 1);
Assert.Equal(0, cmyk4.M); Assert.Equal(0f, cmyk4.M, 1);
Assert.Equal(0, cmyk4.Y); Assert.Equal(0f, cmyk4.Y, 1);
Assert.Equal(0, cmyk4.K); Assert.Equal(0f, cmyk4.K, 1);
} }
/// <summary> /// <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> /// </summary>
[Fact] [Fact]
public void CmykToBgr() public void CmykToColor()
{ {
// Dark moderate pink. // Dark moderate pink.
Cmyk cmyk = new Cmyk(49.8f, 74.9f, 58.4f, 0); Cmyk cmyk = new Cmyk(0f, .5f, .171f, .498f);
Bgra bgra = cmyk; Color color = cmyk;
Assert.Equal(bgra.B, 106); Assert.Equal(color.R, 128 / 255f, 1);
Assert.Equal(bgra.G, 64); Assert.Equal(color.G, 64 / 255f, 1);
Assert.Equal(bgra.R, 128); Assert.Equal(color.B, 106 / 255f, 1);
// Ochre // Ochre
Cmyk cmyk2 = new Cmyk(20, 53.3f, 86.7f, 0); Cmyk cmyk2 = new Cmyk(0, .416f, .833f, .199f);
Bgra bgra2 = cmyk2; Color color2 = cmyk2;
Assert.Equal(bgra2.B, 34); Assert.Equal(color2.R, 204 / 255f, 1);
Assert.Equal(bgra2.G, 119); Assert.Equal(color2.G, 119 / 255f, 1);
Assert.Equal(bgra2.R, 204); Assert.Equal(color2.B, 34 / 255f, 1);
// White // White
Cmyk cmyk3 = new Cmyk(0, 0, 0, 0); Cmyk cmyk3 = new Cmyk(0, 0, 0, 0);
Bgra bgra3 = cmyk3; Color color3 = cmyk3;
Assert.Equal(bgra3.B, 255); Assert.Equal(color3.R, 1f, 1);
Assert.Equal(bgra3.G, 255); Assert.Equal(color3.G, 1f, 1);
Assert.Equal(bgra3.R, 255); Assert.Equal(color3.B, 1f, 1);
// Check others. // Check others.
Random random = new Random(0); Random random = new Random(0);
for (int i = 0; i < 1000; i++) for (int i = 0; i < 1000; i++)
{ {
Bgra bgra4 = new Bgra((byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255)); Color color4 = new Color(random.Next(1), random.Next(1), random.Next(1));
Cmyk cmyk4 = bgra4; Cmyk cmyk4 = color4;
Assert.Equal(bgra4, (Bgra)cmyk4); Assert.Equal(color4, (Color)cmyk4);
} }
} }
} }

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

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

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

@ -10,13 +10,14 @@
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ImageProcessor.Tests</RootNamespace> <RootNamespace>ImageProcessor.Tests</RootNamespace>
<AssemblyName>ImageProcessor.Tests</AssemblyName> <AssemblyName>ImageProcessor.Tests</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<NuGetPackageImportStamp> <NuGetPackageImportStamp>
</NuGetPackageImportStamp> </NuGetPackageImportStamp>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir> <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
<RestorePackages>true</RestorePackages> <RestorePackages>true</RestorePackages>
<TargetFrameworkProfile />
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
@ -37,6 +38,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <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"> <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> <HintPath>..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
<Private>True</Private> <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) },
//{ "Contrast--50", new Contrast(-50) }, //{ "Contrast--50", new Contrast(-50) },
//{ "Alpha--50", new Alpha(50) }, //{ "Alpha--50", new Alpha(50) },
//{ "Invert", new Invert() }, { "Invert", new Invert() },
//{ "Sepia", new Sepia() }, { "Sepia", new Sepia() },
//{ "BlackWhite", new BlackWhite() }, { "BlackWhite", new BlackWhite() },
//{ "Lomograph", new Lomograph() }, { "Lomograph", new Lomograph() },
//{ "Polaroid", new Polaroid() }, { "Polaroid", new Polaroid() },
{ "GreyscaleBt709", new GreyscaleBt709() }, { "GreyscaleBt709", new GreyscaleBt709() },
//{ "GreyscaleBt601", new GreyscaleBt601() }, { "GreyscaleBt601", new GreyscaleBt601() },
}; };
[Theory] [Theory]

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

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

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

@ -1,6 +1,7 @@
 
namespace ImageProcessor.Tests namespace ImageProcessor.Tests
{ {
using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
@ -47,7 +48,7 @@ namespace ImageProcessor.Tests
using (FileStream output = File.OpenWrite($"Resized/{filename}")) using (FileStream output = File.OpenWrite($"Resized/{filename}"))
{ {
//image.Resize(image.Width / 2, image.Height / 2, sampler).Save(output); //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"); Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");
@ -62,10 +63,10 @@ namespace ImageProcessor.Tests
[InlineData(1, 0)] [InlineData(1, 0)]
[InlineData(2, 0)] [InlineData(2, 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(); Lanczos3Resampler sampler = new Lanczos3Resampler();
double result = sampler.GetValue(x); float result = sampler.GetValue(x);
Assert.Equal(result, expected); 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"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <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" version="2.0.0" targetFramework="net45" />
<package id="xunit.abstractions" 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" /> <package id="xunit.assert" version="2.0.0" targetFramework="net45" />

Loading…
Cancel
Save