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);
+ }
+ }
+}