diff --git a/src/ImageSharp/PixelFormats/Gray16.cs b/src/ImageSharp/PixelFormats/Gray16.cs new file mode 100644 index 0000000000..a7e50e3667 --- /dev/null +++ b/src/ImageSharp/PixelFormats/Gray16.cs @@ -0,0 +1,270 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Packed pixel type containing a single 16 bit normalized gray values. + /// + /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. + /// + /// + public struct Gray16 : IPixel, IPackedVector + { + /// + /// RX as in ITU-R recommendation 709 to match libpng + /// + private const float Rx = .2126F; + + /// + /// GX as in ITU-R recommendation 709 to match libpng + /// + private const float Gx = .7152F; + + /// + /// BX as in ITU-R recommendation 709 to match libpng + /// + private const float Bx = .0722F; + + /// + /// Initializes a new instance of the struct. + /// + /// The gray component + public Gray16(byte gray) + { + this.PackedValue = gray; + } + + /// + 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. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Gray16 left, Gray16 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. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Gray16 left, Gray16 right) + { + return left.PackedValue != right.PackedValue; + } + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromScaledVector4(Vector4 vector) + { + this.PackFromVector4(vector); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() + { + var scaledGray = this.PackedValue / 65535f; // ushort.Max as float + return new Vector4(scaledGray, scaledGray, scaledGray, 1.0f); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromVector4(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToVector4() + { + return new Vector4(this.PackedValue, this.PackedValue, this.PackedValue, 1.0f); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba32(Rgba32 source) + { + this.PackedValue = Pack(source.R, source.G, source.B); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromArgb32(Argb32 source) + { + this.PackedValue = Pack(source.R, source.G, source.B); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromBgra32(Bgra32 source) + { + this.PackedValue = Pack(source.R, source.G, source.B); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb24(ref Rgb24 dest) + { + var scaledValue = this.PackedAsByte(); + + dest.R = scaledValue; + dest.G = scaledValue; + dest.B = scaledValue; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) + { + var scaledValue = this.PackedAsByte(); + + dest.R = scaledValue; + dest.G = scaledValue; + dest.B = scaledValue; + dest.A = 255; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToArgb32(ref Argb32 dest) + { + var scaledValue = this.PackedAsByte(); + + dest.R = scaledValue; + dest.G = scaledValue; + dest.B = scaledValue; + dest.A = 255; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToBgr24(ref Bgr24 dest) + { + var scaledValue = this.PackedAsByte(); + + dest.R = scaledValue; + dest.G = scaledValue; + dest.B = scaledValue; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToBgra32(ref Bgra32 dest) + { + var scaledValue = this.PackedAsByte(); + + dest.R = scaledValue; + dest.G = scaledValue; + dest.B = scaledValue; + dest.A = 255; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => + this.PackedValue = Pack(source.R, source.G, source.B); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) + { + dest.R = this.PackedValue; + dest.G = this.PackedValue; + dest.B = this.PackedValue; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => + this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + /// 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 Gray16 other && this.Equals(other); + } + + /// + /// Compares another packed vector with the packed vector. + /// + /// The Gray8 packed vector to compare. + /// True if the packed vectors are equal. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Gray16 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 / 65535f).ToString(); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + /// Packs a into a byte. + /// + /// Red value of the color to pack. + /// Green value of the color to pack. + /// Blue value of the color to pack. + /// The containing the packed value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ushort Pack(float r, float g, float b) + { + float sum = r + g + b; + float val = (r * Rx) + (g * Gx) + (b * Bx); + return (ushort)Math.Round(val * 65535f / sum); // TODO: if this is correct, Rx, Gx, Bx consts could be scaled by 65535f directly! + } + + /// + /// Packs the into a byte. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private byte PackedAsByte() + { + return (byte)(this.PackedValue >> 8); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/Gray8.cs b/src/ImageSharp/PixelFormats/Gray8.cs new file mode 100644 index 0000000000..db05395431 --- /dev/null +++ b/src/ImageSharp/PixelFormats/Gray8.cs @@ -0,0 +1,265 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Packed pixel type containing a single 8 bit normalized gray values. + /// + /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. + /// + /// + public struct Gray8 : IPixel, IPackedVector + { + /// + /// RX as in ITU-R recommendation 709 to match libpng + /// + private const float Rx = .2126F; + + /// + /// GX as in ITU-R recommendation 709 to match libpng + /// + private const float Gx = .7152F; + + /// + /// BX as in ITU-R recommendation 709 to match libpng + /// + private const float Bx = .0722F; + + /// + /// Initializes a new instance of the struct. + /// + /// The gray component + public Gray8(byte gray) + { + this.PackedValue = gray; + } + + /// + 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. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Gray8 left, Gray8 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. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Gray8 left, Gray8 right) + { + return left.PackedValue != right.PackedValue; + } + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromScaledVector4(Vector4 vector) + { + this.PackFromVector4(vector); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() + { + var scaledGray = this.PackedValue / 255f; + return new Vector4(scaledGray, scaledGray, scaledGray, 1.0f); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromVector4(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToVector4() + { + return new Vector4(this.PackedValue, this.PackedValue, this.PackedValue, 1.0f); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba32(Rgba32 source) + { + this.PackedValue = Pack(source.R, source.G, source.B); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromArgb32(Argb32 source) + { + this.PackedValue = Pack(source.R, source.G, source.B); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromBgra32(Bgra32 source) + { + this.PackedValue = Pack(source.R, source.G, source.B); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb24(ref Rgb24 dest) + { + dest.R = this.PackedValue; + dest.G = this.PackedValue; + dest.B = this.PackedValue; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = this.PackedValue; + dest.G = this.PackedValue; + dest.B = this.PackedValue; + dest.A = 255; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToArgb32(ref Argb32 dest) + { + dest.R = this.PackedValue; + dest.G = this.PackedValue; + dest.B = this.PackedValue; + dest.A = 255; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToBgr24(ref Bgr24 dest) + { + dest.R = this.PackedValue; + dest.G = this.PackedValue; + dest.B = this.PackedValue; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToBgra32(ref Bgra32 dest) + { + dest.R = this.PackedValue; + dest.G = this.PackedValue; + dest.B = this.PackedValue; + dest.A = 255; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => + this.PackedValue = Pack(source.R, source.G, source.B); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) + { + ushort gray = (ushort)(this.PackedValue * 255); + dest.R = gray; + dest.G = gray; + dest.B = gray; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => + this.PackFromScaledVector4(source.ToScaledVector4()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromGray8(Gray8 source) => this.PackedValue = source.PackedValue; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromGray16(Gray16 source) => this.PackedValue = (byte)(source.PackedValue / 255); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToGray8(ref Gray8 dest) => dest.PackedValue = this.PackedValue; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToGray16(ref Gray16 dest) => dest.PackedValue = (ushort)(this.PackedValue * 255); + + /// + /// 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 Gray8 other && this.Equals(other); + } + + /// + /// Compares another packed vector with the packed vector. + /// + /// The Gray8 packed vector to compare. + /// True if the packed vectors are equal. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Gray8 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(); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + /// Packs a into a byte. + /// + /// Red value of the color to pack. + /// Green value of the color to pack. + /// Blue value of the color to pack. + /// The containing the packed value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte Pack(float r, float g, float b) + { + float sum = r + g + b; + float val = (r * Rx) + (g * Gx) + (b * Bx); + return (byte)Math.Round(val * 255 / sum); // TODO: if this is correct, Rx, Gx, Bx consts could be scaled by 255 directly! + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/Gray8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Gray8Tests.cs new file mode 100644 index 0000000000..c4ce7339da --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/Gray8Tests.cs @@ -0,0 +1,211 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.PixelFormats +{ + public class Gray8Tests + { + [Theory] + [InlineData(0)] + [InlineData(255)] + [InlineData(10)] + [InlineData(42)] + public void Gray8_PackedValue_EqualsInput(byte input) + { + Assert.Equal(input, new Gray8(input).PackedValue); + } + + [Theory] + [InlineData(0)] + [InlineData(255)] + [InlineData(30)] + public void Gray8_ToVector4(byte input) + { + // arrange + var gray = new Gray8(input); + + // act + var actual = gray.ToVector4(); + + // assert + Assert.Equal(input, actual.X); + Assert.Equal(input, actual.Y); + Assert.Equal(input, actual.Z); + Assert.Equal(1, actual.W); + } + + [Theory] + [InlineData(0)] + [InlineData(255)] + [InlineData(30)] + public void Gray8_ToScaledVector4(byte input) + { + // arrange + var gray = new Gray8(input); + + // act + var actual = gray.ToScaledVector4(); + + // assert + float scaledInput = input / 255f; + Assert.Equal(scaledInput, actual.X); + Assert.Equal(scaledInput, actual.Y); + Assert.Equal(scaledInput, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Gray8_PackFromScaledVector4() + { + // arrange + Gray8 gray = default; + int expected = 128; + Vector4 scaled = new Gray8(128).ToScaledVector4(); + + // act + gray.PackFromScaledVector4(scaled); + byte actual = gray.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Gray8_PackFromScaledVector4_ToRgb24() + { + // arrange + Rgb24 actual = default; + Gray8 gray = default; + var expected = new Rgb24(0, 0, 0); + Vector4 scaled = new Gray8(128).ToScaledVector4(); + + // act + gray.PackFromScaledVector4(scaled); + gray.ToRgb24(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Gray8_PackFromScaledVector4_ToRgba32() + { + // arrange + Rgba32 actual = default; + Gray8 gray = default; + var expected = new Rgba32(0, 0, 0, 128); + Vector4 scaled = new Gray8(128).ToScaledVector4(); + + // act + gray.PackFromScaledVector4(scaled); + gray.ToRgba32(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Gray8_PackFromScaledVector4_ToBgr24() + { + // arrange + Bgr24 actual = default; + Gray8 gray = default; + var expected = new Bgr24(128, 128, 128); + Vector4 scaled = new Gray8(128).ToScaledVector4(); + + // act + gray.PackFromScaledVector4(scaled); + gray.ToBgr24(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Gray8_PackFromScaledVector4_ToBgra32() + { + // arrange + Bgra32 actual = default; + Gray8 gray = default; + var expected = new Bgra32(128,128,128); + Vector4 scaled = new Gray8(128).ToScaledVector4(); + + // act + gray.PackFromScaledVector4(scaled); + gray.ToBgra32(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Gray8_PackFromScaledVector4_ToArgb32() + { + // arrange + Gray8 gray = default; + Argb32 actual = default; + var expected = new Argb32(128, 128, 128); + Vector4 scaled = new Gray8(128).ToScaledVector4(); + + // act + gray.PackFromScaledVector4(scaled); + gray.ToArgb32(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Gray8_PackFromScaledVector4_ToRgba64() + { + // arrange + Gray8 gray = default; + Rgba64 actual = default; + var expected = new Rgba64(65535, 65535, 65535, 65535); + Vector4 scaled = new Gray8(255).ToScaledVector4(); + + // act + gray.PackFromScaledVector4(scaled); + gray.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Gray8_PackFromRgb48_ToRgb48() + { + // arrange + var gray = default(Gray8); + var actual = default(Rgb48); + var expected = new Rgb48(0, 0, 0); + + // act + gray.PackFromRgb48(expected); + gray.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Gray8_PackFromRgba64_ToRgba64() + { + // arrange + var gray = default(Gray8); + var actual = default(Rgba64); + var expected = new Rgba64(0, 0, 0, 65535); + + // act + gray.PackFromRgba64(expected); + gray.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } + } +}