From 1bd63b33b3aff7dccf7f58f9985a61607666e846 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 8 Oct 2018 19:00:01 +0100 Subject: [PATCH] Fix Gray8 --- src/ImageSharp/Common/Helpers/ImageMaths.cs | 67 +++++++ src/ImageSharp/PixelFormats/Alpha8.cs | 105 +++-------- src/ImageSharp/PixelFormats/Gray8.cs | 188 +++++++++----------- 3 files changed, 176 insertions(+), 184 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index c15e0a732..f9ade4cfe 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -13,6 +13,73 @@ namespace SixLabors.ImageSharp /// internal static class ImageMaths { + /// + /// Gets the luminance from the rgb components using the formula as specified by ITU-R Recommendation BT.709. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static byte GetBT709LuminanceBytes(byte r, byte g, byte b) => (byte)((r * .2126F) + (g * .7152F) + (b * .0722F)); + + /// + /// Gets the luminance from the rgb components using the formula as specified by ITU-R Recommendation BT.709. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static ushort GetBT709Luminance(ushort r, ushort g, ushort b) => (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F)); + + /// + /// Scales a value from a 16 bit to it's 8 bit equivalent. + /// + /// The 8 bit compoonent value. + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public static byte DownScaleFrom16BitTo8Bit(ushort component) + { + // To scale to 8 bits From a 16-bit value V the required value (from the PNG specification) is: + // + // (V * 255) / 65535 + // + // This reduces to round(V / 257), or floor((V + 128.5)/257) + // + // Represent V as the two byte value vhi.vlo. Make a guess that the + // result is the top byte of V, vhi, then the correction to this value + // is: + // + // error = floor(((V-vhi.vhi) + 128.5) / 257) + // = floor(((vlo-vhi) + 128.5) / 257) + // + // This can be approximated using integer arithmetic (and a signed + // shift): + // + // error = (vlo-vhi+128) >> 8; + // + // The approximate differs from the exact answer only when (vlo-vhi) is + // 128; it then gives a correction of +1 when the exact correction is + // 0. This gives 128 errors. The exact answer (correct for all 16-bit + // input values) is: + // + // error = (vlo-vhi+128)*65535 >> 24; + // + // An alternative arithmetic calculation which also gives no errors is: + // + // (V * 255 + 32895) >> 16 + return (byte)(((component * 255) + 32895) >> 16); + } + + /// + /// Scales a value from an 8 bit to it's 16 bit equivalent. + /// + /// The 8 bit compoonent value. + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public static ushort UpscaleFrom8BitTo16Bit(byte component) => (ushort)(component * 257); + /// /// Returns the absolute value of a 32-bit signed integer. Uses bit shifting to speed up the operation. /// diff --git a/src/ImageSharp/PixelFormats/Alpha8.cs b/src/ImageSharp/PixelFormats/Alpha8.cs index d795931af..177ce81ef 100644 --- a/src/ImageSharp/PixelFormats/Alpha8.cs +++ b/src/ImageSharp/PixelFormats/Alpha8.cs @@ -19,10 +19,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// Initializes a new instance of the struct. /// /// The alpha component - public Alpha8(float alpha) - { - this.PackedValue = Pack(alpha); - } + public Alpha8(float alpha) => this.PackedValue = Pack(alpha); /// public byte PackedValue { get; set; } @@ -40,10 +37,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// True if the parameter is equal to the parameter; otherwise, false. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Alpha8 left, Alpha8 right) - { - return left.PackedValue == right.PackedValue; - } + public static bool operator ==(Alpha8 left, Alpha8 right) => left.Equals(right); /// /// Compares two objects for equality. @@ -54,77 +48,48 @@ namespace SixLabors.ImageSharp.PixelFormats /// True if the parameter is not equal to the parameter; otherwise, false. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Alpha8 left, Alpha8 right) - { - return left.PackedValue != right.PackedValue; - } + public static bool operator !=(Alpha8 left, Alpha8 right) => !left.Equals(right); /// public PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PackFromScaledVector4(Vector4 vector) - { - this.PackFromVector4(vector); - } + public void PackFromScaledVector4(Vector4 vector) => this.PackFromVector4(vector); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() - { - return this.ToVector4(); - } + public Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PackFromVector4(Vector4 vector) - { - this.PackedValue = Pack(vector.W); - } + public void PackFromVector4(Vector4 vector) => this.PackedValue = Pack(vector.W); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() - { - return new Vector4(0, 0, 0, this.PackedValue / 255F); - } + public Vector4 ToVector4() => new Vector4(0, 0, 0, this.PackedValue / 255F); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PackFromRgba32(Rgba32 source) - { - this.PackedValue = source.A; - } + public void PackFromRgba32(Rgba32 source) => this.PackedValue = source.A; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PackFromArgb32(Argb32 source) - { - this.PackedValue = source.A; - } + public void PackFromArgb32(Argb32 source) => this.PackedValue = source.A; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PackFromBgra32(Bgra32 source) - { - this.PackedValue = source.A; - } + public void PackFromBgra32(Bgra32 source) => this.PackedValue = source.A; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgb24(ref Rgb24 dest) - { - dest = default(Rgb24); - } + public void ToRgb24(ref Rgb24 dest) => dest = default; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToRgba32(ref Rgba32 dest) { - dest.R = 0; - dest.G = 0; - dest.B = 0; + dest.Rgb = default; dest.A = this.PackedValue; } @@ -140,10 +105,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToBgr24(ref Bgr24 dest) - { - dest = default(Bgr24); - } + public void ToBgr24(ref Bgr24 dest) => dest = default; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -161,28 +123,23 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgb48(ref Rgb48 dest) - { - dest.R = 0; - dest.G = 0; - dest.B = 0; - } + public void ToRgb48(ref Rgb48 dest) => dest = default; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PackFromGray8(Gray8 source) => this.PackedValue = 255; + public void PackFromGray8(Gray8 source) => this.PackedValue = byte.MaxValue; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToGray8(ref Gray8 dest) => dest.PackedValue = 0; + public void ToGray8(ref Gray8 dest) => dest = default; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PackFromGray16(Gray16 source) => this.PackedValue = 255; + public void PackFromGray16(Gray16 source) => this.PackedValue = byte.MaxValue; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToGray16(ref Gray16 dest) => dest.PackedValue = 0; + public void ToGray16(ref Gray16 dest) => dest = default; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -190,17 +147,18 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + public void ToRgba64(ref Rgba64 dest) + { + dest.Rgb = default; + dest.A = ImageMaths.UpscaleFrom8BitTo16Bit(this.PackedValue); + } /// /// 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 other && this.Equals(other); - } + public override bool Equals(object obj) => obj is Alpha8 other && this.Equals(other); /// /// Compares another Alpha8 packed vector with the packed vector. @@ -208,19 +166,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// The Alpha8 packed vector to compare. /// True if the packed vectors are equal. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Alpha8 other) - { - return this.PackedValue == other.PackedValue; - } + public bool Equals(Alpha8 other) => this.PackedValue.Equals(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 string ToString() => (this.PackedValue / 255F).ToString(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -232,9 +184,6 @@ namespace SixLabors.ImageSharp.PixelFormats /// The float containing the value to pack. /// The containing the packed values. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte Pack(float alpha) - { - return (byte)Math.Round(alpha.Clamp(0, 1) * 255F); - } + private static byte Pack(float alpha) => (byte)Math.Round(alpha.Clamp(0, 1) * 255F); } } \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/Gray8.cs b/src/ImageSharp/PixelFormats/Gray8.cs index 916e199d0..0a41a6ecc 100644 --- a/src/ImageSharp/PixelFormats/Gray8.cs +++ b/src/ImageSharp/PixelFormats/Gray8.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -30,14 +29,21 @@ namespace SixLabors.ImageSharp.PixelFormats /// private const float Bx = .0722F; + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new Vector4(255F); + + /// + /// The half vector value. + /// + private static readonly Vector4 Half = new Vector4(0.5F); + /// /// Initializes a new instance of the struct. /// /// The gray component - public Gray8(byte gray) - { - this.PackedValue = gray; - } + public Gray8(byte gray) => this.PackedValue = gray; /// public byte PackedValue { get; set; } @@ -45,20 +51,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// Compares two objects for equality. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// + /// 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; - } + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Gray8 left, Gray8 right) => left.PackedValue.Equals(right.PackedValue); /// /// Compares two objects for equality. @@ -68,67 +67,54 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// 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; - } + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Gray8 left, Gray8 right) => !left.PackedValue.Equals(right.PackedValue); /// public PixelOperations CreatePixelOperations() => new PixelOperations(); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PackFromScaledVector4(Vector4 vector) - { - this.PackFromVector4(vector); - } + [MethodImpl(InliningOptions.ShortMethod)] + 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(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() => this.ToVector4(); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public void PackFromVector4(Vector4 vector) { - this.PackedValue = Pack(vector.X, vector.Y, vector.Z); + vector *= MaxBytes; + vector += Half; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + float luminance = (vector.X * Rx) + (vector.Y * Gx) + (vector.Z * Bx); + + this.PackedValue = (byte)luminance; } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public Vector4 ToVector4() { - return new Vector4(this.PackedValue, this.PackedValue, this.PackedValue, 1.0f); + float rgb = this.PackedValue / 255F; + return new Vector4(rgb, rgb, rgb, 1F); } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PackFromRgba32(Rgba32 source) - { - this.PackedValue = Pack(source.R, source.G, source.B); - } + [MethodImpl(InliningOptions.ShortMethod)] + public void PackFromRgba32(Rgba32 source) => this.PackedValue = ImageMaths.GetBT709LuminanceBytes(source.R, source.G, source.B); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PackFromArgb32(Argb32 source) - { - this.PackedValue = Pack(source.R, source.G, source.B); - } + [MethodImpl(InliningOptions.ShortMethod)] + public void PackFromArgb32(Argb32 source) => this.PackedValue = ImageMaths.GetBT709LuminanceBytes(source.R, source.G, source.B); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PackFromBgra32(Bgra32 source) - { - this.PackedValue = Pack(source.R, source.G, source.B); - } + [MethodImpl(InliningOptions.ShortMethod)] + public void PackFromBgra32(Bgra32 source) => this.PackedValue = ImageMaths.GetBT709LuminanceBytes(source.R, source.G, source.B); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public void ToRgb24(ref Rgb24 dest) { dest.R = this.PackedValue; @@ -137,27 +123,27 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public void ToRgba32(ref Rgba32 dest) { dest.R = this.PackedValue; dest.G = this.PackedValue; dest.B = this.PackedValue; - dest.A = 255; + dest.A = byte.MaxValue; } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public void ToArgb32(ref Argb32 dest) { dest.R = this.PackedValue; dest.G = this.PackedValue; dest.B = this.PackedValue; - dest.A = 255; + dest.A = byte.MaxValue; } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public void ToBgr24(ref Bgr24 dest) { dest.R = this.PackedValue; @@ -166,101 +152,91 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public void ToBgra32(ref Bgra32 dest) { dest.R = this.PackedValue; dest.G = this.PackedValue; dest.B = this.PackedValue; - dest.A = 255; + dest.A = byte.MaxValue; } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PackFromRgb48(Rgb48 source) => - this.PackedValue = Pack(source.R, source.G, source.B); + [MethodImpl(InliningOptions.ShortMethod)] + public void PackFromRgb48(Rgb48 source) + => this.PackedValue = ImageMaths.GetBT709LuminanceBytes( + ImageMaths.DownScaleFrom16BitTo8Bit(source.R), + ImageMaths.DownScaleFrom16BitTo8Bit(source.G), + ImageMaths.DownScaleFrom16BitTo8Bit(source.B)); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public void ToRgb48(ref Rgb48 dest) { - ushort gray = (ushort)(this.PackedValue * 255); - dest.R = gray; - dest.G = gray; - dest.B = gray; + ushort luminance = ImageMaths.UpscaleFrom8BitTo16Bit(this.PackedValue); + dest.R = luminance; + dest.G = luminance; + dest.B = luminance; } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PackFromRgba64(Rgba64 source) => - this.PackFromScaledVector4(source.ToScaledVector4()); + [MethodImpl(InliningOptions.ShortMethod)] + public void PackFromRgba64(Rgba64 source) + => this.PackedValue = ImageMaths.GetBT709LuminanceBytes( + ImageMaths.DownScaleFrom16BitTo8Bit(source.R), + ImageMaths.DownScaleFrom16BitTo8Bit(source.G), + ImageMaths.DownScaleFrom16BitTo8Bit(source.B)); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public void PackFromGray8(Gray8 source) => this.PackedValue = source.PackedValue; /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PackFromGray16(Gray16 source) => this.PackedValue = (byte)(source.PackedValue / 255); + [MethodImpl(InliningOptions.ShortMethod)] + public void PackFromGray16(Gray16 source) => this.PackedValue = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba64(ref Rgba64 dest) + { + ushort luminance = ImageMaths.UpscaleFrom8BitTo16Bit(this.PackedValue); + dest.R = luminance; + dest.G = luminance; + dest.B = luminance; + dest.A = ushort.MaxValue; + } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] 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); + [MethodImpl(InliningOptions.ShortMethod)] + public void ToGray16(ref Gray16 dest) => dest.PackedValue = ImageMaths.UpscaleFrom8BitTo16Bit(this.PackedValue); /// /// 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); - } + public override bool Equals(object obj) => 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; - } + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Gray8 other) => this.PackedValue.Equals(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 string ToString() => $"Gray8({this.PackedValue}"; /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] 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 val = (r * Rx) + (g * Gx) + (b * Bx); - return (byte)Math.Round(val * 255); - } } } \ No newline at end of file