diff --git a/.gitignore b/.gitignore index 4e9864be0..ae6765a03 100644 --- a/.gitignore +++ b/.gitignore @@ -108,6 +108,7 @@ TestResults *.Cache ClientBin stylecop.* +!stylecop.json ~$* *.dbmdl Generated_Code #added for RIA/Silverlight projects diff --git a/src/ImageProcessor/Colors/Bgra.cs b/src/ImageProcessor/Colors/Bgra.cs deleted file mode 100644 index 052a036e4..000000000 --- a/src/ImageProcessor/Colors/Bgra.cs +++ /dev/null @@ -1,397 +0,0 @@ -// -// Copyright (c) James South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessor -{ - using System; - using System.ComponentModel; - using System.Runtime.InteropServices; - - /// - /// Represents an BGRA (blue, green, red, alpha) color. - /// - [StructLayout(LayoutKind.Explicit)] - public struct Bgra : IEquatable - { - /// - /// Represents a that has B, G, R, and A values set to zero. - /// - public static readonly Bgra Empty = default(Bgra); - - /// - /// Represents a transparent that has B, G, R, and A values set to 255, 255, 255, 0. - /// - public static readonly Bgra Transparent = new Bgra(255, 255, 255, 0); - - /// - /// Represents a black that has B, G, R, and A values set to 0, 0, 0, 0. - /// - public static readonly Bgra Black = new Bgra(0, 0, 0, 255); - - /// - /// Represents a white that has B, G, R, and A values set to 255, 255, 255, 255. - /// - public static readonly Bgra White = new Bgra(255, 255, 255, 255); - - /// - /// Holds the blue component of the color - /// - [FieldOffset(0)] - public readonly byte B; - - /// - /// Holds the green component of the color - /// - [FieldOffset(1)] - public readonly byte G; - - /// - /// Holds the red component of the color - /// - [FieldOffset(2)] - public readonly byte R; - - /// - /// Holds the alpha component of the color - /// - [FieldOffset(3)] - public readonly byte A; - - /// - /// Permits the to be treated as a 32 bit integer. - /// - [FieldOffset(0)] - public readonly int BGRA; - - /// - /// The epsilon for comparing floating point numbers. - /// - private const float Epsilon = 0.0001f; - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The blue component of this . - /// - /// - /// The green component of this . - /// - /// - /// The red component of this . - /// - public Bgra(byte b, byte g, byte r) - : this() - { - this.B = b; - this.G = g; - this.R = r; - this.A = 255; - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The blue component of this . - /// - /// - /// The green component of this . - /// - /// - /// The red component of this . - /// - /// - /// The alpha component of this . - /// - public Bgra(byte b, byte g, byte r, byte a) - : this() - { - this.B = b; - this.G = g; - this.R = r; - this.A = a; - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The combined color components. - /// - public Bgra(int bgra) - : this() - { - this.BGRA = bgra; - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The hexadecimal representation of the combined color components arranged - /// in rgb, rrggbb, or aarrggbb format to match web syntax. - /// - 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; - } - } - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.B == 0 && this.G == 0 && this.R == 0 && this.A == 0; - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// - /// The instance of to convert. - /// - /// - /// An instance of . - /// - 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)); - } - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// - /// The instance of to convert. - /// - /// - /// An instance of . - /// - 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); - } - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// - /// The instance of to convert. - /// - /// - /// An instance of . - /// - 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()); - } - - /// - /// Compares two objects. The result specifies whether the values - /// of the , , , and - /// properties of the two objects are equal. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(Bgra left, Bgra right) - { - return left.Equals(right); - } - - /// - /// Compares two objects. The result specifies whether the values - /// of the , , , and - /// properties of the two objects are unequal. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(Bgra left, Bgra right) - { - return !left.Equals(right); - } - - /// - /// Indicates whether this instance and a specified object are equal. - /// - /// - /// true if and this instance are the same type and represent the same value; otherwise, false. - /// - /// Another object to compare to. - public override bool Equals(object obj) - { - if (obj is Bgra) - { - Bgra color = (Bgra)obj; - - return this.BGRA == color.BGRA; - } - - return false; - } - - /// - /// Returns the hash code for this instance. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - 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; - } - } - - /// - /// Returns the fully qualified type name of this instance. - /// - /// - /// A containing a fully qualified type name. - /// - public override string ToString() - { - if (this.IsEmpty) - { - return "Color [ Empty ]"; - } - - return $"Color [ B={this.B}, G={this.G}, R={this.R}, A={this.A} ]"; - } - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// - /// True if the current object is equal to the parameter; otherwise, false. - /// - /// An object to compare with this object. - public bool Equals(Bgra other) - { - return this.BGRA == other.BGRA; - } - } -} diff --git a/src/ImageProcessor/Colors/Color.cs b/src/ImageProcessor/Colors/Color.cs new file mode 100644 index 000000000..d85464471 --- /dev/null +++ b/src/ImageProcessor/Colors/Color.cs @@ -0,0 +1,541 @@ +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Represents a four-component color using red, green, blue, and alpha data. + /// + /// + /// 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. + /// + public struct Color : IEquatable + { + /// + /// Represents a that has R, G, B, and A values set to zero. + /// + public static readonly Color Empty = default(Color); + + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.0001f; + + /// + /// The backing vector for SIMD support. + /// + private Vector4 backingVector; + + /// + /// Initializes a new instance of the struct with the alpha component set to 1. + /// + /// The red component of this . + /// The green component of this . + /// The blue component of this . + public Color(float r, float g, float b) + : this(r, g, b, 1) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component of this . + /// The green component of this . + /// The blue component of this . + /// The alpha component of this . + 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; + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rrggbb, or aarrggbb format to match web syntax. + /// + 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; + } + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector. + /// + public Color(Vector4 vector) + { + this.backingVector = vector; + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector representing the red, green, and blue componenets. + /// + public Color(Vector3 vector) + { + this.backingVector = new Vector4(vector.X, vector.Y, vector.Z, 1); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector representing the red, green, and blue componenets. + /// + /// The alpha component. + public Color(Vector3 vector, float alpha) + { + this.backingVector = new Vector4(vector.X, vector.Y, vector.Z, alpha); + } + + /// + /// Gets or sets the red component of the color. + /// + public float R + { + get + { + return this.backingVector.X; + } + + set + { + this.backingVector.X = value; + } + } + + /// + /// Gets or sets the green component of the color. + /// + public float G + { + get + { + return this.backingVector.Y; + } + + set + { + this.backingVector.Y = value; + } + } + + /// + /// Gets or sets the blue component of the color. + /// + public float B + { + get + { + return this.backingVector.Z; + } + + set + { + this.backingVector.Z = value; + } + } + + /// + /// Gets or sets the alpha component of the color. + /// + public float A + { + get + { + return this.backingVector.W; + } + + set + { + this.backingVector.W = value; + } + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.backingVector.Equals(default(Vector4)); + + /// + /// Gets this color with the component values clamped from 0 to 1. + /// + 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); + } + } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Color(Bgra32 color) + { + return new Color(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f); + } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + 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); + } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + 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); + } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + 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); + } + + /// + /// Computes the product of multiplying a color by a given factor. + /// + /// The color. + /// The multiplication factor. + /// + /// The + /// + public static Color operator *(Color color, float factor) + { + return new Color(color.backingVector * factor); + } + + /// + /// Computes the product of multiplying a color by a given factor. + /// + /// The multiplication factor. + /// The color. + /// + /// The + /// + public static Color operator *(float factor, Color color) + { + return new Color(color.backingVector * factor); + } + + /// + /// Computes the product of multiplying two colors. + /// + /// The color on the left hand of the operand. + /// The color on the right hand of the operand. + /// + /// The + /// + public static Color operator *(Color left, Color right) + { + return new Color(left.backingVector * right.backingVector); + } + + /// + /// Computes the sum of adding two colors. + /// + /// The color on the left hand of the operand. + /// The color on the right hand of the operand. + /// + /// The + /// + 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); + } + + /// + /// Computes the difference left by subtracting one color from another. + /// + /// The color on the left hand of the operand. + /// The color on the right hand of the operand. + /// + /// The + /// + 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); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Color left, Color right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Color left, Color right) + { + return !left.Equals(right); + } + + /// + /// Returns a new color whose components are the average of the components of first and second. + /// + /// The first color. + /// The second color. + /// + /// The + /// + public static Color Average(Color first, Color second) + { + return new Color((first.backingVector + second.backingVector) * .5f); + } + + /// + /// Linearly interpolates from one color to another based on the given amount. + /// + /// The first color value. + /// The second color value. + /// + /// The weight value. At amount = 0, "from" is returned, at amount = 1, "to" is returned. + /// + /// + /// The + /// + public static Color Lerp(Color from, Color to, float amount) + { + amount = amount.Clamp(0f, 1f); + + return (from * (1 - amount)) + (to * amount); + } + + /// + /// Gets a representation for this . + /// + /// A representation for this object. + public Vector4 ToVector4() + { + return new Vector4(this.R, this.G, this.B, this.A); + } + + /// + /// Gets a representation for this . + /// + /// A representation for this object. + public Vector3 ToVector3() + { + return new Vector3(this.R, this.G, this.B); + } + + /// + public override bool Equals(object obj) + { + if (obj is Color) + { + Color color = (Color)obj; + + return this.backingVector == color.backingVector; + } + + return false; + } + + /// + public override int GetHashCode() + { + return GetHashCode(this); + } + + /// + 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.##} ]"; + } + + /// + public bool Equals(Color other) + { + return this.backingVector.Equals(other.backingVector); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private static int GetHashCode(Color color) => color.backingVector.GetHashCode(); + } +} diff --git a/src/ImageProcessor/Colors/Formats/Bgra32.cs b/src/ImageProcessor/Colors/Formats/Bgra32.cs new file mode 100644 index 000000000..237888e61 --- /dev/null +++ b/src/ImageProcessor/Colors/Formats/Bgra32.cs @@ -0,0 +1,204 @@ +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Represents an BGRA (blue, green, red, alpha) color. + /// + public struct Bgra32 : IEquatable + { + /// + /// Represents a that has B, G, R, and A values set to zero. + /// + public static readonly Bgra32 Empty = default(Bgra32); + + /// + /// The backing vector for SIMD support. + /// + private Vector4 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The blue component of this . + /// The green component of this . + /// The red component of this . + public Bgra32(byte b, byte g, byte r) + : this(b, g, r, 255) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The blue component of this . + /// The green component of this . + /// The red component of this . + /// The alpha component of this . + 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); + } + + /// + /// Gets the blue component of the color + /// + public byte B => (byte)this.backingVector.X; + + /// + /// Gets the green component of the color + /// + public byte G => (byte)this.backingVector.Y; + + /// + /// Gets the red component of the color + /// + public byte R => (byte)this.backingVector.Z; + + /// + /// Gets the alpha component of the color + /// + public byte A => (byte)this.backingVector.W; + + /// + /// Gets the integer representation of the color. + /// + public int Bgra => (this.R << 16) | (this.G << 8) | (this.B << 0) | (this.A << 24); + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.backingVector.Equals(default(Vector4)); + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// + /// The instance of to convert. + /// + /// + /// An instance of . + /// + 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()); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Bgra32 left, Bgra32 right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Bgra32 left, Bgra32 right) + { + return !left.Equals(right); + } + + /// + /// Indicates whether this instance and a specified object are equal. + /// + /// + /// true if and this instance are the same type and represent the same value; otherwise, false. + /// + /// Another object to compare to. + public override bool Equals(object obj) + { + if (obj is Bgra32) + { + Bgra32 color = (Bgra32)obj; + + return this.backingVector == color.backingVector; + } + + return false; + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + public override int GetHashCode() + { + return GetHashCode(this); + } + + /// + /// Returns the fully qualified type name of this instance. + /// + /// + /// A containing a fully qualified type name. + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Color [ Empty ]"; + } + + return $"Color [ B={this.B}, G={this.G}, R={this.R}, A={this.A} ]"; + } + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// True if the current object is equal to the parameter; otherwise, false. + /// + /// An object to compare with this object. + public bool Equals(Bgra32 other) + { + return this.backingVector.Equals(other.backingVector); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private static int GetHashCode(Bgra32 color) => color.backingVector.GetHashCode(); + } +} diff --git a/src/ImageProcessor/Colors/Cmyk.cs b/src/ImageProcessor/Colors/Formats/Cmyk.cs similarity index 55% rename from src/ImageProcessor/Colors/Cmyk.cs rename to src/ImageProcessor/Colors/Formats/Cmyk.cs index a11fffdd5..e20fe308c 100644 --- a/src/ImageProcessor/Colors/Cmyk.cs +++ b/src/ImageProcessor/Colors/Formats/Cmyk.cs @@ -7,6 +7,7 @@ namespace ImageProcessor { using System; using System.ComponentModel; + using System.Numerics; /// /// Represents an CMYK (cyan, magenta, yellow, keyline) color. @@ -19,92 +20,95 @@ namespace ImageProcessor public static readonly Cmyk Empty = default(Cmyk); /// - /// Gets the cyan color component. + /// The epsilon for comparing floating point numbers. /// - /// A value ranging between 0 and 100. - public readonly float C; + private const float Epsilon = 0.0001f; /// - /// Gets the magenta color component. + /// The backing vector for SIMD support. /// - /// A value ranging between 0 and 100. - public readonly float M; + private Vector4 backingVector; /// - /// Gets the yellow color component. + /// Initializes a new instance of the struct. /// - /// A value ranging between 0 and 100. - public readonly float Y; + /// The cyan component. + /// The magenta component. + /// The yellow component. + /// The keyline black component. + 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); + } /// - /// Gets the keyline black color component. + /// Gets the cyan color component. + /// A value ranging between 0 and 1. /// - /// A value ranging between 0 and 100. - public readonly float K; + public float C => this.backingVector.X; /// - /// The epsilon for comparing floating point numbers. + /// Gets the magenta color component. + /// A value ranging between 0 and 1. /// - private const float Epsilon = 0.0001f; + public float M => this.backingVector.Y; /// - /// Initializes a new instance of the struct. + /// Gets the yellow color component. + /// A value ranging between 0 and 1. /// - /// The cyan component. - /// The magenta component. - /// The yellow component. - /// The keyline black component. - public Cmyk(float cyan, float magenta, float yellow, float keyline) - { - this.C = Clamp(cyan); - this.M = Clamp(magenta); - this.Y = Clamp(yellow); - this.K = Clamp(keyline); - } + public float Y => this.backingVector.Z; + + /// + /// Gets the keyline black color component. + /// A value ranging between 0 and 1. + /// + public float K => this.backingVector.W; /// /// Gets a value indicating whether this is empty. /// [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => Math.Abs(this.C) < Epsilon - && Math.Abs(this.M) < Epsilon - && Math.Abs(this.Y) < Epsilon - && Math.Abs(this.K) < Epsilon; + public bool IsEmpty => this.backingVector.Equals(default(Vector4)); /// - /// Allows the implicit conversion of an instance of to a + /// Allows the implicit conversion of an instance of to a /// . /// /// - /// The instance of to convert. + /// The instance of to convert. /// /// /// An instance of . /// - public static implicit operator Cmyk(Bgra color) + public static implicit operator Cmyk(Color color) { - float c = (255f - color.R) / 255; - float m = (255f - color.G) / 255; - float y = (255f - color.B) / 255; + color = color.Limited; + + float c = 1f - color.R; + float m = 1f - color.G; + float y = 1f - color.B; float k = Math.Min(c, Math.Min(m, y)); - if (Math.Abs(k - 1.0) <= Epsilon) + if (Math.Abs(k - 1.0f) <= Epsilon) { - return new Cmyk(0, 0, 0, 100); + return new Cmyk(0, 0, 0, 1); } - c = ((c - k) / (1 - k)) * 100; - m = ((m - k) / (1 - k)) * 100; - y = ((y - k) / (1 - k)) * 100; + c = (c - k) / (1 - k); + m = (m - k) / (1 - k); + y = (y - k) / (1 - k); - return new Cmyk(c, m, y, k * 100); + return new Cmyk(c, m, y, k); } /// - /// Compares two objects. The result specifies whether the values - /// of the , , , and - /// properties of the two objects are equal. + /// Compares two objects for equality. /// /// /// The on the left side of the operand. @@ -121,9 +125,7 @@ namespace ImageProcessor } /// - /// Compares two objects. The result specifies whether the values - /// of the , , , and - /// properties of the two objects are unequal. + /// Compares two objects for inequality /// /// /// The on the left side of the operand. @@ -139,52 +141,26 @@ namespace ImageProcessor return !left.Equals(right); } - /// - /// Indicates whether this instance and a specified object are equal. - /// - /// - /// true if and this instance are the same type and represent the same value; otherwise, false. - /// - /// Another object to compare to. + /// public override bool Equals(object obj) { if (obj is Cmyk) { Cmyk color = (Cmyk)obj; - return Math.Abs(this.C - color.C) < Epsilon - && Math.Abs(this.M - color.M) < Epsilon - && Math.Abs(this.Y - color.Y) < Epsilon - && Math.Abs(this.K - color.K) < Epsilon; + return this.backingVector == color.backingVector; } return false; } - /// - /// Returns the hash code for this instance. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// + /// public override int GetHashCode() { - unchecked - { - int hashCode = this.C.GetHashCode(); - hashCode = (hashCode * 397) ^ this.M.GetHashCode(); - hashCode = (hashCode * 397) ^ this.Y.GetHashCode(); - hashCode = (hashCode * 397) ^ this.K.GetHashCode(); - return hashCode; - } + return GetHashCode(this); } - /// - /// Returns the fully qualified type name of this instance. - /// - /// - /// A containing a fully qualified type name. - /// + /// public override string ToString() { if (this.IsEmpty) @@ -195,19 +171,10 @@ namespace ImageProcessor return $"Cmyk [ C={this.C:#0.##}, M={this.M:#0.##}, Y={this.Y:#0.##}, K={this.K:#0.##}]"; } - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// - /// True if the current object is equal to the parameter; otherwise, false. - /// - /// An object to compare with this object. + /// public bool Equals(Cmyk other) { - return Math.Abs(this.C - other.C) < Epsilon - && Math.Abs(this.M - other.M) < Epsilon - && Math.Abs(this.Y - other.Y) < Epsilon - && Math.Abs(this.K - other.Y) < Epsilon; + return this.backingVector.Equals(other.backingVector); } /// @@ -221,7 +188,18 @@ namespace ImageProcessor /// private static float Clamp(float value) { - return value.Clamp(0, 100); + return value.Clamp(0, 1); } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private static int GetHashCode(Cmyk color) => color.backingVector.GetHashCode(); } } diff --git a/src/ImageProcessor/Colors/Hsv.cs b/src/ImageProcessor/Colors/Formats/Hsv.cs similarity index 60% rename from src/ImageProcessor/Colors/Hsv.cs rename to src/ImageProcessor/Colors/Formats/Hsv.cs index 2e184b7e1..a954fcf54 100644 --- a/src/ImageProcessor/Colors/Hsv.cs +++ b/src/ImageProcessor/Colors/Formats/Hsv.cs @@ -7,6 +7,7 @@ namespace ImageProcessor { using System; using System.ComponentModel; + using System.Numerics; /// /// 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); /// - /// Gets the H hue component. - /// A value ranging between 0 and 360. - /// - public readonly float H; - - /// - /// Gets the S saturation component. - /// A value ranging between 0 and 100. - /// - public readonly float S; - - /// - /// Gets the V value (brightness) component. - /// A value ranging between 0 and 100. + /// The epsilon for comparing floating point numbers. /// - public readonly float V; + private const float Epsilon = 0.0001f; /// - /// The epsilon for comparing floating point numbers. + /// The backing vector for SIMD support. /// - private const float Epsilon = 0.0001f; + private Vector3 backingVector; /// /// Initializes a new instance of the struct. @@ -49,34 +37,49 @@ namespace ImageProcessor /// The v value (brightness) component. public Hsv(float h, float s, float v) { - this.H = h.Clamp(0, 360); - this.S = s.Clamp(0, 100); - this.V = v.Clamp(0, 100); + this.backingVector.X = h.Clamp(0, 360); + this.backingVector.Y = s.Clamp(0, 1); + this.backingVector.Z = v.Clamp(0, 1); } + /// + /// Gets the hue component. + /// A value ranging between 0 and 360. + /// + public float H => this.backingVector.X; + + /// + /// Gets the saturation component. + /// A value ranging between 0 and 1. + /// + public float S => this.backingVector.Y; + + /// + /// Gets the value (brightness) component. + /// A value ranging between 0 and 1. + /// + public float V => this.backingVector.Z; + /// /// Gets a value indicating whether this is empty. /// [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => Math.Abs(this.H) < Epsilon - && Math.Abs(this.S) < Epsilon - && Math.Abs(this.V) < Epsilon; + public bool IsEmpty => this.backingVector.Equals(default(Vector3)); /// - /// Allows the implicit conversion of an instance of to a + /// Allows the implicit conversion of an instance of to a /// . /// - /// - /// The instance of to convert. - /// + /// The instance of to convert. /// /// An instance of . /// - public static implicit operator Hsv(Bgra color) + public static implicit operator Hsv(Color color) { - float r = color.R / 255f; - float g = color.G / 255f; - float b = color.B / 255f; + color = color.Limited; + float r = color.R; + float g = color.G; + float b = color.B; float max = Math.Max(r, Math.Max(g, b)); float min = Math.Min(r, Math.Min(g, b)); @@ -87,7 +90,7 @@ namespace ImageProcessor if (Math.Abs(chroma) < Epsilon) { - return new Hsv(0, s * 100, v * 100); + return new Hsv(0, s, v); } if (Math.Abs(chroma) < Epsilon) @@ -115,13 +118,11 @@ namespace ImageProcessor s = chroma / v; - return new Hsv(h, s * 100, v * 100); + return new Hsv(h, s, v); } /// - /// Compares two objects. The result specifies whether the values - /// of the , , and - /// properties of the two objects are equal. + /// Compares two objects for equality. /// /// /// The on the left side of the operand. @@ -138,9 +139,7 @@ namespace ImageProcessor } /// - /// Compares two objects. The result specifies whether the values - /// of the , , and - /// properties of the two objects are unequal. + /// Compares two objects for inequality. /// /// /// The on the left side of the operand. @@ -156,50 +155,26 @@ namespace ImageProcessor return !left.Equals(right); } - /// - /// Indicates whether this instance and a specified object are equal. - /// - /// - /// true if and this instance are the same type and represent the same value; otherwise, false. - /// - /// Another object to compare to. + /// public override bool Equals(object obj) { if (obj is Hsv) { Hsv color = (Hsv)obj; - return Math.Abs(this.H - color.H) < Epsilon - && Math.Abs(this.S - color.S) < Epsilon - && Math.Abs(this.V - color.V) < Epsilon; + return this.backingVector == color.backingVector; } return false; } - /// - /// Returns the hash code for this instance. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// + /// public override int GetHashCode() { - unchecked - { - int hashCode = this.H.GetHashCode(); - hashCode = (hashCode * 397) ^ this.S.GetHashCode(); - hashCode = (hashCode * 397) ^ this.V.GetHashCode(); - return hashCode; - } + return GetHashCode(this); } - /// - /// Returns the fully qualified type name of this instance. - /// - /// - /// A containing a fully qualified type name. - /// + /// public override string ToString() { if (this.IsEmpty) @@ -210,18 +185,21 @@ namespace ImageProcessor return $"Hsv [ H={this.H:#0.##}, S={this.S:#0.##}, V={this.V:#0.##} ]"; } + /// + public bool Equals(Hsv other) + { + return this.backingVector.Equals(other.backingVector); + } + /// - /// Indicates whether the current object is equal to another object of the same type. + /// Returns the hash code for this instance. /// + /// + /// The instance of to return the hash code for. + /// /// - /// True if the current object is equal to the parameter; otherwise, false. + /// A 32-bit signed integer that is the hash code for this instance. /// - /// An object to compare with this object. - public bool Equals(Hsv other) - { - return Math.Abs(this.H - other.H) < Epsilon - && Math.Abs(this.S - other.S) < Epsilon - && Math.Abs(this.V - other.V) < Epsilon; - } + private static int GetHashCode(Hsv color) => color.backingVector.GetHashCode(); } } diff --git a/src/ImageProcessor/Colors/YCbCr.cs b/src/ImageProcessor/Colors/Formats/YCbCr.cs similarity index 59% rename from src/ImageProcessor/Colors/YCbCr.cs rename to src/ImageProcessor/Colors/Formats/YCbCr.cs index c99fe55fb..c23d0ae0b 100644 --- a/src/ImageProcessor/Colors/YCbCr.cs +++ b/src/ImageProcessor/Colors/Formats/YCbCr.cs @@ -7,10 +7,11 @@ namespace ImageProcessor { using System; using System.ComponentModel; + using System.Numerics; /// /// 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. /// /// public struct YCbCr : IEquatable @@ -21,64 +22,64 @@ namespace ImageProcessor public static readonly YCbCr Empty = default(YCbCr); /// - /// Gets the Y luminance component. - /// A value ranging between 0 and 255. + /// The backing vector for SIMD support. /// - public readonly float Y; + private Vector3 backingVector; /// - /// Gets the Cb chroma component. - /// A value ranging between 0 and 255. + /// Initializes a new instance of the struct. /// - public readonly float Cb; + /// The y luminance component. + /// The cb chroma component. + /// The cr chroma component. + 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); + } /// - /// Gets the Cr chroma component. + /// Gets the Y luminance component. /// A value ranging between 0 and 255. /// - public readonly float Cr; + public float Y => this.backingVector.X; /// - /// The epsilon for comparing floating point numbers. + /// Gets the Cb chroma component. + /// A value ranging between 0 and 255. /// - private const float Epsilon = 0.0001f; + public float Cb => this.backingVector.Y; /// - /// Initializes a new instance of the struct. + /// Gets the Cr chroma component. + /// A value ranging between 0 and 255. /// - /// The y luminance component. - /// The cb chroma component. - /// The cr chroma component. - public YCbCr(float y, float cb, float cr) - { - this.Y = y.ToByte(); - this.Cb = cb.ToByte(); - this.Cr = cr.ToByte(); - } + public float Cr => this.backingVector.Z; /// /// Gets a value indicating whether this is empty. /// [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => Math.Abs(this.Y) < Epsilon - && Math.Abs(this.Cb) < Epsilon - && Math.Abs(this.Cr) < Epsilon; + public bool IsEmpty => this.backingVector.Equals(default(Vector3)); /// - /// Allows the implicit conversion of an instance of to a + /// Allows the implicit conversion of an instance of to a /// . /// /// - /// The instance of to convert. + /// The instance of to convert. /// /// /// An instance of . /// - public static implicit operator YCbCr(Bgra color) + public static implicit operator YCbCr(Color color) { - byte b = color.B; - byte g = color.G; - byte r = color.R; + color = color.Limited; + float r = color.R * 255f; + float g = color.G * 255f; + float b = color.B * 255f; float y = (float)((0.299 * r) + (0.587 * g) + (0.114 * b)); float cb = 128 + (float)((-0.168736 * r) - (0.331264 * g) + (0.5 * b)); @@ -88,9 +89,7 @@ namespace ImageProcessor } /// - /// Compares two objects. The result specifies whether the values - /// of the , , and - /// properties of the two objects are equal. + /// Compares two objects for equality. /// /// /// The on the left side of the operand. @@ -107,9 +106,7 @@ namespace ImageProcessor } /// - /// Compares two objects. The result specifies whether the values - /// of the , , and - /// properties of the two objects are unequal. + /// Compares two objects for inequality. /// /// /// The on the left side of the operand. @@ -125,50 +122,26 @@ namespace ImageProcessor return !left.Equals(right); } - /// - /// Indicates whether this instance and a specified object are equal. - /// - /// - /// true if and this instance are the same type and represent the same value; otherwise, false. - /// - /// Another object to compare to. + /// public override bool Equals(object obj) { if (obj is YCbCr) { YCbCr color = (YCbCr)obj; - return Math.Abs(this.Y - color.Y) < Epsilon - && Math.Abs(this.Cb - color.Cb) < Epsilon - && Math.Abs(this.Cr - color.Cr) < Epsilon; + return this.backingVector == color.backingVector; } return false; } - /// - /// Returns the hash code for this instance. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// + /// public override int GetHashCode() { - unchecked - { - int hashCode = this.Y.GetHashCode(); - hashCode = (hashCode * 397) ^ this.Cb.GetHashCode(); - hashCode = (hashCode * 397) ^ this.Cr.GetHashCode(); - return hashCode; - } + return GetHashCode(this); } - /// - /// Returns the fully qualified type name of this instance. - /// - /// - /// A containing a fully qualified type name. - /// + /// public override string ToString() { if (this.IsEmpty) @@ -179,18 +152,21 @@ namespace ImageProcessor return $"YCbCr [ Y={this.Y:#0.##}, Cb={this.Cb:#0.##}, Cr={this.Cr:#0.##} ]"; } + /// + public bool Equals(YCbCr other) + { + return this.backingVector.Equals(other.backingVector); + } + /// - /// Indicates whether the current object is equal to another object of the same type. + /// Returns the hash code for this instance. /// + /// + /// The instance of to return the hash code for. + /// /// - /// True if the current object is equal to the parameter; otherwise, false. + /// A 32-bit signed integer that is the hash code for this instance. /// - /// An object to compare with this object. - public bool Equals(YCbCr other) - { - return Math.Abs(this.Y - other.Y) < Epsilon - && Math.Abs(this.Cb - other.Cb) < Epsilon - && Math.Abs(this.Cr - other.Cr) < Epsilon; - } + private static int GetHashCode(YCbCr color) => color.backingVector.GetHashCode(); } } diff --git a/src/ImageProcessor/Common/Extensions/EnumerableExtensions.cs b/src/ImageProcessor/Common/Extensions/EnumerableExtensions.cs new file mode 100644 index 000000000..c492bcf8e --- /dev/null +++ b/src/ImageProcessor/Common/Extensions/EnumerableExtensions.cs @@ -0,0 +1,88 @@ +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor +{ + using System; + using System.Collections.Generic; + + /// + /// Encapsulates a series of time saving extension methods to the interface. + /// + public static class EnumerableExtensions + { + /// + /// Generates a sequence of integral numbers within a specified range. + /// + /// + /// The start index, inclusive. + /// + /// + /// The end index, exclusive. + /// + /// + /// The incremental step. + /// + /// + /// The that contains a range of sequential integral numbers. + /// + public static IEnumerable 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); + } + + /// + /// Generates a sequence of integral numbers within a specified range. + /// + /// + /// The start index, inclusive. + /// + /// + /// A method that has one parameter and returns a calculating the end index + /// + /// + /// The incremental step. + /// + /// + /// The that contains a range of sequential integral numbers. + /// + public static IEnumerable SteppedRange(int fromInclusive, Func toDelegate, int step) + { + return RangeIterator(fromInclusive, toDelegate, step); + } + + /// + /// Generates a sequence of integral numbers within a specified range. + /// + /// + /// The start index, inclusive. + /// + /// + /// A method that has one parameter and returns a calculating the end index + /// + /// + /// The incremental step. + /// + /// + /// The that contains a range of sequential integral numbers. + /// + private static IEnumerable RangeIterator(int fromInclusive, Func toDelegate, int step) + { + int i = fromInclusive; + while (toDelegate(i)) + { + yield return i; + i += step; + } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Common/Helpers/ImageMaths.cs b/src/ImageProcessor/Common/Helpers/ImageMaths.cs index 71f7d9670..b2bf0dede 100644 --- a/src/ImageProcessor/Common/Helpers/ImageMaths.cs +++ b/src/ImageProcessor/Common/Helpers/ImageMaths.cs @@ -12,6 +12,12 @@ namespace ImageProcessor /// internal static class ImageMaths { + /// + /// Represents PI, the ratio of a circle's circumference to its diameter. + /// + // ReSharper disable once InconsistentNaming + public const float PI = 3.1415926535897931f; + /// /// Returns the result of a B-C filter against the given value. /// @@ -20,11 +26,11 @@ namespace ImageProcessor /// The B-Spline curve variable. /// The Cardinal curve variable. /// - /// The . + /// The . /// - public static double GetBcValue(double x, double b, double c) + public static float GetBcValue(float x, float b, float c) { - double temp; + float temp; if (x < 0) { @@ -54,19 +60,19 @@ namespace ImageProcessor /// The value to calculate the result for. /// /// - /// The . + /// The . /// - public static double SinC(double x) + public static float SinC(float x) { - const double Epsilon = .0001; + const float Epsilon = .00001f; if (Math.Abs(x) > Epsilon) { - x *= Math.PI; - return Clean(Math.Sin(x) / x); + x *= PI; + return Clean((float)Math.Sin(x) / x); } - return 1.0; + return 1.0f; } /// @@ -74,15 +80,15 @@ namespace ImageProcessor /// /// The value to clean. /// - /// The + /// The /// . - private static double Clean(double x) + private static float Clean(float x) { - const double Epsilon = .0001; + const float Epsilon = .00001f; if (Math.Abs(x) < Epsilon) { - return 0.0; + return 0f; } return x; diff --git a/src/ImageProcessor/Common/Helpers/PixelOperations.cs b/src/ImageProcessor/Common/Helpers/PixelOperations.cs index a876b45ef..a116c0e35 100644 --- a/src/ImageProcessor/Common/Helpers/PixelOperations.cs +++ b/src/ImageProcessor/Common/Helpers/PixelOperations.cs @@ -12,102 +12,42 @@ namespace ImageProcessor /// public static class PixelOperations { - /// - /// The array of bytes representing each possible value of color component - /// converted from sRGB to the linear color space. - /// - private static readonly Lazy LinearBytes = new Lazy(GetLinearBytes); - - /// - /// The array of bytes representing each possible value of color component - /// converted from linear to the sRGB color space. - /// - private static readonly Lazy SrgbBytes = new Lazy(GetSrgbBytes); - - /// - /// The array of bytes representing each possible value of color component - /// converted from gamma to the linear color space. - /// - private static readonly Lazy LinearGammaBytes = new Lazy(GetLinearGammaBytes); - - /// - /// The array of bytes representing each possible value of color component - /// converted from linear to the gamma color space. - /// - private static readonly Lazy GammaLinearBytes = new Lazy(GetGammaLinearBytes); - /// /// Converts an pixel from an sRGB color-space to the equivalent linear color-space. /// /// - /// The to convert. + /// The to convert. /// /// - /// The . + /// The . /// - public static Bgra ToLinear(Bgra composite) + public static Color ToLinear(Color composite) { - // Create only once and lazily. - // byte[] ramp = LinearGammaBytes.Value; - byte[] ramp = LinearBytes.Value; + // TODO: Figure out a way to either cache these values quickly or perform the calcuations together. + composite.R = SrgbToLinear(composite.R); + composite.G = SrgbToLinear(composite.G); + composite.B = SrgbToLinear(composite.B); - return new Bgra(ramp[composite.B], ramp[composite.G], ramp[composite.R], composite.A); + return composite; } /// /// Converts a pixel from a linear color-space to the equivalent sRGB color-space. /// /// - /// The to convert. + /// The to convert. /// /// - /// The . - /// - 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); - } - - /// - /// Gets an array of bytes representing each possible value of color component - /// converted from sRGB to the linear color space. - /// - /// - /// The . - /// - 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; - } - - /// - /// Gets an array of bytes representing each possible value of color component - /// converted from linear to the sRGB color space. - /// - /// - /// The . + /// The . /// - private static byte[] GetSrgbBytes() + public static Color ToSrgb(Color linear) { - byte[] ramp = new byte[256]; - for (int x = 0; x < 256; ++x) - { - byte val = (255f * LinearToSrgb(x / 255f)).ToByte(); - ramp[x] = val; - } + // TODO: Figure out a way to either cache these values quickly or perform the calcuations together. + linear.R = LinearToSrgb(linear.R); + linear.G = LinearToSrgb(linear.G); + linear.B = LinearToSrgb(linear.B); - return ramp; + return linear; } /// @@ -121,14 +61,12 @@ namespace ImageProcessor /// private static float SrgbToLinear(float signal) { - float a = 0.055f; - - if (signal <= 0.04045) + if (signal <= 0.04045f) { return signal / 12.92f; } - return (float)Math.Pow((signal + a) / (1 + a), 2.4); + return (float)Math.Pow((signal + 0.055f) / 1.055f, 2.4f); } /// @@ -142,52 +80,12 @@ namespace ImageProcessor /// private static float LinearToSrgb(float signal) { - float a = 0.055f; - - if (signal <= 0.0031308) + if (signal <= 0.0031308f) { return signal * 12.92f; } - return ((float)((1 + a) * Math.Pow(signal, 1 / 2.4f))) - a; - } - - /// - /// Gets an array of bytes representing each possible value of color component - /// converted from gamma to the linear color space. - /// - /// - /// The . - /// - 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; - } - - /// - /// Gets an array of bytes representing each possible value of color component - /// converted from linear to the gamma color space. - /// - /// - /// The . - /// - private static byte[] GetGammaLinearBytes() - { - byte[] ramp = new byte[256]; - for (int x = 0; x < 256; ++x) - { - byte val = (255f * Math.Pow(x / 255f, 1 / 2.2)).ToByte(); - ramp[x] = val; - } - - return ramp; + return (1.055f * (float)Math.Pow(signal, 0.41666666f)) - 0.055f; } } } \ No newline at end of file diff --git a/src/ImageProcessor/Filters/Alpha.cs b/src/ImageProcessor/Filters/Alpha.cs index 93c335550..4f2e01f70 100644 --- a/src/ImageProcessor/Filters/Alpha.cs +++ b/src/ImageProcessor/Filters/Alpha.cs @@ -6,6 +6,7 @@ namespace ImageProcessor.Filters { using System; + using System.Threading.Tasks; /// /// An to change the Alpha of an . @@ -33,24 +34,27 @@ namespace ImageProcessor.Filters /// protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { - double alpha = this.Value / 100.0; + float alpha = this.Value / 100f; int sourceY = sourceRectangle.Y; int sourceBottom = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; - for (int y = startY; y < endY; y++) - { - if (y >= sourceY && y < sourceBottom) - { - for (int x = startX; x < endX; x++) + Parallel.For( + startY, + endY, + y => { - Bgra color = source[x, y]; - double a = color.A * alpha; - target[x, y] = new Bgra(color.B, color.G, color.R, a.ToByte()); - } - } - } + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x++) + { + Color color = source[x, y]; + color.A = color.A * alpha; + target[x, y] = color; + } + } + }); } } } diff --git a/src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs b/src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs index 3f7eb7591..17a49b8fb 100644 --- a/src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs +++ b/src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs @@ -5,6 +5,8 @@ namespace ImageProcessor.Filters { + using System.Threading.Tasks; + /// /// The color matrix filter. /// @@ -34,68 +36,56 @@ namespace ImageProcessor.Filters /// protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { + bool gamma = this.GammaAdjust; int sourceY = sourceRectangle.Y; int sourceBottom = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; ColorMatrix matrix = this.Value; - Bgra previousColor = source[0, 0]; - Bgra pixelValue = this.ApplyMatrix(previousColor, matrix); - for (int y = startY; y < endY; y++) - { - if (y >= sourceY && y < sourceBottom) - { - for (int x = startX; x < endX; x++) + Parallel.For( + startY, + endY, + y => { - Bgra sourceColor = source[x, y]; - - // Check if this is the same as the last pixel. If so use that value - // rather than calculating it again. This is an inexpensive optimization. - if (sourceColor != previousColor) + if (y >= sourceY && y < sourceBottom) { - // Perform the operation on the pixel. - pixelValue = this.ApplyMatrix(sourceColor, matrix); - - // And setup the previous pointer - previousColor = sourceColor; + for (int x = startX; x < endX; x++) + { + target[x, y] = ApplyMatrix(source[x, y], matrix, gamma); + } } - - target[x, y] = pixelValue; - } - } - } + }); } /// /// Applies the color matrix against the given color. /// - /// The source color. + /// The source color. /// The matrix. + /// Whether to perform gamma adjustments. /// - /// The . + /// The . /// - private Bgra ApplyMatrix(Bgra sourceColor, ColorMatrix matrix) + private static Color ApplyMatrix(Color color, ColorMatrix matrix, bool gamma) { - bool gamma = this.GammaAdjust; - if (gamma) { - sourceColor = PixelOperations.ToLinear(sourceColor); + color = PixelOperations.ToLinear(color); } - int sr = sourceColor.R; - int sg = sourceColor.G; - int sb = sourceColor.B; - int sa = sourceColor.A; + float sr = color.R; + float sg = color.G; + float sb = color.B; + float sa = color.A; // TODO: Investigate RGBAW - byte r = ((sr * matrix.Matrix00) + (sg * matrix.Matrix10) + (sb * matrix.Matrix20) + (sa * matrix.Matrix30) + (255f * matrix.Matrix40)).ToByte(); - byte g = ((sr * matrix.Matrix01) + (sg * matrix.Matrix11) + (sb * matrix.Matrix21) + (sa * matrix.Matrix31) + (255f * matrix.Matrix41)).ToByte(); - byte b = ((sr * matrix.Matrix02) + (sg * matrix.Matrix12) + (sb * matrix.Matrix22) + (sa * matrix.Matrix32) + (255f * matrix.Matrix42)).ToByte(); - byte a = ((sr * matrix.Matrix03) + (sg * matrix.Matrix13) + (sb * matrix.Matrix23) + (sa * matrix.Matrix33) + (255f * matrix.Matrix43)).ToByte(); + color.R = (sr * matrix.Matrix00) + (sg * matrix.Matrix10) + (sb * matrix.Matrix20) + (sa * matrix.Matrix30) + matrix.Matrix40; + color.G = (sr * matrix.Matrix01) + (sg * matrix.Matrix11) + (sb * matrix.Matrix21) + (sa * matrix.Matrix31) + matrix.Matrix41; + color.B = (sr * matrix.Matrix02) + (sg * matrix.Matrix12) + (sb * matrix.Matrix22) + (sa * matrix.Matrix32) + matrix.Matrix42; + color.A = (sr * matrix.Matrix03) + (sg * matrix.Matrix13) + (sb * matrix.Matrix23) + (sa * matrix.Matrix33) + matrix.Matrix43; - return gamma ? PixelOperations.ToSrgb(new Bgra(b, g, r, a)) : new Bgra(b, g, r, a); + return gamma ? PixelOperations.ToSrgb(color) : color; } } } diff --git a/src/ImageProcessor/Filters/ColorMatrix/Invert.cs b/src/ImageProcessor/Filters/ColorMatrix/Invert.cs deleted file mode 100644 index 24c8f52a0..000000000 --- a/src/ImageProcessor/Filters/ColorMatrix/Invert.cs +++ /dev/null @@ -1,35 +0,0 @@ -// -// Copyright © James South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessor.Filters -{ - /// - /// Inverts the colors of the image. - /// - public class Invert : ColorMatrixFilter - { - /// - /// The inversion matrix. - /// TODO: With gamma adjustment enabled this leaves the image too bright. - /// - 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 } - }); - - /// - /// Initializes a new instance of the class. - /// - public Invert() - : base(Matrix, false) - { - } - } -} diff --git a/src/ImageProcessor/Filters/Contrast.cs b/src/ImageProcessor/Filters/Contrast.cs index 556b3828f..0986dc3d0 100644 --- a/src/ImageProcessor/Filters/Contrast.cs +++ b/src/ImageProcessor/Filters/Contrast.cs @@ -6,6 +6,7 @@ namespace ImageProcessor.Filters { using System; + using System.Threading.Tasks; /// /// An to change the contrast of an . @@ -33,48 +34,52 @@ namespace ImageProcessor.Filters /// protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { - double contrast = (100.0 + this.Value) / 100.0; + float contrast = (100f + this.Value) / 100f; int sourceY = sourceRectangle.Y; int sourceBottom = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; - for (int y = startY; y < endY; y++) - { - if (y >= sourceY && y < sourceBottom) - { - for (int x = startX; x < endX; x++) + Parallel.For( + startY, + endY, + y => { - Bgra sourceColor = source[x, y]; - sourceColor = PixelOperations.ToLinear(sourceColor); + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x++) + { + target[x, y] = AdjustContrast(source[x, y], contrast); + } + } + }); + } + + /// + /// Returns a with the contrast adjusted. + /// + /// The source color. + /// The contrast adjustment factor. + /// + /// The . + /// + private static Color AdjustContrast(Color color, float contrast) + { + color = PixelOperations.ToLinear(color); - double r = sourceColor.R / 255.0; - r -= 0.5; - r *= contrast; - r += 0.5; - r *= 255; - r = r.ToByte(); + color.R -= 0.5f; + color.R *= contrast; + color.R += 0.5f; - double g = sourceColor.G / 255.0; - g -= 0.5; - g *= contrast; - g += 0.5; - g *= 255; - g = g.ToByte(); + color.G -= 0.5f; + color.G *= contrast; + color.G += 0.5f; - double b = sourceColor.B / 255.0; - b -= 0.5; - b *= contrast; - b += 0.5; - b *= 255; - b = b.ToByte(); + color.B -= 0.5f; + color.B *= contrast; + color.B += 0.5f; - Bgra destinationColor = new Bgra(b.ToByte(), g.ToByte(), r.ToByte(), sourceColor.A); - destinationColor = PixelOperations.ToSrgb(destinationColor); - target[x, y] = destinationColor; - } - } - } + return PixelOperations.ToSrgb(color); } } } diff --git a/src/ImageProcessor/Filters/Invert.cs b/src/ImageProcessor/Filters/Invert.cs index d24979a2d..d2fa9445a 100644 --- a/src/ImageProcessor/Filters/Invert.cs +++ b/src/ImageProcessor/Filters/Invert.cs @@ -5,6 +5,8 @@ namespace ImageProcessor.Filters { + using System.Threading.Tasks; + /// /// An to invert the colors of an . /// @@ -18,19 +20,24 @@ namespace ImageProcessor.Filters int startX = sourceRectangle.X; int endX = sourceRectangle.Right; - for (int y = startY; y < endY; y++) - { - if (y >= sourceY && y < sourceBottom) - { - for (int x = startX; x < endX; x++) + Parallel.For( + startY, + endY, + y => { - // TODO: This doesn't work for gamma test images. - Bgra color = source[x, y]; - Bgra targetColor = new Bgra((255 - color.B).ToByte(), (255 - color.G).ToByte(), (255 - color.R).ToByte(), color.A); - target[x, y] = targetColor; - } - } - } + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x++) + { + // TODO: This doesn't work for gamma test images. + Color color = source[x, y]; + color.R = 1 - color.R; + color.G = 1 - color.G; + color.B = 1 - color.B; + target[x, y] = color; + } + } + }); } } } diff --git a/src/ImageProcessor/Formats/Bmp/BmpDecoder.cs b/src/ImageProcessor/Formats/Bmp/BmpDecoder.cs index cc1b6b464..2e47b607a 100644 --- a/src/ImageProcessor/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageProcessor/Formats/Bmp/BmpDecoder.cs @@ -67,9 +67,8 @@ namespace ImageProcessor.Formats bool isBmp = false; if (header.Length >= 2) { - isBmp = - header[0] == 0x42 && // B - header[1] == 0x4D; // M + isBmp = header[0] == 0x42 && // B + header[1] == 0x4D; // M } return isBmp; diff --git a/src/ImageProcessor/Formats/Bmp/BmpDecoderCore.cs b/src/ImageProcessor/Formats/Bmp/BmpDecoderCore.cs index 1fb911bdd..fa434da84 100644 --- a/src/ImageProcessor/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageProcessor/Formats/Bmp/BmpDecoderCore.cs @@ -1,17 +1,13 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright (c) James South and contributors. -// Licensed under the Apache License, Version 2.0. +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. // -// -// Performs the bmp decoding operation. -// -// -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessor.Formats { using System; using System.IO; + using System.Threading.Tasks; /// /// Performs the bmp decoding operation. @@ -108,7 +104,7 @@ namespace ImageProcessor.Formats + $"bigger then the max allowed size '{ImageBase.MaxWidth}x{ImageBase.MaxHeight}'"); } - byte[] imageData = new byte[this.infoHeader.Width * this.infoHeader.Height * 4]; + float[] imageData = new float[this.infoHeader.Width * this.infoHeader.Height * 4]; switch (this.infoHeader.Compression) { @@ -179,12 +175,12 @@ namespace ImageProcessor.Formats /// /// Reads the color palette from the stream. /// - /// The image data to assign the palette to. + /// The image data to assign the palette to. /// The containing the colors. /// The width of the bitmap. /// The height of the bitmap. /// The number of bits per pixel. - private void ReadRgbPalette(byte[] imageData, byte[] colors, int width, int height, int bits) + private void ReadRgbPalette(float[] imageData, byte[] colors, int width, int height, int bits) { // Pixels per byte (bits per pixel) int ppb = 8 / bits; @@ -194,7 +190,7 @@ namespace ImageProcessor.Formats // Bit mask int mask = 0xFF >> (8 - bits); - byte[] data = new byte[(arrayWidth * height)]; + byte[] data = new byte[arrayWidth * height]; this.currentStream.Read(data, 0, data.Length); @@ -205,134 +201,154 @@ namespace ImageProcessor.Formats alignment = 4 - alignment; } - for (int y = 0; y < height; y++) - { - int rowOffset = y * (arrayWidth + alignment); + Parallel.For( + 0, + height, + y => + { + int rowOffset = y * (arrayWidth + alignment); - for (int x = 0; x < arrayWidth; x++) - { - int offset = rowOffset + x; + for (int x = 0; x < arrayWidth; x++) + { + int offset = rowOffset + x; - // Revert the y value, because bitmaps are saved from down to top - int row = Invert(y, height); + // Revert the y value, because bitmaps are saved from down to top + int row = Invert(y, height); - int colOffset = x * ppb; + int colOffset = x * ppb; - for (int shift = 0; shift < ppb && (colOffset + shift) < width; shift++) - { - int colorIndex = (data[offset] >> (8 - bits - (shift * bits))) & mask; + for (int shift = 0; shift < ppb && (colOffset + shift) < width; shift++) + { + int colorIndex = ((data[offset] >> (8 - bits - (shift * bits))) & mask) * 4; + int arrayOffset = ((row * width) + (colOffset + shift)) * 4; - int arrayOffset = ((row * width) + (colOffset + shift)) * 4; - imageData[arrayOffset + 0] = colors[colorIndex * 4]; - imageData[arrayOffset + 1] = colors[(colorIndex * 4) + 1]; - imageData[arrayOffset + 2] = colors[(colorIndex * 4) + 2]; - imageData[arrayOffset + 3] = 255; - } - } - } + // We divide by 255 as we will store the colors in our floating point format. + // Stored in r-> g-> b-> a order. + imageData[arrayOffset] = colors[colorIndex + 2] / 255f; // r + imageData[arrayOffset + 1] = colors[colorIndex + 1] / 255f; // g + imageData[arrayOffset + 2] = colors[colorIndex] / 255f; // b + imageData[arrayOffset + 3] = 1; // a + } + } + }); } /// /// Reads the 16 bit color palette from the stream /// - /// The image data to assign the palette to. + /// The image data to assign the palette to. /// The width of the bitmap. /// The height of the bitmap. - private void ReadRgb16(byte[] imageData, int width, int height) + private void ReadRgb16(float[] imageData, int width, int height) { - const int ScaleR = 256 / 32; - const int ScaleG = 256 / 64; + // We divide here as we will store the colors in our floating point format. + const int ScaleR = (256 / 32) / 32; + const int ScaleG = (256 / 64) / 64; int alignment; byte[] data = this.GetImageArray(width, height, 2, out alignment); - for (int y = 0; y < height; y++) - { - int rowOffset = y * ((width * 2) + alignment); + Parallel.For( + 0, + height, + y => + { + int rowOffset = y * ((width * 2) + alignment); - // Revert the y value, because bitmaps are saved from down to top - int row = Invert(y, height); + // Revert the y value, because bitmaps are saved from down to top + int row = Invert(y, height); - for (int x = 0; x < width; x++) - { - int offset = rowOffset + (x * 2); + for (int x = 0; x < width; x++) + { + int offset = rowOffset + (x * 2); - short temp = BitConverter.ToInt16(data, offset); + short temp = BitConverter.ToInt16(data, offset); - byte r = (byte)(((temp & Rgb16RMask) >> 11) * ScaleR); - byte g = (byte)(((temp & Rgb16GMask) >> 5) * ScaleG); - byte b = (byte)((temp & Rgb16BMask) * ScaleR); + float r = ((temp & Rgb16RMask) >> 11) * ScaleR; + float g = ((temp & Rgb16GMask) >> 5) * ScaleG; + float b = (temp & Rgb16BMask) * ScaleR; - int arrayOffset = ((row * width) + x) * 4; + int arrayOffset = ((row * width) + x) * 4; - imageData[arrayOffset + 0] = b; - imageData[arrayOffset + 1] = g; - imageData[arrayOffset + 2] = r; - imageData[arrayOffset + 3] = 255; - } - } + // Stored in r-> g-> b-> a order. + imageData[arrayOffset] = r; + imageData[arrayOffset + 1] = g; + imageData[arrayOffset + 2] = b; + imageData[arrayOffset + 3] = 1; + } + }); } /// /// Reads the 24 bit color palette from the stream /// - /// The image data to assign the palette to. + /// The image data to assign the palette to. /// The width of the bitmap. /// The height of the bitmap. - private void ReadRgb24(byte[] imageData, int width, int height) + private void ReadRgb24(float[] imageData, int width, int height) { int alignment; byte[] data = this.GetImageArray(width, height, 3, out alignment); - for (int y = 0; y < height; y++) - { - int rowOffset = y * ((width * 3) + alignment); - - // Revert the y value, because bitmaps are saved from down to top - int row = Invert(y, height); + Parallel.For( + 0, + height, + y => + { + int rowOffset = y * ((width * 3) + alignment); - for (int x = 0; x < width; x++) - { - int offset = rowOffset + (x * 3); - int arrayOffset = ((row * width) + x) * 4; + // Revert the y value, because bitmaps are saved from down to top + int row = Invert(y, height); - imageData[arrayOffset + 0] = data[offset + 0]; - imageData[arrayOffset + 1] = data[offset + 1]; - imageData[arrayOffset + 2] = data[offset + 2]; - imageData[arrayOffset + 3] = 255; - } - } + for (int x = 0; x < width; x++) + { + int offset = rowOffset + (x * 3); + int arrayOffset = ((row * width) + x) * 4; + + // We divide by 255 as we will store the colors in our floating point format. + // Stored in r-> g-> b-> a order. + imageData[arrayOffset] = data[offset + 2] / 255f; + imageData[arrayOffset + 1] = data[offset + 1] / 255f; + imageData[arrayOffset + 2] = data[offset] / 255f; + imageData[arrayOffset + 3] = 1; + } + }); } /// /// Reads the 32 bit color palette from the stream /// - /// The image data to assign the palette to. + /// The image data to assign the palette to. /// The width of the bitmap. /// The height of the bitmap. - private void ReadRgb32(byte[] imageData, int width, int height) + private void ReadRgb32(float[] imageData, int width, int height) { int alignment; byte[] data = this.GetImageArray(width, height, 4, out alignment); - for (int y = 0; y < height; y++) - { - int rowOffset = y * ((width * 4) + alignment); - - // Revert the y value, because bitmaps are saved from down to top - int row = Invert(y, height); + Parallel.For( + 0, + height, + y => + { + int rowOffset = y * ((width * 4) + alignment); - for (int x = 0; x < width; x++) - { - int offset = rowOffset + (x * 4); + // Revert the y value, because bitmaps are saved from down to top + int row = Invert(y, height); - var arrayOffset = ((row * width) + x) * 4; - imageData[arrayOffset + 0] = data[offset + 0]; - imageData[arrayOffset + 1] = data[offset + 1]; - imageData[arrayOffset + 2] = data[offset + 2]; - imageData[arrayOffset + 3] = 255; // Can we get alpha here? - } - } + for (int x = 0; x < width; x++) + { + int offset = rowOffset + (x * 4); + int arrayOffset = ((row * width) + x) * 4; + + // We divide by 255 as we will store the colors in our floating point format. + // Stored in r-> g-> b-> a order. + imageData[arrayOffset] = data[offset + 2] / 255f; + imageData[arrayOffset + 1] = data[offset + 1] / 255f; + imageData[arrayOffset + 2] = data[offset] / 255f; + imageData[arrayOffset + 3] = 1; // TODO: Can we use our real alpha here? + } + }); } /// diff --git a/src/ImageProcessor/Formats/Bmp/BmpEncoder.cs b/src/ImageProcessor/Formats/Bmp/BmpEncoder.cs index ed833bf62..c37809635 100644 --- a/src/ImageProcessor/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageProcessor/Formats/Bmp/BmpEncoder.cs @@ -100,7 +100,7 @@ namespace ImageProcessor.Formats amount = 4 - amount; } - byte[] data = image.Pixels; + float[] data = image.Pixels; for (int y = image.Height - 1; y >= 0; y--) { @@ -108,9 +108,11 @@ namespace ImageProcessor.Formats { int offset = ((y * image.Width) + x) * 4; - writer.Write(data[offset + 0]); - writer.Write(data[offset + 1]); - writer.Write(data[offset + 2]); + // Limit the output range and multiply out from our floating point. + // Convert back to b-> g-> r-> a order. + writer.Write((byte)(data[offset + 2].Clamp(0, 1) * 255)); + writer.Write((byte)(data[offset + 1].Clamp(0, 1) * 255)); + writer.Write((byte)(data[offset].Clamp(0, 1) * 255)); } for (int i = 0; i < amount; i++) diff --git a/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs b/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs index ae91b3d03..8675cbc7a 100644 --- a/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs @@ -31,7 +31,7 @@ namespace ImageProcessor.Formats /// /// The current frame. /// - private byte[] currentFrame; + private float[] currentFrame; /// /// The logical screen descriptor. @@ -288,15 +288,15 @@ namespace ImageProcessor.Formats if (this.currentFrame == null) { - this.currentFrame = new byte[imageWidth * imageHeight * 4]; + this.currentFrame = new float[imageWidth * imageHeight * 4]; } - byte[] lastFrame = null; + float[] lastFrame = null; if (this.graphicsControlExtension != null && this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious) { - lastFrame = new byte[imageWidth * imageHeight * 4]; + lastFrame = new float[imageWidth * imageHeight * 4]; Array.Copy(this.currentFrame, lastFrame, lastFrame.Length); } @@ -352,18 +352,20 @@ namespace ImageProcessor.Formats this.graphicsControlExtension.TransparencyFlag == false || this.graphicsControlExtension.TransparencyIndex != index) { + // We divide by 255 as we will store the colors in our floating point format. + // Stored in r-> g-> b-> a order. int indexOffset = index * 3; - this.currentFrame[offset + 0] = colorTable[indexOffset + 2]; - this.currentFrame[offset + 1] = colorTable[indexOffset + 1]; - this.currentFrame[offset + 2] = colorTable[indexOffset + 0]; - this.currentFrame[offset + 3] = 255; + this.currentFrame[offset + 0] = colorTable[indexOffset] / 255f; // r + this.currentFrame[offset + 1] = colorTable[indexOffset + 1] / 255f; // g + this.currentFrame[offset + 2] = colorTable[indexOffset + 2] / 255f; // b + this.currentFrame[offset + 3] = 1; // a } i++; } } - byte[] pixels = new byte[imageWidth * imageHeight * 4]; + float[] pixels = new float[imageWidth * imageHeight * 4]; Array.Copy(this.currentFrame, pixels, pixels.Length); @@ -406,6 +408,7 @@ namespace ImageProcessor.Formats { offset = ((y * imageWidth) + x) * 4; + // Stored in r-> g-> b-> a order. this.currentFrame[offset + 0] = 0; this.currentFrame[offset + 1] = 0; this.currentFrame[offset + 2] = 0; diff --git a/src/ImageProcessor/Formats/Gif/GifEncoder.cs b/src/ImageProcessor/Formats/Gif/GifEncoder.cs index cf8dc73d0..735d26c39 100644 --- a/src/ImageProcessor/Formats/Gif/GifEncoder.cs +++ b/src/ImageProcessor/Formats/Gif/GifEncoder.cs @@ -127,7 +127,7 @@ namespace ImageProcessor.Formats QuantizedImage quantizedImage = quantizer.Quantize(image); // Grab the pallete and write it to the stream. - Bgra[] pallete = quantizedImage.Palette; + Bgra32[] pallete = quantizedImage.Palette; int pixelCount = pallete.Length; // Get max colors for bit depth. @@ -137,7 +137,7 @@ namespace ImageProcessor.Formats for (int i = 0; i < pixelCount; i++) { int offset = i * 3; - Bgra color = pallete[i]; + Bgra32 color = pallete[i]; colorTable[offset + 2] = color.B; colorTable[offset + 1] = color.G; colorTable[offset + 0] = color.R; diff --git a/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs b/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs index 7358d015a..6f6f375e9 100644 --- a/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs +++ b/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs @@ -70,7 +70,7 @@ namespace ImageProcessor.Formats /// This function need only be overridden if your quantize algorithm needs two passes, /// such as an Octree quantizer. /// - protected override void InitialQuantizePixel(Bgra pixel) + protected override void InitialQuantizePixel(Bgra32 pixel) { // Add the color to the Octree this.octree.AddColor(pixel); @@ -85,7 +85,7 @@ namespace ImageProcessor.Formats /// /// The quantized value /// - protected override byte QuantizePixel(Bgra pixel) + protected override byte QuantizePixel(Bgra32 pixel) { // The color at [maxColors] is set to transparent byte paletteIndex = (byte)this.maxColors; @@ -105,13 +105,13 @@ namespace ImageProcessor.Formats /// /// The new color palette /// - protected override List GetPalette() + protected override List GetPalette() { // First off convert the Octree to maxColors colors - List palette = this.octree.Palletize(Math.Max(this.maxColors - 1, 1)); + List palette = this.octree.Palletize(Math.Max(this.maxColors - 1, 1)); // Add empty color for transparency - palette.Add(Bgra.Empty); + palette.Add(Bgra32.Empty); return palette; } @@ -190,18 +190,18 @@ namespace ImageProcessor.Formats /// Add a given color value to the Octree /// /// - /// The containing color information to add. + /// The containing color information to add. /// - public void AddColor(Bgra pixel) + public void AddColor(Bgra32 pixel) { // Check if this request is for the same color as the last - if (this.previousColor == pixel.BGRA) + if (this.previousColor == pixel.Bgra) { // If so, check if I have a previous node setup. This will only occur if the first color in the image // happens to be black, with an alpha component of zero. - if (null == this.previousNode) + if (this.previousNode == null) { - this.previousColor = pixel.BGRA; + this.previousColor = pixel.Bgra; this.root.AddColor(pixel, this.maxColorBits, 0, this); } else @@ -212,7 +212,7 @@ namespace ImageProcessor.Formats } else { - this.previousColor = pixel.BGRA; + this.previousColor = pixel.Bgra; this.root.AddColor(pixel, this.maxColorBits, 0, this); } } @@ -226,7 +226,7 @@ namespace ImageProcessor.Formats /// /// An with the palletized colors /// - public List Palletize(int colorCount) + public List Palletize(int colorCount) { while (this.Leaves > colorCount) { @@ -234,7 +234,7 @@ namespace ImageProcessor.Formats } // Now palletize the nodes - List palette = new List(this.Leaves); + List palette = new List(this.Leaves); int paletteIndex = 0; this.root.ConstructPalette(palette, ref paletteIndex); @@ -246,12 +246,12 @@ namespace ImageProcessor.Formats /// Get the palette index for the passed color /// /// - /// The containing the pixel data. + /// The containing the pixel data. /// /// /// The index of the given structure. /// - public int GetPaletteIndex(Bgra pixel) + public int GetPaletteIndex(Bgra32 pixel) { return this.root.GetPaletteIndex(pixel, 0); } @@ -274,7 +274,7 @@ namespace ImageProcessor.Formats { // Find the deepest level containing at least one reducible node int index = this.maxColorBits - 1; - while ((index > 0) && (null == this.reducibleNodes[index])) + while ((index > 0) && (this.reducibleNodes[index] == null)) { index--; } @@ -387,7 +387,7 @@ namespace ImageProcessor.Formats /// /// The tree to which this node belongs /// - public void AddColor(Bgra pixel, int colorBits, int level, Octree octree) + public void AddColor(Bgra32 pixel, int colorBits, int level, Octree octree) { // Update the color information if this is a leaf if (this.leaf) @@ -407,7 +407,7 @@ namespace ImageProcessor.Formats OctreeNode child = this.children[index]; - if (null == child) + if (child == null) { // Create a new child node and store it in the array child = new OctreeNode(level + 1, colorBits, octree); @@ -431,7 +431,7 @@ namespace ImageProcessor.Formats // Loop through all children and add their information to this node for (int index = 0; index < 8; index++) { - if (null != this.children[index]) + if (this.children[index] != null) { this.red += this.children[index].red; this.green += this.children[index].green; @@ -458,7 +458,7 @@ namespace ImageProcessor.Formats /// /// The current palette index /// - public void ConstructPalette(List palette, ref int index) + public void ConstructPalette(List palette, ref int index) { if (this.leaf) { @@ -470,14 +470,14 @@ namespace ImageProcessor.Formats byte b = (this.blue / this.pixelCount).ToByte(); // And set the color of the palette entry - palette.Add(new Bgra(b, g, r)); + palette.Add(new Bgra32(b, g, r)); } else { // Loop through children looking for leaves for (int i = 0; i < 8; i++) { - if (null != this.children[i]) + if (this.children[i] != null) { this.children[i].ConstructPalette(palette, ref index); } @@ -489,7 +489,7 @@ namespace ImageProcessor.Formats /// Return the palette index for the passed color /// /// - /// The representing the pixel. + /// The representing the pixel. /// /// /// The level. @@ -497,7 +497,7 @@ namespace ImageProcessor.Formats /// /// The representing the index of the pixel in the palette. /// - public int GetPaletteIndex(Bgra pixel, int level) + public int GetPaletteIndex(Bgra32 pixel, int level) { int index = this.paletteIndex; @@ -508,7 +508,7 @@ namespace ImageProcessor.Formats ((pixel.G & Mask[level]) >> (shift - 1)) | ((pixel.B & Mask[level]) >> shift); - if (null != this.children[pixelIndex]) + if (this.children[pixelIndex] != null) { index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1); } @@ -527,7 +527,7 @@ namespace ImageProcessor.Formats /// /// The pixel to add. /// - public void Increment(Bgra pixel) + public void Increment(Bgra32 pixel) { this.pixelCount++; this.red += pixel.R; diff --git a/src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs b/src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs index 76c273fe8..5bb8f9ec6 100644 --- a/src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs +++ b/src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs @@ -19,7 +19,7 @@ namespace ImageProcessor.Formats /// The image height. /// The color palette. /// The quantized pixels. - public QuantizedImage(int width, int height, Bgra[] palette, byte[] pixels) + public QuantizedImage(int width, int height, Bgra32[] palette, byte[] pixels) { Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); @@ -51,7 +51,7 @@ namespace ImageProcessor.Formats /// /// Gets the color palette of this . /// - public Bgra[] Palette { get; } + public Bgra32[] Palette { get; } /// /// Gets the pixels of this . @@ -69,12 +69,12 @@ namespace ImageProcessor.Formats Image image = new Image(); int pixelCount = this.Pixels.Length; - byte[] bgraPixels = new byte[pixelCount * 4]; + float[] bgraPixels = new float[pixelCount * 4]; for (int i = 0; i < pixelCount; i++) { int offset = i * 4; - Bgra color = this.Palette[this.Pixels[i]]; + Bgra32 color = this.Palette[this.Pixels[i]]; bgraPixels[offset + 0] = color.B; bgraPixels[offset + 1] = color.G; bgraPixels[offset + 2] = color.R; diff --git a/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs b/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs index dda33d479..c35a9f7f4 100644 --- a/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs +++ b/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs @@ -18,7 +18,7 @@ namespace ImageProcessor.Formats private readonly bool singlePass; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// If true, the quantization only needs to loop through the source pixels once @@ -56,7 +56,8 @@ namespace ImageProcessor.Formats byte[] quantizedPixels = new byte[width * height]; - List palette = this.GetPalette(); + // Get the pallete + List palette = this.GetPalette(); this.SecondPass(imageBase, quantizedPixels, width, height); @@ -94,8 +95,9 @@ namespace ImageProcessor.Formats { int i = 0; - // Convert the first pixel, so that I have values going into the loop - Bgra previousPixel = source[0, 0]; + // Convert the first pixel, so that I have values going into the loop. + // Implicit cast here from Color. + Bgra32 previousPixel = source[0, 0]; byte pixelValue = this.QuantizePixel(previousPixel); output[0] = pixelValue; @@ -104,7 +106,8 @@ namespace ImageProcessor.Formats { for (int x = 0; x < width; x++) { - Bgra sourcePixel = source[x, y]; + // Implicit cast here from Color. + Bgra32 sourcePixel = source[x, y]; // Check if this is the same as the last pixel. If so use that value // rather than calculating it again. This is an inexpensive optimization. @@ -132,7 +135,7 @@ namespace ImageProcessor.Formats /// This function need only be overridden if your quantize algorithm needs two passes, /// such as an Octree quantizer. /// - protected virtual void InitialQuantizePixel(Bgra pixel) + protected virtual void InitialQuantizePixel(Bgra32 pixel) { } @@ -145,7 +148,7 @@ namespace ImageProcessor.Formats /// /// The quantized value /// - protected abstract byte QuantizePixel(Bgra pixel); + protected abstract byte QuantizePixel(Bgra32 pixel); /// /// Retrieve the palette for the quantized image @@ -153,6 +156,6 @@ namespace ImageProcessor.Formats /// /// The new color palette /// - protected abstract List GetPalette(); + protected abstract List GetPalette(); } } \ No newline at end of file diff --git a/src/ImageProcessor/Formats/Jpg/JpegDecoder.cs b/src/ImageProcessor/Formats/Jpg/JpegDecoder.cs index 766923f7e..541400a4e 100644 --- a/src/ImageProcessor/Formats/Jpg/JpegDecoder.cs +++ b/src/ImageProcessor/Formats/Jpg/JpegDecoder.cs @@ -7,6 +7,8 @@ namespace ImageProcessor.Formats { using System; using System.IO; + using System.Threading.Tasks; + using BitMiracle.LibJpeg; /// @@ -97,50 +99,41 @@ namespace ImageProcessor.Formats int pixelWidth = jpg.Width; int pixelHeight = jpg.Height; - byte[] pixels = new byte[pixelWidth * pixelHeight * 4]; + float[] pixels = new float[pixelWidth * pixelHeight * 4]; if (!(jpg.Colorspace == Colorspace.RGB && jpg.BitsPerComponent == 8)) { throw new NotSupportedException("JpegDecoder only support RGB color space."); } - for (int y = 0; y < pixelHeight; y++) - { - SampleRow row = jpg.GetRow(y); + Parallel.For( + 0, + pixelHeight, + y => + { + SampleRow row = jpg.GetRow(y); - for (int x = 0; x < pixelWidth; x++) - { - Sample sample = row.GetAt(x); + for (int x = 0; x < pixelWidth; x++) + { + Sample sample = row.GetAt(x); - int offset = ((y * pixelWidth) + x) * 4; + int offset = ((y * pixelWidth) + x) * 4; - pixels[offset + 0] = (byte)sample[2]; - pixels[offset + 1] = (byte)sample[1]; - pixels[offset + 2] = (byte)sample[0]; - pixels[offset + 3] = 255; - } - } + pixels[offset + 0] = sample[0] / 255f; + pixels[offset + 1] = sample[1] / 255f; + pixels[offset + 2] = sample[2] / 255f; + pixels[offset + 3] = 1; + } + }); image.SetPixels(pixelWidth, pixelHeight, pixels); } /// - /// + /// Returns a value indicating whether the given bytes identify Jpeg data. /// - /// - /// - 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; - } - + /// The bytes representing the file header. + /// The private static bool IsJpeg(byte[] header) { bool isJpg = @@ -152,5 +145,22 @@ namespace ImageProcessor.Formats return isJpg; } + + /// + /// Returns a value indicating whether the given bytes identify EXIF data. + /// + /// The bytes representing the file header. + /// The + 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; + } } } diff --git a/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs b/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs index 35d6e286e..84ce9c423 100644 --- a/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs +++ b/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs @@ -7,6 +7,7 @@ namespace ImageProcessor.Formats { using System; using System.IO; + using System.Threading.Tasks; using BitMiracle.LibJpeg; @@ -88,26 +89,29 @@ namespace ImageProcessor.Formats int pixelWidth = image.Width; int pixelHeight = image.Height; - byte[] sourcePixels = image.Pixels; + float[] sourcePixels = image.Pixels; SampleRow[] rows = new SampleRow[pixelHeight]; - for (int y = 0; y < pixelHeight; y++) - { - byte[] samples = new byte[pixelWidth * 3]; - - for (int x = 0; x < pixelWidth; x++) - { - int start = x * 3; - int source = ((y * pixelWidth) + x) * 4; - - samples[start] = sourcePixels[source + 2]; - samples[start + 1] = sourcePixels[source + 1]; - samples[start + 2] = sourcePixels[source]; - } - - rows[y] = new SampleRow(samples, pixelWidth, 8, 3); - } + Parallel.For( + 0, + pixelHeight, + y => + { + byte[] samples = new byte[pixelWidth * 3]; + + for (int x = 0; x < pixelWidth; x++) + { + int start = x * 3; + int source = ((y * pixelWidth) + x) * 4; + + samples[start] = (byte)(sourcePixels[source].Clamp(0, 1) * 255); + samples[start + 1] = (byte)(sourcePixels[source + 1].Clamp(0, 1) * 255); + samples[start + 2] = (byte)(sourcePixels[source + 2].Clamp(0, 1) * 255); + } + + rows[y] = new SampleRow(samples, pixelWidth, 8, 3); + }); JpegImage jpg = new JpegImage(rows, Colorspace.RGB); jpg.WriteJpeg(stream, new CompressionParameters { Quality = this.Quality }); diff --git a/src/ImageProcessor/Formats/Png/GrayscaleReader.cs b/src/ImageProcessor/Formats/Png/GrayscaleReader.cs index 552776a67..244c0e8c0 100644 --- a/src/ImageProcessor/Formats/Png/GrayscaleReader.cs +++ b/src/ImageProcessor/Formats/Png/GrayscaleReader.cs @@ -1,12 +1,7 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright (c) James South and contributors. -// Licensed under the Apache License, Version 2.0. +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. // -// -// Color reader for reading grayscale colors from a png file. -// -// -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessor.Formats { @@ -37,31 +32,25 @@ namespace ImageProcessor.Formats this.useAlpha = useAlpha; } - /// - /// Reads the specified scanline. - /// - /// The scanline. - /// The pixels, where the colors should be stored in BGRA format. - /// - /// The header, which contains information about the png file, like - /// the width of the image and the height. - /// - public void ReadScanline(byte[] scanline, byte[] pixels, PngHeader header) + /// + public void ReadScanline(byte[] scanline, float[] pixels, PngHeader header) { int offset; byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth); + // We divide by 255 as we will store the colors in our floating point format. + // Stored in r-> g-> b-> a order. if (this.useAlpha) { for (int x = 0; x < header.Width / 2; x++) { offset = ((this.row * header.Width) + x) * 4; - pixels[offset + 0] = newScanline[x * 2]; - pixels[offset + 1] = newScanline[x * 2]; - pixels[offset + 2] = newScanline[x * 2]; - pixels[offset + 3] = newScanline[(x * 2) + 1]; + pixels[offset] = newScanline[x * 2] / 255f; + pixels[offset + 1] = newScanline[x * 2] / 255f; + pixels[offset + 2] = newScanline[x * 2] / 255f; + pixels[offset + 3] = newScanline[(x * 2) + 1] / 255f; } } else @@ -70,10 +59,10 @@ namespace ImageProcessor.Formats { offset = ((this.row * header.Width) + x) * 4; - pixels[offset + 0] = newScanline[x]; - pixels[offset + 1] = newScanline[x]; - pixels[offset + 2] = newScanline[x]; - pixels[offset + 3] = 255; + pixels[offset] = newScanline[x] / 255f; + pixels[offset + 1] = newScanline[x] / 255f; + pixels[offset + 2] = newScanline[x] / 255f; + pixels[offset + 3] = 1; } } diff --git a/src/ImageProcessor/Formats/Png/IColorReader.cs b/src/ImageProcessor/Formats/Png/IColorReader.cs index 40c14d6ec..0d7ff27e3 100644 --- a/src/ImageProcessor/Formats/Png/IColorReader.cs +++ b/src/ImageProcessor/Formats/Png/IColorReader.cs @@ -1,18 +1,12 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright (c) James South and contributors. -// Licensed under the Apache License, Version 2.0. +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. // -// -// Encapsulates methods for color readers, which are responsible for reading -// different color formats from a png file. -// -// -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessor.Formats { /// - /// 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. /// public interface IColorReader @@ -26,6 +20,6 @@ namespace ImageProcessor.Formats /// The header, which contains information about the png file, like /// the width of the image and the height. /// - void ReadScanline(byte[] scanline, byte[] pixels, PngHeader header); + void ReadScanline(byte[] scanline, float[] pixels, PngHeader header); } } diff --git a/src/ImageProcessor/Formats/Png/PaletteIndexReader.cs b/src/ImageProcessor/Formats/Png/PaletteIndexReader.cs index a44d34c29..550ac358b 100644 --- a/src/ImageProcessor/Formats/Png/PaletteIndexReader.cs +++ b/src/ImageProcessor/Formats/Png/PaletteIndexReader.cs @@ -1,12 +1,7 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright (c) James South and contributors. -// Licensed under the Apache License, Version 2.0. +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. // -// -// A color reader for reading palette indices from the png file. -// -// -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessor.Formats { @@ -43,14 +38,8 @@ namespace ImageProcessor.Formats this.paletteAlpha = paletteAlpha; } - /// - /// Reads the specified scanline. - /// - /// The scanline. - /// The pixels, where the colors should be stored in BGRA format. - /// The header, which contains information about the png file, like - /// the width of the image and the height. - public void ReadScanline(byte[] scanline, byte[] pixels, PngHeader header) + /// + public void ReadScanline(byte[] scanline, float[] pixels, PngHeader header) { byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth); int offset, index; @@ -65,13 +54,14 @@ namespace ImageProcessor.Formats index = newScanline[i]; offset = ((this.row * header.Width) + i) * 4; + int pixelOffset = index * 3; - pixels[offset + 0] = this.palette[(index * 3) + 2]; - pixels[offset + 1] = this.palette[(index * 3) + 1]; - pixels[offset + 2] = this.palette[(index * 3) + 0]; + pixels[offset] = this.palette[pixelOffset] / 255f; + pixels[offset + 1] = this.palette[pixelOffset + 1] / 255f; + pixels[offset + 2] = this.palette[pixelOffset + 2] / 255f; pixels[offset + 3] = this.paletteAlpha.Length > index - ? this.paletteAlpha[index] - : (byte)255; + ? this.paletteAlpha[index] / 255f + : 1; } } else @@ -81,11 +71,12 @@ namespace ImageProcessor.Formats index = newScanline[i]; offset = ((this.row * header.Width) + i) * 4; + int pixelOffset = index * 3; - pixels[offset + 0] = this.palette[(index * 3) + 2]; - pixels[offset + 1] = this.palette[(index * 3) + 1]; - pixels[offset + 2] = this.palette[(index * 3) + 0]; - pixels[offset + 3] = 255; + pixels[offset] = this.palette[pixelOffset] / 255f; + pixels[offset + 1] = this.palette[pixelOffset + 1] / 255f; + pixels[offset + 2] = this.palette[pixelOffset + 2] / 255f; + pixels[offset + 3] = 1; } } diff --git a/src/ImageProcessor/Formats/Png/PngDecoderCore.cs b/src/ImageProcessor/Formats/Png/PngDecoderCore.cs index 9f837a6bf..1bf8e7740 100644 --- a/src/ImageProcessor/Formats/Png/PngDecoderCore.cs +++ b/src/ImageProcessor/Formats/Png/PngDecoderCore.cs @@ -16,9 +16,6 @@ namespace ImageProcessor.Formats using System.Linq; using System.Text; - //using ICSharpCode.SharpZipLib.Checksums; - //using ICSharpCode.SharpZipLib.Zip.Compression.Streams; - /// /// Performs the png decoding operation. /// @@ -145,7 +142,7 @@ namespace ImageProcessor.Formats + $"max allowed size '{ImageBase.MaxWidth}x{ImageBase.MaxHeight}'"); } - byte[] pixels = new byte[this.header.Width * this.header.Height * 4]; + float[] pixels = new float[this.header.Width * this.header.Height * 4]; PngColorTypeInformation colorTypeInformation = ColorTypes[this.header.ColorType]; @@ -248,10 +245,10 @@ namespace ImageProcessor.Formats /// /// The containing data. /// - /// The containing pixel data. + /// The containing pixel data. /// The color reader. /// The color type information. - 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; diff --git a/src/ImageProcessor/Formats/Png/PngEncoder.cs b/src/ImageProcessor/Formats/Png/PngEncoder.cs index 14a312220..fd3c531d0 100644 --- a/src/ImageProcessor/Formats/Png/PngEncoder.cs +++ b/src/ImageProcessor/Formats/Png/PngEncoder.cs @@ -8,9 +8,6 @@ namespace ImageProcessor.Formats using System; using System.IO; - //using ICSharpCode.SharpZipLib.Checksums; - //using ICSharpCode.SharpZipLib.Zip.Compression.Streams; - /// /// Image encoder for writing image data to a stream in png format. /// @@ -36,7 +33,7 @@ namespace ImageProcessor.Formats public int Quality { get; set; } /// - public string MimeType => "image/jpepngg"; + public string MimeType => "image/png"; /// public string Extension => "png"; @@ -225,7 +222,7 @@ namespace ImageProcessor.Formats /// The image base. private void WriteDataChunksFast(Stream stream, ImageBase imageBase) { - byte[] pixels = imageBase.Pixels; + float[] pixels = imageBase.Pixels; // Convert the pixel array to a new array for adding // the filter byte. @@ -297,7 +294,7 @@ namespace ImageProcessor.Formats /// The image base. private void WriteDataChunks(Stream stream, ImageBase imageBase) { - byte[] pixels = imageBase.Pixels; + float[] pixels = imageBase.Pixels; byte[] data = new byte[(imageBase.Width * imageBase.Height * 4) + imageBase.Height]; @@ -321,19 +318,19 @@ namespace ImageProcessor.Formats // Calculate the offset for the original pixel array. int pixelOffset = ((y * imageBase.Width) + x) * 4; - data[dataOffset + 0] = pixels[pixelOffset + 2]; - data[dataOffset + 1] = pixels[pixelOffset + 1]; - data[dataOffset + 2] = pixels[pixelOffset + 0]; - data[dataOffset + 3] = pixels[pixelOffset + 3]; + data[dataOffset] = (byte)(pixels[pixelOffset].Clamp(0, 1) * 255); + data[dataOffset + 1] = (byte)(pixels[pixelOffset + 1].Clamp(0, 1) * 255); + data[dataOffset + 2] = (byte)(pixels[pixelOffset + 2].Clamp(0, 1) * 255); + data[dataOffset + 3] = (byte)(pixels[pixelOffset + 3].Clamp(0, 1) * 255); if (y > 0) { int lastOffset = (((y - 1) * imageBase.Width) + x) * 4; - data[dataOffset + 0] -= pixels[lastOffset + 2]; - data[dataOffset + 1] -= pixels[lastOffset + 1]; - data[dataOffset + 2] -= pixels[lastOffset + 0]; - data[dataOffset + 3] -= pixels[lastOffset + 3]; + data[dataOffset] -= (byte)(pixels[lastOffset].Clamp(0, 1) * 255); + data[dataOffset + 1] -= (byte)(pixels[lastOffset + 1].Clamp(0, 1) * 255); + data[dataOffset + 2] -= (byte)(pixels[lastOffset + 2].Clamp(0, 1) * 255); + data[dataOffset + 3] -= (byte)(pixels[lastOffset + 3].Clamp(0, 1) * 255); } } } diff --git a/src/ImageProcessor/Formats/Png/TrueColorReader.cs b/src/ImageProcessor/Formats/Png/TrueColorReader.cs index c60e68306..0ce755a3f 100644 --- a/src/ImageProcessor/Formats/Png/TrueColorReader.cs +++ b/src/ImageProcessor/Formats/Png/TrueColorReader.cs @@ -1,13 +1,7 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright (c) James South and contributors. -// Licensed under the Apache License, Version 2.0. +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. // -// -// 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. -// -// -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessor.Formats { @@ -37,14 +31,8 @@ namespace ImageProcessor.Formats this.useAlpha = useAlpha; } - /// - /// Reads the specified scanline. - /// - /// The scanline. - /// The pixels, where the colors should be stored in BGRA format. - /// The header, which contains information about the png file, like - /// the width of the image and the height. - public void ReadScanline(byte[] scanline, byte[] pixels, PngHeader header) + /// + public void ReadScanline(byte[] scanline, float[] pixels, PngHeader header) { int offset; @@ -56,10 +44,10 @@ namespace ImageProcessor.Formats { offset = ((this.row * header.Width) + (x >> 2)) * 4; - pixels[offset + 0] = newScanline[x + 2]; - pixels[offset + 1] = newScanline[x + 1]; - pixels[offset + 2] = newScanline[x + 0]; - pixels[offset + 3] = newScanline[x + 3]; + pixels[offset + 0] = newScanline[x] / 255f; + pixels[offset + 1] = newScanline[x + 1] / 255f; + pixels[offset + 2] = newScanline[x + 2] / 255f; + pixels[offset + 3] = newScanline[x + 3] / 255f; } } else @@ -67,11 +55,12 @@ namespace ImageProcessor.Formats for (int x = 0; x < newScanline.Length / 3; x++) { offset = ((this.row * header.Width) + x) * 4; + int pixelOffset = x * 3; - pixels[offset + 0] = newScanline[(x * 3) + 2]; - pixels[offset + 1] = newScanline[(x * 3) + 1]; - pixels[offset + 2] = newScanline[(x * 3) + 0]; - pixels[offset + 3] = 255; + pixels[offset + 0] = newScanline[pixelOffset] / 255f; + pixels[offset + 1] = newScanline[pixelOffset + 1] / 255f; + pixels[offset + 2] = newScanline[pixelOffset + 2] / 255f; + pixels[offset + 3] = 1; } } diff --git a/src/ImageProcessor/Formats/Png/Zlib/InflaterInputBuffer.cs b/src/ImageProcessor/Formats/Png/Zlib/InflaterInputBuffer.cs index 42fa1753a..5c70ad41c 100644 --- a/src/ImageProcessor/Formats/Png/Zlib/InflaterInputBuffer.cs +++ b/src/ImageProcessor/Formats/Png/Zlib/InflaterInputBuffer.cs @@ -3,9 +3,6 @@ using System; using System.IO; - //using ICSharpCode.SharpZipLib.Zip; - //using ICSharpCode.SharpZipLib.Zip.Compression; - /// /// An input buffer customised for use by /// @@ -14,7 +11,6 @@ /// public class InflaterInputBuffer { - #region Constructors /// /// Initialise a new instance of with a default buffer size /// @@ -39,7 +35,6 @@ rawData = new byte[bufferSize]; clearText = rawData; } - #endregion /// /// Get the length of bytes bytes in the @@ -127,17 +122,7 @@ toRead -= count; } -#if !NETCF_1_0 && !NOCRYPTO - if (cryptoTransform != null) - { - clearTextLength = cryptoTransform.TransformBlock(rawData, 0, rawLength, clearText, 0); - } - else -#endif - { - clearTextLength = rawLength; - } - + clearTextLength = rawLength; available = clearTextLength; } @@ -178,12 +163,14 @@ return 0; } } + int toCopy = Math.Min(currentLength, available); - System.Array.Copy(rawData, rawLength - (int)available, outBuffer, currentOffset, toCopy); + Array.Copy(rawData, rawLength - (int)available, outBuffer, currentOffset, toCopy); currentOffset += toCopy; currentLength -= toCopy; available -= toCopy; } + return length; } @@ -270,57 +257,12 @@ return (uint)ReadLeInt() | ((long)ReadLeInt() << 32); } -#if !NETCF_1_0 && !NOCRYPTO - /// - /// Get/set the to apply to any data. - /// - /// Set this value to null to have no transform applied. - public ICryptoTransform CryptoTransform - { - set - { - cryptoTransform = value; - if (cryptoTransform != null) - { - if (rawData == clearText) - { - if (internalClearText == null) - { - internalClearText = new byte[rawData.Length]; - } - clearText = internalClearText; - } - clearTextLength = rawLength; - if (available > 0) - { - cryptoTransform.TransformBlock(rawData, rawLength - available, available, clearText, rawLength - available); - } - } - else - { - clearText = rawData; - clearTextLength = rawLength; - } - } - } -#endif - - #region Instance Fields int rawLength; byte[] rawData; int clearTextLength; byte[] clearText; -#if !NETCF_1_0 && !NOCRYPTO - byte[] internalClearText; -#endif - int available; - -#if !NETCF_1_0 && !NOCRYPTO - ICryptoTransform cryptoTransform; -#endif Stream inputStream; - #endregion } } diff --git a/src/ImageProcessor/Formats/Png/Zlib/InflaterInputStream.cs b/src/ImageProcessor/Formats/Png/Zlib/InflaterInputStream.cs index 62cba8bb4..b6a35e420 100644 --- a/src/ImageProcessor/Formats/Png/Zlib/InflaterInputStream.cs +++ b/src/ImageProcessor/Formats/Png/Zlib/InflaterInputStream.cs @@ -149,16 +149,6 @@ } } - /// - /// Clear any cryptographic state. - /// - protected void StopDecrypting() - { -#if !NETCF_1_0 && !NOCRYPTO - inputBuffer.CryptoTransform = null; -#endif - } - /// /// Returns 0 once the end of the stream (EOF) has been reached. /// Otherwise returns 1. diff --git a/src/ImageProcessor/Formats/Png/Zlib/ZipConstants.cs b/src/ImageProcessor/Formats/Png/Zlib/ZipConstants.cs index 6dccc6a96..9ba46abe4 100644 --- a/src/ImageProcessor/Formats/Png/Zlib/ZipConstants.cs +++ b/src/ImageProcessor/Formats/Png/Zlib/ZipConstants.cs @@ -7,7 +7,6 @@ /// public static class ZipConstants { - #region Versions /// /// The version made by field for entries in the central header when created by this library /// @@ -30,9 +29,7 @@ /// The version required for Zip64 extensions (4.5 or higher) /// public const int VersionZip64 = 45; - #endregion - #region Header Sizes /// /// Size of local entry header (excluding variable length fields at end) /// @@ -62,9 +59,7 @@ /// Size of 'classic' cryptographic header stored before any entry data /// public const int CryptoHeaderSize = 12; - #endregion - #region Header Signatures /// /// Signature for local entry header @@ -121,52 +116,8 @@ /// End of central directory record signature /// public const int EndOfCentralDirectorySignature = 'P' | ('K' << 8) | (5 << 16) | (6 << 24); - - #endregion - -#if NETCF_1_0 || NETCF_2_0 - // This isnt so great but is better than nothing. - // Trying to work out an appropriate OEM code page would be good. - // 850 is a good default for english speakers particularly in Europe. - static int defaultCodePage = CultureInfo.CurrentCulture.TextInfo.ANSICodePage; -#elif PCL static Encoding defaultEncoding = Encoding.UTF8; -#else - /// - /// 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 CodePageNoOEM in sources (might also mean CP_OEMCP, but Encoding puts it so). - /// This was observed on Ukranian and Hindu systems. - /// Given this value, throws an . - /// So replace it with some fallback, e.g. 437 which is the default cpcp in a console in a default Windows installation. - /// - 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 - /// - /// 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. - /// - 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 + /// /// PCL don't support CodePage so we used Encoding instead of /// @@ -176,12 +127,12 @@ { return defaultEncoding; } + set { defaultEncoding = value; } } -#endif /// /// Convert a portion of a byte array to a string. @@ -201,11 +152,8 @@ { return string.Empty; } -#if !PCL - return Encoding.GetEncoding(DefaultCodePage).GetString(data, 0, count); -#else + return DefaultEncoding.GetString(data, 0, count); -#endif } /// @@ -294,11 +242,8 @@ { return new byte[0]; } -#if !PCL - return Encoding.GetEncoding(DefaultCodePage).GetBytes(str); -#else + return DefaultEncoding.GetBytes(str); -#endif } /// @@ -320,10 +265,8 @@ { return Encoding.UTF8.GetBytes(str); } - else - { - return ConvertToArray(str); - } + + return ConvertToArray(str); } } } diff --git a/src/ImageProcessor/IImageBase.cs b/src/ImageProcessor/IImageBase.cs index 62fc821b3..cdbe2104a 100644 --- a/src/ImageProcessor/IImageBase.cs +++ b/src/ImageProcessor/IImageBase.cs @@ -20,7 +20,7 @@ namespace ImageProcessor /// and stores the blue, the green, the red and the alpha value for /// each pixel in this order. /// - byte[] Pixels { get; } + float[] Pixels { get; } /// /// Gets the width in pixels. @@ -66,8 +66,8 @@ namespace ImageProcessor /// The y-coordinate of the pixel. Must be greater /// than zero and smaller than the width of the pixel. /// - /// The at the specified position. - Bgra this[int x, int y] { get; set; } + /// The at the specified position. + Color this[int x, int y] { get; set; } /// /// Sets the pixel array of the image. @@ -85,6 +85,6 @@ namespace ImageProcessor /// /// Thrown if the length is not equal to Width * Height * 4. /// - void SetPixels(int width, int height, byte[] pixels); + void SetPixels(int width, int height, float[] pixels); } } diff --git a/src/ImageProcessor/ImageBase.cs b/src/ImageProcessor/ImageBase.cs index 405cd888f..5e1aaa1a6 100644 --- a/src/ImageProcessor/ImageBase.cs +++ b/src/ImageProcessor/ImageBase.cs @@ -40,7 +40,7 @@ namespace ImageProcessor this.Width = width; this.Height = height; - this.Pixels = new byte[width * height * 4]; + this.Pixels = new float[width * height * 4]; } /// @@ -56,13 +56,13 @@ namespace ImageProcessor { Guard.NotNull(other, nameof(other), "Other image cannot be null."); - byte[] pixels = other.Pixels; + float[] pixels = other.Pixels; this.Width = other.Width; this.Height = other.Height; this.Quality = other.Quality; this.FrameDelay = other.FrameDelay; - this.Pixels = new byte[pixels.Length]; + this.Pixels = new float[pixels.Length]; Array.Copy(pixels, this.Pixels, pixels.Length); } @@ -84,7 +84,7 @@ namespace ImageProcessor /// and stores the blue, the green, the red and the alpha value for /// each pixel in this order. /// - public byte[] Pixels { get; private set; } + public float[] Pixels { get; private set; } /// /// Gets the width in pixels. @@ -106,9 +106,7 @@ namespace ImageProcessor /// public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height); - /// - /// Gets or sets th quality of the image. This affects the output quality of lossy image formats. - /// + /// public int Quality { get; set; } /// @@ -119,19 +117,8 @@ namespace ImageProcessor /// public int FrameDelay { get; set; } - /// - /// Gets or sets the color of a pixel at the specified position. - /// - /// - /// The x-coordinate of the pixel. Must be greater - /// than zero and smaller than the width of the pixel. - /// - /// - /// The y-coordinate of the pixel. Must be greater - /// than zero and smaller than the width of the pixel. - /// - /// The at the specified position. - public Bgra this[int x, int y] + /// + public Color this[int x, int y] { get { @@ -148,7 +135,7 @@ namespace ImageProcessor #endif int start = ((y * this.Width) + x) * 4; - return new Bgra(this.Pixels[start], this.Pixels[start + 1], this.Pixels[start + 2], this.Pixels[start + 3]); + return new Color(this.Pixels[start], this.Pixels[start + 1], this.Pixels[start + 2], this.Pixels[start + 3]); } set @@ -166,31 +153,17 @@ namespace ImageProcessor #endif int start = ((y * this.Width) + x) * 4; - this.Pixels[start + 0] = value.B; + this.Pixels[start + 0] = value.R; this.Pixels[start + 1] = value.G; - this.Pixels[start + 2] = value.R; + this.Pixels[start + 2] = value.B; this.Pixels[start + 3] = value.A; } } - /// - /// Sets the pixel array of the image. - /// - /// - /// The new width of the image. Must be greater than zero. - /// The new height of the image. Must be greater than zero. - /// - /// The array with colors. Must be a multiple - /// of four, width and height. - /// - /// - /// Thrown if either or are less than or equal to 0. - /// - /// - /// Thrown if the length is not equal to Width * Height * 4. - /// - public void SetPixels(int width, int height, byte[] pixels) + /// + public void SetPixels(int width, int height, float[] pixels) { +#if DEBUG if (width <= 0) { throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than or equals than zero."); @@ -205,7 +178,7 @@ namespace ImageProcessor { throw new ArgumentException("Pixel array must have the length of Width * Height * 4."); } - +#endif this.Width = width; this.Height = height; this.Pixels = pixels; diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index 77ea223ad..48294d14f 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -39,9 +39,13 @@ TRACE prompt 4 + bin\Release\ImageProcessor.XML + true - + + + @@ -80,8 +84,8 @@ - - + + @@ -196,7 +200,7 @@ - + @@ -235,13 +239,14 @@ - - + + +