diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index ef457f7ce..a0ce62f68 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -23,6 +23,7 @@ namespace SixLabors.ImageSharp private const int ShuffleAlphaControl = 0b_11_11_11_11; #endif + // TODO: Obsolete - remove #if !SUPPORTS_BITOPERATIONS /// /// Gets the counts the number of bits needed to hold an integer. @@ -45,6 +46,16 @@ namespace SixLabors.ImageSharp }; #endif +#if !SUPPORTS_BITOPERATIONS + private static ReadOnlySpan Log2DeBruijn => new byte[32] + { + 00, 09, 01, 10, 13, 21, 02, 29, + 11, 14, 16, 18, 22, 25, 03, 30, + 08, 12, 20, 28, 15, 17, 24, 07, + 19, 27, 23, 06, 26, 05, 04, 31 + }; +#endif + /// /// Determine the Greatest CommonDivisor (GCD) of two numbers. /// @@ -868,5 +879,52 @@ namespace SixLabors.ImageSharp return bitInUnsignedInteger - BitOperations.LeadingZeroCount(number); #endif } + + /// + /// Calculates floored log of the specified value, base 2. + /// Note that by convention, input value 0 returns 0 since Log(0) is undefined. + /// + /// The value. + public static int Log2(uint value) + { +#if SUPPORTS_BITOPERATIONS + return BitOperations.Log2(value); +#else + return Log2SoftwareFallback(value); +#endif + } + +#if !SUPPORTS_BITOPERATIONS + /// + /// Calculates floored log of the specified value, base 2. + /// Note that by convention, input value 0 returns 0 since Log(0) is undefined. + /// Bit hacking with deBruijn sequence, extremely fast yet does not use any intrinsics so should work on every platform. + /// + /// + /// Description of this bit hacking can be found here: + /// https://cstheory.stackexchange.com/questions/19524/using-the-de-bruijn-sequence-to-find-the-lceil-log-2-v-rceil-of-an-integer + /// + /// The value. + private static int Log2SoftwareFallback(uint value) + { + // No AggressiveInlining due to large method size + // Has conventional contract 0->0 (Log(0) is undefined) by default, no need for if checking + + + // Fill trailing zeros with ones, eg 00010010 becomes 00011111 + value |= value >> 01; + value |= value >> 02; + value |= value >> 04; + value |= value >> 08; + value |= value >> 16; + + // uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check + return Unsafe.AddByteOffset( + ref MemoryMarshal.GetReference(Log2DeBruijn), + + // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here + (IntPtr)(int)((value * 0x07C4ACDDu) >> 27)); + } +#endif } }