From 650a3dbd96a78f97cb63cd5f476bf9ea4139197c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 28 Nov 2016 13:52:06 +1100 Subject: [PATCH 01/23] Add Alpha8 --- src/ImageSharp/Colors/PackedPixel/Alpha8.cs | 155 ++++++++++++++++++ .../Colors/PackedVectorTests.cs | 57 +++++++ 2 files changed, 212 insertions(+) create mode 100644 src/ImageSharp/Colors/PackedPixel/Alpha8.cs create mode 100644 tests/ImageSharp.Tests/Colors/PackedVectorTests.cs diff --git a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs new file mode 100644 index 000000000..72011f30d --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs @@ -0,0 +1,155 @@ +// +// 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 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 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/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs b/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs new file mode 100644 index 000000000..dd3e5e3b1 --- /dev/null +++ b/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs @@ -0,0 +1,57 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Colors +{ + using System.Numerics; + + using Xunit; + + /// + /// The packed vector tests. + /// + public class PackedVectorTests + { + [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 }); + } + } +} \ No newline at end of file From c184861afc14772c18dc0de2d7d02b0e0b86fb61 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 29 Nov 2016 01:11:45 +1100 Subject: [PATCH 02/23] Add Bgr565 --- src/ImageSharp/Colors/Color.cs | 2 +- src/ImageSharp/Colors/PackedPixel/Alpha8.cs | 2 +- src/ImageSharp/Colors/PackedPixel/Bgr565.cs | 171 ++++++++++++++++++ src/ImageSharp/Colors/PackedPixel/README.md | 3 + .../Colors/PackedVectorTests.cs | 69 +++++++ 5 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 src/ImageSharp/Colors/PackedPixel/Bgr565.cs create mode 100644 src/ImageSharp/Colors/PackedPixel/README.md diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs index 1435377a5..c6c33edda 100644 --- a/src/ImageSharp/Colors/Color.cs +++ b/src/ImageSharp/Colors/Color.cs @@ -219,7 +219,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 index 72011f30d..5f2bf86d4 100644 --- a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs +++ b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs @@ -50,7 +50,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 !=(Alpha8 left, Alpha8 right) { diff --git a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs new file mode 100644 index 000000000..23558f305 --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/Bgr565.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 vector 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)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 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 a 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/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/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs b/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs index dd3e5e3b1..22bb17931 100644 --- a/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Tests.Colors { + using System; using System.Numerics; using Xunit; @@ -31,6 +32,7 @@ namespace ImageSharp.Tests.Colors // Test ordering Vector4 vector = new Alpha8(.5F).ToVector4(); + Assert.Equal(vector.X, 0); Assert.Equal(vector.Y, 0); Assert.Equal(vector.Z, 0); @@ -53,5 +55,72 @@ namespace ImageSharp.Tests.Colors 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); + + 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[] { 24, 0, 131 }); + + new Bgr565(x, y, z).ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 24, 0, 131, 255 }); + + new Bgr565(x, y, z).ToBytes(bgr, 0, ComponentOrder.ZYX); + Assert.Equal(bgr, new byte[] { 131, 0, 24 }); + + new Bgr565(x, y, z).ToBytes(bgra, 0, ComponentOrder.ZYXW); + Assert.Equal(bgra, new byte[] { 131, 0, 24, 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 From aed23abf1db2bd8d2d860151039350d38b27f325 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 29 Nov 2016 01:12:19 +1100 Subject: [PATCH 03/23] Add new To method --- src/ImageSharp/Image/Image.cs | 58 ++++++++++++++++++- src/ImageSharp/Image/ImageFrame.cs | 51 ++++++++++++++-- .../Formats/GeneralFormatTests.cs | 1 + 3 files changed, 105 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Image/Image.cs b/src/ImageSharp/Image/Image.cs index 0bcf7c6eb..cfac0c7a0 100644 --- a/src/ImageSharp/Image/Image.cs +++ b/src/ImageSharp/Image/Image.cs @@ -11,6 +11,7 @@ namespace ImageSharp using System.IO; using System.Linq; using System.Text; + using System.Threading.Tasks; using Formats; @@ -245,7 +246,7 @@ namespace ImageSharp /// /// Returns a Base64 encoded string from the given image. /// - /// data:image/gif;base64,R0lGODlhAQABAIABAEdJRgAAACwAAAAAAQABAAACAkQBAA== + /// /// The public string ToBase64String() { @@ -257,6 +258,57 @@ namespace ImageSharp } } + /// + /// Returns a copy of the image in the given pixel format. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The + public Image To() + where TColor2 : struct, IPackedPixel + where TPacked2 : struct + { + 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(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 +330,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..2a49a3980 100644 --- a/src/ImageSharp/Image/ImageFrame.cs +++ b/src/ImageSharp/Image/ImageFrame.cs @@ -5,6 +5,8 @@ namespace ImageSharp { + using System.Threading.Tasks; + /// /// Represents a single frame in a animation. /// @@ -24,9 +26,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 +38,52 @@ namespace ImageSharp return $"ImageFrame: {this.Width}x{this.Height}"; } + /// + /// Returns a copy of the image frame in the given pixel format. + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The + public ImageFrame To() + where TColor2 : struct, IPackedPixel + where TPacked2 : struct + { + 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(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.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 7d88ca6e1..542a131d8 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -52,6 +52,7 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { Image image = file.CreateImage(); + // Image image = file.CreateImage().To(); using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { From b380ffa85937c573049049d5542452118fad173b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 29 Nov 2016 16:19:23 +1100 Subject: [PATCH 04/23] Add Bgra4444 --- src/ImageSharp/Colors/PackedPixel/Alpha8.cs | 2 +- src/ImageSharp/Colors/PackedPixel/Bgr565.cs | 5 +- src/ImageSharp/Colors/PackedPixel/Bgra4444.cs | 164 ++++++++++++++++++ .../Colors/PackedVectorTests.cs | 52 ++++++ .../Formats/GeneralFormatTests.cs | 1 + 5 files changed, 220 insertions(+), 4 deletions(-) create mode 100644 src/ImageSharp/Colors/PackedPixel/Bgra4444.cs diff --git a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs index 5f2bf86d4..0ea120213 100644 --- a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs +++ b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs @@ -9,7 +9,7 @@ namespace ImageSharp using System.Numerics; /// - /// Packed vector type containing a single 8 bit normalized W values that is ranging from 0 to 1. + /// Packed pixel type containing a single 8 bit normalized W values that is ranging from 0 to 1. /// public struct Alpha8 : IPackedPixel, IEquatable { diff --git a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs index 23558f305..72a1668b8 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs @@ -9,8 +9,7 @@ namespace ImageSharp using System.Numerics; /// - /// Packed vector 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. + /// 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 { @@ -155,7 +154,7 @@ namespace ImageSharp } /// - /// Packs a components into a . + /// Packs the components into a . /// /// The x-component /// The y-component 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/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs b/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs index 22bb17931..0f1b537de 100644 --- a/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs @@ -84,6 +84,8 @@ namespace ImageSharp.Tests.Colors 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]; @@ -102,6 +104,56 @@ namespace ImageSharp.Tests.Colors Assert.Equal(bgra, new byte[] { 131, 0, 24, 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 }); + } + // Comparison helpers with small tolerance to allow for floating point rounding during computations. public static bool Equal(float a, float b) { diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 542a131d8..77057cd7b 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -53,6 +53,7 @@ namespace ImageSharp.Tests { Image image = file.CreateImage(); // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { From c3f2ecb7b113204a38e84dae6da9ab59ae3c0be0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 29 Nov 2016 17:17:03 +1100 Subject: [PATCH 05/23] Add Bgra5551 --- src/ImageSharp/Colors/PackedPixel/Bgra5551.cs | 171 ++++++++++++++++++ .../Colors/PackedVectorTests.cs | 46 +++++ .../Formats/GeneralFormatTests.cs | 1 + 3 files changed, 218 insertions(+) create mode 100644 src/ImageSharp/Colors/PackedPixel/Bgra5551.cs diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs new file mode 100644 index 000000000..bf3febb50 --- /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, IPackedVector + { + /// + /// 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/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs b/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs index 0f1b537de..b24381ace 100644 --- a/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs @@ -154,6 +154,52 @@ namespace ImageSharp.Tests.Colors 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 }); + } + // Comparison helpers with small tolerance to allow for floating point rounding during computations. public static bool Equal(float a, float b) { diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 77057cd7b..db4f3c466 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -54,6 +54,7 @@ namespace ImageSharp.Tests Image image = file.CreateImage(); // Image image = file.CreateImage().To(); // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { From aeccdb49a1e1e12e6d8752c602397c0030d16a9f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 29 Nov 2016 22:24:48 +1100 Subject: [PATCH 06/23] Add Byte4 --- src/ImageSharp/Colors/Color.cs | 17 +- src/ImageSharp/Colors/PackedPixel/Byte4.cs | 178 ++++++++++++++++++ .../Colors/PackedVectorTests.cs | 51 +++++ .../Formats/GeneralFormatTests.cs | 1 + 4 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Colors/PackedPixel/Byte4.cs diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs index c6c33edda..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; /// diff --git a/src/ImageSharp/Colors/PackedPixel/Byte4.cs b/src/ImageSharp/Colors/PackedPixel/Byte4.cs new file mode 100644 index 000000000..5ba35d037 --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/Byte4.cs @@ -0,0 +1,178 @@ +// +// 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, IPackedVector + { + /// + /// 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/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs b/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs index b24381ace..5ba7a3e9a 100644 --- a/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs @@ -200,6 +200,57 @@ namespace ImageSharp.Tests.Colors 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 }); + } + // Comparison helpers with small tolerance to allow for floating point rounding during computations. public static bool Equal(float a, float b) { diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index db4f3c466..f9a44f574 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -55,6 +55,7 @@ namespace ImageSharp.Tests // 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}")) { From df01a543cc155f6e20fecca843744e61898937d6 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 30 Nov 2016 12:24:48 +1100 Subject: [PATCH 07/23] Add HalfSingle --- src/ImageSharp/Colors/PackedPixel/Alpha8.cs | 4 +- src/ImageSharp/Colors/PackedPixel/Bgra5551.cs | 2 +- src/ImageSharp/Colors/PackedPixel/Byte4.cs | 3 +- .../Colors/PackedPixel/HalfSingle.cs | 160 ++++++++++++++++++ .../Colors/PackedPixel/HalfTypeHelper.cs | 144 ++++++++++++++++ ...ckedVectorTests.cs => PackedPixelTests.cs} | 40 ++++- .../Formats/GeneralFormatTests.cs | 1 + 7 files changed, 345 insertions(+), 9 deletions(-) create mode 100644 src/ImageSharp/Colors/PackedPixel/HalfSingle.cs create mode 100644 src/ImageSharp/Colors/PackedPixel/HalfTypeHelper.cs rename tests/ImageSharp.Tests/Colors/{PackedVectorTests.cs => PackedPixelTests.cs} (88%) diff --git a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs index 0ea120213..234976b48 100644 --- a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs +++ b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs @@ -16,9 +16,7 @@ namespace ImageSharp /// /// Initializes a new instance of the struct. /// - /// - /// The alpha component - /// + /// The alpha component public Alpha8(float alpha) { this.PackedValue = Pack(alpha); diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs index bf3febb50..50aa67914 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs @@ -11,7 +11,7 @@ namespace ImageSharp /// /// 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, IPackedVector + public struct Bgra5551 : IPackedPixel, IEquatable { /// /// Initializes a new instance of the struct. diff --git a/src/ImageSharp/Colors/PackedPixel/Byte4.cs b/src/ImageSharp/Colors/PackedPixel/Byte4.cs index 5ba35d037..9b4be5711 100644 --- a/src/ImageSharp/Colors/PackedPixel/Byte4.cs +++ b/src/ImageSharp/Colors/PackedPixel/Byte4.cs @@ -3,7 +3,6 @@ // Licensed under the Apache License, Version 2.0. // - namespace ImageSharp { using System; @@ -12,7 +11,7 @@ namespace ImageSharp /// /// Packed pixel type containing four 8-bit unsigned integer values, ranging from 0 to 255. /// - public struct Byte4 : IPackedPixel, IEquatable, IPackedVector + public struct Byte4 : IPackedPixel, IEquatable { /// /// Initializes a new instance of the struct. diff --git a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs new file mode 100644 index 000000000..09811228a --- /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) / 255F); + } + + /// + 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/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs similarity index 88% rename from tests/ImageSharp.Tests/Colors/PackedVectorTests.cs rename to tests/ImageSharp.Tests/Colors/PackedPixelTests.cs index 5ba7a3e9a..a30fecc1c 100644 --- a/tests/ImageSharp.Tests/Colors/PackedVectorTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -11,9 +11,9 @@ namespace ImageSharp.Tests.Colors using Xunit; /// - /// The packed vector tests. + /// The packed pixel tests. /// - public class PackedVectorTests + public class PackedPixelTests { [Fact] public void Alpha8() @@ -251,6 +251,40 @@ namespace ImageSharp.Tests.Colors 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 }); + } + // Comparison helpers with small tolerance to allow for floating point rounding during computations. public static bool Equal(float a, float b) { diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index f9a44f574..24ff33361 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -56,6 +56,7 @@ namespace ImageSharp.Tests // 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}")) { From ba7b24d2f448e61b86c1d6c3882563fba7a2190d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 30 Nov 2016 13:37:06 +1100 Subject: [PATCH 08/23] Add HalfVector2 --- .../Colors/PackedPixel/HalfSingle.cs | 2 +- .../Colors/PackedPixel/HalfVector2.cs | 187 ++++++++++++++++++ .../Colors/PackedPixelTests.cs | 37 ++++ 3 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Colors/PackedPixel/HalfVector2.cs diff --git a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs index 09811228a..b2919b46a 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs @@ -93,7 +93,7 @@ namespace ImageSharp /// public void PackFromBytes(byte x, byte y, byte z, byte w) { - this.PackFromVector4(new Vector4(x, y, z, w) / 255F); + this.PackFromVector4(new Vector4(x, y, z, w) / MaxBytes); } /// 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/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs index a30fecc1c..dcb4b164d 100644 --- a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs @@ -285,6 +285,43 @@ namespace ImageSharp.Tests.Colors 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 }); + } + // Comparison helpers with small tolerance to allow for floating point rounding during computations. public static bool Equal(float a, float b) { From ed3bb2543ca74f3c42d1d84ba9563dc708609661 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 30 Nov 2016 14:01:27 +1100 Subject: [PATCH 09/23] Add HalfVector4 --- .../Colors/PackedPixel/HalfVector4.cs | 182 ++++++++++++++++++ .../Colors/PackedPixelTests.cs | 46 +++++ .../Formats/GeneralFormatTests.cs | 2 + 3 files changed, 230 insertions(+) create mode 100644 src/ImageSharp/Colors/PackedPixel/HalfVector4.cs 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/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs index dcb4b164d..cadc8deda 100644 --- a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs @@ -322,6 +322,52 @@ namespace ImageSharp.Tests.Colors 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 }); + } + // Comparison helpers with small tolerance to allow for floating point rounding during computations. public static bool Equal(float a, float b) { diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 24ff33361..641f30bfe 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -57,6 +57,8 @@ namespace ImageSharp.Tests // 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}")) { From 9721bdc56c278b9308cad3cfa86c06fd97d4ab1c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 30 Nov 2016 22:23:21 +1100 Subject: [PATCH 10/23] Add NormalizedByte2 --- .../Colors/PackedPixel/NormalizedByte2.cs | 187 ++++++++++++++++++ .../Colors/PackedPixelTests.cs | 42 ++++ .../Formats/GeneralFormatTests.cs | 1 + 3 files changed, 230 insertions(+) create mode 100644 src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs new file mode 100644 index 000000000..a05f98e45 --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.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 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 vector value. + /// + private static readonly Vector4 Half = new Vector4(0.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) + { + 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 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/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs index cadc8deda..5e9ba9be4 100644 --- a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs @@ -368,6 +368,48 @@ namespace ImageSharp.Tests.Colors 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 ToVector2 + 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); + + 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[] { 26, 0, 0 }); + + new NormalizedByte2(x, y).ToBytes(rgba, 0, ComponentOrder.XYZW); + Assert.Equal(rgba, new byte[] { 26, 0, 0, 255 }); + + new NormalizedByte2(x, y).ToBytes(bgr, 0, ComponentOrder.ZYX); + Assert.Equal(bgr, new byte[] { 0, 0, 26 }); + + new NormalizedByte2(x, y).ToBytes(bgra, 0, ComponentOrder.ZYXW); + Assert.Equal(bgra, new byte[] { 0, 0, 26, 255 }); + } + // Comparison helpers with small tolerance to allow for floating point rounding during computations. public static bool Equal(float a, float b) { diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 641f30bfe..8da086e17 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -59,6 +59,7 @@ namespace ImageSharp.Tests // 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}")) { From e3e99bea570830a8cbd3b01528b2ab3e2af2d402 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 1 Dec 2016 01:47:53 +1100 Subject: [PATCH 11/23] NormalizedByte2 Add NormalizedByte4 --- .../Colors/PackedPixel/NormalizedByte2.cs | 36 ++-- .../Colors/PackedPixel/NormalizedByte4.cs | 197 ++++++++++++++++++ .../Colors/PackedPixelTests.cs | 68 +++++- .../Formats/GeneralFormatTests.cs | 1 + 4 files changed, 285 insertions(+), 17 deletions(-) create mode 100644 src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs index a05f98e45..43378dfc5 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs @@ -19,9 +19,14 @@ namespace ImageSharp private static readonly Vector4 MaxBytes = new Vector4(255); /// - /// The half vector value. + /// The half the maximum byte value. /// - private static readonly Vector4 Half = new Vector4(0.5F); + 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. @@ -106,40 +111,47 @@ namespace ImageSharp /// public void PackFromBytes(byte x, byte y, byte z, byte w) { - this.PackFromVector4(new Vector4(x, y, z, w) / MaxBytes); + 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 *= MaxBytes; + 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] = 0; bytes[startIndex + 1] = (byte)vector.Y; bytes[startIndex + 2] = (byte)vector.X; break; case ComponentOrder.ZYXW: - bytes[startIndex] = (byte)vector.Z; + bytes[startIndex] = 0; bytes[startIndex + 1] = (byte)vector.Y; bytes[startIndex + 2] = (byte)vector.X; - bytes[startIndex + 3] = (byte)vector.W; + bytes[startIndex + 3] = 255; break; case ComponentOrder.XYZ: bytes[startIndex] = (byte)vector.X; bytes[startIndex + 1] = (byte)vector.Y; - bytes[startIndex + 2] = (byte)vector.Z; + bytes[startIndex + 2] = 0; 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; + bytes[startIndex + 2] = 0; + bytes[startIndex + 3] = 255; break; default: throw new NotSupportedException(); @@ -178,8 +190,8 @@ namespace ImageSharp /// 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; + 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); } 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/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs index 5e9ba9be4..9e391bea7 100644 --- a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs @@ -383,7 +383,7 @@ namespace ImageSharp.Tests.Colors 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 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())); @@ -391,6 +391,9 @@ namespace ImageSharp.Tests.Colors 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]; @@ -398,16 +401,71 @@ namespace ImageSharp.Tests.Colors byte[] bgra = new byte[4]; new NormalizedByte2(x, y).ToBytes(rgb, 0, ComponentOrder.XYZ); - Assert.Equal(rgb, new byte[] { 26, 0, 0 }); + Assert.Equal(rgb, new byte[] { 141, 90, 0 }); new NormalizedByte2(x, y).ToBytes(rgba, 0, ComponentOrder.XYZW); - Assert.Equal(rgba, new byte[] { 26, 0, 0, 255 }); + Assert.Equal(rgba, new byte[] { 141, 90, 0, 255 }); new NormalizedByte2(x, y).ToBytes(bgr, 0, ComponentOrder.ZYX); - Assert.Equal(bgr, new byte[] { 0, 0, 26 }); + Assert.Equal(bgr, new byte[] { 0, 90, 141 }); new NormalizedByte2(x, y).ToBytes(bgra, 0, ComponentOrder.ZYXW); - Assert.Equal(bgra, new byte[] { 0, 0, 26, 255 }); + 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 }); } // Comparison helpers with small tolerance to allow for floating point rounding during computations. diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 8da086e17..8b621768c 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -60,6 +60,7 @@ namespace ImageSharp.Tests // 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}")) { From 36c1bab8569ef987bc1e0644379ec8f5db53512f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 1 Dec 2016 01:59:39 +1100 Subject: [PATCH 12/23] Add conversion note. --- tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 8b621768c..dc175e390 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -52,6 +52,7 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { Image image = file.CreateImage(); + // Image image = file.CreateImage().To(); // Image image = file.CreateImage().To(); // Image image = file.CreateImage().To(); @@ -60,6 +61,8 @@ namespace ImageSharp.Tests // Image image = file.CreateImage().To(); // Image image = file.CreateImage().To(); // Image image = file.CreateImage().To(); + + // TODO: Conversion between types who's vector ranges are different are not possible. // Image image = file.CreateImage().To(); using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) From 43b1dd18450ab5444cf352392bcdaef3c2a4a8b2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 1 Dec 2016 12:10:17 +1100 Subject: [PATCH 13/23] Fix To() Method to allow vector scaling. --- src/ImageSharp/Image/Image.cs | 16 ++++++++++++++-- src/ImageSharp/Image/ImageFrame.cs | 17 +++++++++++++++-- .../Formats/GeneralFormatTests.cs | 5 +++-- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Image/Image.cs b/src/ImageSharp/Image/Image.cs index cfac0c7a0..fcb72ced8 100644 --- a/src/ImageSharp/Image/Image.cs +++ b/src/ImageSharp/Image/Image.cs @@ -10,6 +10,7 @@ namespace ImageSharp using System.Diagnostics; using System.IO; using System.Linq; + using System.Numerics; using System.Text; using System.Threading.Tasks; @@ -260,14 +261,25 @@ namespace ImageSharp /// /// Returns a copy of the image in the given pixel format. + /// + /// Most color formats when converted to vectors have a range of 0 to 1. Some however, , for example scale between + /// -1 to 1. This requires additional computation to convert between the formats. + /// For example, if I wanted to convert from to the following function would be required. v => (2F * v) - Vector4.One + /// /// + /// A function that allows for the correction of vector scaling between color formats. /// The pixel format. /// The packed format. uint, long, float. /// The - public Image To() + public Image To(Func scaleFunc = null) where TColor2 : struct, IPackedPixel where TPacked2 : struct { + if (scaleFunc == null) + { + scaleFunc = v => v; + } + Image target = new Image(this.Width, this.Height) { Quality = this.Quality, @@ -290,7 +302,7 @@ namespace ImageSharp for (int x = 0; x < target.Width; x++) { TColor2 color = default(TColor2); - color.PackFromVector4(pixels[x, y].ToVector4()); + color.PackFromVector4(scaleFunc(pixels[x, y].ToVector4())); targetPixels[x, y] = color; } }); diff --git a/src/ImageSharp/Image/ImageFrame.cs b/src/ImageSharp/Image/ImageFrame.cs index 2a49a3980..fd4d7fa52 100644 --- a/src/ImageSharp/Image/ImageFrame.cs +++ b/src/ImageSharp/Image/ImageFrame.cs @@ -5,6 +5,8 @@ namespace ImageSharp { + using System; + using System.Numerics; using System.Threading.Tasks; /// @@ -40,14 +42,25 @@ namespace ImageSharp /// /// Returns a copy of the image frame in the given pixel format. + /// + /// Most color formats when converted to vectors have a range of 0 to 1. Some however, , for example scale between + /// -1 to 1. This requires additional computation to convert between the formats. + /// For example, if I wanted to convert from to the following function would be required. v => (2F * v) - Vector4.One + /// /// + /// A function that allows for the correction of vector scaling between color formats. /// The pixel format. /// The packed format. uint, long, float. /// The - public ImageFrame To() + public ImageFrame To(Func scaleFunc = null) where TColor2 : struct, IPackedPixel where TPacked2 : struct { + if (scaleFunc == null) + { + scaleFunc = v => v; + } + ImageFrame target = new ImageFrame { Quality = this.Quality, @@ -68,7 +81,7 @@ namespace ImageSharp for (int x = 0; x < target.Width; x++) { TColor2 color = default(TColor2); - color.PackFromVector4(pixels[x, y].ToVector4()); + color.PackFromVector4(scaleFunc(pixels[x, y].ToVector4())); targetPixels[x, y] = color; } }); diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index dc175e390..4639d7bee 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; @@ -62,8 +63,8 @@ namespace ImageSharp.Tests // Image image = file.CreateImage().To(); // Image image = file.CreateImage().To(); - // TODO: Conversion between types who's vector ranges are different are not possible. - // Image image = file.CreateImage().To(); + // TODO: Conversion between types who's vector ranges are different are not possible without scaling function, Make static version of known ones. + // Image image = file.CreateImage().To(v => (2F * v) - Vector4.One); using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { From c52f6fc796fc7a7d7ee772d391402c3c3af89dcd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 1 Dec 2016 15:06:39 +1100 Subject: [PATCH 14/23] Add NormalizedShort2 --- .../Colors/PackedPixel/NormalizedByte2.cs | 4 +- .../Colors/PackedPixel/NormalizedShort2.cs | 206 ++++++++++++++++++ .../Colors/PackedPixelTests.cs | 50 +++++ .../Formats/GeneralFormatTests.cs | 4 +- 4 files changed, 260 insertions(+), 4 deletions(-) create mode 100644 src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs index 43378dfc5..3ff419ab6 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs @@ -92,8 +92,8 @@ namespace ImageSharp public Vector2 ToVector2() { return new Vector2( - ((sbyte)((this.PackedValue >> 0) & 0xFF)) / 127F, - ((sbyte)((this.PackedValue >> 8) & 0xFF)) / 127F); + (sbyte)((this.PackedValue >> 0) & 0xFF) / 127F, + (sbyte)((this.PackedValue >> 8) & 0xFF) / 127F); } /// 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/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs index 9e391bea7..8702e8edd 100644 --- a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs @@ -468,6 +468,56 @@ namespace ImageSharp.Tests.Colors 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 }); + } + // Comparison helpers with small tolerance to allow for floating point rounding during computations. public static bool Equal(float a, float b) { diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 4639d7bee..2f1436236 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -61,11 +61,11 @@ namespace ImageSharp.Tests // Image image = file.CreateImage().To(); // Image image = file.CreateImage().To(); // Image image = file.CreateImage().To(); - // Image image = file.CreateImage().To(); // TODO: Conversion between types who's vector ranges are different are not possible without scaling function, Make static version of known ones. + // Image image = file.CreateImage().To(v => (2F * v) - Vector4.One); // Image image = file.CreateImage().To(v => (2F * v) - Vector4.One); - + // Image image = file.CreateImage().To(v => (2F * v) - Vector4.One); using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { image.Save(output); From cdf379806303e5a040978d49894467827cb229d4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 1 Dec 2016 15:19:15 +1100 Subject: [PATCH 15/23] Update benchmarkdotnet --- tests/ImageSharp.Benchmarks/project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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": { From 40777de029869f83ef22fbf14e43c90d7376d771 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 1 Dec 2016 15:52:58 +1100 Subject: [PATCH 16/23] Add NormalizedShort4 --- .../Colors/PackedPixel/NormalizedShort4.cs | 203 ++++++++++++++++++ .../Colors/PackedPixelTests.cs | 46 ++++ .../Formats/GeneralFormatTests.cs | 1 + 3 files changed, 250 insertions(+) create mode 100644 src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs 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/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs index 8702e8edd..d25979e1e 100644 --- a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs @@ -518,6 +518,52 @@ namespace ImageSharp.Tests.Colors 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 }); + } + // Comparison helpers with small tolerance to allow for floating point rounding during computations. public static bool Equal(float a, float b) { diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 2f1436236..f094253b8 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -66,6 +66,7 @@ namespace ImageSharp.Tests // Image image = file.CreateImage().To(v => (2F * v) - Vector4.One); // Image image = file.CreateImage().To(v => (2F * v) - Vector4.One); // Image image = file.CreateImage().To(v => (2F * v) - Vector4.One); + // Image image = file.CreateImage().To(v => (2F * v) - Vector4.One); using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { image.Save(output); From 35db34fdc45c2fbbddf4a0a71629a338f462b1eb Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 1 Dec 2016 16:33:37 +1100 Subject: [PATCH 17/23] Add Rg32 --- src/ImageSharp/Colors/PackedPixel/Rg32.cs | 173 ++++++++++++++++++ .../Colors/PackedPixelTests.cs | 42 +++++ .../Formats/GeneralFormatTests.cs | 1 + 3 files changed, 216 insertions(+) create mode 100644 src/ImageSharp/Colors/PackedPixel/Rg32.cs 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/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs index d25979e1e..75700337f 100644 --- a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs @@ -564,6 +564,48 @@ namespace ImageSharp.Tests.Colors 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 }); + } + // Comparison helpers with small tolerance to allow for floating point rounding during computations. public static bool Equal(float a, float b) { diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index f094253b8..b2191d909 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -61,6 +61,7 @@ namespace ImageSharp.Tests // Image image = file.CreateImage().To(); // Image image = file.CreateImage().To(); // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); // TODO: Conversion between types who's vector ranges are different are not possible without scaling function, Make static version of known ones. // Image image = file.CreateImage().To(v => (2F * v) - Vector4.One); From 3c6a4d5e4c1d2b6072c74f1b34972a2c415ac41e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 1 Dec 2016 19:04:17 +1100 Subject: [PATCH 18/23] Add Rgba1010102, Make Bgr565 conversion more accurate. --- src/ImageSharp/Colors/PackedPixel/Bgr565.cs | 28 +-- .../Colors/PackedPixel/Rgba1010102.cs | 172 ++++++++++++++++++ .../Colors/PackedPixelTests.cs | 59 +++++- .../Formats/GeneralFormatTests.cs | 1 + 4 files changed, 242 insertions(+), 18 deletions(-) create mode 100644 src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs diff --git a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs index 72a1668b8..214e03c5f 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs @@ -103,26 +103,26 @@ namespace ImageSharp switch (componentOrder) { case ComponentOrder.ZYX: - bytes[startIndex] = (byte)vector.Z; - bytes[startIndex + 1] = (byte)vector.Y; - bytes[startIndex + 2] = (byte)vector.X; + 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)vector.Z; - bytes[startIndex + 1] = (byte)vector.Y; - bytes[startIndex + 2] = (byte)vector.X; - bytes[startIndex + 3] = (byte)vector.W; + 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)vector.X; - bytes[startIndex + 1] = (byte)vector.Y; - bytes[startIndex + 2] = (byte)vector.Z; + 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)vector.X; - bytes[startIndex + 1] = (byte)vector.Y; - bytes[startIndex + 2] = (byte)vector.Z; - bytes[startIndex + 3] = (byte)vector.W; + 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(); 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/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs index 75700337f..1c6f90970 100644 --- a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs @@ -92,16 +92,16 @@ namespace ImageSharp.Tests.Colors byte[] bgra = new byte[4]; new Bgr565(x, y, z).ToBytes(rgb, 0, ComponentOrder.XYZ); - Assert.Equal(rgb, new byte[] { 24, 0, 131 }); + Assert.Equal(rgb, new byte[] { 25, 0, 132 }); new Bgr565(x, y, z).ToBytes(rgba, 0, ComponentOrder.XYZW); - Assert.Equal(rgba, new byte[] { 24, 0, 131, 255 }); + Assert.Equal(rgba, new byte[] { 25, 0, 132, 255 }); new Bgr565(x, y, z).ToBytes(bgr, 0, ComponentOrder.ZYX); - Assert.Equal(bgr, new byte[] { 131, 0, 24 }); + Assert.Equal(bgr, new byte[] { 132, 0, 25 }); new Bgr565(x, y, z).ToBytes(bgra, 0, ComponentOrder.ZYXW); - Assert.Equal(bgra, new byte[] { 131, 0, 24, 255 }); + Assert.Equal(bgra, new byte[] { 132, 0, 25, 255 }); } [Fact] @@ -606,6 +606,57 @@ namespace ImageSharp.Tests.Colors 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 }); + } + // Comparison helpers with small tolerance to allow for floating point rounding during computations. public static bool Equal(float a, float b) { diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index b2191d909..6c95951d8 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -62,6 +62,7 @@ namespace ImageSharp.Tests // Image image = file.CreateImage().To(); // Image image = file.CreateImage().To(); // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); // TODO: Conversion between types who's vector ranges are different are not possible without scaling function, Make static version of known ones. // Image image = file.CreateImage().To(v => (2F * v) - Vector4.One); From ffc7994ebfc931c26f0dd6127ee47bfdd71cdd19 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 1 Dec 2016 19:32:39 +1100 Subject: [PATCH 19/23] Add Rgba64 --- src/ImageSharp/Colors/PackedPixel/Rgba64.cs | 170 ++++++++++++++++++ .../Colors/PackedPixelTests.cs | 48 +++++ .../Formats/GeneralFormatTests.cs | 1 + 3 files changed, 219 insertions(+) create mode 100644 src/ImageSharp/Colors/PackedPixel/Rgba64.cs 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/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs index 1c6f90970..58d9d56d7 100644 --- a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs @@ -657,6 +657,54 @@ namespace ImageSharp.Tests.Colors 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 }); + } + // Comparison helpers with small tolerance to allow for floating point rounding during computations. public static bool Equal(float a, float b) { diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 6c95951d8..16a60d190 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -63,6 +63,7 @@ namespace ImageSharp.Tests // Image image = file.CreateImage().To(); // Image image = file.CreateImage().To(); // Image image = file.CreateImage().To(); + // Image image = file.CreateImage().To(); // TODO: Conversion between types who's vector ranges are different are not possible without scaling function, Make static version of known ones. // Image image = file.CreateImage().To(v => (2F * v) - Vector4.One); From b0a0fcf377419bb0d864422faa2e6b1c05ea6314 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 1 Dec 2016 22:06:21 +1100 Subject: [PATCH 20/23] Add Short2 That was hard! --- src/ImageSharp/Colors/PackedPixel/Short2.cs | 199 ++++++++++++++++++ .../Colors/PackedPixelTests.cs | 49 +++++ .../Formats/GeneralFormatTests.cs | 2 + 3 files changed, 250 insertions(+) create mode 100644 src/ImageSharp/Colors/PackedPixel/Short2.cs diff --git a/src/ImageSharp/Colors/PackedPixel/Short2.cs b/src/ImageSharp/Colors/PackedPixel/Short2.cs new file mode 100644 index 000000000..e25ef9eb0 --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/Short2.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 pixel type containing two 16-bit signed integer values. + /// + public struct Short2 : IPackedPixel, IEquatable + { + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new Vector4(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; + + 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/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs index 58d9d56d7..c7d0e1c73 100644 --- a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs @@ -705,6 +705,55 @@ namespace ImageSharp.Tests.Colors 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); + + new Short2(x, y).ToBytes(rgba, 0, ComponentOrder.XYZW); + + new Short2(x, y).ToBytes(bgr, 0, ComponentOrder.ZYX); + + new Short2(x, y).ToBytes(bgra, 0, ComponentOrder.ZYXW); + + Short2 r = new Short2(); + r.ToBytes(rgba, 0, ComponentOrder.XYZW); + } + // Comparison helpers with small tolerance to allow for floating point rounding during computations. public static bool Equal(float a, float b) { diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 16a60d190..6196eed43 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -70,6 +70,8 @@ namespace ImageSharp.Tests // Image image = file.CreateImage().To(v => (2F * v) - Vector4.One); // Image image = file.CreateImage().To(v => (2F * v) - Vector4.One); // Image image = file.CreateImage().To(v => (2F * v) - Vector4.One); + // Image image = file.CreateImage().To(v => (65534 * v) - new Vector4(32767)); + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { image.Save(output); From efd5a5a1d809a64344be8b91cea73733d48e609f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 1 Dec 2016 22:06:42 +1100 Subject: [PATCH 21/23] Add correct tests --- tests/ImageSharp.Tests/Colors/PackedPixelTests.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs index c7d0e1c73..08786eddd 100644 --- a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs @@ -743,15 +743,21 @@ namespace ImageSharp.Tests.Colors 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 }); } // Comparison helpers with small tolerance to allow for floating point rounding during computations. From efd444c226115e6072951cb305888ce0fef02971 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 2 Dec 2016 00:43:12 +1100 Subject: [PATCH 22/23] Add Short4 --- src/ImageSharp/Colors/PackedPixel/Short2.cs | 3 +- src/ImageSharp/Colors/PackedPixel/Short4.cs | 216 ++++++++++++++++++ .../Colors/PackedPixelTests.cs | 57 +++++ 3 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Colors/PackedPixel/Short4.cs diff --git a/src/ImageSharp/Colors/PackedPixel/Short2.cs b/src/ImageSharp/Colors/PackedPixel/Short2.cs index e25ef9eb0..310c44ec8 100644 --- a/src/ImageSharp/Colors/PackedPixel/Short2.cs +++ b/src/ImageSharp/Colors/PackedPixel/Short2.cs @@ -16,7 +16,7 @@ namespace ImageSharp /// /// The maximum byte value. /// - private static readonly Vector4 MaxBytes = new Vector4(255); + private static readonly Vector2 MaxBytes = new Vector2(255); /// /// The half the maximum byte value. @@ -113,6 +113,7 @@ namespace ImageSharp vector *= 255; vector += Half; vector += Round; + vector = Vector2.Clamp(vector, Vector2.Zero, MaxBytes); switch (componentOrder) { 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/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs index 08786eddd..785125ca1 100644 --- a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs @@ -760,6 +760,63 @@ namespace ImageSharp.Tests.Colors 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) { From 940801612dd4193067ed687a7bc6f2c3df47b12b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 2 Dec 2016 00:53:31 +1100 Subject: [PATCH 23/23] Add PackedPixelConverterHelper Fix #27 Allows the conversion between all the known packed pixel types. --- README.md | 32 +- .../PackedPixel/PackedPixelConverterHelper.cs | 276 ++++++++++++++++++ src/ImageSharp/Image/Image.cs | 12 +- src/ImageSharp/Image/ImageFrame.cs | 12 +- .../Formats/GeneralFormatTests.cs | 14 +- 5 files changed, 311 insertions(+), 35 deletions(-) create mode 100644 src/ImageSharp/Colors/PackedPixel/PackedPixelConverterHelper.cs 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/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/Image/Image.cs b/src/ImageSharp/Image/Image.cs index fcb72ced8..2884eddbd 100644 --- a/src/ImageSharp/Image/Image.cs +++ b/src/ImageSharp/Image/Image.cs @@ -261,13 +261,8 @@ namespace ImageSharp /// /// Returns a copy of the image in the given pixel format. - /// - /// Most color formats when converted to vectors have a range of 0 to 1. Some however, , for example scale between - /// -1 to 1. This requires additional computation to convert between the formats. - /// For example, if I wanted to convert from to the following function would be required. v => (2F * v) - Vector4.One - /// /// - /// A function that allows for the correction of vector scaling between color formats. + /// A function that allows for the correction of vector scaling between unknown color formats. /// The pixel format. /// The packed format. uint, long, float. /// The @@ -275,10 +270,7 @@ namespace ImageSharp where TColor2 : struct, IPackedPixel where TPacked2 : struct { - if (scaleFunc == null) - { - scaleFunc = v => v; - } + scaleFunc = PackedPixelConverterHelper.ComputeScaleFunction(scaleFunc); Image target = new Image(this.Width, this.Height) { diff --git a/src/ImageSharp/Image/ImageFrame.cs b/src/ImageSharp/Image/ImageFrame.cs index fd4d7fa52..c0e5b3f16 100644 --- a/src/ImageSharp/Image/ImageFrame.cs +++ b/src/ImageSharp/Image/ImageFrame.cs @@ -42,13 +42,8 @@ namespace ImageSharp /// /// Returns a copy of the image frame in the given pixel format. - /// - /// Most color formats when converted to vectors have a range of 0 to 1. Some however, , for example scale between - /// -1 to 1. This requires additional computation to convert between the formats. - /// For example, if I wanted to convert from to the following function would be required. v => (2F * v) - Vector4.One - /// /// - /// A function that allows for the correction of vector scaling between color formats. + /// A function that allows for the correction of vector scaling between unknown color formats. /// The pixel format. /// The packed format. uint, long, float. /// The @@ -56,10 +51,7 @@ namespace ImageSharp where TColor2 : struct, IPackedPixel where TPacked2 : struct { - if (scaleFunc == null) - { - scaleFunc = v => v; - } + scaleFunc = PackedPixelConverterHelper.ComputeScaleFunction(scaleFunc); ImageFrame target = new ImageFrame { diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 6196eed43..575fcf091 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -64,14 +64,12 @@ namespace ImageSharp.Tests // Image image = file.CreateImage().To(); // Image image = file.CreateImage().To(); // Image image = file.CreateImage().To(); - - // TODO: Conversion between types who's vector ranges are different are not possible without scaling function, Make static version of known ones. - // Image image = file.CreateImage().To(v => (2F * v) - Vector4.One); - // Image image = file.CreateImage().To(v => (2F * v) - Vector4.One); - // Image image = file.CreateImage().To(v => (2F * v) - Vector4.One); - // Image image = file.CreateImage().To(v => (2F * v) - Vector4.One); - // Image image = file.CreateImage().To(v => (65534 * v) - new Vector4(32767)); - + // 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);