diff --git a/README.md b/README.md index 5c0ca8298..de9d40fba 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ To clone it locally click the "Clone in Windows" button above or run the followi git clone https://github.com/JimBobSquarePants/ImageSharp ``` -###What works so far/ What is planned? +### What works so far/ What is planned? - Encoding/decoding of image formats (plugable). - [x] Jpeg (Includes Subsampling. Progressive writing required) @@ -43,16 +43,34 @@ git clone https://github.com/JimBobSquarePants/ImageSharp - [x] Xiaolin Wu - [x] Palette - Basic color structs with implicit operators. - - [x] Color - 32bit color in RGBA order. - - [x] BGRA32 + - [x] Color - 32bit color in RGBA order (IPackedPixel\). + - [x] Bgra32 - [x] CIE Lab - [x] CIE XYZ - [x] CMYK - [x] HSV - [x] HSL - [x] YCbCr -- Basic shape primitives (Vector backed) - - [x] Rectangle (Doesn't contain all System.Drawing methods) +- IPackedPixel\ representations of color models. Compatible with Microsoft XNA Game Studio and MonoGame. + - [x] Alpha8 + - [x] Bgr565 + - [x] Bgra444 + - [x] Bgra565 + - [x] Byte4 + - [x] HalfSingle + - [x] HalfVector2 + - [x] HalfVector4 + - [x] NormalizedByte2 + - [x] NormalizedByte4 + - [x] NormalizedShort2 + - [x] NormalizedShort4 + - [x] Rg32 + - [x] Rgba1010102 + - [x] Rgba64 + - [x] Short2 + - [x] Short4 +- Basic shape primitives. + - [x] Rectangle - [x] Size - [x] Point - [x] Ellipse @@ -131,9 +149,9 @@ git clone https://github.com/JimBobSquarePants/ImageSharp - [x] Threshold - Drawing - [ ] Path brush (Need help) - - [ ] Pattern brush (Need help) + - [ ] Hatch brush (Need help) - [ ] Elliptical brush (Need help) - - [ ] Gradient brush (vignette? Need help) + - [ ] Gradient brush (Need help) - Other stuff I haven't thought of. ### What might never happen diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs index 1435377a5..109544ada 100644 --- a/src/ImageSharp/Colors/Color.cs +++ b/src/ImageSharp/Colors/Color.cs @@ -10,7 +10,7 @@ namespace ImageSharp using System.Numerics; /// - /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 255. + /// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. /// The color components are stored in red, green, blue, and alpha order. /// /// @@ -19,9 +19,24 @@ namespace ImageSharp /// public partial struct Color : IPackedPixel, IEquatable { + /// + /// The shift count for the red component + /// private const int RedShift = 0; + + /// + /// The shift count for the green component + /// private const int GreenShift = 8; + + /// + /// The shift count for the blue component + /// private const int BlueShift = 16; + + /// + /// The shift count for the alpha component + /// private const int AlphaShift = 24; /// @@ -219,7 +234,7 @@ namespace ImageSharp /// The on the left side of the operand. /// The on the right side of the operand. /// - /// True if the parameter is equal to the parameter; otherwise, false. + /// True if the parameter is not equal to the parameter; otherwise, false. /// public static bool operator !=(Color left, Color right) { diff --git a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs new file mode 100644 index 000000000..234976b48 --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs @@ -0,0 +1,153 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Packed pixel type containing a single 8 bit normalized W values that is ranging from 0 to 1. + /// + public struct Alpha8 : IPackedPixel, IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// The alpha component + public Alpha8(float alpha) + { + this.PackedValue = Pack(alpha); + } + + /// + public byte PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Alpha8 left, Alpha8 right) + { + return left.PackedValue == right.PackedValue; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(Alpha8 left, Alpha8 right) + { + return left.PackedValue != right.PackedValue; + } + + /// + public void PackFromVector4(Vector4 vector) + { + this.PackedValue = Pack(vector.W); + } + + /// + public Vector4 ToVector4() + { + return new Vector4(0, 0, 0, this.PackedValue / 255F); + } + + /// + public void PackFromBytes(byte x, byte y, byte z, byte w) + { + this.PackedValue = w; + } + + /// + public void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder) + { + switch (componentOrder) + { + case ComponentOrder.ZYX: + bytes[startIndex] = 0; + bytes[startIndex + 1] = 0; + bytes[startIndex + 2] = 0; + break; + case ComponentOrder.ZYXW: + bytes[startIndex] = 0; + bytes[startIndex + 1] = 0; + bytes[startIndex + 2] = 0; + bytes[startIndex + 3] = this.PackedValue; + break; + case ComponentOrder.XYZ: + bytes[startIndex] = 0; + bytes[startIndex + 1] = 0; + bytes[startIndex + 2] = 0; + break; + case ComponentOrder.XYZW: + bytes[startIndex] = 0; + bytes[startIndex + 1] = 0; + bytes[startIndex + 2] = 0; + bytes[startIndex + 3] = this.PackedValue; + break; + default: + throw new NotSupportedException(); + } + } + + /// + /// Compares an object with the packed vector. + /// + /// The object to compare. + /// True if the object is equal to the packed vector. + public override bool Equals(object obj) + { + return (obj is Alpha8) && this.Equals((Alpha8)obj); + } + + /// + /// Compares another Alpha8 packed vector with the packed vector. + /// + /// The Alpha8 packed vector to compare. + /// True if the packed vectors are equal. + public bool Equals(Alpha8 other) + { + return this.PackedValue == other.PackedValue; + } + + /// + /// Gets a string representation of the packed vector. + /// + /// A string representation of the packed vector. + public override string ToString() + { + return (this.PackedValue / 255F).ToString(); + } + + /// + public override int GetHashCode() + { + return this.PackedValue.GetHashCode(); + } + + /// + /// Packs a into a byte. + /// + /// The float containing the value to pack. + /// The containing the packed values. + private static byte Pack(float alpha) + { + return (byte)Math.Round(alpha.Clamp(0, 1) * 255F); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs new file mode 100644 index 000000000..214e03c5f --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs @@ -0,0 +1,170 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. The x and z components use 5 bits, and the y component uses 6 bits. + /// + public struct Bgr565 : IPackedPixel, IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + public Bgr565(float x, float y, float z) + { + this.PackedValue = Pack(x, y, z); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed value. + /// + public Bgr565(Vector3 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z); + } + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Bgr565 left, Bgr565 right) + { + return left.PackedValue == right.PackedValue; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(Bgr565 left, Bgr565 right) + { + return left.PackedValue != right.PackedValue; + } + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + public Vector3 ToVector3() + { + return new Vector3( + ((this.PackedValue >> 11) & 0x1F) * (1F / 31F), + ((this.PackedValue >> 5) & 0x3F) * (1F / 63F), + (this.PackedValue & 0x1F) * (1F / 31F)); + } + + /// + public void PackFromVector4(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z); + } + + /// + public Vector4 ToVector4() + { + return new Vector4(this.ToVector3(), 1F); + } + + /// + public void PackFromBytes(byte x, byte y, byte z, byte w) + { + this.PackFromVector4(new Vector4(x, y, z, w) / 255F); + } + + /// + public void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder) + { + Vector4 vector = this.ToVector4() * 255F; + + switch (componentOrder) + { + case ComponentOrder.ZYX: + bytes[startIndex] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + break; + case ComponentOrder.ZYXW: + bytes[startIndex] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + break; + case ComponentOrder.XYZ: + bytes[startIndex] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); + break; + case ComponentOrder.XYZW: + bytes[startIndex] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + break; + default: + throw new NotSupportedException(); + } + } + + /// + public override bool Equals(object obj) + { + return (obj is Bgr565) && this.Equals((Bgr565)obj); + } + + /// + public bool Equals(Bgr565 other) + { + return this.PackedValue == other.PackedValue; + } + + /// + public override string ToString() + { + return this.ToVector3().ToString(); + } + + /// + public override int GetHashCode() + { + return this.PackedValue.GetHashCode(); + } + + /// + /// Packs the components into a . + /// + /// The x-component + /// The y-component + /// The z-component + /// The containing the packed values. + private static ushort Pack(float x, float y, float z) + { + return (ushort)((((int)Math.Round(x.Clamp(0, 1) * 31F) & 0x1F) << 11) | + (((int)Math.Round(y.Clamp(0, 1) * 63F) & 0x3F) << 5) | + ((int)Math.Round(z.Clamp(0, 1) * 31F) & 0x1F)); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs b/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs new file mode 100644 index 000000000..630415d03 --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs @@ -0,0 +1,164 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Packed pixel type containing unsigned normalized values, ranging from 0 to 1, using 4 bits each for x, y, z, and w. + /// + public struct Bgra4444 : IPackedPixel, IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + public Bgra4444(float x, float y, float z, float w) + { + this.PackedValue = Pack(x, y, z, w); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the components for the packed vector. + public Bgra4444(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Bgra4444 left, Bgra4444 right) + { + return left.PackedValue == right.PackedValue; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(Bgra4444 left, Bgra4444 right) + { + return left.PackedValue != right.PackedValue; + } + + /// + public Vector4 ToVector4() + { + const float Max = 1 / 15F; + + return new Vector4( + ((this.PackedValue >> 8) & 0x0F) * Max, + ((this.PackedValue >> 4) & 0x0F) * Max, + (this.PackedValue & 0x0F) * Max, + ((this.PackedValue >> 12) & 0x0F) * Max); + } + + /// + public void PackFromVector4(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + public void PackFromBytes(byte x, byte y, byte z, byte w) + { + this.PackFromVector4(new Vector4(x, y, z, w) / 255F); + } + + /// + public void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder) + { + Vector4 vector = this.ToVector4() * 255F; + + switch (componentOrder) + { + case ComponentOrder.ZYX: + bytes[startIndex] = (byte)vector.Z; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.X; + break; + case ComponentOrder.ZYXW: + bytes[startIndex] = (byte)vector.Z; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.X; + bytes[startIndex + 3] = (byte)vector.W; + break; + case ComponentOrder.XYZ: + bytes[startIndex] = (byte)vector.X; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.Z; + break; + case ComponentOrder.XYZW: + bytes[startIndex] = (byte)vector.X; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.Z; + bytes[startIndex + 3] = (byte)vector.W; + break; + default: + throw new NotSupportedException(); + } + } + + /// + public override bool Equals(object obj) + { + return (obj is Bgra4444) && this.Equals((Bgra4444)obj); + } + + /// + public bool Equals(Bgra4444 other) + { + return this.PackedValue == other.PackedValue; + } + + /// + public override string ToString() + { + return this.ToVector4().ToString(); + } + + /// + public override int GetHashCode() + { + return this.PackedValue.GetHashCode(); + } + + /// + /// Packs the components into a . + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + /// The containing the packed values. + private static ushort Pack(float x, float y, float z, float w) + { + return (ushort)((((int)Math.Round(w.Clamp(0, 1) * 15F) & 0x0F) << 12) | + (((int)Math.Round(x.Clamp(0, 1) * 15F) & 0x0F) << 8) | + (((int)Math.Round(y.Clamp(0, 1) * 15F) & 0x0F) << 4) | + ((int)Math.Round(z.Clamp(0, 1) * 15F) & 0x0F)); + } + } +} diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs new file mode 100644 index 000000000..50aa67914 --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs @@ -0,0 +1,171 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. The x , y and z components use 5 bits, and the w component uses 1 bit. + /// + public struct Bgra5551 : IPackedPixel, IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + public Bgra5551(float x, float y, float z, float w) + { + this.PackedValue = Pack(x, y, z, w); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public Bgra5551(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Bgra5551 left, Bgra5551 right) + { + return left.PackedValue == right.PackedValue; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(Bgra5551 left, Bgra5551 right) + { + return left.PackedValue != right.PackedValue; + } + + /// + public Vector4 ToVector4() + { + return new Vector4( + ((this.PackedValue >> 10) & 0x1F) / 31F, + ((this.PackedValue >> 5) & 0x1F) / 31F, + ((this.PackedValue >> 0) & 0x1F) / 31F, + (this.PackedValue >> 15) & 0x01); + } + + /// + public void PackFromVector4(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + public void PackFromBytes(byte x, byte y, byte z, byte w) + { + this.PackFromVector4(new Vector4(x, y, z, w) / 255F); + } + + /// + public void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder) + { + Vector4 vector = this.ToVector4() * 255F; + + switch (componentOrder) + { + case ComponentOrder.ZYX: + bytes[startIndex] = (byte)vector.Z; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.X; + break; + case ComponentOrder.ZYXW: + bytes[startIndex] = (byte)vector.Z; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.X; + bytes[startIndex + 3] = (byte)vector.W; + break; + case ComponentOrder.XYZ: + bytes[startIndex] = (byte)vector.X; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.Z; + break; + case ComponentOrder.XYZW: + bytes[startIndex] = (byte)vector.X; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.Z; + bytes[startIndex + 3] = (byte)vector.W; + break; + default: + throw new NotSupportedException(); + } + } + + /// + public override bool Equals(object obj) + { + return (obj is Bgra5551) && this.Equals((Bgra5551)obj); + } + + /// + public bool Equals(Bgra5551 other) + { + return this.PackedValue == other.PackedValue; + } + + /// + /// Gets a string representation of the packed vector. + /// + /// A string representation of the packed vector. + public override string ToString() + { + return this.ToVector4().ToString(); + } + + /// + /// Gets a hash code of the packed vector. + /// + /// The hash code for the packed vector. + public override int GetHashCode() + { + return this.PackedValue.GetHashCode(); + } + + /// + /// Packs the components into a . + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + /// The containing the packed values. + private static ushort Pack(float x, float y, float z, float w) + { + return (ushort)( + (((int)Math.Round(x.Clamp(0, 1) * 31F) & 0x1F) << 10) | + (((int)Math.Round(y.Clamp(0, 1) * 31F) & 0x1F) << 5) | + (((int)Math.Round(z.Clamp(0, 1) * 31F) & 0x1F) << 0) | + (((int)Math.Round(w.Clamp(0, 1)) & 0x1) << 15)); + } + } +} diff --git a/src/ImageSharp/Colors/PackedPixel/Byte4.cs b/src/ImageSharp/Colors/PackedPixel/Byte4.cs new file mode 100644 index 000000000..9b4be5711 --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/Byte4.cs @@ -0,0 +1,177 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Packed pixel type containing four 8-bit unsigned integer values, ranging from 0 to 255. + /// + public struct Byte4 : IPackedPixel, IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// + /// A vector containing the initial values for the components of the Byte4 structure. + /// + public Byte4(Vector4 vector) + { + this.PackedValue = Pack(ref vector); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + public Byte4(float x, float y, float z, float w) + { + Vector4 vector = new Vector4(x, y, z, w); + this.PackedValue = Pack(ref vector); + } + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Byte4 left, Byte4 right) + { + return left.PackedValue == right.PackedValue; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(Byte4 left, Byte4 right) + { + return left.PackedValue != right.PackedValue; + } + + /// + /// Sets the packed representation from a Vector4. + /// + /// The vector to create the packed representation from. + public void PackFromVector4(Vector4 vector) + { + this.PackedValue = Pack(ref vector); + } + + /// + /// Expands the packed representation into a Vector4. + /// + /// The expanded vector. + public Vector4 ToVector4() + { + return new Vector4( + this.PackedValue & 0xFF, + (this.PackedValue >> 0x8) & 0xFF, + (this.PackedValue >> 0x10) & 0xFF, + (this.PackedValue >> 0x18) & 0xFF); + } + + /// + public void PackFromBytes(byte x, byte y, byte z, byte w) + { + this.PackFromVector4(new Vector4(x, y, z, w) / 255F); + } + + /// + public void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder) + { + Vector4 vector = this.ToVector4() * 255F; + + switch (componentOrder) + { + case ComponentOrder.ZYX: + bytes[startIndex] = (byte)vector.Z; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.X; + break; + case ComponentOrder.ZYXW: + bytes[startIndex] = (byte)vector.Z; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.X; + bytes[startIndex + 3] = (byte)vector.W; + break; + case ComponentOrder.XYZ: + bytes[startIndex] = (byte)vector.X; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.Z; + break; + case ComponentOrder.XYZW: + bytes[startIndex] = (byte)vector.X; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.Z; + bytes[startIndex + 3] = (byte)vector.W; + break; + default: + throw new NotSupportedException(); + } + } + + /// + public override bool Equals(object obj) + { + return (obj is Byte4) && this.Equals((Byte4)obj); + } + + /// + public bool Equals(Byte4 other) + { + return this == other; + } + + /// + public override int GetHashCode() + { + return this.PackedValue.GetHashCode(); + } + + /// + /// Returns a string representation of the current instance. + /// + /// String that represents the object. + public override string ToString() + { + return this.PackedValue.ToString("x8"); + } + + /// + /// Packs a vector into a uint. + /// + /// The vector containing the values to pack. + /// The containing the packed values. + private static uint Pack(ref Vector4 vector) + { + const float Max = 255F; + const float Min = 0F; + + // Clamp the value between min and max values + uint byte4 = (uint)Math.Round(vector.X.Clamp(Min, Max)) & 0xFF; + uint byte3 = ((uint)Math.Round(vector.Y.Clamp(Min, Max)) & 0xFF) << 0x8; + uint byte2 = ((uint)Math.Round(vector.Z.Clamp(Min, Max)) & 0xFF) << 0x10; + uint byte1 = ((uint)Math.Round(vector.W.Clamp(Min, Max)) & 0xFF) << 0x18; + + return byte4 | byte3 | byte2 | byte1; + } + } +} diff --git a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs new file mode 100644 index 000000000..b2919b46a --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs @@ -0,0 +1,160 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Packed pixel type containing a single 16 bit floating point value. + /// + public struct HalfSingle : IPackedPixel, IEquatable + { + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new Vector4(255); + + /// + /// The half vector value. + /// + private static readonly Vector4 Half = new Vector4(0.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The single component. + public HalfSingle(float single) + { + this.PackedValue = HalfTypeHelper.Pack(single); + } + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(HalfSingle left, HalfSingle right) + { + return left.PackedValue == right.PackedValue; + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(HalfSingle left, HalfSingle right) + { + return left.PackedValue != right.PackedValue; + } + + /// + /// Expands the packed representation into a . + /// + /// The . + public float ToSingle() + { + return HalfTypeHelper.Unpack(this.PackedValue); + } + + /// + public void PackFromVector4(Vector4 vector) + { + this.PackedValue = HalfTypeHelper.Pack(vector.X); + } + + /// + public Vector4 ToVector4() + { + return new Vector4(this.ToSingle(), 0, 0, 1); + } + + /// + public void PackFromBytes(byte x, byte y, byte z, byte w) + { + this.PackFromVector4(new Vector4(x, y, z, w) / MaxBytes); + } + + /// + public void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder) + { + Vector4 vector = this.ToVector4(); + vector *= MaxBytes; + vector += Half; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + + switch (componentOrder) + { + case ComponentOrder.ZYX: + bytes[startIndex] = (byte)vector.Z; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.X; + break; + case ComponentOrder.ZYXW: + bytes[startIndex] = (byte)vector.Z; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.X; + bytes[startIndex + 3] = (byte)vector.W; + break; + case ComponentOrder.XYZ: + bytes[startIndex] = (byte)vector.X; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.Z; + break; + case ComponentOrder.XYZW: + bytes[startIndex] = (byte)vector.X; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.Z; + bytes[startIndex + 3] = (byte)vector.W; + break; + default: + throw new NotSupportedException(); + } + } + + /// + public override bool Equals(object obj) + { + return (obj is HalfSingle) && this.Equals((HalfSingle)obj); + } + + /// + public bool Equals(HalfSingle other) + { + return this.PackedValue == other.PackedValue; + } + + /// + public override string ToString() + { + return this.ToSingle().ToString(); + } + + /// + public override int GetHashCode() + { + return this.PackedValue.GetHashCode(); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/HalfTypeHelper.cs b/src/ImageSharp/Colors/PackedPixel/HalfTypeHelper.cs new file mode 100644 index 000000000..ae71294bc --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/HalfTypeHelper.cs @@ -0,0 +1,144 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Runtime.InteropServices; + + /// + /// Helper methods for packing and unpacking floating point values + /// + internal class HalfTypeHelper + { + /// + /// Packs a into an + /// + /// The float to pack + /// The + internal static ushort Pack(float value) + { + Uif uif = new Uif { F = value }; + return Pack(uif.I); + } + + /// + /// Packs an into a + /// + /// The integer to pack. + /// The + internal static ushort Pack(int value) + { + int s = (value >> 16) & 0x00008000; + int e = ((value >> 23) & 0x000000ff) - (127 - 15); + int m = value & 0x007fffff; + + if (e <= 0) + { + if (e < -10) + { + return (ushort)s; + } + + m = m | 0x00800000; + + int t = 14 - e; + int a = (1 << (t - 1)) - 1; + int b = (m >> t) & 1; + + m = (m + a + b) >> t; + + return (ushort)(s | m); + } + + if (e == 0xff - (127 - 15)) + { + if (m == 0) + { + return (ushort)(s | 0x7c00); + } + + m >>= 13; + return (ushort)(s | 0x7c00 | m | ((m == 0) ? 1 : 0)); + } + + m = m + 0x00000fff + ((m >> 13) & 1); + + if ((m & 0x00800000) != 0) + { + m = 0; + e += 1; + } + + if (e > 30) + { + return (ushort)(s | 0x7c00); + } + + return (ushort)(s | (e << 10) | (m >> 13)); + } + + /// + /// Unpacks a into a . + /// + /// The value. + /// The . + internal static float Unpack(ushort value) + { + uint result; + uint mantissa = (uint)(value & 1023); + uint exponent = 0xfffffff2; + + if ((value & -33792) == 0) + { + if (mantissa != 0) + { + while ((mantissa & 1024) == 0) + { + exponent--; + mantissa = mantissa << 1; + } + mantissa &= 0xfffffbff; + result = ((uint)((((uint)value & 0x8000) << 16) | ((exponent + 127) << 23))) | (mantissa << 13); + } + else + { + result = (uint)((value & 0x8000) << 16); + } + } + else + { + result = ((((uint)value & 0x8000) << 16) | ((((((uint)value >> 10) & 0x1f) - 15) + 127) << 23)) | (mantissa << 13); + } + + Uif uif = new Uif { U = result }; + return uif.F; + } + + /// + /// Maps the position of number types in memory + /// + [StructLayout(LayoutKind.Explicit)] + private struct Uif + { + /// + /// The float. + /// + [FieldOffset(0)] + public float F; + + /// + /// The integer. + /// + [FieldOffset(0)] + public int I; + + /// + /// The unsigned integer. + /// + [FieldOffset(0)] + public uint U; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs new file mode 100644 index 000000000..c41e01b2a --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs @@ -0,0 +1,187 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Packed pixel type containing two 16-bit floating-point values. + /// + public struct HalfVector2 : IPackedPixel, IEquatable + { + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new Vector4(255); + + /// + /// The half vector value. + /// + private static readonly Vector4 Half = new Vector4(0.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + public HalfVector2(float x, float y) + { + this.PackedValue = Pack(x, y); + } + + /// + /// Initializes a new instance of the struct. + /// + /// A vector containing the initial values for the components. + public HalfVector2(Vector2 vector) + { + this.PackedValue = Pack(vector.X, vector.Y); + } + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(HalfVector2 left, HalfVector2 right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(HalfVector2 left, HalfVector2 right) + { + return !left.Equals(right); + } + + /// + /// Expands the packed representation into a . + /// + /// The . + public Vector2 ToVector2() + { + Vector2 vector; + vector.X = HalfTypeHelper.Unpack((ushort)this.PackedValue); + vector.Y = HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x10)); + return vector; + } + + /// + public void PackFromVector4(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y); + } + + /// + public Vector4 ToVector4() + { + Vector2 vector = this.ToVector2(); + return new Vector4(vector.X, vector.Y, 0F, 1F); + } + + /// + public void PackFromBytes(byte x, byte y, byte z, byte w) + { + this.PackFromVector4(new Vector4(x, y, z, w) / MaxBytes); + } + + /// + public void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder) + { + Vector4 vector = this.ToVector4(); + vector *= MaxBytes; + vector += Half; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + + switch (componentOrder) + { + case ComponentOrder.ZYX: + bytes[startIndex] = (byte)vector.Z; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.X; + break; + case ComponentOrder.ZYXW: + bytes[startIndex] = (byte)vector.Z; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.X; + bytes[startIndex + 3] = (byte)vector.W; + break; + case ComponentOrder.XYZ: + bytes[startIndex] = (byte)vector.X; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.Z; + break; + case ComponentOrder.XYZW: + bytes[startIndex] = (byte)vector.X; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.Z; + bytes[startIndex + 3] = (byte)vector.W; + break; + default: + throw new NotSupportedException(); + } + } + + /// + public override string ToString() + { + return this.ToVector2().ToString(); + } + + /// + public override int GetHashCode() + { + return this.PackedValue.GetHashCode(); + } + + /// + public override bool Equals(object obj) + { + return (obj is HalfVector2) && this.Equals((HalfVector2)obj); + } + + /// + public bool Equals(HalfVector2 other) + { + return this.PackedValue.Equals(other.PackedValue); + } + + /// + /// Packs the components into a . + /// + /// The x-component + /// The y-component + /// The containing the packed values. + private static uint Pack(float x, float y) + { + uint num2 = HalfTypeHelper.Pack(x); + uint num = (uint)(HalfTypeHelper.Pack(y) << 0x10); + return num2 | num; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs b/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs new file mode 100644 index 000000000..4c4da339b --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs @@ -0,0 +1,182 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Packed pixel type containing four 16-bit floating-point values. + /// + public struct HalfVector4 : IPackedPixel, IEquatable + { + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new Vector4(255); + + /// + /// The half vector value. + /// + private static readonly Vector4 Half = new Vector4(0.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + /// The z-component. + /// The w-component. + public HalfVector4(float x, float y, float z, float w) + { + var vector = new Vector4(x, y, z, w); + this.PackedValue = PackHelper(ref vector); + } + + /// + /// Initializes a new instance of the struct. + /// + /// A vector containing the initial values for the components + public HalfVector4(Vector4 vector) + { + this.PackedValue = PackHelper(ref vector); + } + + /// + public ulong PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(HalfVector4 left, HalfVector4 right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(HalfVector4 left, HalfVector4 right) + { + return !left.Equals(right); + } + + /// + public void PackFromVector4(Vector4 vector) + { + this.PackedValue = PackHelper(ref vector); + } + + /// + public Vector4 ToVector4() + { + return new Vector4( + HalfTypeHelper.Unpack((ushort)this.PackedValue), + HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x10)), + HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x20)), + HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x30))); + } + + /// + public void PackFromBytes(byte x, byte y, byte z, byte w) + { + this.PackFromVector4(new Vector4(x, y, z, w) / MaxBytes); + } + + /// + public void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder) + { + Vector4 vector = this.ToVector4(); + vector *= MaxBytes; + vector += Half; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + + switch (componentOrder) + { + case ComponentOrder.ZYX: + bytes[startIndex] = (byte)vector.Z; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.X; + break; + case ComponentOrder.ZYXW: + bytes[startIndex] = (byte)vector.Z; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.X; + bytes[startIndex + 3] = (byte)vector.W; + break; + case ComponentOrder.XYZ: + bytes[startIndex] = (byte)vector.X; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.Z; + break; + case ComponentOrder.XYZW: + bytes[startIndex] = (byte)vector.X; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.Z; + bytes[startIndex + 3] = (byte)vector.W; + break; + default: + throw new NotSupportedException(); + } + } + + /// + public override string ToString() + { + return this.ToVector4().ToString(); + } + + /// + public override int GetHashCode() + { + return this.PackedValue.GetHashCode(); + } + + /// + public override bool Equals(object obj) + { + return (obj is HalfVector4) && this.Equals((HalfVector4)obj); + } + + /// + public bool Equals(HalfVector4 other) + { + return this.PackedValue.Equals(other.PackedValue); + } + + /// + /// Packs a into a . + /// + /// The vector containing the values to pack. + /// The containing the packed values. + private static ulong PackHelper(ref Vector4 vector) + { + ulong num4 = HalfTypeHelper.Pack(vector.X); + ulong num3 = (ulong)HalfTypeHelper.Pack(vector.Y) << 0x10; + ulong num2 = (ulong)HalfTypeHelper.Pack(vector.Z) << 0x20; + ulong num1 = (ulong)HalfTypeHelper.Pack(vector.W) << 0x30; + return num4 | num3 | num2 | num1; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs new file mode 100644 index 000000000..3ff419ab6 --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs @@ -0,0 +1,199 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Packed packed pixel type containing two 8-bit signed normalized values, ranging from −1 to 1. + /// + public struct NormalizedByte2 : IPackedPixel, IEquatable + { + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new Vector4(255); + + /// + /// The half the maximum byte value. + /// + private static readonly Vector4 Half = new Vector4(127); + + /// + /// The vector value used for rounding. + /// + private static readonly Vector4 Round = new Vector4(.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public NormalizedByte2(Vector2 vector) + { + this.PackedValue = Pack(vector.X, vector.Y); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + public NormalizedByte2(float x, float y) + { + this.PackedValue = Pack(x, y); + } + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(NormalizedByte2 left, NormalizedByte2 right) + { + return left.PackedValue == right.PackedValue; + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(NormalizedByte2 left, NormalizedByte2 right) + { + return left.PackedValue != right.PackedValue; + } + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + public Vector2 ToVector2() + { + return new Vector2( + (sbyte)((this.PackedValue >> 0) & 0xFF) / 127F, + (sbyte)((this.PackedValue >> 8) & 0xFF) / 127F); + } + + /// + public void PackFromVector4(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y); + } + + /// + public Vector4 ToVector4() + { + return new Vector4(this.ToVector2(), 0F, 1F); + } + + /// + public void PackFromBytes(byte x, byte y, byte z, byte w) + { + Vector4 vector = new Vector4(x, y, z, w); + vector -= Round; + vector -= Half; + vector -= Round; + vector /= Half; + this.PackFromVector4(vector); + } + + /// + public void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder) + { + Vector4 vector = this.ToVector4(); + vector *= Half; + vector += Round; + vector += Half; + vector += Round; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + + switch (componentOrder) + { + case ComponentOrder.ZYX: + bytes[startIndex] = 0; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.X; + break; + case ComponentOrder.ZYXW: + bytes[startIndex] = 0; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.X; + bytes[startIndex + 3] = 255; + break; + case ComponentOrder.XYZ: + bytes[startIndex] = (byte)vector.X; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = 0; + break; + case ComponentOrder.XYZW: + bytes[startIndex] = (byte)vector.X; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = 0; + bytes[startIndex + 3] = 255; + break; + default: + throw new NotSupportedException(); + } + } + + /// + public override bool Equals(object obj) + { + return (obj is NormalizedByte2) && this.Equals((NormalizedByte2)obj); + } + + /// + public bool Equals(NormalizedByte2 other) + { + return this.PackedValue == other.PackedValue; + } + + /// + public override int GetHashCode() + { + return this.PackedValue.GetHashCode(); + } + + /// + public override string ToString() + { + return this.PackedValue.ToString("X"); + } + + /// + /// Packs the components into a . + /// + /// The x-component + /// The y-component + /// The containing the packed values. + private static ushort Pack(float x, float y) + { + int byte2 = ((ushort)Math.Round(x.Clamp(-1F, 1F) * 127F) & 0xFF) << 0; + int byte1 = ((ushort)Math.Round(y.Clamp(-1F, 1F) * 127F) & 0xFF) << 8; + + return (ushort)(byte2 | byte1); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs new file mode 100644 index 000000000..2638ff7f5 --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs @@ -0,0 +1,197 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Packed pixel type containing four 8-bit signed normalized values, ranging from −1 to 1. + /// + public struct NormalizedByte4 : IPackedPixel, IEquatable + { + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new Vector4(255); + + /// + /// The half the maximum byte value. + /// + private static readonly Vector4 Half = new Vector4(127); + + /// + /// The vector value used for rounding. + /// + private static readonly Vector4 Round = new Vector4(.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public NormalizedByte4(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + /// The z-component. + /// The w-component. + public NormalizedByte4(float x, float y, float z, float w) + { + this.PackedValue = Pack(x, y, z, w); + } + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(NormalizedByte4 left, NormalizedByte4 right) + { + return left.PackedValue == right.PackedValue; + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(NormalizedByte4 left, NormalizedByte4 right) + { + return left.PackedValue != right.PackedValue; + } + + /// + public void PackFromVector4(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + public Vector4 ToVector4() + { + return new Vector4( + (sbyte)((this.PackedValue >> 0) & 0xFF) / 127F, + (sbyte)((this.PackedValue >> 8) & 0xFF) / 127F, + (sbyte)((this.PackedValue >> 16) & 0xFF) / 127F, + (sbyte)((this.PackedValue >> 24) & 0xFF) / 127F); + } + + /// + public void PackFromBytes(byte x, byte y, byte z, byte w) + { + Vector4 vector = new Vector4(x, y, z, w); + vector -= Round; + vector -= Half; + vector -= Round; + vector /= Half; + this.PackFromVector4(vector); + } + + /// + public void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder) + { + Vector4 vector = this.ToVector4(); + vector *= Half; + vector += Round; + vector += Half; + vector += Round; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + + switch (componentOrder) + { + case ComponentOrder.ZYX: + bytes[startIndex] = (byte)vector.Z; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.X; + break; + case ComponentOrder.ZYXW: + bytes[startIndex] = (byte)vector.Z; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.X; + bytes[startIndex + 3] = (byte)vector.W; + break; + case ComponentOrder.XYZ: + bytes[startIndex] = (byte)vector.X; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.Z; + break; + case ComponentOrder.XYZW: + bytes[startIndex] = (byte)vector.X; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.Z; + bytes[startIndex + 3] = (byte)vector.W; + break; + default: + throw new NotSupportedException(); + } + } + + /// + public override bool Equals(object obj) + { + return (obj is NormalizedByte4) && this.Equals((NormalizedByte4)obj); + } + + /// + public bool Equals(NormalizedByte4 other) + { + return this.PackedValue == other.PackedValue; + } + + /// + public override int GetHashCode() + { + return this.PackedValue.GetHashCode(); + } + + /// + public override string ToString() + { + return this.PackedValue.ToString("X"); + } + + /// + /// Packs the components into a . + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + /// The containing the packed values. + private static uint Pack(float x, float y, float z, float w) + { + uint byte4 = ((uint)Math.Round(x.Clamp(-1F, 1F) * 127F) & 0xFF) << 0; + uint byte3 = ((uint)Math.Round(y.Clamp(-1F, 1F) * 127F) & 0xFF) << 8; + uint byte2 = ((uint)Math.Round(z.Clamp(-1F, 1F) * 127F) & 0xFF) << 16; + uint byte1 = ((uint)Math.Round(w.Clamp(-1F, 1F) * 127F) & 0xFF) << 24; + + return byte4 | byte3 | byte2 | byte1; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs new file mode 100644 index 000000000..0d63f1b4d --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs @@ -0,0 +1,206 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Packed pixel type containing two 16-bit signed normalized values, ranging from −1 to 1. + /// + public struct NormalizedShort2 : IPackedPixel, IEquatable + { + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new Vector4(255); + + /// + /// The half the maximum byte value. + /// + private static readonly Vector4 Half = new Vector4(127); + + /// + /// The vector value used for rounding. + /// + private static readonly Vector4 Round = new Vector4(.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public NormalizedShort2(Vector2 vector) + { + this.PackedValue = Pack(vector.X, vector.Y); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + public NormalizedShort2(float x, float y) + { + this.PackedValue = Pack(x, y); + } + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(NormalizedShort2 left, NormalizedShort2 right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(NormalizedShort2 left, NormalizedShort2 right) + { + return !left.Equals(right); + } + + /// + public void PackFromVector4(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y); + } + + /// + public Vector4 ToVector4() + { + return new Vector4(this.ToVector2(), 0, 1); + } + + /// + public void PackFromBytes(byte x, byte y, byte z, byte w) + { + Vector4 vector = new Vector4(x, y, z, w); + vector -= Round; + vector -= Half; + vector -= Round; + vector /= Half; + this.PackFromVector4(vector); + } + + /// + public void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder) + { + Vector4 vector = this.ToVector4(); + vector *= Half; + vector += Round; + vector += Half; + vector += Round; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + + switch (componentOrder) + { + case ComponentOrder.ZYX: + bytes[startIndex] = 0; + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + break; + case ComponentOrder.ZYXW: + bytes[startIndex] = 0; + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 3] = 255; + break; + case ComponentOrder.XYZ: + bytes[startIndex] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = 0; + break; + case ComponentOrder.XYZW: + bytes[startIndex] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = 0; + bytes[startIndex + 3] = 255; + break; + default: + throw new NotSupportedException(); + } + } + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + public Vector2 ToVector2() + { + const float MaxVal = 0x7FFF; + + return new Vector2( + (short)(this.PackedValue & 0xFFFF) / MaxVal, + (short)(this.PackedValue >> 0x10) / MaxVal); + } + + /// + public override bool Equals(object obj) + { + return (obj is NormalizedShort2) && this.Equals((NormalizedShort2)obj); + } + + /// + public bool Equals(NormalizedShort2 other) + { + return this.PackedValue.Equals(other.PackedValue); + } + + /// + public override int GetHashCode() + { + return this.PackedValue.GetHashCode(); + } + + /// + public override string ToString() + { + return this.PackedValue.ToString("X"); + } + + /// + /// Packs the components into a . + /// + /// The x-component + /// The y-component + /// The containing the packed values. + private static uint Pack(float x, float y) + { + const float MaxPos = 0x7FFF; + const float MinNeg = -MaxPos; + + // Clamp the value between min and max values + // Round rather than truncate. + uint word2 = (uint)((int)(float)Math.Round(x * MaxPos).Clamp(MinNeg, MaxPos) & 0xFFFF); + uint word1 = (uint)(((int)(float)Math.Round(y * MaxPos).Clamp(MinNeg, MaxPos) & 0xFFFF) << 0x10); + + return word2 | word1; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs new file mode 100644 index 000000000..0f43c54b1 --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs @@ -0,0 +1,203 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Packed pixel type containing four 16-bit signed normalized values, ranging from −1 to 1. + /// + public struct NormalizedShort4 : IPackedPixel, IEquatable + { + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new Vector4(255); + + /// + /// The half the maximum byte value. + /// + private static readonly Vector4 Half = new Vector4(127); + + /// + /// The vector value used for rounding. + /// + private static readonly Vector4 Round = new Vector4(.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public NormalizedShort4(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + /// The z-component. + /// The w-component. + public NormalizedShort4(float x, float y, float z, float w) + { + this.PackedValue = Pack(x, y, z, w); + } + + /// + public ulong PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(NormalizedShort4 left, NormalizedShort4 right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(NormalizedShort4 left, NormalizedShort4 right) + { + return !left.Equals(right); + } + + /// + public void PackFromVector4(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + public Vector4 ToVector4() + { + const float MaxVal = 0x7FFF; + + return new Vector4( + (short)((this.PackedValue >> 0x00) & 0xFFFF) / MaxVal, + (short)((this.PackedValue >> 0x10) & 0xFFFF) / MaxVal, + (short)((this.PackedValue >> 0x20) & 0xFFFF) / MaxVal, + (short)((this.PackedValue >> 0x30) & 0xFFFF) / MaxVal); + } + + /// + public void PackFromBytes(byte x, byte y, byte z, byte w) + { + Vector4 vector = new Vector4(x, y, z, w); + vector -= Round; + vector -= Half; + vector -= Round; + vector /= Half; + this.PackFromVector4(vector); + } + + /// + public void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder) + { + Vector4 vector = this.ToVector4(); + vector *= Half; + vector += Round; + vector += Half; + vector += Round; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + + switch (componentOrder) + { + case ComponentOrder.ZYX: + bytes[startIndex] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + break; + case ComponentOrder.ZYXW: + bytes[startIndex] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + break; + case ComponentOrder.XYZ: + bytes[startIndex] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); + break; + case ComponentOrder.XYZW: + bytes[startIndex] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + break; + default: + throw new NotSupportedException(); + } + } + + /// + public override bool Equals(object obj) + { + return (obj is NormalizedShort4) && this.Equals((NormalizedShort4)obj); + } + + /// + public bool Equals(NormalizedShort4 other) + { + return this.PackedValue.Equals(other.PackedValue); + } + + /// + public override int GetHashCode() + { + return this.PackedValue.GetHashCode(); + } + + /// + public override string ToString() + { + return this.PackedValue.ToString("X"); + } + + /// + /// Packs the components into a . + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + /// The containing the packed values. + private static ulong Pack(float x, float y, float z, float w) + { + const float MaxPos = 0x7FFF; + const float MinNeg = -MaxPos; + + // Clamp the value between min and max values + ulong word4 = ((ulong)(float)Math.Round(x * MaxPos).Clamp(MinNeg, MaxPos) & 0xFFFF) << 0x00; + ulong word3 = ((ulong)(float)Math.Round(y * MaxPos).Clamp(MinNeg, MaxPos) & 0xFFFF) << 0x10; + ulong word2 = ((ulong)(float)Math.Round(z * MaxPos).Clamp(MinNeg, MaxPos) & 0xFFFF) << 0x20; + ulong word1 = ((ulong)(float)Math.Round(w * MaxPos).Clamp(MinNeg, MaxPos) & 0xFFFF) << 0x30; + + return word4 | word3 | word2 | word1; + } + } +} diff --git a/src/ImageSharp/Colors/PackedPixel/PackedPixelConverterHelper.cs b/src/ImageSharp/Colors/PackedPixel/PackedPixelConverterHelper.cs new file mode 100644 index 000000000..84f89a33b --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/PackedPixelConverterHelper.cs @@ -0,0 +1,276 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Assists with the conversion of known packed pixel formats from one to another. + /// + internal static class PackedPixelConverterHelper + { + /// + /// A non operative function. Simply returns the original vector. + /// + private static readonly Func Noop = vector4 => vector4; + + /// + /// Returns the correct scaling function for the given types The compute scale function. + /// + /// The scale function. + /// The source pixel format. + /// The target pixel format. + /// The + public static Func ComputeScaleFunction(Func scaleFunc) + { + // Custom type with a custom function. + if (scaleFunc != null) + { + return scaleFunc; + } + + Type source = typeof(TColor); + Type target = typeof(TColor2); + + // Standard to offset + if (IsStandardNormalizedType(source)) + { + if (IsOffsetNormalizedType(target) || IsOffsetTwoComponentNormalizedType(target)) + { + // Expand the range then offset the center down. + return vector4 => (2F * vector4) - Vector4.One; + } + + if (IsOffsetType(target) || IsOffsetTwoComponentType(target)) + { + return v => (65534 * v) - new Vector4(32767); + } + } + + // Normalized offsets. All four components. + if (IsOffsetNormalizedType(source)) + { + return FromOffsetNormalizedType(target); + } + + // Offset. All four components. + if (IsOffsetType(source)) + { + return FromOffsetType(target); + } + + // Normalized offsets. First component pair only. + if (IsOffsetTwoComponentNormalizedType(source)) + { + return FromOffsetTwoComponentNormalizedType(target); + } + + // Offsets. First component pair only. + if (IsOffsetTwoComponentType(source)) + { + return FromOffsetTwoComponentType(target); + } + + return Noop; + } + + /// + /// Returns the correct conversion function to convert from types having vector values representing all four components + /// ranging from -1 to 1. + /// + /// The target type + /// The + private static Func FromOffsetNormalizedType(Type target) + { + if (IsStandardNormalizedType(target)) + { + // Compress the range then offset the center up. + return vector4 => (vector4 / 2F) + new Vector4(.5F); + } + + if (IsOffsetType(target) || IsOffsetTwoComponentType(target)) + { + // Multiply out the range, two component won't read the last two values. + return vector4 => (vector4 * 32767F); + } + + return Noop; + } + + /// + /// Returns the correct conversion function to convert from types having vector values representing all four components + /// ranging from -32767 to 32767. + /// + /// The target type + /// The + private static Func FromOffsetType(Type target) + { + if (IsStandardNormalizedType(target)) + { + // Compress the range then offset the center up. + return vector4 => (vector4 / 65534) + new Vector4(.5F); + } + + if (IsOffsetNormalizedType(target) || IsOffsetTwoComponentNormalizedType(target)) + { + // Compress the range. Two component won't read the last two values. + return vector4 => (vector4 / 32767); + } + + return Noop; + } + + /// + /// Returns the correct conversion function to convert from types having vector with the first component pair ranging from -1 to 1. + /// and the second component pair ranging from 0 to 1. + /// + /// The target type + /// The + private static Func FromOffsetTwoComponentNormalizedType(Type target) + { + if (IsStandardNormalizedType(target)) + { + return vector4 => + { + // Compress the range then offset the center up for first pair. + Vector4 v = (vector4 / 2F) + new Vector4(.5F); + return new Vector4(v.X, v.Y, 0, 1); + }; + } + + if (IsOffsetNormalizedType(target)) + { + // Copy the first two components and set second pair to 0 and 1 equivalent. + return vector4 => new Vector4(vector4.X, vector4.Y, -1, 1); + } + + if (IsOffsetTwoComponentType(target)) + { + // Multiply. Two component won't read the last two values. + return vector4 => (vector4 * 32767); + } + + if (IsOffsetType(target)) + { + return vector4 => + { + // Multiply the first two components and set second pair to 0 and 1 equivalent. + Vector4 v = vector4 * 32767; + return new Vector4(v.X, v.Y, -32767, 32767); + }; + } + + return Noop; + } + + /// + /// Returns the correct conversion function to convert from types having vector with the first component pair ranging from -32767 to 32767. + /// and the second component pair ranging from 0 to 1. + /// + /// The target type + /// The + private static Func FromOffsetTwoComponentType(Type target) + { + if (IsStandardNormalizedType(target)) + { + return vector4 => + { + Vector4 v = (vector4 / 65534) + new Vector4(.5F); + return new Vector4(v.X, v.Y, 0, 1); + }; + } + + if (IsOffsetType(target)) + { + // Copy the first two components and set second pair to 0 and 1 equivalent. + return vector4 => new Vector4(vector4.X, vector4.Y, -32767, 32767); + } + + if (IsOffsetNormalizedType(target)) + { + return vector4 => + { + // Divide the first two components and set second pair to 0 and 1 equivalent. + Vector4 v = vector4 / 32767; + return new Vector4(v.X, v.Y, -1, 1); + }; + } + + if (IsOffsetTwoComponentNormalizedType(target)) + { + // Divide. Two component won't read the last two values. + return vector4 => (vector4 / 32767); + } + + return Noop; + } + + /// + /// Identifies the type as having vector component values ranging from 0 to 1. + /// + /// The type to test. + /// The + private static bool IsStandardNormalizedType(Type type) + { + return type == typeof(Color) + || type == typeof(Bgr565) + || type == typeof(Bgra4444) + || type == typeof(Bgra5551) + || type == typeof(Byte4) + || type == typeof(HalfSingle) + || type == typeof(HalfVector2) + || type == typeof(HalfVector4) + || type == typeof(Rg32) + || type == typeof(Rgba1010102) + || type == typeof(Rgba64); + } + + /// + /// Identifies the type as having vector values representing the first component pair ranging from -1 to 1. + /// and the second component pair ranging from 0 to 1. + /// + /// The type to test. + /// The + private static bool IsOffsetTwoComponentNormalizedType(Type type) + { + return type == typeof(NormalizedByte2) + || type == typeof(NormalizedShort2); + } + + /// + /// Identifies the type as having vector values representing all four components ranging from -1 to 1. + /// + /// The type to test. + /// The + private static bool IsOffsetNormalizedType(Type type) + { + return type == typeof(NormalizedByte4) + || type == typeof(NormalizedShort4); + } + + /// + /// Identifies the type as having vector values representing the first component pair ranging from -32767 to 32767. + /// and the second component pair ranging from 0 to 1. + /// + /// The type to test. + /// The + private static bool IsOffsetTwoComponentType(Type type) + { + return type == typeof(Short2); + } + + /// + /// Identifies the type as having vector values representing all four components ranging from -32767 to 32767. + /// + /// The type to test. + /// The + private static bool IsOffsetType(Type type) + { + return type == typeof(Short4); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/README.md b/src/ImageSharp/Colors/PackedPixel/README.md new file mode 100644 index 000000000..61500de68 --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/README.md @@ -0,0 +1,3 @@ +Pixel formats adapted and extended from: + +https://github.com/MonoGame/MonoGame \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/Rg32.cs b/src/ImageSharp/Colors/PackedPixel/Rg32.cs new file mode 100644 index 000000000..8ca53a829 --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/Rg32.cs @@ -0,0 +1,173 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Packed pixel type containing two 16-bit unsigned normalized values ranging from 0 to 1. + /// + public struct Rg32 : IPackedPixel, IEquatable, IPackedVector + { + /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + public Rg32(float x, float y) + { + this.PackedValue = Pack(x, y); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public Rg32(Vector2 vector) + { + this.PackedValue = Pack(vector.X, vector.Y); + } + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Rg32 left, Rg32 right) + { + return left.PackedValue == right.PackedValue; + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(Rg32 left, Rg32 right) + { + return left.PackedValue != right.PackedValue; + } + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + public Vector2 ToVector2() + { + return new Vector2( + (this.PackedValue & 0xFFFF) / 65535F, + ((this.PackedValue >> 16) & 0xFFFF) / 65535F); + } + + /// + public void PackFromVector4(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y); + } + + /// + public Vector4 ToVector4() + { + return new Vector4(this.ToVector2(), 0F, 1F); + } + + /// + public void PackFromBytes(byte x, byte y, byte z, byte w) + { + this.PackFromVector4(new Vector4(x, y, z, w) / 255F); + } + + /// + public void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder) + { + Vector4 vector = this.ToVector4() * 255F; + + switch (componentOrder) + { + case ComponentOrder.ZYX: + bytes[startIndex] = (byte)vector.Z; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.X; + break; + case ComponentOrder.ZYXW: + bytes[startIndex] = (byte)vector.Z; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.X; + bytes[startIndex + 3] = (byte)vector.W; + break; + case ComponentOrder.XYZ: + bytes[startIndex] = (byte)vector.X; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.Z; + break; + case ComponentOrder.XYZW: + bytes[startIndex] = (byte)vector.X; + bytes[startIndex + 1] = (byte)vector.Y; + bytes[startIndex + 2] = (byte)vector.Z; + bytes[startIndex + 3] = (byte)vector.W; + break; + default: + throw new NotSupportedException(); + } + } + + /// + public override bool Equals(object obj) + { + return (obj is Rg32) && this.Equals((Rg32)obj); + } + + /// + public bool Equals(Rg32 other) + { + return this.PackedValue == other.PackedValue; + } + + /// + public override string ToString() + { + return this.ToVector2().ToString(); + } + + /// + public override int GetHashCode() + { + return this.PackedValue.GetHashCode(); + } + + /// + /// Packs the components into a . + /// + /// The x-component + /// The y-component + /// The containing the packed values. + private static uint Pack(float x, float y) + { + return (uint)( + ((int)Math.Round(x.Clamp(0, 1) * 65535F) & 0xFFFF) | + (((int)Math.Round(y.Clamp(0, 1) * 65535F) & 0xFFFF) << 16)); + } + } +} diff --git a/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs b/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs new file mode 100644 index 000000000..54ac6279f --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs @@ -0,0 +1,172 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Packed vector type containing unsigned normalized values ranging from 0 to 1. + /// The x, y and z components use 10 bits, and the w component uses 2 bits. + /// + public struct Rgba1010102 : IPackedPixel, IEquatable, IPackedVector + { + /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + public Rgba1010102(float x, float y, float z, float w) + { + this.PackedValue = Pack(x, y, z, w); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public Rgba1010102(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Rgba1010102 left, Rgba1010102 right) + { + return left.PackedValue == right.PackedValue; + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(Rgba1010102 left, Rgba1010102 right) + { + return left.PackedValue != right.PackedValue; + } + + /// + public Vector4 ToVector4() + { + return new Vector4( + ((this.PackedValue >> 0) & 0x03FF) / 1023F, + ((this.PackedValue >> 10) & 0x03FF) / 1023F, + ((this.PackedValue >> 20) & 0x03FF) / 1023F, + ((this.PackedValue >> 30) & 0x03) / 3F); + } + + /// + public void PackFromVector4(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + public void PackFromBytes(byte x, byte y, byte z, byte w) + { + this.PackFromVector4(new Vector4(x, y, z, w) / 255F); + } + + /// + public void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder) + { + Vector4 vector = this.ToVector4() * 255F; + + switch (componentOrder) + { + case ComponentOrder.ZYX: + bytes[startIndex] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + break; + case ComponentOrder.ZYXW: + bytes[startIndex] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + break; + case ComponentOrder.XYZ: + bytes[startIndex] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); + break; + case ComponentOrder.XYZW: + bytes[startIndex] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + break; + default: + throw new NotSupportedException(); + } + } + + /// + public override bool Equals(object obj) + { + return (obj is Rgba1010102) && this.Equals((Rgba1010102)obj); + } + + /// + public bool Equals(Rgba1010102 other) + { + return this.PackedValue == other.PackedValue; + } + + /// + public override string ToString() + { + return this.ToVector4().ToString(); + } + + /// + public override int GetHashCode() + { + return this.PackedValue.GetHashCode(); + } + + /// + /// Packs the components into a . + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + /// The containing the packed values. + private static uint Pack(float x, float y, float z, float w) + { + return (uint)( + (((int)Math.Round(x.Clamp(0, 1) * 1023F) & 0x03FF) << 0) | + (((int)Math.Round(y.Clamp(0, 1) * 1023F) & 0x03FF) << 10) | + (((int)Math.Round(z.Clamp(0, 1) * 1023F) & 0x03FF) << 20) | + (((int)Math.Round(w.Clamp(0, 1) * 3F) & 0x03) << 30)); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/Rgba64.cs b/src/ImageSharp/Colors/PackedPixel/Rgba64.cs new file mode 100644 index 000000000..3744fb1ec --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/Rgba64.cs @@ -0,0 +1,170 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Packed pixel type containing four 16-bit unsigned normalized values ranging from 0 to 1. + /// + public struct Rgba64 : IPackedPixel, IEquatable, IPackedVector + { + /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + public Rgba64(float x, float y, float z, float w) + { + this.PackedValue = Pack(x, y, z, w); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the components values. + public Rgba64(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + public ulong PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Rgba64 left, Rgba64 right) + { + return left.PackedValue == right.PackedValue; + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(Rgba64 left, Rgba64 right) + { + return left.PackedValue != right.PackedValue; + } + + /// + public Vector4 ToVector4() + { + return new Vector4( + (this.PackedValue & 0xFFFF) / 65535F, + ((this.PackedValue >> 16) & 0xFFFF) / 65535F, + ((this.PackedValue >> 32) & 0xFFFF) / 65535F, + ((this.PackedValue >> 48) & 0xFFFF) / 65535F); + } + + /// + public void PackFromVector4(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + public void PackFromBytes(byte x, byte y, byte z, byte w) + { + this.PackFromVector4(new Vector4(x, y, z, w) / 255F); + } + + /// + public void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder) + { + Vector4 vector = this.ToVector4() * 255F; + + switch (componentOrder) + { + case ComponentOrder.ZYX: + bytes[startIndex] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + break; + case ComponentOrder.ZYXW: + bytes[startIndex] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + break; + case ComponentOrder.XYZ: + bytes[startIndex] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); + break; + case ComponentOrder.XYZW: + bytes[startIndex] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + break; + default: + throw new NotSupportedException(); + } + } + + /// + public override bool Equals(object obj) + { + return (obj is Rgba64) && this.Equals((Rgba64)obj); + } + + /// + public bool Equals(Rgba64 other) + { + return this.PackedValue == other.PackedValue; + } + + /// + public override string ToString() + { + return this.ToVector4().ToString(); + } + + /// + public override int GetHashCode() + { + return this.PackedValue.GetHashCode(); + } + + /// + /// Packs the components into a . + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + /// The containing the packed values. + private static ulong Pack(float x, float y, float z, float w) + { + return (ulong)Math.Round(x.Clamp(0, 1) * 65535F) | + ((ulong)Math.Round(y.Clamp(0, 1) * 65535F) << 16) | + ((ulong)Math.Round(z.Clamp(0, 1) * 65535F) << 32) | + ((ulong)Math.Round(w.Clamp(0, 1) * 65535F) << 48); + } + } +} diff --git a/src/ImageSharp/Colors/PackedPixel/Short2.cs b/src/ImageSharp/Colors/PackedPixel/Short2.cs new file mode 100644 index 000000000..310c44ec8 --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/Short2.cs @@ -0,0 +1,200 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Packed pixel type containing two 16-bit signed integer values. + /// + public struct Short2 : IPackedPixel, IEquatable + { + /// + /// The maximum byte value. + /// + private static readonly Vector2 MaxBytes = new Vector2(255); + + /// + /// The half the maximum byte value. + /// + private static readonly Vector2 Half = new Vector2(127); + + /// + /// The vector value used for rounding. + /// + private static readonly Vector2 Round = new Vector2(.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public Short2(Vector2 vector) + { + this.PackedValue = Pack(vector.X, vector.Y); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + public Short2(float x, float y) + { + this.PackedValue = Pack(x, y); + } + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Short2 left, Short2 right) + { + return left.PackedValue == right.PackedValue; + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(Short2 left, Short2 right) + { + return left.PackedValue != right.PackedValue; + } + + /// + public void PackFromVector4(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y); + } + + /// + public Vector4 ToVector4() + { + return new Vector4((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10), 0, 1); + } + + /// + public void PackFromBytes(byte x, byte y, byte z, byte w) + { + Vector2 vector = new Vector2(x, y) / 255; + vector *= 65534; + vector -= new Vector2(32767); + this.PackedValue = Pack(vector.X, vector.Y); + } + + /// + public void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder) + { + Vector2 vector = this.ToVector2(); + vector /= 65534; + vector *= 255; + vector += Half; + vector += Round; + vector = Vector2.Clamp(vector, Vector2.Zero, MaxBytes); + + switch (componentOrder) + { + case ComponentOrder.ZYX: + bytes[startIndex] = 0; + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + break; + case ComponentOrder.ZYXW: + bytes[startIndex] = 0; + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 3] = 255; + break; + case ComponentOrder.XYZ: + bytes[startIndex] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = 0; + break; + case ComponentOrder.XYZW: + bytes[startIndex] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = 0; + bytes[startIndex + 3] = 255; + break; + default: + throw new NotSupportedException(); + } + } + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + public Vector2 ToVector2() + { + return new Vector2((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10)); + } + + /// + public override bool Equals(object obj) + { + return (obj is Short2) && this.Equals((Short2)obj); + } + + /// + public bool Equals(Short2 other) + { + return this == other; + } + + /// + public override int GetHashCode() + { + return this.PackedValue.GetHashCode(); + } + + /// + public override string ToString() + { + return this.PackedValue.ToString("x8"); + } + + /// + /// Packs the components into a . + /// + /// The x-component + /// The y-component + /// The containing the packed values. + private static uint Pack(float x, float y) + { + // Largest two byte positive number 0xFFFF >> 1; + const float MaxPos = 0x7FFF; + const float MinNeg = ~(int)MaxPos; + + // Clamp the value between min and max values + uint word2 = (uint)Math.Round(x.Clamp(MinNeg, MaxPos)) & 0xFFFF; + uint word1 = ((uint)Math.Round(y.Clamp(MinNeg, MaxPos)) & 0xFFFF) << 0x10; + + return word2 | word1; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/Short4.cs b/src/ImageSharp/Colors/PackedPixel/Short4.cs new file mode 100644 index 000000000..190c579ce --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/Short4.cs @@ -0,0 +1,216 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Packed pixel type containing four 16-bit signed integer values. + /// + public struct Short4 : IPackedPixel, IEquatable + { + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new Vector4(255); + + /// + /// The half the maximum byte value. + /// + private static readonly Vector4 Half = new Vector4(127); + + /// + /// The vector value used for rounding. + /// + private static readonly Vector4 Round = new Vector4(.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// A vector containing the initial values for the components. + public Short4(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + /// The z-component. + /// The w-component. + public Short4(float x, float y, float z, float w) + { + this.PackedValue = Pack(x, y, z, w); + } + + /// + public ulong PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator ==(Short4 left, Short4 right) + { + return left.PackedValue == right.PackedValue; + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(Short4 left, Short4 right) + { + return left.PackedValue != right.PackedValue; + } + + /// + public void PackFromVector4(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + public Vector4 ToVector4() + { + return new Vector4( + (short)(this.PackedValue & 0xFFFF), + (short)((this.PackedValue >> 0x10) & 0xFFFF), + (short)((this.PackedValue >> 0x20) & 0xFFFF), + (short)((this.PackedValue >> 0x30) & 0xFFFF)); + } + + /// + public void PackFromBytes(byte x, byte y, byte z, byte w) + { + Vector4 vector = new Vector4(x, y, z, w) / 255; + vector *= 65534; + vector -= new Vector4(32767); + this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + public void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder) + { + Vector4 vector = this.ToVector4(); + vector /= 65534; + vector *= 255; + vector += Half; + vector += Round; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + + switch (componentOrder) + { + case ComponentOrder.ZYX: + bytes[startIndex] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + break; + case ComponentOrder.ZYXW: + bytes[startIndex] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + break; + case ComponentOrder.XYZ: + bytes[startIndex] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); + break; + case ComponentOrder.XYZW: + bytes[startIndex] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + break; + default: + throw new NotSupportedException(); + } + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object with which to make the comparison. + /// true if the current instance is equal to the specified object; false otherwise. + public override bool Equals(object obj) + { + return (obj is Short4) && this == (Short4)obj; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object with which to make the comparison. + /// true if the current instance is equal to the specified object; false otherwise. + public bool Equals(Short4 other) + { + return this == other; + } + + /// + /// Gets the hash code for the current instance. + /// + /// Hash code for the instance. + public override int GetHashCode() + { + return this.PackedValue.GetHashCode(); + } + + /// + /// Returns a string representation of the current instance. + /// + /// String that represents the object. + public override string ToString() + { + return this.PackedValue.ToString("x16"); + } + + /// + /// Packs the components into a . + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + /// The containing the packed values. + private static ulong Pack(float x, float y, float z, float w) + { + // Largest two byte positive number 0xFFFF >> 1; + const float MaxPos = 0x7FFF; + + // Two's complement + const float MinNeg = ~(int)MaxPos; + + // Clamp the value between min and max values + ulong word4 = ((ulong)Math.Round(x.Clamp(MinNeg, MaxPos)) & 0xFFFF) << 0x00; + ulong word3 = ((ulong)Math.Round(y.Clamp(MinNeg, MaxPos)) & 0xFFFF) << 0x10; + ulong word2 = ((ulong)Math.Round(z.Clamp(MinNeg, MaxPos)) & 0xFFFF) << 0x20; + ulong word1 = ((ulong)Math.Round(w.Clamp(MinNeg, MaxPos)) & 0xFFFF) << 0x30; + + return word4 | word3 | word2 | word1; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Image/Image.cs b/src/ImageSharp/Image/Image.cs index 0bcf7c6eb..2884eddbd 100644 --- a/src/ImageSharp/Image/Image.cs +++ b/src/ImageSharp/Image/Image.cs @@ -10,7 +10,9 @@ namespace ImageSharp using System.Diagnostics; using System.IO; using System.Linq; + using System.Numerics; using System.Text; + using System.Threading.Tasks; using Formats; @@ -245,7 +247,7 @@ namespace ImageSharp /// /// Returns a Base64 encoded string from the given image. /// - /// data:image/gif;base64,R0lGODlhAQABAIABAEdJRgAAACwAAAAAAQABAAACAkQBAA== + /// /// The public string ToBase64String() { @@ -257,6 +259,60 @@ namespace ImageSharp } } + /// + /// Returns a copy of the image in the given pixel format. + /// + /// A function that allows for the correction of vector scaling between unknown color formats. + /// The pixel format. + /// The packed format. uint, long, float. + /// The + public Image To(Func scaleFunc = null) + where TColor2 : struct, IPackedPixel + where TPacked2 : struct + { + scaleFunc = PackedPixelConverterHelper.ComputeScaleFunction(scaleFunc); + + Image target = new Image(this.Width, this.Height) + { + Quality = this.Quality, + FrameDelay = this.FrameDelay, + HorizontalResolution = this.HorizontalResolution, + VerticalResolution = this.VerticalResolution, + CurrentImageFormat = this.CurrentImageFormat, + RepeatCount = this.RepeatCount + }; + + using (PixelAccessor pixels = this.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + 0, + target.Height, + Bootstrapper.Instance.ParallelOptions, + y => + { + for (int x = 0; x < target.Width; x++) + { + TColor2 color = default(TColor2); + color.PackFromVector4(scaleFunc(pixels[x, y].ToVector4())); + targetPixels[x, y] = color; + } + }); + } + + if (this.ExifProfile != null) + { + target.ExifProfile = new ExifProfile(this.ExifProfile); + } + + foreach (ImageFrame frame in this.Frames) + { + target.Frames.Add(frame.To()); + } + + return target; + } + /// /// Copies the properties from the other . /// @@ -278,6 +334,10 @@ namespace ImageSharp } } + /// + /// Creates a new from this instance + /// + /// The internal virtual ImageFrame ToFrame() { return new ImageFrame(this); diff --git a/src/ImageSharp/Image/ImageFrame.cs b/src/ImageSharp/Image/ImageFrame.cs index d63618766..c0e5b3f16 100644 --- a/src/ImageSharp/Image/ImageFrame.cs +++ b/src/ImageSharp/Image/ImageFrame.cs @@ -5,6 +5,10 @@ namespace ImageSharp { + using System; + using System.Numerics; + using System.Threading.Tasks; + /// /// Represents a single frame in a animation. /// @@ -24,9 +28,7 @@ namespace ImageSharp /// /// Initializes a new instance of the class. /// - /// - /// The image to create the frame from. - /// + /// The image to create the frame from. public ImageFrame(ImageBase image) : base(image) { @@ -38,9 +40,55 @@ namespace ImageSharp return $"ImageFrame: {this.Width}x{this.Height}"; } + /// + /// Returns a copy of the image frame in the given pixel format. + /// + /// A function that allows for the correction of vector scaling between unknown color formats. + /// The pixel format. + /// The packed format. uint, long, float. + /// The + public ImageFrame To(Func scaleFunc = null) + where TColor2 : struct, IPackedPixel + where TPacked2 : struct + { + scaleFunc = PackedPixelConverterHelper.ComputeScaleFunction(scaleFunc); + + ImageFrame target = new ImageFrame + { + Quality = this.Quality, + FrameDelay = this.FrameDelay + }; + + target.InitPixels(this.Width, this.Height); + + using (PixelAccessor pixels = this.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + 0, + target.Height, + Bootstrapper.Instance.ParallelOptions, + y => + { + for (int x = 0; x < target.Width; x++) + { + TColor2 color = default(TColor2); + color.PackFromVector4(scaleFunc(pixels[x, y].ToVector4())); + targetPixels[x, y] = color; + } + }); + } + + return target; + } + + /// + /// Clones the current instance. + /// + /// The internal virtual ImageFrame Clone() { return new ImageFrame(this); } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/project.json b/tests/ImageSharp.Benchmarks/project.json index 1e8b8ef15..4dd5c77f3 100644 --- a/tests/ImageSharp.Benchmarks/project.json +++ b/tests/ImageSharp.Benchmarks/project.json @@ -14,7 +14,7 @@ "allowUnsafe": true }, "dependencies": { - "BenchmarkDotNet.Diagnostics.Windows": "0.9.9.116", + "BenchmarkDotNet.Diagnostics.Windows": "0.10.0", "ImageSharp": "1.0.0-*" }, "commands": { diff --git a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs new file mode 100644 index 000000000..785125ca1 --- /dev/null +++ b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs @@ -0,0 +1,841 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Colors +{ + using System; + using System.Numerics; + + using Xunit; + + /// + /// The packed pixel tests. + /// + public class PackedPixelTests + { + [Fact] + public void Alpha8() + { + // Test the limits. + Assert.Equal(0x0, new Alpha8(0F).PackedValue); + Assert.Equal(0xFF, new Alpha8(1F).PackedValue); + + // Test clamping. + Assert.Equal(0x0, new Alpha8(-1234F).PackedValue); + Assert.Equal(0xFF, new Alpha8(1234F).PackedValue); + + // Test ordering + Assert.Equal(124, new Alpha8(124F / 0xFF).PackedValue); + Assert.Equal(26, new Alpha8(0.1F).PackedValue); + + // Test ordering + Vector4 vector = new Alpha8(.5F).ToVector4(); + + Assert.Equal(vector.X, 0); + Assert.Equal(vector.Y, 0); + Assert.Equal(vector.Z, 0); + Assert.Equal(vector.W, .5F, 2); + + byte[] rgb = new byte[3]; + byte[] rgba = new byte[4]; + byte[] bgr = new byte[3]; + byte[] bgra = new byte[4]; + + new Alpha8(.5F).ToBytes(rgb, 0, ComponentOrder.XYZ); + Assert.Equal(rgb, new byte[] { 0, 0, 0 }); + + new Alpha8(.5F).ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 0, 0, 0, 128 }); + + new Alpha8(.5F).ToBytes(rgb, 0, ComponentOrder.ZYX); + Assert.Equal(bgr, new byte[] { 0, 0, 0 }); + + new Alpha8(.5F).ToBytes(rgb, 0, ComponentOrder.ZYXW); + Assert.Equal(bgra, new byte[] { 0, 0, 0, 128 }); + } + + [Fact] + public void Bgr565() + { + // Test the limits. + Assert.Equal(0x0, new Bgr565(Vector3.Zero).PackedValue); + Assert.Equal(0xFFFF, new Bgr565(Vector3.One).PackedValue); + + // Test ToVector3. + Assert.True(Equal(Vector3.One, new Bgr565(Vector3.One).ToVector3())); + Assert.True(Equal(Vector3.Zero, new Bgr565(Vector3.Zero).ToVector3())); + Assert.True(Equal(Vector3.UnitX, new Bgr565(Vector3.UnitX).ToVector3())); + Assert.True(Equal(Vector3.UnitY, new Bgr565(Vector3.UnitY).ToVector3())); + Assert.True(Equal(Vector3.UnitZ, new Bgr565(Vector3.UnitZ).ToVector3())); + + // Test clamping. + Assert.True(Equal(Vector3.Zero, new Bgr565(Vector3.One * -1234F).ToVector3())); + Assert.True(Equal(Vector3.One, new Bgr565(Vector3.One * 1234F).ToVector3())); + + // Make sure the swizzle is correct. + Assert.Equal(0xF800, new Bgr565(Vector3.UnitX).PackedValue); + Assert.Equal(0x07E0, new Bgr565(Vector3.UnitY).PackedValue); + Assert.Equal(0x001F, new Bgr565(Vector3.UnitZ).PackedValue); + + float x = 0.1F; + float y = -0.3F; + float z = 0.5F; + Assert.Equal(6160, new Bgr565(x, y, z).PackedValue); + + + // Test ordering + byte[] rgb = new byte[3]; + byte[] rgba = new byte[4]; + byte[] bgr = new byte[3]; + byte[] bgra = new byte[4]; + + new Bgr565(x, y, z).ToBytes(rgb, 0, ComponentOrder.XYZ); + Assert.Equal(rgb, new byte[] { 25, 0, 132 }); + + new Bgr565(x, y, z).ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 25, 0, 132, 255 }); + + new Bgr565(x, y, z).ToBytes(bgr, 0, ComponentOrder.ZYX); + Assert.Equal(bgr, new byte[] { 132, 0, 25 }); + + new Bgr565(x, y, z).ToBytes(bgra, 0, ComponentOrder.ZYXW); + Assert.Equal(bgra, new byte[] { 132, 0, 25, 255 }); + } + + [Fact] + public void Bgra4444() + { + // Test the limits. + Assert.Equal(0x0, new Bgra4444(Vector4.Zero).PackedValue); + Assert.Equal(0xFFFF, new Bgra4444(Vector4.One).PackedValue); + + // Test ToVector4. + Assert.True(Equal(Vector4.One, new Bgra4444(Vector4.One).ToVector4())); + Assert.True(Equal(Vector4.Zero, new Bgra4444(Vector4.Zero).ToVector4())); + Assert.True(Equal(Vector4.UnitX, new Bgra4444(Vector4.UnitX).ToVector4())); + Assert.True(Equal(Vector4.UnitY, new Bgra4444(Vector4.UnitY).ToVector4())); + Assert.True(Equal(Vector4.UnitZ, new Bgra4444(Vector4.UnitZ).ToVector4())); + Assert.True(Equal(Vector4.UnitW, new Bgra4444(Vector4.UnitW).ToVector4())); + + // Test clamping. + Assert.True(Equal(Vector4.Zero, new Bgra4444(Vector4.One * -1234.0f).ToVector4())); + Assert.True(Equal(Vector4.One, new Bgra4444(Vector4.One * 1234.0f).ToVector4())); + + // Make sure the swizzle is correct. + Assert.Equal(0x0F00, new Bgra4444(Vector4.UnitX).PackedValue); + Assert.Equal(0x00F0, new Bgra4444(Vector4.UnitY).PackedValue); + Assert.Equal(0x000F, new Bgra4444(Vector4.UnitZ).PackedValue); + Assert.Equal(0xF000, new Bgra4444(Vector4.UnitW).PackedValue); + + float x = 0.1f; + float y = -0.3f; + float z = 0.5f; + float w = -0.7f; + Assert.Equal(520, new Bgra4444(x, y, z, w).PackedValue); + + // Test ordering + byte[] rgb = new byte[3]; + byte[] rgba = new byte[4]; + byte[] bgr = new byte[3]; + byte[] bgra = new byte[4]; + + new Bgra4444(x, y, z, w).ToBytes(rgb, 0, ComponentOrder.XYZ); + Assert.Equal(rgb, new byte[] { 34, 0, 136 }); + + new Bgra4444(x, y, z, w).ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 34, 0, 136, 0 }); + + new Bgra4444(x, y, z, w).ToBytes(bgr, 0, ComponentOrder.ZYX); + Assert.Equal(bgr, new byte[] { 136, 0, 34 }); + + new Bgra4444(x, y, z, w).ToBytes(bgra, 0, ComponentOrder.ZYXW); + Assert.Equal(bgra, new byte[] { 136, 0, 34, 0 }); + } + + [Fact] + public void Bgra5551() + { + // Test the limits. + Assert.Equal(0x0, new Bgra5551(Vector4.Zero).PackedValue); + Assert.Equal(0xFFFF, new Bgra5551(Vector4.One).PackedValue); + + // Test ToVector4 + Assert.True(Equal(Vector4.Zero, new Bgra5551(Vector4.Zero).ToVector4())); + Assert.True(Equal(Vector4.One, new Bgra5551(Vector4.One).ToVector4())); + + // Test clamping. + Assert.Equal(Vector4.Zero, new Bgra5551(Vector4.One * -1234.0f).ToVector4()); + Assert.Equal(Vector4.One, new Bgra5551(Vector4.One * 1234.0f).ToVector4()); + + // Test Ordering + float x = 0x1a; + float y = 0x16; + float z = 0xd; + float w = 0x1; + Assert.Equal(0xeacd, new Bgra5551(x / 0x1f, y / 0x1f, z / 0x1f, w).PackedValue); + x = 0.1f; + y = -0.3f; + z = 0.5f; + w = -0.7f; + Assert.Equal(3088, new Bgra5551(x, y, z, w).PackedValue); + + // Test ordering + byte[] rgb = new byte[3]; + byte[] rgba = new byte[4]; + byte[] bgr = new byte[3]; + byte[] bgra = new byte[4]; + + new Bgra5551(x, y, z, w).ToBytes(rgb, 0, ComponentOrder.XYZ); + Assert.Equal(rgb, new byte[] { 24, 0, 131 }); + + new Bgra5551(x, y, z, w).ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 24, 0, 131, 0 }); + + new Bgra5551(x, y, z, w).ToBytes(bgr, 0, ComponentOrder.ZYX); + Assert.Equal(bgr, new byte[] { 131, 0, 24 }); + + new Bgra5551(x, y, z, w).ToBytes(bgra, 0, ComponentOrder.ZYXW); + Assert.Equal(bgra, new byte[] { 131, 0, 24, 0 }); + } + + [Fact] + public void Byte4() + { + // Test the limits. + Assert.Equal((uint)0x0, new Byte4(Vector4.Zero).PackedValue); + Assert.Equal(0xFFFFFFFF, new Byte4(Vector4.One * 255).PackedValue); + + // Test ToVector4. + Assert.True(Equal(Vector4.One * 255, new Byte4(Vector4.One * 255).ToVector4())); + Assert.True(Equal(Vector4.Zero, new Byte4(Vector4.Zero).ToVector4())); + Assert.True(Equal(Vector4.UnitX * 255, new Byte4(Vector4.UnitX * 255).ToVector4())); + Assert.True(Equal(Vector4.UnitY * 255, new Byte4(Vector4.UnitY * 255).ToVector4())); + Assert.True(Equal(Vector4.UnitZ * 255, new Byte4(Vector4.UnitZ * 255).ToVector4())); + Assert.True(Equal(Vector4.UnitW * 255, new Byte4(Vector4.UnitW * 255).ToVector4())); + + // Test clamping. + Assert.True(Equal(Vector4.Zero, new Byte4(Vector4.One * -1234.0f).ToVector4())); + Assert.True(Equal(Vector4.One * 255, new Byte4(Vector4.One * 1234.0f).ToVector4())); + + // Test ordering + float x = 0x2d; + float y = 0x36; + float z = 0x7b; + float w = 0x1a; + Assert.Equal((uint)0x1a7b362d, new Byte4(x, y, z, w).PackedValue); + + x = 127.5f; + y = -12.3f; + z = 0.5f; + w = -0.7f; + Assert.Equal((uint)128, new Byte4(x, y, z, w).PackedValue); + + // Test ordering + byte[] rgb = new byte[3]; + byte[] rgba = new byte[4]; + byte[] bgr = new byte[3]; + byte[] bgra = new byte[4]; + + new Byte4(x, y, z, w).ToBytes(rgb, 0, ComponentOrder.XYZ); + Assert.Equal(rgb, new byte[] { 128, 0, 0 }); + + new Byte4(x, y, z, w).ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 128, 0, 0, 0 }); + + new Byte4(x, y, z, w).ToBytes(bgr, 0, ComponentOrder.ZYX); + Assert.Equal(bgr, new byte[] { 0, 0, 128 }); + + new Byte4(x, y, z, w).ToBytes(bgra, 0, ComponentOrder.ZYXW); + Assert.Equal(bgra, new byte[] { 0, 0, 128, 0 }); + } + + [Fact] + public void HalfSingle() + { + // Test limits + Assert.Equal(15360, new HalfSingle(1F).PackedValue); + Assert.Equal(0, new HalfSingle(0F).PackedValue); + Assert.Equal(48128, new HalfSingle(-1F).PackedValue); + + // Test values + Assert.Equal(11878, new HalfSingle(0.1F).PackedValue); + Assert.Equal(46285, new HalfSingle(-0.3F).PackedValue); + + // Test ordering + float x = .5F; + Assert.True(Equal(new Vector4(x, 0, 0, 1), new HalfSingle(x).ToVector4())); + + byte[] rgb = new byte[3]; + byte[] rgba = new byte[4]; + byte[] bgr = new byte[3]; + byte[] bgra = new byte[4]; + + new HalfSingle(x).ToBytes(rgb, 0, ComponentOrder.XYZ); + Assert.Equal(rgb, new byte[] { 128, 0, 0 }); + + new HalfSingle(x).ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 128, 0, 0, 255 }); + + new HalfSingle(x).ToBytes(bgr, 0, ComponentOrder.ZYX); + Assert.Equal(bgr, new byte[] { 0, 0, 128 }); + + new HalfSingle(x).ToBytes(bgra, 0, ComponentOrder.ZYXW); + Assert.Equal(bgra, new byte[] { 0, 0, 128, 255 }); + } + + [Fact] + public void HalfVector2() + { + // Test PackedValue + Assert.Equal(0u, new HalfVector2(Vector2.Zero).PackedValue); + Assert.Equal(1006648320u, new HalfVector2(Vector2.One).PackedValue); + Assert.Equal(3033345638u, new HalfVector2(0.1f, -0.3f).PackedValue); + + // Test ToVector2 + Assert.True(Equal(Vector2.Zero, new HalfVector2(Vector2.Zero).ToVector2())); + Assert.True(Equal(Vector2.One, new HalfVector2(Vector2.One).ToVector2())); + Assert.True(Equal(Vector2.UnitX, new HalfVector2(Vector2.UnitX).ToVector2())); + Assert.True(Equal(Vector2.UnitY, new HalfVector2(Vector2.UnitY).ToVector2())); + + // Test ordering + float x = .5F; + float y = .25F; + Assert.True(Equal(new Vector4(x, y, 0, 1), new HalfVector2(x, y).ToVector4())); + + byte[] rgb = new byte[3]; + byte[] rgba = new byte[4]; + byte[] bgr = new byte[3]; + byte[] bgra = new byte[4]; + + new HalfVector2(x, y).ToBytes(rgb, 0, ComponentOrder.XYZ); + Assert.Equal(rgb, new byte[] { 128, 64, 0 }); + + new HalfVector2(x, y).ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 128, 64, 0, 255 }); + + new HalfVector2(x, y).ToBytes(bgr, 0, ComponentOrder.ZYX); + Assert.Equal(bgr, new byte[] { 0, 64, 128 }); + + new HalfVector2(x, y).ToBytes(bgra, 0, ComponentOrder.ZYXW); + Assert.Equal(bgra, new byte[] { 0, 64, 128, 255 }); + } + + [Fact] + public void HalfVector4() + { + // Test PackedValue + Assert.Equal(0uL, new HalfVector4(Vector4.Zero).PackedValue); + Assert.Equal(4323521613979991040uL, new HalfVector4(Vector4.One).PackedValue); + Assert.Equal(13547034390470638592uL, new HalfVector4(-Vector4.One).PackedValue); + Assert.Equal(15360uL, new HalfVector4(Vector4.UnitX).PackedValue); + Assert.Equal(1006632960uL, new HalfVector4(Vector4.UnitY).PackedValue); + Assert.Equal(65970697666560uL, new HalfVector4(Vector4.UnitZ).PackedValue); + Assert.Equal(4323455642275676160uL, new HalfVector4(Vector4.UnitW).PackedValue); + Assert.Equal(4035285078724390502uL, new HalfVector4(0.1f, 0.3f, 0.4f, 0.5f).PackedValue); + + // Test ToVector4 + Assert.True(Equal(Vector4.Zero, new HalfVector4(Vector4.Zero).ToVector4())); + Assert.True(Equal(Vector4.One, new HalfVector4(Vector4.One).ToVector4())); + Assert.True(Equal(-Vector4.One, new HalfVector4(-Vector4.One).ToVector4())); + Assert.True(Equal(Vector4.UnitX, new HalfVector4(Vector4.UnitX).ToVector4())); + Assert.True(Equal(Vector4.UnitY, new HalfVector4(Vector4.UnitY).ToVector4())); + Assert.True(Equal(Vector4.UnitZ, new HalfVector4(Vector4.UnitZ).ToVector4())); + Assert.True(Equal(Vector4.UnitW, new HalfVector4(Vector4.UnitW).ToVector4())); + + // Test ordering + float x = .25F; + float y = .5F; + float z = .75F; + float w = 1F; + + byte[] rgb = new byte[3]; + byte[] rgba = new byte[4]; + byte[] bgr = new byte[3]; + byte[] bgra = new byte[4]; + + new HalfVector4(x, y, z, w).ToBytes(rgb, 0, ComponentOrder.XYZ); + Assert.Equal(rgb, new byte[] { 64, 128, 191 }); + + new HalfVector4(x, y, z, w).ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 64, 128, 191, 255 }); + + new HalfVector4(x, y, z, w).ToBytes(bgr, 0, ComponentOrder.ZYX); + Assert.Equal(bgr, new byte[] { 191, 128, 64 }); + + new HalfVector4(x, y, z, w).ToBytes(bgra, 0, ComponentOrder.ZYXW); + Assert.Equal(bgra, new byte[] { 191, 128, 64, 255 }); + } + + [Fact] + public void NormalizedByte2() + { + // Test PackedValue + Assert.Equal(0x0, new NormalizedByte2(Vector2.Zero).PackedValue); + Assert.Equal(0x7F7F, new NormalizedByte2(Vector2.One).PackedValue); + Assert.Equal(0x8181, new NormalizedByte2(-Vector2.One).PackedValue); + + // Test ToVector2 + Assert.True(Equal(Vector2.One, new NormalizedByte2(Vector2.One).ToVector2())); + Assert.True(Equal(Vector2.Zero, new NormalizedByte2(Vector2.Zero).ToVector2())); + Assert.True(Equal(-Vector2.One, new NormalizedByte2(-Vector2.One).ToVector2())); + Assert.True(Equal(Vector2.One, new NormalizedByte2(Vector2.One * 1234.0f).ToVector2())); + Assert.True(Equal(-Vector2.One, new NormalizedByte2(Vector2.One * -1234.0f).ToVector2())); + + // Test ToVector4 + Assert.True(Equal(new Vector4(1, 1, 0, 1), new NormalizedByte2(Vector2.One).ToVector4())); + Assert.True(Equal(new Vector4(0, 0, 0, 1), new NormalizedByte2(Vector2.Zero).ToVector4())); + + // Test Ordering + float x = 0.1f; + float y = -0.3f; + Assert.Equal(0xda0d, new NormalizedByte2(x, y).PackedValue); + NormalizedByte2 n = new NormalizedByte2(); + n.PackFromBytes(141, 90, 0, 0); + Assert.Equal(0xda0d, n.PackedValue); + + byte[] rgb = new byte[3]; + byte[] rgba = new byte[4]; + byte[] bgr = new byte[3]; + byte[] bgra = new byte[4]; + + new NormalizedByte2(x, y).ToBytes(rgb, 0, ComponentOrder.XYZ); + Assert.Equal(rgb, new byte[] { 141, 90, 0 }); + + new NormalizedByte2(x, y).ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 141, 90, 0, 255 }); + + new NormalizedByte2(x, y).ToBytes(bgr, 0, ComponentOrder.ZYX); + Assert.Equal(bgr, new byte[] { 0, 90, 141 }); + + new NormalizedByte2(x, y).ToBytes(bgra, 0, ComponentOrder.ZYXW); + Assert.Equal(bgra, new byte[] { 0, 90, 141, 255 }); + } + + [Fact] + public void NormalizedByte4() + { + // Test PackedValue + Assert.Equal((uint)0x0, new NormalizedByte4(Vector4.Zero).PackedValue); + Assert.Equal((uint)0x7F7F7F7F, new NormalizedByte4(Vector4.One).PackedValue); + Assert.Equal(0x81818181, new NormalizedByte4(-Vector4.One).PackedValue); + + // Test ToVector4 + Assert.True(Equal(Vector4.One, new NormalizedByte4(Vector4.One).ToVector4())); + Assert.True(Equal(Vector4.Zero, new NormalizedByte4(Vector4.Zero).ToVector4())); + Assert.True(Equal(-Vector4.One, new NormalizedByte4(-Vector4.One).ToVector4())); + Assert.True(Equal(Vector4.One, new NormalizedByte4(Vector4.One * 1234.0f).ToVector4())); + Assert.True(Equal(-Vector4.One, new NormalizedByte4(Vector4.One * -1234.0f).ToVector4())); + + // Test Ordering + float x = 0.1f; + float y = -0.3f; + float z = 0.5f; + float w = -0.7f; + Assert.Equal(0xA740DA0D, new NormalizedByte4(x, y, z, w).PackedValue); + NormalizedByte4 n = new NormalizedByte4(); + n.PackFromBytes(141, 90, 192, 39); + Assert.Equal(0xA740DA0D, n.PackedValue); + + Assert.Equal((uint)958796544, new NormalizedByte4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); + + byte[] rgb = new byte[3]; + byte[] rgba = new byte[4]; + byte[] bgr = new byte[3]; + byte[] bgra = new byte[4]; + + new NormalizedByte4(x, y, z, w).ToBytes(rgb, 0, ComponentOrder.XYZ); + Assert.Equal(rgb, new byte[] { 141, 90, 192 }); + + new NormalizedByte4(x, y, z, w).ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 141, 90, 192, 39 }); + + new NormalizedByte4(x, y, z, w).ToBytes(bgr, 0, ComponentOrder.ZYX); + Assert.Equal(bgr, new byte[] { 192, 90, 141 }); + + new NormalizedByte4(x, y, z, w).ToBytes(bgra, 0, ComponentOrder.ZYXW); + Assert.Equal(bgra, new byte[] { 192, 90, 141, 39 }); + + // http://community.monogame.net/t/normalizedbyte4-texture2d-gives-different-results-from-xna/8012/8 + NormalizedByte4 r = new NormalizedByte4(); + r.PackFromBytes(9, 115, 202, 127); + r.ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 9, 115, 202, 127 }); + + r.PackedValue = 0x7FCA7309; + r.ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 9, 115, 202, 127 }); + } + + [Fact] + public void NormalizedShort2() + { + Assert.Equal((uint)0x0, new NormalizedShort2(Vector2.Zero).PackedValue); + Assert.Equal((uint)0x7FFF7FFF, new NormalizedShort2(Vector2.One).PackedValue); + Assert.Equal(0x80018001, new NormalizedShort2(-Vector2.One).PackedValue); + + Assert.True(Equal(Vector2.One, new NormalizedShort2(Vector2.One).ToVector2())); + Assert.True(Equal(Vector2.Zero, new NormalizedShort2(Vector2.Zero).ToVector2())); + Assert.True(Equal(-Vector2.One, new NormalizedShort2(-Vector2.One).ToVector2())); + Assert.True(Equal(Vector2.One, new NormalizedShort2(Vector2.One * 1234.0f).ToVector2())); + Assert.True(Equal(-Vector2.One, new NormalizedShort2(Vector2.One * -1234.0f).ToVector2())); + + Assert.True(Equal(new Vector4(1, 1, 0, 1), (new NormalizedShort2(Vector2.One)).ToVector4())); + Assert.True(Equal(new Vector4(0, 0, 0, 1), (new NormalizedShort2(Vector2.Zero)).ToVector4())); + + // Test Ordering + float x = 0.35f; + float y = -0.2f; + Assert.Equal(0xE6672CCC, new NormalizedShort2(x, y).PackedValue); + x = 0.1f; + y = -0.3f; + Assert.Equal(3650751693, new NormalizedShort2(x, y).PackedValue); + + byte[] rgb = new byte[3]; + byte[] rgba = new byte[4]; + byte[] bgr = new byte[3]; + byte[] bgra = new byte[4]; + + NormalizedShort2 n = new NormalizedShort2(); + n.PackFromBytes(141, 90, 0, 0); + n.ToBytes(rgb, 0, ComponentOrder.XYZ); + Assert.Equal(rgb, new byte[] { 141, 90, 0 }); + + // TODO: I don't think this can ever pass since the bytes are already truncated. + // Assert.Equal(3650751693, n.PackedValue); + + new NormalizedShort2(x, y).ToBytes(rgb, 0, ComponentOrder.XYZ); + Assert.Equal(rgb, new byte[] { 141, 90, 0 }); + + new NormalizedShort2(x, y).ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 141, 90, 0, 255 }); + + new NormalizedShort2(x, y).ToBytes(bgr, 0, ComponentOrder.ZYX); + Assert.Equal(bgr, new byte[] { 0, 90, 141 }); + + new NormalizedShort2(x, y).ToBytes(bgra, 0, ComponentOrder.ZYXW); + Assert.Equal(bgra, new byte[] { 0, 90, 141, 255 }); + } + + [Fact] + public void NormalizedShort4() + { + // Test PackedValue + Assert.Equal((ulong)0x0, new NormalizedShort4(Vector4.Zero).PackedValue); + Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, new NormalizedShort4(Vector4.One).PackedValue); + Assert.Equal(0x8001800180018001, new NormalizedShort4(-Vector4.One).PackedValue); + + // Test ToVector4 + Assert.True(Equal(Vector4.One, new NormalizedShort4(Vector4.One).ToVector4())); + Assert.True(Equal(Vector4.Zero, new NormalizedShort4(Vector4.Zero).ToVector4())); + Assert.True(Equal(-Vector4.One, new NormalizedShort4(-Vector4.One).ToVector4())); + Assert.True(Equal(Vector4.One, new NormalizedShort4(Vector4.One * 1234.0f).ToVector4())); + Assert.True(Equal(-Vector4.One, new NormalizedShort4(Vector4.One * -1234.0f).ToVector4())); + + // Test Ordering + float x = 0.1f; + float y = -0.3f; + float z = 0.5f; + float w = -0.7f; + Assert.Equal(0xa6674000d99a0ccd, new NormalizedShort4(x, y, z, w).PackedValue); + Assert.Equal((ulong)4150390751449251866, new NormalizedShort4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); + + byte[] rgb = new byte[3]; + byte[] rgba = new byte[4]; + byte[] bgr = new byte[3]; + byte[] bgra = new byte[4]; + + new NormalizedShort4(x, y, z, w).ToBytes(rgb, 0, ComponentOrder.XYZ); + Assert.Equal(rgb, new byte[] { 141, 90, 192 }); + + new NormalizedShort4(x, y, z, w).ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 141, 90, 192, 39 }); + + new NormalizedShort4(x, y, z, w).ToBytes(bgr, 0, ComponentOrder.ZYX); + Assert.Equal(bgr, new byte[] { 192, 90, 141 }); + + new NormalizedShort4(x, y, z, w).ToBytes(bgra, 0, ComponentOrder.ZYXW); + Assert.Equal(bgra, new byte[] { 192, 90, 141, 39 }); + + NormalizedShort4 r = new NormalizedShort4(); + r.PackFromBytes(9, 115, 202, 127); + r.ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 9, 115, 202, 127 }); + } + + [Fact] + public void Rg32() + { + // Test the limits. + Assert.Equal((uint)0x0, new Rg32(Vector2.Zero).PackedValue); + Assert.Equal(0xFFFFFFFF, new Rg32(Vector2.One).PackedValue); + + // Test ToVector2 + Assert.True(Equal(Vector2.Zero, new Rg32(Vector2.Zero).ToVector2())); + Assert.True(Equal(Vector2.One, new Rg32(Vector2.One).ToVector2())); + + // Test clamping. + Assert.True(Equal(Vector2.Zero, new Rg32(Vector2.One * -1234.0f).ToVector2())); + Assert.True(Equal(Vector2.One, new Rg32(Vector2.One * 1234.0f).ToVector2())); + + // Test Ordering + float x = 0xb6dc; + float y = 0xA59f; + Assert.Equal(0xa59fb6dc, new Rg32(x / 0xffff, y / 0xffff).PackedValue); + x = 0.1f; + y = -0.3f; + Assert.Equal((uint)6554, new Rg32(x, y).PackedValue); + + // Test ordering + byte[] rgb = new byte[3]; + byte[] rgba = new byte[4]; + byte[] bgr = new byte[3]; + byte[] bgra = new byte[4]; + + new Rg32(x, y).ToBytes(rgb, 0, ComponentOrder.XYZ); + Assert.Equal(rgb, new byte[] { 25, 0, 0 }); + + new Rg32(x, y).ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 25, 0, 0, 255 }); + + new Rg32(x, y).ToBytes(bgr, 0, ComponentOrder.ZYX); + Assert.Equal(bgr, new byte[] { 0, 0, 25 }); + + new Rg32(x, y).ToBytes(bgra, 0, ComponentOrder.ZYXW); + Assert.Equal(bgra, new byte[] { 0, 0, 25, 255 }); + } + + [Fact] + public void Rgba1010102() + { + // Test the limits. + Assert.Equal((uint)0x0, new Rgba1010102(Vector4.Zero).PackedValue); + Assert.Equal(0xFFFFFFFF, new Rgba1010102(Vector4.One).PackedValue); + + // Test ToVector4 + Assert.True(Equal(Vector4.Zero, new Rgba1010102(Vector4.Zero).ToVector4())); + Assert.True(Equal(Vector4.One, new Rgba1010102(Vector4.One).ToVector4())); + + // Test clamping. + Assert.True(Equal(Vector4.Zero, new Rgba1010102(Vector4.One * -1234.0f).ToVector4())); + Assert.True(Equal(Vector4.One, new Rgba1010102(Vector4.One * 1234.0f).ToVector4())); + + // Test Ordering + float x = 0x2db; + float y = 0x36d; + float z = 0x3b7; + float w = 0x1; + Assert.Equal((uint)0x7B7DB6DB, new Rgba1010102(x / 0x3ff, y / 0x3ff, z / 0x3ff, w / 3).PackedValue); + x = 0.1f; + y = -0.3f; + z = 0.5f; + w = -0.7f; + Assert.Equal((uint)536871014, new Rgba1010102(x, y, z, w).PackedValue); + + byte[] rgb = new byte[3]; + byte[] rgba = new byte[4]; + byte[] bgr = new byte[3]; + byte[] bgra = new byte[4]; + + new Rgba1010102(x, y, z, w).ToBytes(rgb, 0, ComponentOrder.XYZ); + Assert.Equal(rgb, new byte[] { 25, 0, 128 }); + + new Rgba1010102(x, y, z, w).ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 25, 0, 128, 0 }); + + new Rgba1010102(x, y, z, w).ToBytes(bgr, 0, ComponentOrder.ZYX); + Assert.Equal(bgr, new byte[] { 128, 0, 25 }); + + new Rgba1010102(x, y, z, w).ToBytes(bgra, 0, ComponentOrder.ZYXW); + Assert.Equal(bgra, new byte[] { 128, 0, 25, 0 }); + + // Alpha component accuracy will be awful. + Rgba1010102 r = new Rgba1010102(); + r.PackFromBytes(25, 0, 128, 0); + r.ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 25, 0, 128, 0 }); + } + + [Fact] + public void Rgba64() + { + // Test the limits. + Assert.Equal((ulong)0x0, new Rgba64(Vector4.Zero).PackedValue); + Assert.Equal(0xFFFFFFFFFFFFFFFF, new Rgba64(Vector4.One).PackedValue); + + // Test ToVector4 + Assert.True(Equal(Vector4.Zero, new Rgba64(Vector4.Zero).ToVector4())); + Assert.True(Equal(Vector4.One, new Rgba64(Vector4.One).ToVector4())); + + // Test clamping. + Assert.True(Equal(Vector4.Zero, new Rgba64(Vector4.One * -1234.0f).ToVector4())); + Assert.True(Equal(Vector4.One, new Rgba64(Vector4.One * 1234.0f).ToVector4())); + + // Test data ordering + Assert.Equal(0xC7AD8F5C570A1EB8, new Rgba64(((float)0x1EB8) / 0xffff, ((float)0x570A) / 0xffff, ((float)0x8F5C) / 0xffff, ((float)0xC7AD) / 0xffff).PackedValue); + Assert.Equal(0xC7AD8F5C570A1EB8, new Rgba64(0.12f, 0.34f, 0.56f, 0.78f).PackedValue); + + float x = 0.08f; + float y = 0.15f; + float z = 0.30f; + float w = 0.45f; + Assert.Equal((ulong)0x73334CCC2666147B, new Rgba64(x, y, z, w).PackedValue); + + byte[] rgb = new byte[3]; + byte[] rgba = new byte[4]; + byte[] bgr = new byte[3]; + byte[] bgra = new byte[4]; + + new Rgba64(x, y, z, w).ToBytes(rgb, 0, ComponentOrder.XYZ); + Assert.Equal(rgb, new byte[] { 20, 38, 76 }); + + new Rgba64(x, y, z, w).ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 20, 38, 76, 115 }); + + new Rgba64(x, y, z, w).ToBytes(bgr, 0, ComponentOrder.ZYX); + Assert.Equal(bgr, new byte[] { 76, 38, 20 }); + + new Rgba64(x, y, z, w).ToBytes(bgra, 0, ComponentOrder.ZYXW); + Assert.Equal(bgra, new byte[] { 76, 38, 20, 115 }); + + Rgba64 r = new Rgba64(); + r.PackFromBytes(20, 38, 76, 115); + r.ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 20, 38, 76, 115 }); + } + + [Fact] + public void Short2() + { + // Test the limits. + Assert.Equal((uint)0x0, new Short2(Vector2.Zero).PackedValue); + Assert.Equal((uint)0x7FFF7FFF, new Short2(Vector2.One * 0x7FFF).PackedValue); + Assert.Equal(0x80008000, new Short2(Vector2.One * -0x8000).PackedValue); + + // Test ToVector2. + Assert.True(Equal(Vector2.One * 0x7FFF, new Short2(Vector2.One * 0x7FFF).ToVector2())); + Assert.True(Equal(Vector2.Zero, new Short2(Vector2.Zero).ToVector2())); + Assert.True(Equal(Vector2.One * -0x8000, new Short2(Vector2.One * -0x8000).ToVector2())); + Assert.True(Equal(Vector2.UnitX * 0x7FFF, new Short2(Vector2.UnitX * 0x7FFF).ToVector2())); + Assert.True(Equal(Vector2.UnitY * 0x7FFF, new Short2(Vector2.UnitY * 0x7FFF).ToVector2())); + + // Test clamping. + Assert.True(Equal(Vector2.One * 0x7FFF, new Short2(Vector2.One * 1234567.0f).ToVector2())); + Assert.True(Equal(Vector2.One * -0x8000, new Short2(Vector2.One * -1234567.0f).ToVector2())); + + // Test ToVector4. + Assert.True(Equal(new Vector4(0x7FFF, 0x7FFF, 0, 1), (new Short2(Vector2.One * 0x7FFF)).ToVector4())); + Assert.True(Equal(new Vector4(0, 0, 0, 1), (new Short2(Vector2.Zero)).ToVector4())); + Assert.True(Equal(new Vector4(-0x8000, -0x8000, 0, 1), (new Short2(Vector2.One * -0x8000)).ToVector4())); + + // Test ordering + float x = 0x2db1; + float y = 0x361d; + Assert.Equal((uint)0x361d2db1, new Short2(x, y).PackedValue); + x = 127.5f; + y = -5.3f; + Assert.Equal(4294639744, new Short2(x, y).PackedValue); + + byte[] rgb = new byte[3]; + byte[] rgba = new byte[4]; + byte[] bgr = new byte[3]; + byte[] bgra = new byte[4]; + + new Short2(x, y).ToBytes(rgb, 0, ComponentOrder.XYZ); + Assert.Equal(rgb, new byte[] { 128, 127, 0 }); + + new Short2(x, y).ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 128, 127, 0, 255 }); + + new Short2(x, y).ToBytes(bgr, 0, ComponentOrder.ZYX); + Assert.Equal(bgr, new byte[] { 0, 127, 128 }); + + new Short2(x, y).ToBytes(bgra, 0, ComponentOrder.ZYXW); + Assert.Equal(bgra, new byte[] { 0, 127, 128, 255 }); + + Short2 r = new Short2(); + r.PackFromBytes(20, 38, 0, 255); + r.ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 20, 38, 0, 255 }); + } + + [Fact] + public void Short4() + { + // Test the limits. + Assert.Equal((ulong)0x0, new Short4(Vector4.Zero).PackedValue); + Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, new Short4(Vector4.One * 0x7FFF).PackedValue); + Assert.Equal(0x8000800080008000, new Short4(Vector4.One * -0x8000).PackedValue); + + // Test ToVector4. + Assert.Equal(Vector4.One * 0x7FFF, new Short4(Vector4.One * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.Zero, new Short4(Vector4.Zero).ToVector4()); + Assert.Equal(Vector4.One * -0x8000, new Short4(Vector4.One * -0x8000).ToVector4()); + Assert.Equal(Vector4.UnitX * 0x7FFF, new Short4(Vector4.UnitX * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.UnitY * 0x7FFF, new Short4(Vector4.UnitY * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.UnitZ * 0x7FFF, new Short4(Vector4.UnitZ * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.UnitW * 0x7FFF, new Short4(Vector4.UnitW * 0x7FFF).ToVector4()); + + // Test clamping. + Assert.Equal(Vector4.One * 0x7FFF, new Short4(Vector4.One * 1234567.0f).ToVector4()); + Assert.Equal(Vector4.One * -0x8000, new Short4(Vector4.One * -1234567.0f).ToVector4()); + + // Test Ordering + float x = 0.1f; + float y = -0.3f; + float z = 0.5f; + float w = -0.7f; + Assert.Equal(18446462598732840960, new Short4(x, y, z, w).PackedValue); + + x = 11547; + y = 12653; + z = 29623; + w = 193; + Assert.Equal((ulong)0x00c173b7316d2d1b, new Short4(x, y, z, w).PackedValue); + + byte[] rgb = new byte[3]; + byte[] rgba = new byte[4]; + byte[] bgr = new byte[3]; + byte[] bgra = new byte[4]; + + new Short4(x, y, z, w).ToBytes(rgb, 0, ComponentOrder.XYZ); + Assert.Equal(rgb, new byte[] { 172, 177, 243 }); + + new Short4(x, y, z, w).ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 172, 177, 243, 128 }); + + new Short4(x, y, z, w).ToBytes(bgr, 0, ComponentOrder.ZYX); + Assert.Equal(bgr, new byte[] { 243, 177, 172 }); + + new Short4(x, y, z, w).ToBytes(bgra, 0, ComponentOrder.ZYXW); + Assert.Equal(bgra, new byte[] { 243, 177, 172, 128 }); + + Short4 r = new Short4(); + r.PackFromBytes(20, 38, 0, 255); + r.ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 20, 38, 0, 255 }); + } + + // Comparison helpers with small tolerance to allow for floating point rounding during computations. + public static bool Equal(float a, float b) + { + return Math.Abs(a - b) < 1e-5; + } + + public static bool Equal(Vector2 a, Vector2 b) + { + return Equal(a.X, b.X) && Equal(a.Y, b.Y); + } + + public static bool Equal(Vector3 a, Vector3 b) + { + return Equal(a.X, b.X) && Equal(a.Y, b.Y) && Equal(a.Z, b.Z); + } + + public static bool Equal(Vector4 a, Vector4 b) + { + return Equal(a.X, b.X) && Equal(a.Y, b.Y) && Equal(a.Z, b.Z) && Equal(a.W, b.W); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 7d88ca6e1..575fcf091 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -7,6 +7,7 @@ namespace ImageSharp.Tests { using System; using System.IO; + using System.Numerics; using Xunit; @@ -53,6 +54,22 @@ namespace ImageSharp.Tests { Image image = file.CreateImage(); + // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { image.Save(output);