// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Png { /// /// This class contains reference implementations to produce verification data for unit tests /// internal static partial class ReferenceImplementations { /// /// Encodes the scanline /// /// The scanline to encode /// The previous scanline. /// The filtered scanline result. /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void EncodePaethFilter(ReadOnlySpan scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); sum = 0; // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) resultBaseRef = 4; int x = 0; for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) { byte scan = Unsafe.Add(ref scanBaseRef, x); byte above = Unsafe.Add(ref prevBaseRef, x); ++x; ref byte res = ref Unsafe.Add(ref resultBaseRef, x); res = (byte)(scan - PaethPredictor(0, above, 0)); sum += Numerics.Abs(unchecked((sbyte)res)); } for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) { byte scan = Unsafe.Add(ref scanBaseRef, x); byte left = Unsafe.Add(ref scanBaseRef, xLeft); byte above = Unsafe.Add(ref prevBaseRef, x); byte upperLeft = Unsafe.Add(ref prevBaseRef, xLeft); ++x; ref byte res = ref Unsafe.Add(ref resultBaseRef, x); res = (byte)(scan - PaethPredictor(left, above, upperLeft)); sum += Numerics.Abs(unchecked((sbyte)res)); } sum -= 4; } /// /// Encodes the scanline /// /// The scanline to encode /// The filtered scanline result. /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void EncodeSubFilter(ReadOnlySpan scanline, Span result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); sum = 0; // Sub(x) = Raw(x) - Raw(x-bpp) resultBaseRef = 1; int x = 0; for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) { byte scan = Unsafe.Add(ref scanBaseRef, x); ++x; ref byte res = ref Unsafe.Add(ref resultBaseRef, x); res = scan; sum += Numerics.Abs(unchecked((sbyte)res)); } for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) { byte scan = Unsafe.Add(ref scanBaseRef, x); byte prev = Unsafe.Add(ref scanBaseRef, xLeft); ++x; ref byte res = ref Unsafe.Add(ref resultBaseRef, x); res = (byte)(scan - prev); sum += Numerics.Abs(unchecked((sbyte)res)); } sum -= 1; } /// /// Encodes the scanline /// /// The scanline to encode /// The previous scanline. /// The filtered scanline result. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void EncodeUpFilter(ReadOnlySpan scanline, Span previousScanline, Span result, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); sum = 0; // Up(x) = Raw(x) - Prior(x) resultBaseRef = 2; int x = 0; for (; x < scanline.Length; /* Note: ++x happens in the body to avoid one add operation */) { byte scan = Unsafe.Add(ref scanBaseRef, x); byte above = Unsafe.Add(ref prevBaseRef, x); ++x; ref byte res = ref Unsafe.Add(ref resultBaseRef, x); res = (byte)(scan - above); sum += Numerics.Abs(unchecked((sbyte)res)); } sum -= 2; } /// /// Encodes the scanline /// /// The scanline to encode /// The previous scanline. /// The filtered scanline result. /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void EncodeAverageFilter(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); sum = 0; // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) resultBaseRef = 3; int x = 0; for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) { byte scan = Unsafe.Add(ref scanBaseRef, x); byte above = Unsafe.Add(ref prevBaseRef, x); ++x; ref byte res = ref Unsafe.Add(ref resultBaseRef, x); res = (byte)(scan - (above >> 1)); sum += Numerics.Abs(unchecked((sbyte)res)); } for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) { byte scan = Unsafe.Add(ref scanBaseRef, x); byte left = Unsafe.Add(ref scanBaseRef, xLeft); byte above = Unsafe.Add(ref prevBaseRef, x); ++x; ref byte res = ref Unsafe.Add(ref resultBaseRef, x); res = (byte)(scan - Average(left, above)); sum += Numerics.Abs(unchecked((sbyte)res)); } sum -= 3; } /// /// Calculates the average value of two bytes /// /// The left byte /// The above byte /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Average(byte left, byte above) => (left + above) >> 1; /// /// Computes a simple linear function of the three neighboring pixels (left, above, upper left), then chooses /// as predictor the neighboring pixel closest to the computed value. /// /// The left neighbor pixel. /// The above neighbor pixel. /// The upper left neighbor pixel. /// /// The . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte PaethPredictor(byte left, byte above, byte upperLeft) { int p = left + above - upperLeft; int pa = Numerics.Abs(p - left); int pb = Numerics.Abs(p - above); int pc = Numerics.Abs(p - upperLeft); if (pa <= pb && pa <= pc) { return left; } if (pb <= pc) { return above; } return upperLeft; } } }