diff --git a/HPPH.Benchmark/HPPH.Benchmark.csproj b/HPPH.Benchmark/HPPH.Benchmark.csproj new file mode 100644 index 0000000..d519e0a --- /dev/null +++ b/HPPH.Benchmark/HPPH.Benchmark.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/HPPH.Benchmark/Program.cs b/HPPH.Benchmark/Program.cs new file mode 100644 index 0000000..a751bf5 --- /dev/null +++ b/HPPH.Benchmark/Program.cs @@ -0,0 +1 @@ +Console.WriteLine("Empty"); \ No newline at end of file diff --git a/HPPH.DotSettings b/HPPH.DotSettings new file mode 100644 index 0000000..8860971 --- /dev/null +++ b/HPPH.DotSettings @@ -0,0 +1,3 @@ + + BGR + BGRA \ No newline at end of file diff --git a/HPPH.Generators/ColorFormatData.cs b/HPPH.Generators/ColorFormatData.cs new file mode 100644 index 0000000..02625b7 --- /dev/null +++ b/HPPH.Generators/ColorFormatData.cs @@ -0,0 +1,50 @@ +using System.Text; + +namespace HPPH.Generators; + +internal readonly struct ColorFormatData(string typeName, int bpp, char firstEntry, char secondEntry, char thirdEntry, char fourthEntry) +{ + public readonly string TypeName = typeName; + public readonly int Bpp = bpp; + public readonly string FirstEntry = firstEntry.ToString().ToLowerInvariant(); + public readonly string SecondEntry = secondEntry.ToString().ToLowerInvariant(); + public readonly string ThirdEntry = thirdEntry.ToString().ToLowerInvariant(); + public readonly string FourthEntry = fourthEntry.ToString().ToLowerInvariant(); + + public string FirstEntryName => GetEntryName(FirstEntry); + public string SecondEntryName => GetEntryName(SecondEntry); + public string ThirdEntryName => GetEntryName(ThirdEntry); + public string FourthEntryName => GetEntryName(FourthEntry); + + public string Format + { + get + { + StringBuilder sb = new(); + + if (!string.IsNullOrWhiteSpace(FirstEntry)) + sb.Append(FirstEntry); + + if (!string.IsNullOrWhiteSpace(SecondEntry)) + sb.Append(SecondEntry); + + if (!string.IsNullOrWhiteSpace(ThirdEntry)) + sb.Append(ThirdEntry); + + if (!string.IsNullOrWhiteSpace(FourthEntry)) + sb.Append(FourthEntry); + + return sb.ToString().ToUpper(); + } + } + + private static string GetEntryName(string entry) + => entry switch + { + "a" => "Alpha", + "r" => "Red", + "g" => "Green", + "b" => "Blue", + _ => string.Empty + }; +} \ No newline at end of file diff --git a/HPPH.Generators/ColorFormatSourceGenerator.cs b/HPPH.Generators/ColorFormatSourceGenerator.cs new file mode 100644 index 0000000..42e7b72 --- /dev/null +++ b/HPPH.Generators/ColorFormatSourceGenerator.cs @@ -0,0 +1,90 @@ +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace HPPH.Generators; + +[Generator(LanguageNames.CSharp)] +public class ColorSourceGenerator : IIncrementalGenerator +{ + #region Properties & Fields + + private static readonly Regex COLOR_FORMAT_REGEX = new("^Color(?[ARGB]{3,4})$", RegexOptions.Compiled); + + private static readonly IGeneratorFeature[] FEATURES = + [ + new Colors(), + new Average(), + new MinMax(), + new Sum(), + new Quantize() + ]; + + private const string COLOR_GENERATOR_ATTRIBUTE_SOURCE = """ + namespace HPPH; + + [AttributeUsage(AttributeTargets.Struct)] + internal class ColorGeneratorAttribute : Attribute; + """; + + #endregion + + #region Methods + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // if (!Debugger.IsAttached) + // Debugger.Launch(); + + context.RegisterPostInitializationOutput(ctx => ctx.AddSource("ColorGeneratorAttribute.g.cs", SourceText.From(COLOR_GENERATOR_ATTRIBUTE_SOURCE, Encoding.UTF8))); + + IncrementalValueProvider> classes = context.SyntaxProvider + .ForAttributeWithMetadataName("HPPH.ColorGeneratorAttribute", static (_, __) => true, Transform) + .Where(type => type.HasValue) + .Select((data, _) => data!.Value) + .Collect(); + context.RegisterSourceOutput(classes, GenerateCode); + } + + private static ColorFormatData? Transform(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) + { + if (context.TargetNode is not StructDeclarationSyntax structDeclaration) return null; + if (context.SemanticModel.GetDeclaredSymbol(structDeclaration, cancellationToken) is not ITypeSymbol type) return null; + + Match m = COLOR_FORMAT_REGEX.Match(type.Name); + if (!m.Success) return null; + + string colorFormat = m.Groups["colorFormat"].Value; + if (colorFormat.Distinct().Count() != colorFormat.Length) return null; + + return new ColorFormatData(type.Name, + colorFormat.Length, + colorFormat.Length > 0 ? colorFormat[0] : ' ', + colorFormat.Length > 1 ? colorFormat[1] : ' ', + colorFormat.Length > 2 ? colorFormat[2] : ' ', + colorFormat.Length > 3 ? colorFormat[3] : ' '); + } + + private static void GenerateCode(SourceProductionContext context, ImmutableArray colorFormats) + { + if (colorFormats.IsDefaultOrEmpty) return; + + foreach (ColorFormatData formatData in colorFormats) + { + foreach (IGeneratorFeature feature in FEATURES) + foreach ((string name, string source) in feature.GenerateFor(formatData)) + context.AddSource($"{name}.g.cs", SourceText.From(source, Encoding.UTF8)); + } + + foreach (IGeneratorFeature feature in FEATURES) + foreach ((string name, string source) in feature.GenerateFor(colorFormats)) + context.AddSource($"{name}.g.cs", SourceText.From(source, Encoding.UTF8)); + } + + #endregion +} \ No newline at end of file diff --git a/HPPH.Generators/ColorSortData.cs b/HPPH.Generators/ColorSortData.cs new file mode 100644 index 0000000..730c498 --- /dev/null +++ b/HPPH.Generators/ColorSortData.cs @@ -0,0 +1,3 @@ +namespace HPPH.Generators; + +internal record struct ColorSortData(string Namespace, string Class, string ClassModifiers, string Signature, string DataTypeName, string SortValueName); \ No newline at end of file diff --git a/HPPH.Generators/ColorSortSourceGenerator.cs b/HPPH.Generators/ColorSortSourceGenerator.cs new file mode 100644 index 0000000..c05c75d --- /dev/null +++ b/HPPH.Generators/ColorSortSourceGenerator.cs @@ -0,0 +1,135 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace HPPH.Generators; + +[Generator(LanguageNames.CSharp)] +public class ColorSortSourceGenerator : IIncrementalGenerator +{ + #region Properties & Fields + + private const string SORT_GENERATOR_ATTRIBUTE_SOURCE = """ + namespace HPPH; + + [AttributeUsage(AttributeTargets.Method)] + internal class ColorSortGeneratorAttribute(string dataTypeName, string sortValueName) : Attribute + { + public string DataTypeName { get; } = dataTypeName; + public string SortValueName { get; } = sortValueName; + } + """; + + #endregion + + #region Methods + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + //if (!Debugger.IsAttached) + // Debugger.Launch(); + + context.RegisterPostInitializationOutput(ctx => ctx.AddSource("ColorSortGeneratorAttribute.g.cs", SourceText.From(SORT_GENERATOR_ATTRIBUTE_SOURCE, Encoding.UTF8))); + + IncrementalValueProvider> classes = context.SyntaxProvider + .ForAttributeWithMetadataName("HPPH.ColorSortGeneratorAttribute", static (_, __) => true, Transform) + .Where(type => type.HasValue) + .Select((data, _) => data!.Value) + .Collect(); + context.RegisterSourceOutput(classes, GenerateCode); + } + + private static ColorSortData? Transform(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) + { + if (context.TargetNode is not MethodDeclarationSyntax methodDeclaration) return null; + if (context.SemanticModel.GetDeclaredSymbol(methodDeclaration, cancellationToken) is not IMethodSymbol method) return null; + if (!method.IsPartialDefinition) return null; + + foreach (AttributeData attribute in method.GetAttributes()) + { + if (attribute.AttributeClass?.Name != "ColorSortGeneratorAttribute") + continue; + + if (!attribute.ConstructorArguments.IsEmpty) + { + ImmutableArray args = attribute.ConstructorArguments; + if (args.Any(arg => arg.Kind == TypedConstantKind.Error)) break; + + return new ColorSortData(method.ContainingNamespace.Name, method.ContainingType.Name, ((ClassDeclarationSyntax)methodDeclaration.Parent!).Modifiers.ToString(), $"{methodDeclaration.Modifiers} {methodDeclaration.ReturnType} {methodDeclaration.Identifier}{methodDeclaration.TypeParameterList}{methodDeclaration.ParameterList} {methodDeclaration.ConstraintClauses}", args[0].Value!.ToString(), args[1].Value!.ToString()); + } + } + + return null; + } + + private static void GenerateCode(SourceProductionContext context, ImmutableArray colorSorts) + { + if (colorSorts.IsDefaultOrEmpty) return; + + Dictionary<(string, string, string), string> sourceMapping = colorSorts.Select(x => (x.Namespace, x.Class, x.ClassModifiers)).Distinct().ToDictionary(x => x, _ => string.Empty); + + foreach (ColorSortData colorSort in colorSorts) + sourceMapping[(colorSort.Namespace, colorSort.Class, colorSort.ClassModifiers)] += GenerateSortMethodSource(colorSort); + + foreach (KeyValuePair<(string @namespace, string @class, string classModifier), string> data in sourceMapping) + { + context.AddSource($"{data.Key.@class}.g.cs", SourceText.From($$""" + using System.Buffers; + + namespace {{data.Key.@namespace}}; + + {{data.Key.classModifier}} class {{data.Key.@class}} + { + {{data.Value}} + } + """, Encoding.UTF8)); + } + } + + private static string GenerateSortMethodSource(ColorSortData colorSort) + => $$""" + {{colorSort.Signature}} + { + fixed ({{colorSort.DataTypeName}}* ptr = colors) + { + {{colorSort.DataTypeName}}* end = ptr + colors.Length; + + Span histogram = stackalloc int[256]; + histogram.Clear(); + for ({{colorSort.DataTypeName}}* color = ptr; color < end; color++) + histogram[(*color).{{colorSort.SortValueName}}]++; + + {{colorSort.DataTypeName}}[] bucketsArray = ArrayPool<{{colorSort.DataTypeName}}>.Shared.Rent(colors.Length); + try + { + Span<{{colorSort.DataTypeName}}> buckets = bucketsArray.AsSpan()[..colors.Length]; + Span currentBucketIndex = stackalloc int[256]; + + int offset = 0; + for (int i = 0; i < histogram.Length; i++) + { + currentBucketIndex[i] = offset; + offset += histogram[i]; + } + + for ({{colorSort.DataTypeName}}* color = ptr; color < end; color++) + buckets[currentBucketIndex[(*color).{{colorSort.SortValueName}}]++] = (*color); + + buckets.CopyTo(colors); + } + finally + { + ArrayPool<{{colorSort.DataTypeName}}>.Shared.Return(bucketsArray); + } + } + } + + """; + + #endregion +} \ No newline at end of file diff --git a/HPPH.Generators/Features/Average.cs b/HPPH.Generators/Features/Average.cs new file mode 100644 index 0000000..0441165 --- /dev/null +++ b/HPPH.Generators/Features/Average.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace HPPH.Generators; + +internal class Average : IGeneratorFeature +{ + public IEnumerable<(string name, string source)> GenerateFor(ColorFormatData colorFormat) + { + yield return ($"ColorFormat{colorFormat.Format}.Average", GenerateColorFormatAverage(colorFormat)); + } + + public IEnumerable<(string name, string source)> GenerateFor(ImmutableArray colorFormats) + { + yield return ("IColorFormat.Average", GenerateColorFormatInterfaceAverage()); + } + + private static string GenerateColorFormatAverage(ColorFormatData colorFormat) + { + return $$""" + using System.Runtime.InteropServices; + + namespace HPPH; + + public sealed partial class ColorFormat{{colorFormat.Format}} + { + #region Methods + + unsafe IColor IColorFormat.Average(ReadOnlySpan data) => PixelHelper.Average(MemoryMarshal.Cast(data)); + + #endregion + } + """; + } + + private static string GenerateColorFormatInterfaceAverage() + { + return """ + namespace HPPH; + + public partial interface IColorFormat + { + internal IColor Average(ReadOnlySpan data); + } + """; + } +} \ No newline at end of file diff --git a/HPPH.Generators/Features/Colors.cs b/HPPH.Generators/Features/Colors.cs new file mode 100644 index 0000000..d60bc18 --- /dev/null +++ b/HPPH.Generators/Features/Colors.cs @@ -0,0 +1,200 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Text; + +namespace HPPH.Generators; + +internal class Colors : IGeneratorFeature +{ + public IEnumerable<(string name, string source)> GenerateFor(ColorFormatData colorFormat) + { + string colorStructCode = GenerateColorStructCode(colorFormat); + if (!string.IsNullOrWhiteSpace(colorStructCode)) + { + yield return (colorFormat.TypeName, colorStructCode!); + yield return ($"ColorFormat{colorFormat.Format}", GenerateColorFormatCode(colorFormat)); + } + } + + public IEnumerable<(string name, string source)> GenerateFor(ImmutableArray colorFormats) + { + yield return ("IColorFormat.Instances.cs", GenerateColorFormats(colorFormats)); + } + + private static string GenerateColorStructCode(ColorFormatData colorFormat) + => colorFormat.Bpp switch + { + 3 => $$""" + // ReSharper disable ConvertToAutoProperty + // ReSharper disable ConvertToAutoPropertyWhenPossible + // ReSharper disable ReplaceWithPrimaryConstructorParameter + + using System.Diagnostics; + using System.Runtime.InteropServices; + + namespace HPPH; + + /// + /// Represents a color in 24 bit {{colorFormat.Format}}-format. + /// + /// + /// Initializes a new instance of the class. + /// + /// The {{colorFormat.FirstEntryName}}-component of the color. + /// The {{colorFormat.SecondEntryName}}-component of the color. + /// The {{colorFormat.ThirdEntryName}}-component of the color. + [DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] + [StructLayout(LayoutKind.Sequential)] + public readonly partial struct {{colorFormat.TypeName}}(byte {{colorFormat.FirstEntry}}, byte {{colorFormat.SecondEntry}}, byte {{colorFormat.ThirdEntry}}): IColor + { + #region Properties & Fields + + /// + public static IColorFormat ColorFormat => IColorFormat.{{colorFormat.Format}}; + + private readonly byte _{{colorFormat.FirstEntry}} = {{colorFormat.FirstEntry}}; + private readonly byte _{{colorFormat.SecondEntry}} = {{colorFormat.SecondEntry}}; + private readonly byte _{{colorFormat.ThirdEntry}} = {{colorFormat.ThirdEntry}}; + + /// + public byte R => _r; + + /// + public byte G => _g; + + /// + public byte B => _b; + + /// + public byte A => byte.MaxValue; + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; + + /// + public static IColor Create(byte r, byte g, byte b, byte a) => new {{colorFormat.TypeName}}({{colorFormat.FirstEntry}}, {{colorFormat.SecondEntry}}, {{colorFormat.ThirdEntry}}); + + #endregion + } + + """, + + 4 => $$""" + // ReSharper disable ConvertToAutoProperty + // ReSharper disable ConvertToAutoPropertyWhenPossible + // ReSharper disable ReplaceWithPrimaryConstructorParameter + + using System.Diagnostics; + using System.Runtime.InteropServices; + + namespace HPPH; + + /// + /// Represents a color in 32 bit {{colorFormat.Format}}-format. + /// + /// + /// Initializes a new instance of the class. + /// + /// The {{colorFormat.FirstEntryName}}-component of the color. + /// The {{colorFormat.SecondEntryName}}-component of the color. + /// The {{colorFormat.ThirdEntryName}}-component of the color. + /// The {{colorFormat.FourthEntryName}}-component of the color. + [DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] + [StructLayout(LayoutKind.Sequential)] + public readonly partial struct {{colorFormat.TypeName}}(byte {{colorFormat.FirstEntry}}, byte {{colorFormat.SecondEntry}}, byte {{colorFormat.ThirdEntry}}, byte {{colorFormat.FourthEntry}}) : IColor + { + #region Properties & Fields + + /// + public static IColorFormat ColorFormat => IColorFormat.{{colorFormat.Format}}; + + private readonly byte _{{colorFormat.FirstEntry}} = {{colorFormat.FirstEntry}}; + private readonly byte _{{colorFormat.SecondEntry}} = {{colorFormat.SecondEntry}}; + private readonly byte _{{colorFormat.ThirdEntry}} = {{colorFormat.ThirdEntry}}; + private readonly byte _{{colorFormat.FourthEntry}} = {{colorFormat.FourthEntry}}; + + /// + public byte R => _r; + + /// + public byte G => _g; + + /// + public byte B => _b; + + /// + public byte A => _a; + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; + + /// + public static IColor Create(byte r, byte g, byte b, byte a) => new {{colorFormat.TypeName}}({{colorFormat.FirstEntry}}, {{colorFormat.SecondEntry}}, {{colorFormat.ThirdEntry}}, {{colorFormat.FourthEntry}}); + + #endregion + } + + """, + _ => null + }; + + private static string GenerateColorFormatCode(ColorFormatData colorFormat) + { + return $$""" + namespace HPPH; + + public sealed partial class ColorFormat{{colorFormat.Format}} : IColorFormat + { + #region Properties & Fields + + public static ColorFormat{{colorFormat.Format}} Instance { get; } = new(); + + public int BytesPerPixel => {{colorFormat.Bpp}}; + + public string Name => "{{colorFormat.Format}}"; + + #endregion + + #region Constructors + + private ColorFormat{{colorFormat.Format}}() {} + + #endregion + } + """; + } + + private static string GenerateColorFormats(ImmutableArray colorFormats) + { + StringBuilder sb = new(); + sb.AppendLine(""" + namespace HPPH; + + public partial interface IColorFormat + { + #region Instances + """); + + sb.AppendLine(); + + foreach (ColorFormatData colorFormat in colorFormats) + sb.AppendLine($" public static ColorFormat{colorFormat.Format} {colorFormat.Format} => ColorFormat{colorFormat.Format}.Instance;"); + + sb.AppendLine(); + + sb.AppendLine(""" + #endregion + } + """); + + return sb.ToString(); + } +} \ No newline at end of file diff --git a/HPPH.Generators/Features/IGeneratorFeature.cs b/HPPH.Generators/Features/IGeneratorFeature.cs new file mode 100644 index 0000000..9c7a8e2 --- /dev/null +++ b/HPPH.Generators/Features/IGeneratorFeature.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace HPPH.Generators; + +internal interface IGeneratorFeature +{ + public IEnumerable<(string name, string source)> GenerateFor(ColorFormatData colorFormat); + public IEnumerable<(string name, string source)> GenerateFor(ImmutableArray colorFormats); +} \ No newline at end of file diff --git a/HPPH.Generators/Features/MinMax.cs b/HPPH.Generators/Features/MinMax.cs new file mode 100644 index 0000000..c84aeda --- /dev/null +++ b/HPPH.Generators/Features/MinMax.cs @@ -0,0 +1,162 @@ +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace HPPH.Generators; + +internal class MinMax : IGeneratorFeature +{ + public IEnumerable<(string name, string source)> GenerateFor(ColorFormatData colorFormat) + { + yield return ($"MinMax{colorFormat.Format}", GenerateMinMaxStruct(colorFormat)); + yield return ($"ColorFormat{colorFormat.Format}.MinMax", GenerateColorFormatMinMax(colorFormat)); + } + + public IEnumerable<(string name, string source)> GenerateFor(ImmutableArray colorFormats) + { + yield return ("IColorFormat.MinMax", GenerateColorFormatInterfaceMinMax()); + } + + private static string GenerateMinMaxStruct(ColorFormatData colorFormat) + => colorFormat.Bpp switch + { + 3 => $$""" + // ReSharper disable ConvertToAutoProperty + // ReSharper disable ReplaceWithPrimaryConstructorParameter + + using System.Diagnostics; + using System.Runtime.InteropServices; + + namespace HPPH; + + [DebuggerDisplay("[A: {AlphaMin}-{AlphaMax}, R: {RedMin}-{RedMax}, G: {GreenMin}-{GreenMax}, B: {BlueMin}-{BlueMax}]")] + [StructLayout(LayoutKind.Sequential)] + public readonly partial struct MinMax{{colorFormat.Format}}(byte {{colorFormat.FirstEntry}}Min, byte {{colorFormat.FirstEntry}}Max, byte {{colorFormat.SecondEntry}}Min, byte {{colorFormat.SecondEntry}}Max, byte {{colorFormat.ThirdEntry}}Min, byte {{colorFormat.ThirdEntry}}Max) : IMinMax + { + #region Properties & Fields + + private readonly byte _{{colorFormat.FirstEntry}}Min = {{colorFormat.FirstEntry}}Min; + private readonly byte _{{colorFormat.FirstEntry}}Max = {{colorFormat.FirstEntry}}Max; + + private readonly byte _{{colorFormat.SecondEntry}}Min = {{colorFormat.SecondEntry}}Min; + private readonly byte _{{colorFormat.SecondEntry}}Max = {{colorFormat.SecondEntry}}Max; + + private readonly byte _{{colorFormat.ThirdEntry}}Min = {{colorFormat.ThirdEntry}}Min; + private readonly byte _{{colorFormat.ThirdEntry}}Max = {{colorFormat.ThirdEntry}}Max; + + public byte RedMin => _rMin; + public byte RedMax => _rMax; + + public byte GreenMin => _gMin; + public byte GreenMax => _gMax; + + public byte BlueMin => _bMin; + public byte BlueMax => _bMax; + + public byte AlphaMin => byte.MaxValue; + public byte AlphaMax => byte.MaxValue; + + public byte RedRange => (byte)(_rMax - _rMin); + public byte GreenRange => (byte)(_gMax - _gMin); + public byte BlueRange => (byte)(_bMax - _bMin); + public byte AlphaRange => 0; + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {AlphaMin}-{AlphaMax}, R: {RedMin}-{RedMax}, G: {GreenMin}-{GreenMax}, B: {BlueMin}-{BlueMax}]"; + + #endregion + } + + """, + + 4 => $$""" + // ReSharper disable ConvertToAutoProperty + // ReSharper disable ReplaceWithPrimaryConstructorParameter + + using System.Diagnostics; + using System.Runtime.InteropServices; + + namespace HPPH; + + [DebuggerDisplay("[A: {AlphaMin}-{AlphaMax}, R: {RedMin}-{RedMax}, G: {GreenMin}-{GreenMax}, B: {BlueMin}-{BlueMax}]")] + [StructLayout(LayoutKind.Sequential)] + public readonly partial struct MinMax{{colorFormat.Format}}(byte {{colorFormat.FirstEntry}}Min, byte {{colorFormat.FirstEntry}}Max, byte {{colorFormat.SecondEntry}}Min, byte {{colorFormat.SecondEntry}}Max, byte {{colorFormat.ThirdEntry}}Min, byte {{colorFormat.ThirdEntry}}Max, byte {{colorFormat.FourthEntry}}Min, byte {{colorFormat.FourthEntry}}Max) : IMinMax + { + #region Properties & Fields + + private readonly byte _{{colorFormat.FirstEntry}}Min = {{colorFormat.FirstEntry}}Min; + private readonly byte _{{colorFormat.FirstEntry}}Max = {{colorFormat.FirstEntry}}Max; + + private readonly byte _{{colorFormat.SecondEntry}}Min = {{colorFormat.SecondEntry}}Min; + private readonly byte _{{colorFormat.SecondEntry}}Max = {{colorFormat.SecondEntry}}Max; + + private readonly byte _{{colorFormat.ThirdEntry}}Min = {{colorFormat.ThirdEntry}}Min; + private readonly byte _{{colorFormat.ThirdEntry}}Max = {{colorFormat.ThirdEntry}}Max; + + private readonly byte _{{colorFormat.FourthEntry}}Min = {{colorFormat.FourthEntry}}Min; + private readonly byte _{{colorFormat.FourthEntry}}Max = {{colorFormat.FourthEntry}}Max; + + public byte RedMin => _rMin; + public byte RedMax => _rMax; + + public byte GreenMin => _gMin; + public byte GreenMax => _gMax; + + public byte BlueMin => _bMin; + public byte BlueMax => _bMax; + + public byte AlphaMin => _aMin; + public byte AlphaMax => _aMax; + + public byte RedRange => (byte)(_rMax - _rMin); + public byte GreenRange => (byte)(_gMax - _gMin); + public byte BlueRange => (byte)(_bMax - _bMin); + public byte AlphaRange => (byte)(_aMax - _aMin); + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {AlphaMin}-{AlphaMax}, R: {RedMin}-{RedMax}, G: {GreenMin}-{GreenMax}, B: {BlueMin}-{BlueMax}]"; + + #endregion + } + + """, + _ => null + }; + + private static string GenerateColorFormatMinMax(ColorFormatData colorFormat) + { + return $$""" + using System.Runtime.InteropServices; + + namespace HPPH; + + public sealed partial class ColorFormat{{colorFormat.Format}} + { + #region Methods + + unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + + #endregion + } + """; + } + + private static string GenerateColorFormatInterfaceMinMax() + { + return """ + namespace HPPH; + + public partial interface IColorFormat + { + internal IMinMax MinMax(ReadOnlySpan data); + } + """; + } +} \ No newline at end of file diff --git a/HPPH.Generators/Features/Quantize.cs b/HPPH.Generators/Features/Quantize.cs new file mode 100644 index 0000000..c0b6224 --- /dev/null +++ b/HPPH.Generators/Features/Quantize.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace HPPH.Generators; + +internal class Quantize : IGeneratorFeature +{ + public IEnumerable<(string name, string source)> GenerateFor(ColorFormatData colorFormat) + { + yield return ($"ColorFormat{colorFormat.Format}.Quantize", GenerateColorFormatQuantize(colorFormat)); + } + + public IEnumerable<(string name, string source)> GenerateFor(ImmutableArray colorFormats) + { + yield return ("IColorFormat.Quantize", GenerateColorFormatInterfaceQuantize()); + } + + private static string GenerateColorFormatQuantize(ColorFormatData colorFormat) + { + return $$""" + using System.Runtime.InteropServices; + + namespace HPPH; + + public sealed partial class ColorFormat{{colorFormat.Format}} + { + #region Methods + + unsafe IColor[] IColorFormat.CreateColorPalette(ReadOnlySpan data, int paletteSize) + { + Color{{colorFormat.Format}}[] colors = PixelHelper.CreateColorPalette(MemoryMarshal.Cast(data), paletteSize); + + IColor[] result = new IColor[colors.Length]; + for(int i = 0; i < colors.Length; i++) + result[i] = colors[i]; + + return result; + } + + #endregion + } + """; + } + + private static string GenerateColorFormatInterfaceQuantize() + { + return """ + namespace HPPH; + + public partial interface IColorFormat + { + internal IColor[] CreateColorPalette(ReadOnlySpan data, int paletteSize); + } + """; + } +} \ No newline at end of file diff --git a/HPPH.Generators/Features/Sum.cs b/HPPH.Generators/Features/Sum.cs new file mode 100644 index 0000000..0b869bb --- /dev/null +++ b/HPPH.Generators/Features/Sum.cs @@ -0,0 +1,129 @@ +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace HPPH.Generators; + +internal class Sum : IGeneratorFeature +{ + public IEnumerable<(string name, string source)> GenerateFor(ColorFormatData colorFormat) + { + yield return ($"Sum{colorFormat.Format}", GenerateSumStruct(colorFormat)); + yield return ($"ColorFormat{colorFormat.Format}.Sum", GenerateColorFormatSum(colorFormat)); + } + + public IEnumerable<(string name, string source)> GenerateFor(ImmutableArray colorFormats) + { + yield return ("IColorFormat.Sum", GenerateColorFormatInterfaceSum()); + } + + private static string GenerateSumStruct(ColorFormatData colorFormat) + => colorFormat.Bpp switch + { + 3 => $$""" + // ReSharper disable ConvertToAutoProperty + // ReSharper disable ConvertToAutoPropertyWhenPossible + // ReSharper disable ReplaceWithPrimaryConstructorParameter + + using System.Diagnostics; + using System.Runtime.InteropServices; + + namespace HPPH; + + [DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] + [StructLayout(LayoutKind.Sequential)] + public readonly partial struct Sum{{colorFormat.Format}}(long {{colorFormat.FirstEntry}}, long {{colorFormat.SecondEntry}}, long {{colorFormat.ThirdEntry}}, long a) : ISum + { + #region Properties & Fields + + private readonly long _{{colorFormat.FirstEntry}} = {{colorFormat.FirstEntry}}; + private readonly long _{{colorFormat.SecondEntry}} = {{colorFormat.SecondEntry}}; + private readonly long _{{colorFormat.ThirdEntry}} = {{colorFormat.ThirdEntry}}; + private readonly long _a = a; + + public long A => _a; + public long R => _r; + public long G => _g; + public long B => _b; + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; + + #endregion + } + + """, + + 4 => $$""" + // ReSharper disable ConvertToAutoProperty + // ReSharper disable ConvertToAutoPropertyWhenPossible + // ReSharper disable ReplaceWithPrimaryConstructorParameter + + using System.Diagnostics; + using System.Runtime.InteropServices; + + namespace HPPH; + + [DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] + [StructLayout(LayoutKind.Sequential)] + public readonly partial struct Sum{{colorFormat.Format}}(long {{colorFormat.FirstEntry}}, long {{colorFormat.SecondEntry}}, long {{colorFormat.ThirdEntry}}, long {{colorFormat.FourthEntry}}) : ISum + { + #region Properties & Fields + + private readonly long _{{colorFormat.FirstEntry}} = {{colorFormat.FirstEntry}}; + private readonly long _{{colorFormat.SecondEntry}} = {{colorFormat.SecondEntry}}; + private readonly long _{{colorFormat.ThirdEntry}} = {{colorFormat.ThirdEntry}}; + private readonly long _{{colorFormat.FourthEntry}} = {{colorFormat.FourthEntry}}; + + public long R => _r; + public long G => _g; + public long B => _b; + public long A => _a; + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; + + #endregion + } + + """, + _ => null + }; + + private static string GenerateColorFormatSum(ColorFormatData colorFormat) + { + return $$""" + using System.Runtime.InteropServices; + + namespace HPPH; + + public sealed partial class ColorFormat{{colorFormat.Format}} + { + #region Methods + + unsafe ISum IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + + #endregion + } + """; + } + + private static string GenerateColorFormatInterfaceSum() + { + return """ + namespace HPPH; + + public partial interface IColorFormat + { + internal ISum Sum(ReadOnlySpan data); + } + """; + } +} \ No newline at end of file diff --git a/HPPH.Generators/HPPH.Generators.csproj b/HPPH.Generators/HPPH.Generators.csproj new file mode 100644 index 0000000..18611e6 --- /dev/null +++ b/HPPH.Generators/HPPH.Generators.csproj @@ -0,0 +1,21 @@ + + + + netstandard2.0 + true + true + true + false + true + latest + True + False + True + True + + + + + + + diff --git a/HPPH.Generators/HPPH.Generators.csproj.DotSettings b/HPPH.Generators/HPPH.Generators.csproj.DotSettings new file mode 100644 index 0000000..3a36d17 --- /dev/null +++ b/HPPH.Generators/HPPH.Generators.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/HPPH.Reference/HPPH.Reference.csproj b/HPPH.Reference/HPPH.Reference.csproj new file mode 100644 index 0000000..69fb405 --- /dev/null +++ b/HPPH.Reference/HPPH.Reference.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/HPPH.Reference/HPPHExtensions.cs b/HPPH.Reference/HPPHExtensions.cs new file mode 100644 index 0000000..8212d6d --- /dev/null +++ b/HPPH.Reference/HPPHExtensions.cs @@ -0,0 +1,14 @@ +namespace HPPH.Reference; + +internal static class HPPHExtensions +{ + public static byte GetByteValueFromPercentage(this float percentage) + { + if (float.IsNaN(percentage) || (percentage < 0)) return 0; + + return (byte)(percentage >= 1.0f ? 255 : percentage * 256.0f); + } + + public static float GetPercentageFromByteValue(this byte value) + => value == 255 ? 1.0f : (value / 256.0f); +} \ No newline at end of file diff --git a/HPPH.Reference/PixelHelper.Average.cs b/HPPH.Reference/PixelHelper.Average.cs new file mode 100644 index 0000000..5544155 --- /dev/null +++ b/HPPH.Reference/PixelHelper.Average.cs @@ -0,0 +1,43 @@ +namespace HPPH.Reference; + +public static partial class ReferencePixelHelper +{ + #region Methods + + public static IColor Average(IImage image) + { + float count = image.Width * image.Height; + + ISum sum = Sum(image); + return new ColorRGBA((sum.R / count).GetByteValueFromPercentage(), + (sum.G / count).GetByteValueFromPercentage(), + (sum.B / count).GetByteValueFromPercentage(), + (sum.A / count).GetByteValueFromPercentage()); + } + + public static T Average(RefImage image) + where T : struct, IColor + { + float count = image.Width * image.Height; + + ISum sum = Sum(image); + return (T)T.Create((sum.R / count).GetByteValueFromPercentage(), + (sum.G / count).GetByteValueFromPercentage(), + (sum.B / count).GetByteValueFromPercentage(), + (sum.A / count).GetByteValueFromPercentage()); + } + + public static T Average(ReadOnlySpan colors) + where T : struct, IColor + { + float count = colors.Length; + + ISum sum = Sum(colors); + return (T)T.Create((sum.R / count).GetByteValueFromPercentage(), + (sum.G / count).GetByteValueFromPercentage(), + (sum.B / count).GetByteValueFromPercentage(), + (sum.A / count).GetByteValueFromPercentage()); + } + + #endregion +} diff --git a/HPPH.Reference/PixelHelper.MinMax.cs b/HPPH.Reference/PixelHelper.MinMax.cs new file mode 100644 index 0000000..80ce107 --- /dev/null +++ b/HPPH.Reference/PixelHelper.MinMax.cs @@ -0,0 +1,73 @@ +namespace HPPH.Reference; + +public static partial class ReferencePixelHelper +{ + #region Methods + + public static IMinMax MinMax(IImage image) + { + byte minR = byte.MaxValue, minG = byte.MaxValue, minB = byte.MaxValue, minA = byte.MaxValue; + byte maxR = byte.MinValue, maxG = byte.MinValue, maxB = byte.MinValue, maxA = byte.MinValue; + + foreach (IColor color in image) + { + minR = Math.Min(minR, color.R); + minG = Math.Min(minG, color.G); + minB = Math.Min(minB, color.B); + minA = Math.Min(minA, color.A); + + maxR = Math.Max(maxR, color.R); + maxG = Math.Max(maxG, color.G); + maxB = Math.Max(maxB, color.B); + maxA = Math.Max(maxA, color.A); + } + + return new MinMaxRGBA(minR, maxR, minG, maxG, minB, maxB, minA, maxA); + } + + public static IMinMax MinMax(RefImage image) + where T : struct, IColor + { + byte minR = byte.MaxValue, minG = byte.MaxValue, minB = byte.MaxValue, minA = byte.MaxValue; + byte maxR = byte.MinValue, maxG = byte.MinValue, maxB = byte.MinValue, maxA = byte.MinValue; + + foreach (T color in image) + { + minR = Math.Min(minR, color.R); + minG = Math.Min(minG, color.G); + minB = Math.Min(minB, color.B); + minA = Math.Min(minA, color.A); + + maxR = Math.Max(maxR, color.R); + maxG = Math.Max(maxG, color.G); + maxB = Math.Max(maxB, color.B); + maxA = Math.Max(maxA, color.A); + } + + return new MinMaxRGBA(minR, maxR, minG, maxG, minB, maxB, minA, maxA); + } + + public static IMinMax MinMax(ReadOnlySpan colors) + where T : struct, IColor + { + byte minR = byte.MaxValue, minG = byte.MaxValue, minB = byte.MaxValue, minA = byte.MaxValue; + byte maxR = byte.MinValue, maxG = byte.MinValue, maxB = byte.MinValue, maxA = byte.MinValue; + + foreach (T color in colors) + { + minR = Math.Min(minR, color.R); + minG = Math.Min(minG, color.G); + minB = Math.Min(minB, color.B); + minA = Math.Min(minA, color.A); + + maxR = Math.Max(maxR, color.R); + maxG = Math.Max(maxG, color.G); + maxB = Math.Max(maxB, color.B); + maxA = Math.Max(maxA, color.A); + } + + return new MinMaxRGBA(minR, maxR, minG, maxG, minB, maxB, minA, maxA); + } + + #endregion +} diff --git a/HPPH.Reference/PixelHelper.Quantize.cs b/HPPH.Reference/PixelHelper.Quantize.cs new file mode 100644 index 0000000..7a5e7ee --- /dev/null +++ b/HPPH.Reference/PixelHelper.Quantize.cs @@ -0,0 +1,8 @@ +namespace HPPH.Reference; + +public static partial class ReferencePixelHelper +{ + #region Methods + + #endregion +} diff --git a/HPPH.Reference/PixelHelper.Sort.cs b/HPPH.Reference/PixelHelper.Sort.cs new file mode 100644 index 0000000..9952f3f --- /dev/null +++ b/HPPH.Reference/PixelHelper.Sort.cs @@ -0,0 +1,26 @@ +namespace HPPH.Reference; + +public static partial class ReferencePixelHelper +{ + #region Methods + + // DarthAffe 05.07.2024: LINQ OrderBy uses a stable sorting algorithm -> it's a good reference as the optimized sort is supposed to be stable. + + public static void SortByRed(Span colors) + where T : unmanaged, IColor + => colors.ToArray().OrderBy(x => x.R).ToArray().AsSpan().CopyTo(colors); + + public static void SortByGreen(Span colors) + where T : unmanaged, IColor + => colors.ToArray().OrderBy(x => x.G).ToArray().AsSpan().CopyTo(colors); + + public static void SortByBlue(Span colors) + where T : unmanaged, IColor + => colors.ToArray().OrderBy(x => x.B).ToArray().AsSpan().CopyTo(colors); + + public static void SortByAlpha(Span colors) + where T : unmanaged, IColor + => colors.ToArray().OrderBy(x => x.A).ToArray().AsSpan().CopyTo(colors); + + #endregion +} diff --git a/HPPH.Reference/PixelHelper.Sum.cs b/HPPH.Reference/PixelHelper.Sum.cs new file mode 100644 index 0000000..6ea5e38 --- /dev/null +++ b/HPPH.Reference/PixelHelper.Sum.cs @@ -0,0 +1,55 @@ +namespace HPPH.Reference; + +public static partial class ReferencePixelHelper +{ + #region Methods + + public static ISum Sum(IImage image) + { + long sumR = 0, sumG = 0, sumB = 0, sumA = 0; + + foreach (IColor color in image) + { + sumR += color.R; + sumG += color.G; + sumB += color.B; + sumA += color.A; + } + + return new SumRGBA(sumR, sumG, sumB, sumA); + } + + public static ISum Sum(RefImage image) + where T : struct, IColor + { + long sumR = 0, sumG = 0, sumB = 0, sumA = 0; + + foreach (T color in image) + { + sumR += color.R; + sumG += color.G; + sumB += color.B; + sumA += color.A; + } + + return new SumRGBA(sumR, sumG, sumB, sumA); + } + + public static ISum Sum(ReadOnlySpan colors) + where T : struct, IColor + { + long sumR = 0, sumG = 0, sumB = 0, sumA = 0; + + foreach (T color in colors) + { + sumR += color.R; + sumG += color.G; + sumB += color.B; + sumA += color.A; + } + + return new SumRGBA(sumR, sumG, sumB, sumA); + } + + #endregion +} diff --git a/HPPH.Reference/PixelHelper.cs b/HPPH.Reference/PixelHelper.cs new file mode 100644 index 0000000..faee3b8 --- /dev/null +++ b/HPPH.Reference/PixelHelper.cs @@ -0,0 +1,3 @@ +namespace HPPH.Reference; + +public static partial class ReferencePixelHelper; diff --git a/HPPH.Test/AverageTests.cs b/HPPH.Test/AverageTests.cs new file mode 100644 index 0000000..fbc375a --- /dev/null +++ b/HPPH.Test/AverageTests.cs @@ -0,0 +1,65 @@ +using HPPH.Reference; + +namespace HPPH.Test; + +[TestClass] +public class AverageTests +{ + private static IEnumerable GetTestImages() => Directory.EnumerateFiles(@"..\..\..\..\sample_data", "*.png", SearchOption.AllDirectories); + + [TestMethod] + public void AverageImage3Byte() + { + } + + [TestMethod] + public void AverageRefImage3Byte() + { + } + + [TestMethod] + public void AverageReadOnlySpan3Byte() + { + foreach (string image in GetTestImages()) + { + ColorRGB[] data = ImageHelper.Get3ByteColorsFromImage(image); + ReadOnlySpan span = data; + + ColorRGB reference = ReferencePixelHelper.Average(span); + ColorRGB test = PixelHelper.Average(span); + + Assert.AreEqual(reference.R, test.R, "R differs"); + Assert.AreEqual(reference.G, test.G, "G differs"); + Assert.AreEqual(reference.B, test.B, "B differs"); + Assert.AreEqual(reference.A, test.A, "A differs"); + } + } + + [TestMethod] + public void AverageImage4Byte() + { + } + + [TestMethod] + public void AverageRefImage4Byte() + { + } + + [TestMethod] + public void AverageReadOnlySpan4Byte() + { + foreach (string image in GetTestImages()) + { + ColorRGBA[] data = ImageHelper.Get4ByteColorsFromImage(image); + ReadOnlySpan span = data; + + ColorRGBA reference = ReferencePixelHelper.Average(span); + ColorRGBA test = PixelHelper.Average(span); + + Assert.AreEqual(reference.R, test.R, "R differs"); + Assert.AreEqual(reference.G, test.G, "G differs"); + Assert.AreEqual(reference.B, test.B, "B differs"); + Assert.AreEqual(reference.A, test.A, "A differs"); + } + } +} \ No newline at end of file diff --git a/HPPH.Test/HPPH.Test.csproj b/HPPH.Test/HPPH.Test.csproj new file mode 100644 index 0000000..9ff21f8 --- /dev/null +++ b/HPPH.Test/HPPH.Test.csproj @@ -0,0 +1,29 @@ + + + + net8.0-windows + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + diff --git a/HPPH.Test/ImageHelper.cs b/HPPH.Test/ImageHelper.cs new file mode 100644 index 0000000..f779dd7 --- /dev/null +++ b/HPPH.Test/ImageHelper.cs @@ -0,0 +1,44 @@ +using System.Drawing; + +namespace HPPH.Test; + +internal static class ImageHelper +{ + #region Methods + + public static ColorRGB[] Get3ByteColorsFromImage(string file) + { + using FileStream stream = File.OpenRead(file); + using Bitmap bmp = new(stream); + + ColorRGB[] colors = new ColorRGB[bmp.Width * bmp.Height]; + int i = 0; + for (int x = 0; x < bmp.Width; x++) + for (int y = 0; y < bmp.Height; y++) + { + Color color = bmp.GetPixel(x, y); + colors[i++] = new ColorRGB(color.R, color.G, color.B); + } + + return colors; + } + + public static ColorRGBA[] Get4ByteColorsFromImage(string file) + { + using FileStream stream = File.OpenRead(file); + using Bitmap bmp = new(stream); + + ColorRGBA[] colors = new ColorRGBA[bmp.Width * bmp.Height]; + int i = 0; + for (int x = 0; x < bmp.Width; x++) + for (int y = 0; y < bmp.Height; y++) + { + Color color = bmp.GetPixel(x, y); + colors[i++] = new ColorRGBA(color.R, color.G, color.B, color.A); + } + + return colors; + } + + #endregion +} \ No newline at end of file diff --git a/HPPH.Test/MinMaxTests.cs b/HPPH.Test/MinMaxTests.cs new file mode 100644 index 0000000..5f3ecb8 --- /dev/null +++ b/HPPH.Test/MinMaxTests.cs @@ -0,0 +1,85 @@ +using HPPH.Reference; + +namespace HPPH.Test; + +[TestClass] +public class MinMaxTests +{ + private static IEnumerable GetTestImages() => Directory.EnumerateFiles(@"..\..\..\..\sample_data", "*.png", SearchOption.AllDirectories); + + [TestMethod] + public void MinMaxImage3Byte() + { + } + + [TestMethod] + public void MinMaxRefImage3Byte() + { + } + + [TestMethod] + public void MinMaxReadOnlySpan3Byte() + { + foreach (string image in GetTestImages()) + { + ColorRGB[] data = ImageHelper.Get3ByteColorsFromImage(image); + ReadOnlySpan span = data; + + IMinMax reference = ReferencePixelHelper.MinMax(span); + IMinMax test = PixelHelper.MinMax(span); + + Assert.AreEqual(reference.RedMin, test.RedMin, "RedMin differs"); + Assert.AreEqual(reference.GreenMin, test.GreenMin, "GreenMin differs"); + Assert.AreEqual(reference.BlueMin, test.BlueMin, "BlueMin differs"); + Assert.AreEqual(reference.AlphaMin, test.AlphaMin, "AlphaMin differs"); + + Assert.AreEqual(reference.RedMax, test.RedMax, "RedMax differs"); + Assert.AreEqual(reference.GreenMax, test.GreenMax, "GreenMax differs"); + Assert.AreEqual(reference.BlueMax, test.BlueMax, "BlueMax differs"); + Assert.AreEqual(reference.AlphaMax, test.AlphaMax, "AlphaMax differs"); + + Assert.AreEqual(reference.RedRange, test.RedRange, "RedRange differs"); + Assert.AreEqual(reference.GreenRange, test.GreenRange, "GreenRange differs"); + Assert.AreEqual(reference.BlueRange, test.BlueRange, "BlueRange differs"); + Assert.AreEqual(reference.AlphaRange, test.AlphaRange, "AlphaRange differs"); + } + } + + [TestMethod] + public void MinMaxImage4Byte() + { + } + + [TestMethod] + public void MinMaxRefImage4Byte() + { + } + + [TestMethod] + public void MinMaxReadOnlySpan4Byte() + { + foreach (string image in GetTestImages()) + { + ColorRGBA[] data = ImageHelper.Get4ByteColorsFromImage(image); + ReadOnlySpan span = data; + + IMinMax reference = ReferencePixelHelper.MinMax(span); + IMinMax test = PixelHelper.MinMax(span); + + Assert.AreEqual(reference.RedMin, test.RedMin, "RedMin differs"); + Assert.AreEqual(reference.GreenMin, test.GreenMin, "GreenMin differs"); + Assert.AreEqual(reference.BlueMin, test.BlueMin, "BlueMin differs"); + Assert.AreEqual(reference.AlphaMin, test.AlphaMin, "AlphaMin differs"); + + Assert.AreEqual(reference.RedMax, test.RedMax, "RedMax differs"); + Assert.AreEqual(reference.GreenMax, test.GreenMax, "GreenMax differs"); + Assert.AreEqual(reference.BlueMax, test.BlueMax, "BlueMax differs"); + Assert.AreEqual(reference.AlphaMax, test.AlphaMax, "AlphaMax differs"); + + Assert.AreEqual(reference.RedRange, test.RedRange, "RedRange differs"); + Assert.AreEqual(reference.GreenRange, test.GreenRange, "GreenRange differs"); + Assert.AreEqual(reference.BlueRange, test.BlueRange, "BlueRange differs"); + Assert.AreEqual(reference.AlphaRange, test.AlphaRange, "AlphaRange differs"); + } + } +} \ No newline at end of file diff --git a/HPPH.Test/SortTests.cs b/HPPH.Test/SortTests.cs new file mode 100644 index 0000000..e41159a --- /dev/null +++ b/HPPH.Test/SortTests.cs @@ -0,0 +1,143 @@ +using HPPH.Reference; + +namespace HPPH.Test; + +[TestClass] +public class SortTests +{ + private static IEnumerable GetTestImages() => Directory.EnumerateFiles(@"..\..\..\..\sample_data", "*.png", SearchOption.AllDirectories); + + + [TestMethod] + public void SortByRed3Byte() + { + foreach (string image in GetTestImages()) + { + ColorRGB[] referenceData = ImageHelper.Get3ByteColorsFromImage(image); + ColorRGB[] testData = new ColorRGB[referenceData.Length]; + Span referenceSpan = referenceData; + Span testSpan = testData; + referenceSpan.CopyTo(testSpan); + + ReferencePixelHelper.SortByRed(referenceSpan); + PixelHelper.SortByRed(testSpan); + + for (int i = 0; i < referenceData.Length; i++) + Assert.AreEqual(referenceSpan[i], testSpan[i], $"Index {i} differs"); + } + } + + [TestMethod] + public void SortByGreen3Byte() + { + foreach (string image in GetTestImages()) + { + ColorRGB[] referenceData = ImageHelper.Get3ByteColorsFromImage(image); + ColorRGB[] testData = new ColorRGB[referenceData.Length]; + Span referenceSpan = referenceData; + Span testSpan = testData; + referenceSpan.CopyTo(testSpan); + + ReferencePixelHelper.SortByGreen(referenceSpan); + PixelHelper.SortByGreen(testSpan); + + for (int i = 0; i < referenceData.Length; i++) + Assert.AreEqual(referenceSpan[i], testSpan[i], $"Index {i} differs"); + } + } + + [TestMethod] + public void SortByBlue3Byte() + { + foreach (string image in GetTestImages()) + { + ColorRGB[] referenceData = ImageHelper.Get3ByteColorsFromImage(image); + ColorRGB[] testData = new ColorRGB[referenceData.Length]; + Span referenceSpan = referenceData; + Span testSpan = testData; + referenceSpan.CopyTo(testSpan); + + ReferencePixelHelper.SortByBlue(referenceSpan); + PixelHelper.SortByBlue(testSpan); + + for (int i = 0; i < referenceData.Length; i++) + Assert.AreEqual(referenceSpan[i], testSpan[i], $"Index {i} differs"); + } + } + + [TestMethod] + public void SortByRed4Byte() + { + foreach (string image in GetTestImages()) + { + ColorRGBA[] referenceData = ImageHelper.Get4ByteColorsFromImage(image); + ColorRGBA[] testData = new ColorRGBA[referenceData.Length]; + Span referenceSpan = referenceData; + Span testSpan = testData; + referenceSpan.CopyTo(testSpan); + + ReferencePixelHelper.SortByRed(referenceSpan); + PixelHelper.SortByRed(testSpan); + + for (int i = 0; i < referenceData.Length; i++) + Assert.AreEqual(referenceSpan[i], testSpan[i], $"Index {i} differs"); + } + } + + [TestMethod] + public void SortByGreen4Byte() + { + foreach (string image in GetTestImages()) + { + ColorRGBA[] referenceData = ImageHelper.Get4ByteColorsFromImage(image); + ColorRGBA[] testData = new ColorRGBA[referenceData.Length]; + Span referenceSpan = referenceData; + Span testSpan = testData; + referenceSpan.CopyTo(testSpan); + + ReferencePixelHelper.SortByGreen(referenceSpan); + PixelHelper.SortByGreen(testSpan); + + for (int i = 0; i < referenceData.Length; i++) + Assert.AreEqual(referenceSpan[i], testSpan[i], $"Index {i} differs"); + } + } + + [TestMethod] + public void SortByBlue4Byte() + { + foreach (string image in GetTestImages()) + { + ColorRGBA[] referenceData = ImageHelper.Get4ByteColorsFromImage(image); + ColorRGBA[] testData = new ColorRGBA[referenceData.Length]; + Span referenceSpan = referenceData; + Span testSpan = testData; + referenceSpan.CopyTo(testSpan); + + ReferencePixelHelper.SortByBlue(referenceSpan); + PixelHelper.SortByBlue(testSpan); + + for (int i = 0; i < referenceData.Length; i++) + Assert.AreEqual(referenceSpan[i], testSpan[i], $"Index {i} differs"); + } + } + + [TestMethod] + public void SortByAlpha4Byte() + { + foreach (string image in GetTestImages()) + { + ColorRGBA[] referenceData = ImageHelper.Get4ByteColorsFromImage(image); + ColorRGBA[] testData = new ColorRGBA[referenceData.Length]; + Span referenceSpan = referenceData; + Span testSpan = testData; + referenceSpan.CopyTo(testSpan); + + ReferencePixelHelper.SortByAlpha(referenceSpan); + PixelHelper.SortByAlpha(testSpan); + + for (int i = 0; i < referenceData.Length; i++) + Assert.AreEqual(referenceSpan[i], testSpan[i], $"Index {i} differs"); + } + } +} \ No newline at end of file diff --git a/HPPH.Test/SumTests.cs b/HPPH.Test/SumTests.cs new file mode 100644 index 0000000..d227e5e --- /dev/null +++ b/HPPH.Test/SumTests.cs @@ -0,0 +1,65 @@ +using HPPH.Reference; + +namespace HPPH.Test; + +[TestClass] +public class SumTests +{ + private static IEnumerable GetTestImages() => Directory.EnumerateFiles(@"..\..\..\..\sample_data", "*.png", SearchOption.AllDirectories); + + [TestMethod] + public void SumImage3Byte() + { + } + + [TestMethod] + public void SumRefImage3Byte() + { + } + + [TestMethod] + public void SumReadOnlySpan3Byte() + { + foreach (string image in GetTestImages()) + { + ColorRGB[] data = ImageHelper.Get3ByteColorsFromImage(image); + ReadOnlySpan span = data; + + ISum reference = ReferencePixelHelper.Sum(span); + ISum test = PixelHelper.Sum(span); + + Assert.AreEqual(reference.R, test.R, "R differs"); + Assert.AreEqual(reference.G, test.G, "G differs"); + Assert.AreEqual(reference.B, test.B, "B differs"); + Assert.AreEqual(reference.A, test.A, "A differs"); + } + } + + [TestMethod] + public void SumImage4Byte() + { + } + + [TestMethod] + public void SumRefImage4Byte() + { + } + + [TestMethod] + public void SumReadOnlySpan4Byte() + { + foreach (string image in GetTestImages()) + { + ColorRGBA[] data = ImageHelper.Get4ByteColorsFromImage(image); + ReadOnlySpan span = data; + + ISum reference = ReferencePixelHelper.Sum(span); + ISum test = PixelHelper.Sum(span); + + Assert.AreEqual(reference.R, test.R, "R differs"); + Assert.AreEqual(reference.G, test.G, "G differs"); + Assert.AreEqual(reference.B, test.B, "B differs"); + Assert.AreEqual(reference.A, test.A, "A differs"); + } + } +} \ No newline at end of file diff --git a/HPPH.sln b/HPPH.sln new file mode 100644 index 0000000..67c72eb --- /dev/null +++ b/HPPH.sln @@ -0,0 +1,49 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34714.143 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HPPH", "HPPH\HPPH.csproj", "{FDC79AD8-26FF-4EA4-B1FF-7D4818F57A79}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HPPH.Test", "HPPH.Test\HPPH.Test.csproj", "{11795808-7916-4EAA-A69A-B09FEF46F71D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HPPH.Benchmark", "HPPH.Benchmark\HPPH.Benchmark.csproj", "{233CFF7C-DDA4-4450-AB88-17C1B58EBFB5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HPPH.Reference", "HPPH.Reference\HPPH.Reference.csproj", "{1675FE68-1F51-4202-9567-BB46215B2BBD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HPPH.Generators", "HPPH.Generators\HPPH.Generators.csproj", "{C247512B-E6D2-4591-8AFA-F2268F1AEAB2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FDC79AD8-26FF-4EA4-B1FF-7D4818F57A79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FDC79AD8-26FF-4EA4-B1FF-7D4818F57A79}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FDC79AD8-26FF-4EA4-B1FF-7D4818F57A79}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FDC79AD8-26FF-4EA4-B1FF-7D4818F57A79}.Release|Any CPU.Build.0 = Release|Any CPU + {11795808-7916-4EAA-A69A-B09FEF46F71D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11795808-7916-4EAA-A69A-B09FEF46F71D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11795808-7916-4EAA-A69A-B09FEF46F71D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {11795808-7916-4EAA-A69A-B09FEF46F71D}.Release|Any CPU.Build.0 = Release|Any CPU + {233CFF7C-DDA4-4450-AB88-17C1B58EBFB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {233CFF7C-DDA4-4450-AB88-17C1B58EBFB5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {233CFF7C-DDA4-4450-AB88-17C1B58EBFB5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {233CFF7C-DDA4-4450-AB88-17C1B58EBFB5}.Release|Any CPU.Build.0 = Release|Any CPU + {1675FE68-1F51-4202-9567-BB46215B2BBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1675FE68-1F51-4202-9567-BB46215B2BBD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1675FE68-1F51-4202-9567-BB46215B2BBD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1675FE68-1F51-4202-9567-BB46215B2BBD}.Release|Any CPU.Build.0 = Release|Any CPU + {C247512B-E6D2-4591-8AFA-F2268F1AEAB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C247512B-E6D2-4591-8AFA-F2268F1AEAB2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C247512B-E6D2-4591-8AFA-F2268F1AEAB2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C247512B-E6D2-4591-8AFA-F2268F1AEAB2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EAF6CA55-D81E-4887-9FAC-8D633BBC0096} + EndGlobalSection +EndGlobal diff --git a/HPPH.sln.DotSettings b/HPPH.sln.DotSettings new file mode 100644 index 0000000..c50ef0d --- /dev/null +++ b/HPPH.sln.DotSettings @@ -0,0 +1,2 @@ + + HPPH \ No newline at end of file diff --git a/HPPH/Colors/Colors.cs b/HPPH/Colors/Colors.cs new file mode 100644 index 0000000..9e22c38 --- /dev/null +++ b/HPPH/Colors/Colors.cs @@ -0,0 +1,22 @@ +// ReSharper disable UnusedType.Global +// ReSharper disable InconsistentNaming + +namespace HPPH; + +[ColorGenerator] +public readonly partial struct ColorRGB; + +[ColorGenerator] +public readonly partial struct ColorBGR; + +[ColorGenerator] +public readonly partial struct ColorARGB; + +[ColorGenerator] +public readonly partial struct ColorABGR; + +[ColorGenerator] +public readonly partial struct ColorRGBA; + +[ColorGenerator] +public readonly partial struct ColorBGRA; diff --git a/HPPH/Colors/IColor.cs b/HPPH/Colors/IColor.cs new file mode 100644 index 0000000..82d5a29 --- /dev/null +++ b/HPPH/Colors/IColor.cs @@ -0,0 +1,34 @@ +namespace HPPH; + +/// +/// Represents a generic color made of 4 bytes (alpha, red, green and blue) +/// +public interface IColor +{ + /// + /// Gets the red-component of this color. + /// + byte R { get; } + + /// + /// Gets the green-component of this color. + /// + byte G { get; } + + /// + /// Gets the blue-component of this color. + /// + byte B { get; } + + /// + /// Gets the alpha-component of this color. + /// + byte A { get; } + + /// + /// Gets the color-format of this color. + /// + public static virtual IColorFormat ColorFormat => throw new NotSupportedException(); + + public static virtual IColor Create(byte r, byte g, byte b, byte a) => throw new NotSupportedException(); +} \ No newline at end of file diff --git a/HPPH/Colors/IColorFormat.cs b/HPPH/Colors/IColorFormat.cs new file mode 100644 index 0000000..7c33a71 --- /dev/null +++ b/HPPH/Colors/IColorFormat.cs @@ -0,0 +1,15 @@ +namespace HPPH; + +/// +/// Represents a color format. +/// +// ReSharper disable once InconsistentNaming +public partial interface IColorFormat +{ + /// + /// Gets the Bytes per pixel for this color-format. + /// + int BytesPerPixel { get; } + + string Name { get; } +} \ No newline at end of file diff --git a/HPPH/CommunityToolkit.HighPerformance/ReadOnlyRefEnumerable.cs b/HPPH/CommunityToolkit.HighPerformance/ReadOnlyRefEnumerable.cs new file mode 100644 index 0000000..cbc49f2 --- /dev/null +++ b/HPPH/CommunityToolkit.HighPerformance/ReadOnlyRefEnumerable.cs @@ -0,0 +1,265 @@ +// DarthAffe 05.09.2023: Based on https://github.com/CommunityToolkit/dotnet/blob/b0d6c4f9c0cfb5d860400abb00b0ca1b3e94dfa4/src/CommunityToolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable%7BT%7D.cs + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace HPPH; + +/// +/// A that iterates readonly items from arbitrary memory locations. +/// +/// The type of items to enumerate. +public readonly ref struct ReadOnlyRefEnumerable +{ + #region Properties & Fields + + /// + /// The instance pointing to the first item in the target memory area. + /// + /// The field maps to the total available length. + private readonly ReadOnlySpan _span; + + /// + /// The distance between items in the sequence to enumerate. + /// + /// The distance refers to items, not byte offset. + private readonly int _step; + + /// + /// Gets the total available length for the sequence. + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _span.Length; + } + + /// + /// Gets the element at the specified zero-based index. + /// + /// The zero-based index of the element. + /// A reference to the element at the specified index. + /// + /// Thrown when is invalid. + /// + public ref readonly T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((uint)index >= (uint)Length) throw new IndexOutOfRangeException(); + + ref T r0 = ref MemoryMarshal.GetReference(_span); + nint offset = (nint)(uint)index * (nint)(uint)_step; + ref T ri = ref Unsafe.Add(ref r0, offset); + + return ref ri; + } + } + + /// + /// Gets the element at the specified zero-based index. + /// + /// The zero-based index of the element. + /// A reference to the element at the specified index. + /// + /// Thrown when is invalid. + /// + public ref readonly T this[Index index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref this[index.GetOffset(Length)]; + } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the struct. + /// + /// A reference to the first item of the sequence. + /// The number of items in the sequence. + /// The distance between items in the sequence to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlyRefEnumerable(in T reference, int length, int step) + { + this._step = step; + + _span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in reference), length); + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator GetEnumerator() => new(_span, _step); + + public T[] ToArray() + { + int length = _span.Length; + + // Empty array if no data is mapped + if (length == 0) + return []; + + T[] array = new T[length]; + CopyTo(array); + + return array; + } + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + public void CopyTo(Span destination) + { + if (_step == 1) + { + _span.CopyTo(destination); + return; + } + + ref T sourceRef = ref MemoryMarshal.GetReference(_span); + int length = _span.Length; + if ((uint)destination.Length < (uint)length) + throw new ArgumentException("The target span is too short to copy all the current items to."); + + ref T destinationRef = ref MemoryMarshal.GetReference(destination); + + CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)length, (nint)(uint)_step); + } + + /// + /// Attempts to copy the current instance to a destination . + /// + /// The target of the copy operation. + /// Whether or not the operation was successful. + public bool TryCopyTo(Span destination) + { + if (destination.Length >= _span.Length) + { + CopyTo(destination); + return true; + } + + return false; + } + + private static void CopyTo(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep) + { + nint sourceOffset = 0; + nint destinationOffset = 0; + + while (length >= 8) + { + Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset); + Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 4) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 5) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 6) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 7) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + + length -= 8; + sourceOffset += sourceStep; + destinationOffset += 8; + } + + if (length >= 4) + { + Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset); + Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + + length -= 4; + sourceOffset += sourceStep; + destinationOffset += 4; + } + + while (length > 0) + { + Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset); + + length -= 1; + sourceOffset += sourceStep; + destinationOffset += 1; + } + } + + #endregion + + /// + /// A custom enumerator type to traverse items within a instance. + /// + public ref struct Enumerator + { + #region Properties & Fields + + /// + private readonly ReadOnlySpan _span; + + /// + private readonly int _step; + + /// + /// The current position in the sequence. + /// + private int _position; + + /// + public readonly ref readonly T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + ref T r0 = ref MemoryMarshal.GetReference(_span); + + nint offset = (nint)(uint)_position * (nint)(uint)_step; + ref T ri = ref Unsafe.Add(ref r0, offset); + + return ref ri; + } + } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the struct. + /// + /// The instance with the info on the items to traverse. + /// The distance between items in the sequence to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(ReadOnlySpan span, int step) + { + this._span = span; + this._step = step; + + _position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _span.Length; + + #endregion + } +} diff --git a/HPPH/Data/Generic3ByteData.cs b/HPPH/Data/Generic3ByteData.cs new file mode 100644 index 0000000..d2d239e --- /dev/null +++ b/HPPH/Data/Generic3ByteData.cs @@ -0,0 +1,8 @@ +namespace HPPH; + +internal readonly struct Generic3ByteData(byte b1, byte b2, byte b3) +{ + public readonly byte B1 = b1; + public readonly byte B2 = b2; + public readonly byte B3 = b3; +} \ No newline at end of file diff --git a/HPPH/Data/Generic3ByteMinMax.cs b/HPPH/Data/Generic3ByteMinMax.cs new file mode 100644 index 0000000..ad9c9b8 --- /dev/null +++ b/HPPH/Data/Generic3ByteMinMax.cs @@ -0,0 +1,17 @@ +namespace HPPH; + +internal readonly struct Generic3ByteMinMax(byte b1Min, byte b1Max, byte b2Min, byte b2Max, byte b3Min, byte b3Max) +{ + public readonly byte B1Min = b1Min; + public readonly byte B1Max = b1Max; + + public readonly byte B2Min = b2Min; + public readonly byte B2Max = b2Max; + + public readonly byte B3Min = b3Min; + public readonly byte B3Max = b3Max; + + public byte B1Range => (byte)(B1Max - B1Min); + public byte B2Range => (byte)(B2Max - B2Min); + public byte B3Range => (byte)(B3Max - B3Min); +} diff --git a/HPPH/Data/Generic4ByteData.cs b/HPPH/Data/Generic4ByteData.cs new file mode 100644 index 0000000..1163c61 --- /dev/null +++ b/HPPH/Data/Generic4ByteData.cs @@ -0,0 +1,9 @@ +namespace HPPH; + +internal readonly struct Generic4ByteData(byte b1, byte b2, byte b3, byte b4) +{ + public readonly byte B1 = b1; + public readonly byte B2 = b2; + public readonly byte B3 = b3; + public readonly byte B4 = b4; +} \ No newline at end of file diff --git a/HPPH/Data/Generic4ByteMinMax.cs b/HPPH/Data/Generic4ByteMinMax.cs new file mode 100644 index 0000000..b256660 --- /dev/null +++ b/HPPH/Data/Generic4ByteMinMax.cs @@ -0,0 +1,21 @@ +namespace HPPH; + +internal readonly struct Generic4ByteMinMax(byte b1Min, byte b1Max, byte b2Min, byte b2Max, byte b3Min, byte b3Max, byte b4Min, byte b4Max) +{ + public readonly byte B1Min = b1Min; + public readonly byte B1Max = b1Max; + + public readonly byte B2Min = b2Min; + public readonly byte B2Max = b2Max; + + public readonly byte B3Min = b3Min; + public readonly byte B3Max = b3Max; + + public readonly byte B4Min = b4Min; + public readonly byte B4Max = b4Max; + + public byte B1Range => (byte)(B1Max - B1Min); + public byte B2Range => (byte)(B2Max - B2Min); + public byte B3Range => (byte)(B3Max - B3Min); + public byte B4Range => (byte)(B4Max - B4Min); +} diff --git a/HPPH/Data/Generic4LongData.cs b/HPPH/Data/Generic4LongData.cs new file mode 100644 index 0000000..122c1fc --- /dev/null +++ b/HPPH/Data/Generic4LongData.cs @@ -0,0 +1,9 @@ +namespace HPPH; + +internal readonly struct Generic4LongData(long l1, long l2, long l3, long l4) +{ + public readonly long L1 = l1; + public readonly long L2 = l2; + public readonly long L3 = l3; + public readonly long L4 = l4; +} \ No newline at end of file diff --git a/HPPH/Data/IMinMax.cs b/HPPH/Data/IMinMax.cs new file mode 100644 index 0000000..4650cd7 --- /dev/null +++ b/HPPH/Data/IMinMax.cs @@ -0,0 +1,21 @@ +namespace HPPH; + +public interface IMinMax +{ + public byte RedMin { get; } + public byte RedMax { get; } + + public byte GreenMin { get; } + public byte GreenMax { get; } + + public byte BlueMin { get; } + public byte BlueMax { get; } + + public byte AlphaMin { get; } + public byte AlphaMax { get; } + + public byte RedRange { get; } + public byte GreenRange { get; } + public byte BlueRange { get; } + public byte AlphaRange { get; } +} \ No newline at end of file diff --git a/HPPH/Data/ISum.cs b/HPPH/Data/ISum.cs new file mode 100644 index 0000000..8dcbad9 --- /dev/null +++ b/HPPH/Data/ISum.cs @@ -0,0 +1,9 @@ +namespace HPPH; + +public interface ISum +{ + public long R { get; } + public long G { get; } + public long B { get; } + public long A { get; } +} \ No newline at end of file diff --git a/HPPH/Extensions/HPPHExtensions.cs b/HPPH/Extensions/HPPHExtensions.cs new file mode 100644 index 0000000..66dae82 --- /dev/null +++ b/HPPH/Extensions/HPPHExtensions.cs @@ -0,0 +1,94 @@ +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace HPPH; + +/// +/// Offers some extensions and helper-methods for the work with floats. +/// +internal static class HPPHExtensions +{ + #region Constants + + /// + /// Defines the precision RGB.NET processes floating point comparisons in. + /// + public const float TOLERANCE = 1E-7f; + + #endregion + + #region Methods + + /// + /// Checks if two values are equal respecting the . + /// + /// The first value to compare. + /// The first value to compare. + /// true if the difference is smaller than the ; otherwise, false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EqualsInTolerance(this float value1, float value2) => Math.Abs(value1 - value2) < TOLERANCE; + + /// + /// Clamps the provided value to be bigger or equal min and smaller or equal max. + /// + /// The value to clamp. + /// The lower value of the range the value is clamped to. + /// The higher value of the range the value is clamped to. + /// The clamped value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Clamp(this T value, T min, T max) + where T : INumber + { + // ReSharper disable ConvertIfStatementToReturnStatement - I'm not sure why, but inlining this statement reduces performance by ~10% + if (value < min) return min; + if (value > max) return max; + return value; + // ReSharper restore ConvertIfStatementToReturnStatement + } + + /// + /// Enforces the provided value to be in the specified range by wrapping it around the edges if it exceeds them. + /// + /// The value to wrap. + /// The lower value of the range the value is wrapped into. + /// The higher value of the range the value is wrapped into. + /// The wrapped value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Wrap(this T value, T min, T max) + where T : INumber + { + T range = max - min; + + while (value >= max) + value -= range; + + while (value < min) + value += range; + + return value; + } + + /// + /// Converts a normalized float value in the range [0..1] to a byte [0..255]. + /// + /// The normalized float value to convert. + /// The byte value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte GetByteValueFromPercentage(this float percentage) + { + if (float.IsNaN(percentage) || (percentage < 0)) return 0; + + return (byte)(percentage >= 1.0f ? 255 : percentage * 256.0f); + } + + /// + /// Converts a byte value [0..255] to a normalized float value in the range [0..1]. + /// + /// The byte value to convert. + /// The normalized float value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float GetPercentageFromByteValue(this byte value) + => value == 255 ? 1.0f : (value / 256.0f); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSortSourceGenerator/ColorSortGeneratorAttribute.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSortSourceGenerator/ColorSortGeneratorAttribute.g.cs new file mode 100644 index 0000000..4ae6eb7 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSortSourceGenerator/ColorSortGeneratorAttribute.g.cs @@ -0,0 +1,8 @@ +namespace HPPH; + +[AttributeUsage(AttributeTargets.Method)] +internal class ColorSortGeneratorAttribute(string dataTypeName, string sortValueName) : Attribute +{ + public string DataTypeName { get; } = dataTypeName; + public string SortValueName { get; } = sortValueName; +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSortSourceGenerator/PixelHelper.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSortSourceGenerator/PixelHelper.g.cs new file mode 100644 index 0000000..da7195d --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSortSourceGenerator/PixelHelper.g.cs @@ -0,0 +1,393 @@ +using System.Buffers; + +namespace HPPH; + +public static unsafe partial class PixelHelper +{ + public static partial void SortByRed(Span colors) where T : unmanaged, IColor + { + fixed (T* ptr = colors) + { + T* end = ptr + colors.Length; + + Span histogram = stackalloc int[256]; + histogram.Clear(); + for (T* color = ptr; color < end; color++) + histogram[(*color).R]++; + + T[] bucketsArray = ArrayPool.Shared.Rent(colors.Length); + try + { + Span buckets = bucketsArray.AsSpan()[..colors.Length]; + Span currentBucketIndex = stackalloc int[256]; + + int offset = 0; + for (int i = 0; i < histogram.Length; i++) + { + currentBucketIndex[i] = offset; + offset += histogram[i]; + } + + for (T* color = ptr; color < end; color++) + buckets[currentBucketIndex[(*color).R]++] = (*color); + + buckets.CopyTo(colors); + } + finally + { + ArrayPool.Shared.Return(bucketsArray); + } + } + } + public static partial void SortByGreen(Span colors) where T : unmanaged, IColor + { + fixed (T* ptr = colors) + { + T* end = ptr + colors.Length; + + Span histogram = stackalloc int[256]; + histogram.Clear(); + for (T* color = ptr; color < end; color++) + histogram[(*color).G]++; + + T[] bucketsArray = ArrayPool.Shared.Rent(colors.Length); + try + { + Span buckets = bucketsArray.AsSpan()[..colors.Length]; + Span currentBucketIndex = stackalloc int[256]; + + int offset = 0; + for (int i = 0; i < histogram.Length; i++) + { + currentBucketIndex[i] = offset; + offset += histogram[i]; + } + + for (T* color = ptr; color < end; color++) + buckets[currentBucketIndex[(*color).G]++] = (*color); + + buckets.CopyTo(colors); + } + finally + { + ArrayPool.Shared.Return(bucketsArray); + } + } + } + public static partial void SortByBlue(Span colors) where T : unmanaged, IColor + { + fixed (T* ptr = colors) + { + T* end = ptr + colors.Length; + + Span histogram = stackalloc int[256]; + histogram.Clear(); + for (T* color = ptr; color < end; color++) + histogram[(*color).B]++; + + T[] bucketsArray = ArrayPool.Shared.Rent(colors.Length); + try + { + Span buckets = bucketsArray.AsSpan()[..colors.Length]; + Span currentBucketIndex = stackalloc int[256]; + + int offset = 0; + for (int i = 0; i < histogram.Length; i++) + { + currentBucketIndex[i] = offset; + offset += histogram[i]; + } + + for (T* color = ptr; color < end; color++) + buckets[currentBucketIndex[(*color).B]++] = (*color); + + buckets.CopyTo(colors); + } + finally + { + ArrayPool.Shared.Return(bucketsArray); + } + } + } + public static partial void SortByAlpha(Span colors) where T : unmanaged, IColor + { + fixed (T* ptr = colors) + { + T* end = ptr + colors.Length; + + Span histogram = stackalloc int[256]; + histogram.Clear(); + for (T* color = ptr; color < end; color++) + histogram[(*color).A]++; + + T[] bucketsArray = ArrayPool.Shared.Rent(colors.Length); + try + { + Span buckets = bucketsArray.AsSpan()[..colors.Length]; + Span currentBucketIndex = stackalloc int[256]; + + int offset = 0; + for (int i = 0; i < histogram.Length; i++) + { + currentBucketIndex[i] = offset; + offset += histogram[i]; + } + + for (T* color = ptr; color < end; color++) + buckets[currentBucketIndex[(*color).A]++] = (*color); + + buckets.CopyTo(colors); + } + finally + { + ArrayPool.Shared.Return(bucketsArray); + } + } + } + internal static partial void SortB1(Span colors) + { + fixed (Generic3ByteData* ptr = colors) + { + Generic3ByteData* end = ptr + colors.Length; + + Span histogram = stackalloc int[256]; + histogram.Clear(); + for (Generic3ByteData* color = ptr; color < end; color++) + histogram[(*color).B1]++; + + Generic3ByteData[] bucketsArray = ArrayPool.Shared.Rent(colors.Length); + try + { + Span buckets = bucketsArray.AsSpan()[..colors.Length]; + Span currentBucketIndex = stackalloc int[256]; + + int offset = 0; + for (int i = 0; i < histogram.Length; i++) + { + currentBucketIndex[i] = offset; + offset += histogram[i]; + } + + for (Generic3ByteData* color = ptr; color < end; color++) + buckets[currentBucketIndex[(*color).B1]++] = (*color); + + buckets.CopyTo(colors); + } + finally + { + ArrayPool.Shared.Return(bucketsArray); + } + } + } + internal static partial void SortB2(Span colors) + { + fixed (Generic3ByteData* ptr = colors) + { + Generic3ByteData* end = ptr + colors.Length; + + Span histogram = stackalloc int[256]; + histogram.Clear(); + for (Generic3ByteData* color = ptr; color < end; color++) + histogram[(*color).B2]++; + + Generic3ByteData[] bucketsArray = ArrayPool.Shared.Rent(colors.Length); + try + { + Span buckets = bucketsArray.AsSpan()[..colors.Length]; + Span currentBucketIndex = stackalloc int[256]; + + int offset = 0; + for (int i = 0; i < histogram.Length; i++) + { + currentBucketIndex[i] = offset; + offset += histogram[i]; + } + + for (Generic3ByteData* color = ptr; color < end; color++) + buckets[currentBucketIndex[(*color).B2]++] = (*color); + + buckets.CopyTo(colors); + } + finally + { + ArrayPool.Shared.Return(bucketsArray); + } + } + } + internal static partial void SortB3(Span colors) + { + fixed (Generic3ByteData* ptr = colors) + { + Generic3ByteData* end = ptr + colors.Length; + + Span histogram = stackalloc int[256]; + histogram.Clear(); + for (Generic3ByteData* color = ptr; color < end; color++) + histogram[(*color).B3]++; + + Generic3ByteData[] bucketsArray = ArrayPool.Shared.Rent(colors.Length); + try + { + Span buckets = bucketsArray.AsSpan()[..colors.Length]; + Span currentBucketIndex = stackalloc int[256]; + + int offset = 0; + for (int i = 0; i < histogram.Length; i++) + { + currentBucketIndex[i] = offset; + offset += histogram[i]; + } + + for (Generic3ByteData* color = ptr; color < end; color++) + buckets[currentBucketIndex[(*color).B3]++] = (*color); + + buckets.CopyTo(colors); + } + finally + { + ArrayPool.Shared.Return(bucketsArray); + } + } + } + internal static partial void SortB1(Span colors) + { + fixed (Generic4ByteData* ptr = colors) + { + Generic4ByteData* end = ptr + colors.Length; + + Span histogram = stackalloc int[256]; + histogram.Clear(); + for (Generic4ByteData* color = ptr; color < end; color++) + histogram[(*color).B1]++; + + Generic4ByteData[] bucketsArray = ArrayPool.Shared.Rent(colors.Length); + try + { + Span buckets = bucketsArray.AsSpan()[..colors.Length]; + Span currentBucketIndex = stackalloc int[256]; + + int offset = 0; + for (int i = 0; i < histogram.Length; i++) + { + currentBucketIndex[i] = offset; + offset += histogram[i]; + } + + for (Generic4ByteData* color = ptr; color < end; color++) + buckets[currentBucketIndex[(*color).B1]++] = (*color); + + buckets.CopyTo(colors); + } + finally + { + ArrayPool.Shared.Return(bucketsArray); + } + } + } + internal static partial void SortB2(Span colors) + { + fixed (Generic4ByteData* ptr = colors) + { + Generic4ByteData* end = ptr + colors.Length; + + Span histogram = stackalloc int[256]; + histogram.Clear(); + for (Generic4ByteData* color = ptr; color < end; color++) + histogram[(*color).B2]++; + + Generic4ByteData[] bucketsArray = ArrayPool.Shared.Rent(colors.Length); + try + { + Span buckets = bucketsArray.AsSpan()[..colors.Length]; + Span currentBucketIndex = stackalloc int[256]; + + int offset = 0; + for (int i = 0; i < histogram.Length; i++) + { + currentBucketIndex[i] = offset; + offset += histogram[i]; + } + + for (Generic4ByteData* color = ptr; color < end; color++) + buckets[currentBucketIndex[(*color).B2]++] = (*color); + + buckets.CopyTo(colors); + } + finally + { + ArrayPool.Shared.Return(bucketsArray); + } + } + } + internal static partial void SortB3(Span colors) + { + fixed (Generic4ByteData* ptr = colors) + { + Generic4ByteData* end = ptr + colors.Length; + + Span histogram = stackalloc int[256]; + histogram.Clear(); + for (Generic4ByteData* color = ptr; color < end; color++) + histogram[(*color).B3]++; + + Generic4ByteData[] bucketsArray = ArrayPool.Shared.Rent(colors.Length); + try + { + Span buckets = bucketsArray.AsSpan()[..colors.Length]; + Span currentBucketIndex = stackalloc int[256]; + + int offset = 0; + for (int i = 0; i < histogram.Length; i++) + { + currentBucketIndex[i] = offset; + offset += histogram[i]; + } + + for (Generic4ByteData* color = ptr; color < end; color++) + buckets[currentBucketIndex[(*color).B3]++] = (*color); + + buckets.CopyTo(colors); + } + finally + { + ArrayPool.Shared.Return(bucketsArray); + } + } + } + internal static partial void SortB4(Span colors) + { + fixed (Generic4ByteData* ptr = colors) + { + Generic4ByteData* end = ptr + colors.Length; + + Span histogram = stackalloc int[256]; + histogram.Clear(); + for (Generic4ByteData* color = ptr; color < end; color++) + histogram[(*color).B4]++; + + Generic4ByteData[] bucketsArray = ArrayPool.Shared.Rent(colors.Length); + try + { + Span buckets = bucketsArray.AsSpan()[..colors.Length]; + Span currentBucketIndex = stackalloc int[256]; + + int offset = 0; + for (int i = 0; i < histogram.Length; i++) + { + currentBucketIndex[i] = offset; + offset += histogram[i]; + } + + for (Generic4ByteData* color = ptr; color < end; color++) + buckets[currentBucketIndex[(*color).B4]++] = (*color); + + buckets.CopyTo(colors); + } + finally + { + ArrayPool.Shared.Return(bucketsArray); + } + } + } + +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorABGR.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorABGR.g.cs new file mode 100644 index 0000000..5adf3e4 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorABGR.g.cs @@ -0,0 +1,57 @@ +// ReSharper disable ConvertToAutoProperty +// ReSharper disable ConvertToAutoPropertyWhenPossible +// ReSharper disable ReplaceWithPrimaryConstructorParameter + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace HPPH; + +/// +/// Represents a color in 32 bit ABGR-format. +/// +/// +/// Initializes a new instance of the class. +/// +/// The Alpha-component of the color. +/// The Blue-component of the color. +/// The Green-component of the color. +/// The Red-component of the color. +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly partial struct ColorABGR(byte a, byte b, byte g, byte r) : IColor +{ + #region Properties & Fields + + /// + public static IColorFormat ColorFormat => IColorFormat.ABGR; + + private readonly byte _a = a; + private readonly byte _b = b; + private readonly byte _g = g; + private readonly byte _r = r; + + /// + public byte R => _r; + + /// + public byte G => _g; + + /// + public byte B => _b; + + /// + public byte A => _a; + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; + + /// + public static IColor Create(byte r, byte g, byte b, byte a) => new ColorABGR(a, b, g, r); + + #endregion +} diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorARGB.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorARGB.g.cs new file mode 100644 index 0000000..19d95a1 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorARGB.g.cs @@ -0,0 +1,57 @@ +// ReSharper disable ConvertToAutoProperty +// ReSharper disable ConvertToAutoPropertyWhenPossible +// ReSharper disable ReplaceWithPrimaryConstructorParameter + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace HPPH; + +/// +/// Represents a color in 32 bit ARGB-format. +/// +/// +/// Initializes a new instance of the class. +/// +/// The Alpha-component of the color. +/// The Red-component of the color. +/// The Green-component of the color. +/// The Blue-component of the color. +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly partial struct ColorARGB(byte a, byte r, byte g, byte b) : IColor +{ + #region Properties & Fields + + /// + public static IColorFormat ColorFormat => IColorFormat.ARGB; + + private readonly byte _a = a; + private readonly byte _r = r; + private readonly byte _g = g; + private readonly byte _b = b; + + /// + public byte R => _r; + + /// + public byte G => _g; + + /// + public byte B => _b; + + /// + public byte A => _a; + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; + + /// + public static IColor Create(byte r, byte g, byte b, byte a) => new ColorARGB(a, r, g, b); + + #endregion +} diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorBGR.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorBGR.g.cs new file mode 100644 index 0000000..6133d7b --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorBGR.g.cs @@ -0,0 +1,55 @@ +// ReSharper disable ConvertToAutoProperty +// ReSharper disable ConvertToAutoPropertyWhenPossible +// ReSharper disable ReplaceWithPrimaryConstructorParameter + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace HPPH; + +/// +/// Represents a color in 24 bit BGR-format. +/// +/// +/// Initializes a new instance of the class. +/// +/// The Blue-component of the color. +/// The Green-component of the color. +/// The Red-component of the color. +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly partial struct ColorBGR(byte b, byte g, byte r): IColor +{ + #region Properties & Fields + + /// + public static IColorFormat ColorFormat => IColorFormat.BGR; + + private readonly byte _b = b; + private readonly byte _g = g; + private readonly byte _r = r; + + /// + public byte R => _r; + + /// + public byte G => _g; + + /// + public byte B => _b; + + /// + public byte A => byte.MaxValue; + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; + + /// + public static IColor Create(byte r, byte g, byte b, byte a) => new ColorBGR(b, g, r); + + #endregion +} diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorBGRA.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorBGRA.g.cs new file mode 100644 index 0000000..da510c4 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorBGRA.g.cs @@ -0,0 +1,57 @@ +// ReSharper disable ConvertToAutoProperty +// ReSharper disable ConvertToAutoPropertyWhenPossible +// ReSharper disable ReplaceWithPrimaryConstructorParameter + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace HPPH; + +/// +/// Represents a color in 32 bit BGRA-format. +/// +/// +/// Initializes a new instance of the class. +/// +/// The Blue-component of the color. +/// The Green-component of the color. +/// The Red-component of the color. +/// The Alpha-component of the color. +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly partial struct ColorBGRA(byte b, byte g, byte r, byte a) : IColor +{ + #region Properties & Fields + + /// + public static IColorFormat ColorFormat => IColorFormat.BGRA; + + private readonly byte _b = b; + private readonly byte _g = g; + private readonly byte _r = r; + private readonly byte _a = a; + + /// + public byte R => _r; + + /// + public byte G => _g; + + /// + public byte B => _b; + + /// + public byte A => _a; + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; + + /// + public static IColor Create(byte r, byte g, byte b, byte a) => new ColorBGRA(b, g, r, a); + + #endregion +} diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.Average.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.Average.g.cs new file mode 100644 index 0000000..89256c8 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.Average.g.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatABGR +{ + #region Methods + + unsafe IColor IColorFormat.Average(ReadOnlySpan data) => PixelHelper.Average(MemoryMarshal.Cast(data)); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.MinMax.g.cs new file mode 100644 index 0000000..bfa8909 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.MinMax.g.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatABGR +{ + #region Methods + + unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.Quantize.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.Quantize.g.cs new file mode 100644 index 0000000..d8c4820 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.Quantize.g.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatABGR +{ + #region Methods + + unsafe IColor[] IColorFormat.CreateColorPalette(ReadOnlySpan data, int paletteSize) + { + ColorABGR[] colors = PixelHelper.CreateColorPalette(MemoryMarshal.Cast(data), paletteSize); + + IColor[] result = new IColor[colors.Length]; + for(int i = 0; i < colors.Length; i++) + result[i] = colors[i]; + + return result; + } + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.Sum.g.cs new file mode 100644 index 0000000..8747e73 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.Sum.g.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatABGR +{ + #region Methods + + unsafe ISum IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.g.cs new file mode 100644 index 0000000..076f185 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.g.cs @@ -0,0 +1,20 @@ +namespace HPPH; + +public sealed partial class ColorFormatABGR : IColorFormat +{ + #region Properties & Fields + + public static ColorFormatABGR Instance { get; } = new(); + + public int BytesPerPixel => 4; + + public string Name => "ABGR"; + + #endregion + + #region Constructors + + private ColorFormatABGR() {} + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.Average.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.Average.g.cs new file mode 100644 index 0000000..75a4c40 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.Average.g.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatARGB +{ + #region Methods + + unsafe IColor IColorFormat.Average(ReadOnlySpan data) => PixelHelper.Average(MemoryMarshal.Cast(data)); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.MinMax.g.cs new file mode 100644 index 0000000..37d06a3 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.MinMax.g.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatARGB +{ + #region Methods + + unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.Quantize.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.Quantize.g.cs new file mode 100644 index 0000000..411e19c --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.Quantize.g.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatARGB +{ + #region Methods + + unsafe IColor[] IColorFormat.CreateColorPalette(ReadOnlySpan data, int paletteSize) + { + ColorARGB[] colors = PixelHelper.CreateColorPalette(MemoryMarshal.Cast(data), paletteSize); + + IColor[] result = new IColor[colors.Length]; + for(int i = 0; i < colors.Length; i++) + result[i] = colors[i]; + + return result; + } + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.Sum.g.cs new file mode 100644 index 0000000..0275eff --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.Sum.g.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatARGB +{ + #region Methods + + unsafe ISum IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.g.cs new file mode 100644 index 0000000..5dcb76d --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.g.cs @@ -0,0 +1,20 @@ +namespace HPPH; + +public sealed partial class ColorFormatARGB : IColorFormat +{ + #region Properties & Fields + + public static ColorFormatARGB Instance { get; } = new(); + + public int BytesPerPixel => 4; + + public string Name => "ARGB"; + + #endregion + + #region Constructors + + private ColorFormatARGB() {} + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.Average.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.Average.g.cs new file mode 100644 index 0000000..932d2c7 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.Average.g.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatBGR +{ + #region Methods + + unsafe IColor IColorFormat.Average(ReadOnlySpan data) => PixelHelper.Average(MemoryMarshal.Cast(data)); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.MinMax.g.cs new file mode 100644 index 0000000..2276eb2 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.MinMax.g.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatBGR +{ + #region Methods + + unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.Quantize.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.Quantize.g.cs new file mode 100644 index 0000000..0f82830 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.Quantize.g.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatBGR +{ + #region Methods + + unsafe IColor[] IColorFormat.CreateColorPalette(ReadOnlySpan data, int paletteSize) + { + ColorBGR[] colors = PixelHelper.CreateColorPalette(MemoryMarshal.Cast(data), paletteSize); + + IColor[] result = new IColor[colors.Length]; + for(int i = 0; i < colors.Length; i++) + result[i] = colors[i]; + + return result; + } + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.Sum.g.cs new file mode 100644 index 0000000..4d46ad0 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.Sum.g.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatBGR +{ + #region Methods + + unsafe ISum IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.g.cs new file mode 100644 index 0000000..1de747c --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.g.cs @@ -0,0 +1,20 @@ +namespace HPPH; + +public sealed partial class ColorFormatBGR : IColorFormat +{ + #region Properties & Fields + + public static ColorFormatBGR Instance { get; } = new(); + + public int BytesPerPixel => 3; + + public string Name => "BGR"; + + #endregion + + #region Constructors + + private ColorFormatBGR() {} + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.Average.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.Average.g.cs new file mode 100644 index 0000000..2226df5 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.Average.g.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatBGRA +{ + #region Methods + + unsafe IColor IColorFormat.Average(ReadOnlySpan data) => PixelHelper.Average(MemoryMarshal.Cast(data)); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.MinMax.g.cs new file mode 100644 index 0000000..d954759 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.MinMax.g.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatBGRA +{ + #region Methods + + unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.Quantize.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.Quantize.g.cs new file mode 100644 index 0000000..9c1e1e6 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.Quantize.g.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatBGRA +{ + #region Methods + + unsafe IColor[] IColorFormat.CreateColorPalette(ReadOnlySpan data, int paletteSize) + { + ColorBGRA[] colors = PixelHelper.CreateColorPalette(MemoryMarshal.Cast(data), paletteSize); + + IColor[] result = new IColor[colors.Length]; + for(int i = 0; i < colors.Length; i++) + result[i] = colors[i]; + + return result; + } + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.Sum.g.cs new file mode 100644 index 0000000..e4f3926 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.Sum.g.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatBGRA +{ + #region Methods + + unsafe ISum IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.g.cs new file mode 100644 index 0000000..17feeb9 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.g.cs @@ -0,0 +1,20 @@ +namespace HPPH; + +public sealed partial class ColorFormatBGRA : IColorFormat +{ + #region Properties & Fields + + public static ColorFormatBGRA Instance { get; } = new(); + + public int BytesPerPixel => 4; + + public string Name => "BGRA"; + + #endregion + + #region Constructors + + private ColorFormatBGRA() {} + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.Average.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.Average.g.cs new file mode 100644 index 0000000..9891fb8 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.Average.g.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatRGB +{ + #region Methods + + unsafe IColor IColorFormat.Average(ReadOnlySpan data) => PixelHelper.Average(MemoryMarshal.Cast(data)); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.MinMax.g.cs new file mode 100644 index 0000000..31c5360 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.MinMax.g.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatRGB +{ + #region Methods + + unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.Quantize.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.Quantize.g.cs new file mode 100644 index 0000000..3a7daca --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.Quantize.g.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatRGB +{ + #region Methods + + unsafe IColor[] IColorFormat.CreateColorPalette(ReadOnlySpan data, int paletteSize) + { + ColorRGB[] colors = PixelHelper.CreateColorPalette(MemoryMarshal.Cast(data), paletteSize); + + IColor[] result = new IColor[colors.Length]; + for(int i = 0; i < colors.Length; i++) + result[i] = colors[i]; + + return result; + } + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.Sum.g.cs new file mode 100644 index 0000000..70a9b12 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.Sum.g.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatRGB +{ + #region Methods + + unsafe ISum IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.g.cs new file mode 100644 index 0000000..1208503 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.g.cs @@ -0,0 +1,20 @@ +namespace HPPH; + +public sealed partial class ColorFormatRGB : IColorFormat +{ + #region Properties & Fields + + public static ColorFormatRGB Instance { get; } = new(); + + public int BytesPerPixel => 3; + + public string Name => "RGB"; + + #endregion + + #region Constructors + + private ColorFormatRGB() {} + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.Average.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.Average.g.cs new file mode 100644 index 0000000..609b087 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.Average.g.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatRGBA +{ + #region Methods + + unsafe IColor IColorFormat.Average(ReadOnlySpan data) => PixelHelper.Average(MemoryMarshal.Cast(data)); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.MinMax.g.cs new file mode 100644 index 0000000..381cb6b --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.MinMax.g.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatRGBA +{ + #region Methods + + unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.Quantize.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.Quantize.g.cs new file mode 100644 index 0000000..43a36dd --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.Quantize.g.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatRGBA +{ + #region Methods + + unsafe IColor[] IColorFormat.CreateColorPalette(ReadOnlySpan data, int paletteSize) + { + ColorRGBA[] colors = PixelHelper.CreateColorPalette(MemoryMarshal.Cast(data), paletteSize); + + IColor[] result = new IColor[colors.Length]; + for(int i = 0; i < colors.Length; i++) + result[i] = colors[i]; + + return result; + } + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.Sum.g.cs new file mode 100644 index 0000000..1ffb1ff --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.Sum.g.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace HPPH; + +public sealed partial class ColorFormatRGBA +{ + #region Methods + + unsafe ISum IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.g.cs new file mode 100644 index 0000000..1e29a15 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.g.cs @@ -0,0 +1,20 @@ +namespace HPPH; + +public sealed partial class ColorFormatRGBA : IColorFormat +{ + #region Properties & Fields + + public static ColorFormatRGBA Instance { get; } = new(); + + public int BytesPerPixel => 4; + + public string Name => "RGBA"; + + #endregion + + #region Constructors + + private ColorFormatRGBA() {} + + #endregion +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorGeneratorAttribute.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorGeneratorAttribute.g.cs new file mode 100644 index 0000000..d847095 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorGeneratorAttribute.g.cs @@ -0,0 +1,4 @@ +namespace HPPH; + +[AttributeUsage(AttributeTargets.Struct)] +internal class ColorGeneratorAttribute : Attribute; \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorRGB.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorRGB.g.cs new file mode 100644 index 0000000..34b6b9d --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorRGB.g.cs @@ -0,0 +1,55 @@ +// ReSharper disable ConvertToAutoProperty +// ReSharper disable ConvertToAutoPropertyWhenPossible +// ReSharper disable ReplaceWithPrimaryConstructorParameter + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace HPPH; + +/// +/// Represents a color in 24 bit RGB-format. +/// +/// +/// Initializes a new instance of the class. +/// +/// The Red-component of the color. +/// The Green-component of the color. +/// The Blue-component of the color. +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly partial struct ColorRGB(byte r, byte g, byte b): IColor +{ + #region Properties & Fields + + /// + public static IColorFormat ColorFormat => IColorFormat.RGB; + + private readonly byte _r = r; + private readonly byte _g = g; + private readonly byte _b = b; + + /// + public byte R => _r; + + /// + public byte G => _g; + + /// + public byte B => _b; + + /// + public byte A => byte.MaxValue; + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; + + /// + public static IColor Create(byte r, byte g, byte b, byte a) => new ColorRGB(r, g, b); + + #endregion +} diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorRGBA.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorRGBA.g.cs new file mode 100644 index 0000000..134a1a2 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorRGBA.g.cs @@ -0,0 +1,57 @@ +// ReSharper disable ConvertToAutoProperty +// ReSharper disable ConvertToAutoPropertyWhenPossible +// ReSharper disable ReplaceWithPrimaryConstructorParameter + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace HPPH; + +/// +/// Represents a color in 32 bit RGBA-format. +/// +/// +/// Initializes a new instance of the class. +/// +/// The Red-component of the color. +/// The Green-component of the color. +/// The Blue-component of the color. +/// The Alpha-component of the color. +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly partial struct ColorRGBA(byte r, byte g, byte b, byte a) : IColor +{ + #region Properties & Fields + + /// + public static IColorFormat ColorFormat => IColorFormat.RGBA; + + private readonly byte _r = r; + private readonly byte _g = g; + private readonly byte _b = b; + private readonly byte _a = a; + + /// + public byte R => _r; + + /// + public byte G => _g; + + /// + public byte B => _b; + + /// + public byte A => _a; + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; + + /// + public static IColor Create(byte r, byte g, byte b, byte a) => new ColorRGBA(r, g, b, a); + + #endregion +} diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Average.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Average.g.cs new file mode 100644 index 0000000..9227055 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Average.g.cs @@ -0,0 +1,6 @@ +namespace HPPH; + +public partial interface IColorFormat +{ + internal IColor Average(ReadOnlySpan data); +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Instances.cs.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Instances.cs.g.cs new file mode 100644 index 0000000..49328d2 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Instances.cs.g.cs @@ -0,0 +1,15 @@ +namespace HPPH; + +public partial interface IColorFormat +{ + #region Instances + + public static ColorFormatRGB RGB => ColorFormatRGB.Instance; + public static ColorFormatBGR BGR => ColorFormatBGR.Instance; + public static ColorFormatARGB ARGB => ColorFormatARGB.Instance; + public static ColorFormatABGR ABGR => ColorFormatABGR.Instance; + public static ColorFormatRGBA RGBA => ColorFormatRGBA.Instance; + public static ColorFormatBGRA BGRA => ColorFormatBGRA.Instance; + + #endregion +} diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.MinMax.g.cs new file mode 100644 index 0000000..98aa3ea --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.MinMax.g.cs @@ -0,0 +1,6 @@ +namespace HPPH; + +public partial interface IColorFormat +{ + internal IMinMax MinMax(ReadOnlySpan data); +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Quantize.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Quantize.g.cs new file mode 100644 index 0000000..2bb8aca --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Quantize.g.cs @@ -0,0 +1,6 @@ +namespace HPPH; + +public partial interface IColorFormat +{ + internal IColor[] CreateColorPalette(ReadOnlySpan data, int paletteSize); +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Sum.g.cs new file mode 100644 index 0000000..d05861e --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Sum.g.cs @@ -0,0 +1,6 @@ +namespace HPPH; + +public partial interface IColorFormat +{ + internal ISum Sum(ReadOnlySpan data); +} \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/MinMaxABGR.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/MinMaxABGR.g.cs new file mode 100644 index 0000000..9fb0a3f --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/MinMaxABGR.g.cs @@ -0,0 +1,52 @@ +// ReSharper disable ConvertToAutoProperty +// ReSharper disable ReplaceWithPrimaryConstructorParameter + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace HPPH; + +[DebuggerDisplay("[A: {AlphaMin}-{AlphaMax}, R: {RedMin}-{RedMax}, G: {GreenMin}-{GreenMax}, B: {BlueMin}-{BlueMax}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly partial struct MinMaxABGR(byte aMin, byte aMax, byte bMin, byte bMax, byte gMin, byte gMax, byte rMin, byte rMax) : IMinMax +{ + #region Properties & Fields + + private readonly byte _aMin = aMin; + private readonly byte _aMax = aMax; + + private readonly byte _bMin = bMin; + private readonly byte _bMax = bMax; + + private readonly byte _gMin = gMin; + private readonly byte _gMax = gMax; + + private readonly byte _rMin = rMin; + private readonly byte _rMax = rMax; + + public byte RedMin => _rMin; + public byte RedMax => _rMax; + + public byte GreenMin => _gMin; + public byte GreenMax => _gMax; + + public byte BlueMin => _bMin; + public byte BlueMax => _bMax; + + public byte AlphaMin => _aMin; + public byte AlphaMax => _aMax; + + public byte RedRange => (byte)(_rMax - _rMin); + public byte GreenRange => (byte)(_gMax - _gMin); + public byte BlueRange => (byte)(_bMax - _bMin); + public byte AlphaRange => (byte)(_aMax - _aMin); + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {AlphaMin}-{AlphaMax}, R: {RedMin}-{RedMax}, G: {GreenMin}-{GreenMax}, B: {BlueMin}-{BlueMax}]"; + + #endregion +} diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/MinMaxARGB.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/MinMaxARGB.g.cs new file mode 100644 index 0000000..4065e99 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/MinMaxARGB.g.cs @@ -0,0 +1,52 @@ +// ReSharper disable ConvertToAutoProperty +// ReSharper disable ReplaceWithPrimaryConstructorParameter + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace HPPH; + +[DebuggerDisplay("[A: {AlphaMin}-{AlphaMax}, R: {RedMin}-{RedMax}, G: {GreenMin}-{GreenMax}, B: {BlueMin}-{BlueMax}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly partial struct MinMaxARGB(byte aMin, byte aMax, byte rMin, byte rMax, byte gMin, byte gMax, byte bMin, byte bMax) : IMinMax +{ + #region Properties & Fields + + private readonly byte _aMin = aMin; + private readonly byte _aMax = aMax; + + private readonly byte _rMin = rMin; + private readonly byte _rMax = rMax; + + private readonly byte _gMin = gMin; + private readonly byte _gMax = gMax; + + private readonly byte _bMin = bMin; + private readonly byte _bMax = bMax; + + public byte RedMin => _rMin; + public byte RedMax => _rMax; + + public byte GreenMin => _gMin; + public byte GreenMax => _gMax; + + public byte BlueMin => _bMin; + public byte BlueMax => _bMax; + + public byte AlphaMin => _aMin; + public byte AlphaMax => _aMax; + + public byte RedRange => (byte)(_rMax - _rMin); + public byte GreenRange => (byte)(_gMax - _gMin); + public byte BlueRange => (byte)(_bMax - _bMin); + public byte AlphaRange => (byte)(_aMax - _aMin); + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {AlphaMin}-{AlphaMax}, R: {RedMin}-{RedMax}, G: {GreenMin}-{GreenMax}, B: {BlueMin}-{BlueMax}]"; + + #endregion +} diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/MinMaxBGR.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/MinMaxBGR.g.cs new file mode 100644 index 0000000..ef4d4e7 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/MinMaxBGR.g.cs @@ -0,0 +1,49 @@ +// ReSharper disable ConvertToAutoProperty +// ReSharper disable ReplaceWithPrimaryConstructorParameter + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace HPPH; + +[DebuggerDisplay("[A: {AlphaMin}-{AlphaMax}, R: {RedMin}-{RedMax}, G: {GreenMin}-{GreenMax}, B: {BlueMin}-{BlueMax}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly partial struct MinMaxBGR(byte bMin, byte bMax, byte gMin, byte gMax, byte rMin, byte rMax) : IMinMax +{ + #region Properties & Fields + + private readonly byte _bMin = bMin; + private readonly byte _bMax = bMax; + + private readonly byte _gMin = gMin; + private readonly byte _gMax = gMax; + + private readonly byte _rMin = rMin; + private readonly byte _rMax = rMax; + + public byte RedMin => _rMin; + public byte RedMax => _rMax; + + public byte GreenMin => _gMin; + public byte GreenMax => _gMax; + + public byte BlueMin => _bMin; + public byte BlueMax => _bMax; + + public byte AlphaMin => byte.MaxValue; + public byte AlphaMax => byte.MaxValue; + + public byte RedRange => (byte)(_rMax - _rMin); + public byte GreenRange => (byte)(_gMax - _gMin); + public byte BlueRange => (byte)(_bMax - _bMin); + public byte AlphaRange => 0; + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {AlphaMin}-{AlphaMax}, R: {RedMin}-{RedMax}, G: {GreenMin}-{GreenMax}, B: {BlueMin}-{BlueMax}]"; + + #endregion +} diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/MinMaxBGRA.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/MinMaxBGRA.g.cs new file mode 100644 index 0000000..ab76312 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/MinMaxBGRA.g.cs @@ -0,0 +1,52 @@ +// ReSharper disable ConvertToAutoProperty +// ReSharper disable ReplaceWithPrimaryConstructorParameter + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace HPPH; + +[DebuggerDisplay("[A: {AlphaMin}-{AlphaMax}, R: {RedMin}-{RedMax}, G: {GreenMin}-{GreenMax}, B: {BlueMin}-{BlueMax}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly partial struct MinMaxBGRA(byte bMin, byte bMax, byte gMin, byte gMax, byte rMin, byte rMax, byte aMin, byte aMax) : IMinMax +{ + #region Properties & Fields + + private readonly byte _bMin = bMin; + private readonly byte _bMax = bMax; + + private readonly byte _gMin = gMin; + private readonly byte _gMax = gMax; + + private readonly byte _rMin = rMin; + private readonly byte _rMax = rMax; + + private readonly byte _aMin = aMin; + private readonly byte _aMax = aMax; + + public byte RedMin => _rMin; + public byte RedMax => _rMax; + + public byte GreenMin => _gMin; + public byte GreenMax => _gMax; + + public byte BlueMin => _bMin; + public byte BlueMax => _bMax; + + public byte AlphaMin => _aMin; + public byte AlphaMax => _aMax; + + public byte RedRange => (byte)(_rMax - _rMin); + public byte GreenRange => (byte)(_gMax - _gMin); + public byte BlueRange => (byte)(_bMax - _bMin); + public byte AlphaRange => (byte)(_aMax - _aMin); + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {AlphaMin}-{AlphaMax}, R: {RedMin}-{RedMax}, G: {GreenMin}-{GreenMax}, B: {BlueMin}-{BlueMax}]"; + + #endregion +} diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/MinMaxRGB.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/MinMaxRGB.g.cs new file mode 100644 index 0000000..0c5e9ce --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/MinMaxRGB.g.cs @@ -0,0 +1,49 @@ +// ReSharper disable ConvertToAutoProperty +// ReSharper disable ReplaceWithPrimaryConstructorParameter + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace HPPH; + +[DebuggerDisplay("[A: {AlphaMin}-{AlphaMax}, R: {RedMin}-{RedMax}, G: {GreenMin}-{GreenMax}, B: {BlueMin}-{BlueMax}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly partial struct MinMaxRGB(byte rMin, byte rMax, byte gMin, byte gMax, byte bMin, byte bMax) : IMinMax +{ + #region Properties & Fields + + private readonly byte _rMin = rMin; + private readonly byte _rMax = rMax; + + private readonly byte _gMin = gMin; + private readonly byte _gMax = gMax; + + private readonly byte _bMin = bMin; + private readonly byte _bMax = bMax; + + public byte RedMin => _rMin; + public byte RedMax => _rMax; + + public byte GreenMin => _gMin; + public byte GreenMax => _gMax; + + public byte BlueMin => _bMin; + public byte BlueMax => _bMax; + + public byte AlphaMin => byte.MaxValue; + public byte AlphaMax => byte.MaxValue; + + public byte RedRange => (byte)(_rMax - _rMin); + public byte GreenRange => (byte)(_gMax - _gMin); + public byte BlueRange => (byte)(_bMax - _bMin); + public byte AlphaRange => 0; + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {AlphaMin}-{AlphaMax}, R: {RedMin}-{RedMax}, G: {GreenMin}-{GreenMax}, B: {BlueMin}-{BlueMax}]"; + + #endregion +} diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/MinMaxRGBA.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/MinMaxRGBA.g.cs new file mode 100644 index 0000000..44d1c4a --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/MinMaxRGBA.g.cs @@ -0,0 +1,52 @@ +// ReSharper disable ConvertToAutoProperty +// ReSharper disable ReplaceWithPrimaryConstructorParameter + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace HPPH; + +[DebuggerDisplay("[A: {AlphaMin}-{AlphaMax}, R: {RedMin}-{RedMax}, G: {GreenMin}-{GreenMax}, B: {BlueMin}-{BlueMax}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly partial struct MinMaxRGBA(byte rMin, byte rMax, byte gMin, byte gMax, byte bMin, byte bMax, byte aMin, byte aMax) : IMinMax +{ + #region Properties & Fields + + private readonly byte _rMin = rMin; + private readonly byte _rMax = rMax; + + private readonly byte _gMin = gMin; + private readonly byte _gMax = gMax; + + private readonly byte _bMin = bMin; + private readonly byte _bMax = bMax; + + private readonly byte _aMin = aMin; + private readonly byte _aMax = aMax; + + public byte RedMin => _rMin; + public byte RedMax => _rMax; + + public byte GreenMin => _gMin; + public byte GreenMax => _gMax; + + public byte BlueMin => _bMin; + public byte BlueMax => _bMax; + + public byte AlphaMin => _aMin; + public byte AlphaMax => _aMax; + + public byte RedRange => (byte)(_rMax - _rMin); + public byte GreenRange => (byte)(_gMax - _gMin); + public byte BlueRange => (byte)(_bMax - _bMin); + public byte AlphaRange => (byte)(_aMax - _aMin); + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {AlphaMin}-{AlphaMax}, R: {RedMin}-{RedMax}, G: {GreenMin}-{GreenMax}, B: {BlueMin}-{BlueMax}]"; + + #endregion +} diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/SumABGR.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/SumABGR.g.cs new file mode 100644 index 0000000..5330cbf --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/SumABGR.g.cs @@ -0,0 +1,34 @@ +// ReSharper disable ConvertToAutoProperty +// ReSharper disable ConvertToAutoPropertyWhenPossible +// ReSharper disable ReplaceWithPrimaryConstructorParameter + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace HPPH; + +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly partial struct SumABGR(long a, long b, long g, long r) : ISum +{ + #region Properties & Fields + + private readonly long _a = a; + private readonly long _b = b; + private readonly long _g = g; + private readonly long _r = r; + + public long R => _r; + public long G => _g; + public long B => _b; + public long A => _a; + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; + + #endregion +} diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/SumARGB.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/SumARGB.g.cs new file mode 100644 index 0000000..351ec90 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/SumARGB.g.cs @@ -0,0 +1,34 @@ +// ReSharper disable ConvertToAutoProperty +// ReSharper disable ConvertToAutoPropertyWhenPossible +// ReSharper disable ReplaceWithPrimaryConstructorParameter + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace HPPH; + +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly partial struct SumARGB(long a, long r, long g, long b) : ISum +{ + #region Properties & Fields + + private readonly long _a = a; + private readonly long _r = r; + private readonly long _g = g; + private readonly long _b = b; + + public long R => _r; + public long G => _g; + public long B => _b; + public long A => _a; + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; + + #endregion +} diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/SumBGR.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/SumBGR.g.cs new file mode 100644 index 0000000..df489ce --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/SumBGR.g.cs @@ -0,0 +1,34 @@ +// ReSharper disable ConvertToAutoProperty +// ReSharper disable ConvertToAutoPropertyWhenPossible +// ReSharper disable ReplaceWithPrimaryConstructorParameter + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace HPPH; + +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly partial struct SumBGR(long b, long g, long r, long a) : ISum +{ + #region Properties & Fields + + private readonly long _b = b; + private readonly long _g = g; + private readonly long _r = r; + private readonly long _a = a; + + public long A => _a; + public long R => _r; + public long G => _g; + public long B => _b; + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; + + #endregion +} diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/SumBGRA.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/SumBGRA.g.cs new file mode 100644 index 0000000..2e5cf13 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/SumBGRA.g.cs @@ -0,0 +1,34 @@ +// ReSharper disable ConvertToAutoProperty +// ReSharper disable ConvertToAutoPropertyWhenPossible +// ReSharper disable ReplaceWithPrimaryConstructorParameter + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace HPPH; + +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly partial struct SumBGRA(long b, long g, long r, long a) : ISum +{ + #region Properties & Fields + + private readonly long _b = b; + private readonly long _g = g; + private readonly long _r = r; + private readonly long _a = a; + + public long R => _r; + public long G => _g; + public long B => _b; + public long A => _a; + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; + + #endregion +} diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/SumRGB.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/SumRGB.g.cs new file mode 100644 index 0000000..c852c37 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/SumRGB.g.cs @@ -0,0 +1,34 @@ +// ReSharper disable ConvertToAutoProperty +// ReSharper disable ConvertToAutoPropertyWhenPossible +// ReSharper disable ReplaceWithPrimaryConstructorParameter + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace HPPH; + +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly partial struct SumRGB(long r, long g, long b, long a) : ISum +{ + #region Properties & Fields + + private readonly long _r = r; + private readonly long _g = g; + private readonly long _b = b; + private readonly long _a = a; + + public long A => _a; + public long R => _r; + public long G => _g; + public long B => _b; + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; + + #endregion +} diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/SumRGBA.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/SumRGBA.g.cs new file mode 100644 index 0000000..7799ff2 --- /dev/null +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/SumRGBA.g.cs @@ -0,0 +1,34 @@ +// ReSharper disable ConvertToAutoProperty +// ReSharper disable ConvertToAutoPropertyWhenPossible +// ReSharper disable ReplaceWithPrimaryConstructorParameter + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace HPPH; + +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly partial struct SumRGBA(long r, long g, long b, long a) : ISum +{ + #region Properties & Fields + + private readonly long _r = r; + private readonly long _g = g; + private readonly long _b = b; + private readonly long _a = a; + + public long R => _r; + public long G => _g; + public long B => _b; + public long A => _a; + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {A}, R: {R}, G: {G}, B: {B}]"; + + #endregion +} diff --git a/HPPH/HPPH.csproj b/HPPH/HPPH.csproj new file mode 100644 index 0000000..0561bbc --- /dev/null +++ b/HPPH/HPPH.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + true + true + Generated + + + + + + + + + + + diff --git a/HPPH/HPPH.csproj.DotSettings b/HPPH/HPPH.csproj.DotSettings new file mode 100644 index 0000000..f85fea1 --- /dev/null +++ b/HPPH/HPPH.csproj.DotSettings @@ -0,0 +1,15 @@ + + True + True + True + True + True + True + True + True + True + True + True + True + True + True \ No newline at end of file diff --git a/HPPH/Images/IImage.cs b/HPPH/Images/IImage.cs new file mode 100644 index 0000000..bd5cac6 --- /dev/null +++ b/HPPH/Images/IImage.cs @@ -0,0 +1,189 @@ +namespace HPPH; + +/// +/// Represents a image. +/// +public interface IImage : IEnumerable +{ + /// + /// Gets the color format used in this image. + /// + IColorFormat ColorFormat { get; } + + /// + /// Gets the width of this image. + /// + int Width { get; } + + /// + /// Gets the height of this image. + /// + int Height { get; } + + /// + /// Gets the size in bytes of this image. + /// + int SizeInBytes { get; } + + /// + /// Gets the color at the specified location. + /// + /// The X-location to read. + /// The Y-location to read. + /// The color at the specified location. + IColor this[int x, int y] { get; } + + /// + /// Gets an image representing the specified location. + /// + /// The X-location of the image. + /// The Y-location of the image. + /// The width of the sub-image. + /// + /// + IImage this[int x, int y, int width, int height] { get; } + + /// + /// Gets a list of all rows of this image. + /// + IImageRows Rows { get; } + + /// + /// Gets a list of all columns of this image. + /// + IImageColumns Columns { get; } + + /// + /// Gets an representing this . + /// + /// The color-type of the iamge. + /// The . + RefImage AsRefImage() where TColor : struct, IColor; + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + void CopyTo(Span destination); + + /// + /// Allocates a new array and copies this into it. + /// + /// The new array containing the data of this . + byte[] ToArray(); + + /// + /// Represents a list of rows of an image. + /// + public interface IImageRows : IEnumerable + { + /// + /// Gets the amount of rows in this list. + /// + int Count { get; } + + /// + /// Gets a specific . + /// + /// The ´row to get. + /// The requested . + IImageRow this[int column] { get; } + } + + /// + /// Represents a list of columns of an image. + /// + public interface IImageColumns : IEnumerable + { + /// + /// Gets the amount of columns in this list. + /// + int Count { get; } + + /// + /// Gets a specific . + /// + /// The column to get. + /// The requested . + IImageColumn this[int column] { get; } + } + + /// + /// Represents a single row of an image. + /// + public interface IImageRow : IEnumerable + { + /// + /// Gets the length of the row. + /// + int Length { get; } + + /// + /// Gets the size in bytes of this row. + /// + int SizeInBytes { get; } + + /// + /// Gets the at the specified location. + /// + /// The location to get the color from. + /// The at the specified location. + IColor this[int x] { get; } + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + void CopyTo(Span destination); + + /// + /// Allocates a new array and copies this into it. + /// + /// The new array containing the data of this . + byte[] ToArray(); + } + + /// + /// Represents a single column of an image. + /// + public interface IImageColumn : IEnumerable + { + /// + /// Gets the length of the column. + /// + int Length { get; } + + /// + /// Gets the size in bytes of this column. + /// + int SizeInBytes { get; } + + /// + /// Gets the at the specified location. + /// + /// The location to get the color from. + /// The at the specified location. + IColor this[int y] { get; } + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + void CopyTo(Span destination); + + /// + /// Allocates a new array and copies this into it. + /// + /// The new array containing the data of this . + byte[] ToArray(); + } +} \ No newline at end of file diff --git a/HPPH/Images/Image.cs b/HPPH/Images/Image.cs new file mode 100644 index 0000000..3833d78 --- /dev/null +++ b/HPPH/Images/Image.cs @@ -0,0 +1,428 @@ +using System.Collections; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace HPPH; + +/// +public sealed class Image : IImage + where TColor : struct, IColor +{ + #region Properties & Fields + + private readonly byte[] _buffer; + + private readonly int _x; + private readonly int _y; + private readonly int _stride; + + /// + public IColorFormat ColorFormat + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => TColor.ColorFormat; + } + + /// + public int Width { get; } + + /// + public int Height { get; } + + /// + public int SizeInBytes => Width * Height * ColorFormat.BytesPerPixel; + + #endregion + + #region Indexer + + /// + public IColor this[int x, int y] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((x < 0) || (y < 0) || (x >= Width) || (y >= Height)) throw new IndexOutOfRangeException(); + + return MemoryMarshal.Cast(_buffer)[((_y + y) * _stride) + (_x + x)]; + } + } + + /// + public IImage this[int x, int y, int width, int height] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException(); + + return new Image(_buffer, _x + x, _y + y, width, height, _stride); + } + } + + /// + public IImage.IImageRows Rows + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new ImageRows(_buffer, _x, _y, Width, Height, _stride); + } + + /// + public IImage.IImageColumns Columns + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new ImageColumns(_buffer, _x, _y, Width, Height, _stride); + } + + #endregion + + #region Constructors + + internal Image(byte[] buffer, int x, int y, int width, int height, int stride) + { + this._buffer = buffer; + this._x = x; + this._y = y; + this.Width = width; + this.Height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + /// + public void CopyTo(Span destination) + { + if (destination == null) throw new ArgumentNullException(nameof(destination)); + if (destination.Length < SizeInBytes) throw new ArgumentException("The destination is too small to fit this image.", nameof(destination)); + + int targetStride = Width * ColorFormat.BytesPerPixel; + IImage.IImageRows rows = Rows; + Span target = destination; + foreach (IImage.IImageRow row in rows) + { + row.CopyTo(target); + target = target[targetStride..]; + } + } + + /// + public byte[] ToArray() + { + byte[] array = new byte[SizeInBytes]; + CopyTo(array); + return array; + } + + /// + public RefImage AsRefImage() + where T : struct, IColor + { + if (typeof(T) != typeof(TColor)) throw new ArgumentException("The requested color format does not fit this image.", nameof(T)); + + return new RefImage(MemoryMarshal.Cast(_buffer), _x, _y, Width, Height, _stride); + } + + /// + public IEnumerator GetEnumerator() + { + for (int y = 0; y < Height; y++) + for (int x = 0; x < Width; x++) + yield return this[x, y]; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion + + #region Indexer-Classes + + /// + private sealed class ImageRows : IImage.IImageRows + { + #region Properties & Fields + + private readonly byte[] _buffer; + private readonly int _x; + private readonly int _y; + private readonly int _width; + private readonly int _height; + private readonly int _stride; + + /// + public int Count => _height; + + #endregion + + #region Indexer + + /// + public IImage.IImageRow this[int row] + { + get + { + if ((row < 0) || (row >= _height)) throw new IndexOutOfRangeException(); + + return new ImageRow(_buffer, (((row + _y) * _stride) + _x), _width); + } + } + + #endregion + + #region Constructors + + internal ImageRows(byte[] buffer, int x, int y, int width, int height, int stride) + { + this._buffer = buffer; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + /// + public IEnumerator GetEnumerator() + { + for (int y = 0; y < _height; y++) + yield return this[y]; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion + } + + /// + private sealed class ImageRow : IImage.IImageRow + { + #region Properties & Fields + + private readonly byte[] _buffer; + private readonly int _start; + private readonly int _length; + + /// + public int Length => _length; + + /// + public int SizeInBytes => Length * TColor.ColorFormat.BytesPerPixel; + + #endregion + + #region Indexer + + /// + public IColor this[int x] + { + get + { + if ((x < 0) || (x >= _length)) throw new IndexOutOfRangeException(); + + ReadOnlySpan row = MemoryMarshal.Cast(_buffer)[_start..]; + return row[x]; + } + } + + #endregion + + #region Constructors + + internal ImageRow(byte[] buffer, int start, int length) + { + this._buffer = buffer; + this._start = start; + this._length = length; + } + + #endregion + + #region Methods + + /// + public void CopyTo(Span destination) + { + if (destination == null) throw new ArgumentNullException(nameof(destination)); + if (destination.Length < SizeInBytes) throw new ArgumentException("The destination is too small to fit this image.", nameof(destination)); + + MemoryMarshal.Cast(_buffer).Slice(_start, _length).CopyTo(MemoryMarshal.Cast(destination)); + } + + /// + public byte[] ToArray() + { + byte[] array = new byte[SizeInBytes]; + CopyTo(array); + return array; + } + + /// + public IEnumerator GetEnumerator() + { + for (int x = 0; x < _length; x++) + yield return this[x]; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion + } + + /// + private sealed class ImageColumns : IImage.IImageColumns + { + #region Properties & Fields + + private readonly byte[] _buffer; + private readonly int _x; + private readonly int _y; + private readonly int _width; + private readonly int _height; + private readonly int _stride; + + /// + public int Count => _width; + + #endregion + + #region Indexer + + /// + public IImage.IImageColumn this[int column] + { + get + { + if ((column < 0) || (column >= _width)) throw new IndexOutOfRangeException(); + + return new ImageColumn(_buffer, (_y * _stride) + _x + column, _height, _stride); + } + } + + #endregion + + #region Constructors + + internal ImageColumns(byte[] buffer, int x, int y, int width, int height, int stride) + { + this._buffer = buffer; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + /// + public IEnumerator GetEnumerator() + { + for (int y = 0; y < _height; y++) + yield return this[y]; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion + } + + /// + private sealed class ImageColumn : IImage.IImageColumn + { + #region Properties & Fields + + private readonly byte[] _buffer; + private readonly int _start; + private readonly int _length; + private readonly int _step; + + /// + public int Length => _length; + + /// + public int SizeInBytes => Length * TColor.ColorFormat.BytesPerPixel; + + #endregion + + #region Indexer + + /// + public IColor this[int y] + { + get + { + if ((y < 0) || (y >= _length)) throw new IndexOutOfRangeException(); + + ReadOnlySpan data = MemoryMarshal.Cast(_buffer)[_start..]; + return data[y * _step]; + } + } + + #endregion + + #region Constructors + + internal ImageColumn(byte[] buffer, int start, int length, int step) + { + this._buffer = buffer; + this._start = start; + this._length = length; + this._step = step; + } + + #endregion + + #region Methods + + /// + public void CopyTo(Span destination) + { + if (destination == null) throw new ArgumentNullException(nameof(destination)); + if (destination.Length < SizeInBytes) throw new ArgumentException("The destination is too small to fit this image.", nameof(destination)); + + if (_step == 1) + _buffer.AsSpan(_start, SizeInBytes).CopyTo(destination); + else + { + ReadOnlySpan data = MemoryMarshal.Cast(_buffer)[_start..]; + Span target = MemoryMarshal.Cast(destination); + for (int i = 0; i < Length; i++) + target[i] = data[i * _step]; + } + } + + /// + public byte[] ToArray() + { + byte[] array = new byte[SizeInBytes]; + CopyTo(array); + return array; + } + + /// + public IEnumerator GetEnumerator() + { + for (int y = 0; y < _length; y++) + yield return this[y]; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion + } + + #endregion +} \ No newline at end of file diff --git a/HPPH/Images/RefImage.cs b/HPPH/Images/RefImage.cs new file mode 100644 index 0000000..8e7a725 --- /dev/null +++ b/HPPH/Images/RefImage.cs @@ -0,0 +1,367 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace HPPH; + +public readonly ref struct RefImage + where TColor : struct, IColor +{ + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + + private readonly int _x; + private readonly int _y; + + /// + /// Gets the width of the image. + /// + public int Width { get; } + + /// + /// Gets the height of the image. + /// + public int Height { get; } + + /// + /// Gets the stride (entries per row) of the underlying buffer. + /// Only useful if you want to work with a pinned buffer. + /// + public int RawStride { get; } + + #endregion + + #region Indexer + + public ref readonly TColor this[int x, int y] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((x < 0) || (y < 0) || (x >= Width) || (y >= Height)) throw new IndexOutOfRangeException(); + + ref TColor r0 = ref MemoryMarshal.GetReference(_pixels); + nint offset = (nint)(uint)((_y + y) * RawStride) + (_x + x); + return ref Unsafe.Add(ref r0, offset); + } + } + + public RefImage this[int x, int y, int width, int height] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException(); + + return new RefImage(_pixels, _x + x, _y + y, width, height, RawStride); + } + } + + public ImageRows Rows + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(_pixels, _x, _y, Width, Height, RawStride); + } + + public ImageColumns Columns + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(_pixels, _x, _y, Width, Height, RawStride); + } + + #endregion + + #region Constructors + + internal RefImage(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + { + this._pixels = pixels; + this._x = x; + this._y = y; + this.Width = width; + this.Height = height; + this.RawStride = stride; + } + + #endregion + + #region Methods + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + public void CopyTo(Span destination) + { + if (destination == null) throw new ArgumentNullException(nameof(destination)); + if (destination.Length < (Width * Height)) throw new ArgumentException("The destination is too small to fit this image.", nameof(destination)); + + ImageRows rows = Rows; + Span target = destination; + foreach (ReadOnlyRefEnumerable row in rows) + { + row.CopyTo(target); + target = target[Width..]; + } + } + + /// + /// Allocates a new array and copies this into it. + /// + /// The new array containing the data of this . + public TColor[] ToArray() + { + TColor[] array = new TColor[Width * Height]; + CopyTo(array); + return array; + } + + /// + /// Returns a reference to the first element of this image inside the full image buffer. + /// + public ref readonly TColor GetPinnableReference() + { + if (_pixels.Length == 0) + return ref Unsafe.NullRef(); + + int offset = (_y * RawStride) + _x; + return ref MemoryMarshal.GetReference(_pixels[offset..]); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImageEnumerator GetEnumerator() => new(_pixels); + + #endregion + + public ref struct ImageEnumerator + { + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + private int _position; + + /// + public TColor Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _pixels[_position]; + } + + #endregion + + #region Constructors + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ImageEnumerator(ReadOnlySpan pixels) + { + this._pixels = pixels; + + _position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _pixels.Length; + + #endregion + } + + #region Indexer-Structs + + public readonly ref struct ImageRows + { + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + private readonly int _x; + private readonly int _y; + private readonly int _width; + private readonly int _height; + private readonly int _stride; + + public int Count => _height; + + #endregion + + #region Indexer + + public readonly ReadOnlyRefEnumerable this[int row] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((row < 0) || (row > _height)) throw new IndexOutOfRangeException(); + + ref TColor r0 = ref MemoryMarshal.GetReference(_pixels); + ref TColor rr = ref Unsafe.Add(ref r0, (nint)(uint)(((row + _y) * _stride) + _x)); + + return new ReadOnlyRefEnumerable(rr, _width, 1); + } + } + + #endregion + + #region Constructors + + public ImageRows(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + { + this._pixels = pixels; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImageRowsEnumerator GetEnumerator() => new(this); + + #endregion + + public ref struct ImageRowsEnumerator + { + #region Properties & Fields + + private readonly ImageRows _rows; + private int _position; + + /// + public ReadOnlyRefEnumerable Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _rows[_position]; + } + + #endregion + + #region Constructors + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ImageRowsEnumerator(ImageRows rows) + { + this._rows = rows; + + _position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _rows._height; + + #endregion + } + } + + public readonly ref struct ImageColumns + { + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + private readonly int _x; + private readonly int _y; + private readonly int _width; + private readonly int _height; + private readonly int _stride; + + public int Count => _width; + + #endregion + + #region Indexer + + public ReadOnlyRefEnumerable this[int column] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((column < 0) || (column > _width)) throw new IndexOutOfRangeException(); + + ref TColor r0 = ref MemoryMarshal.GetReference(_pixels); + ref TColor rc = ref Unsafe.Add(ref r0, (nint)(uint)((_y * _stride) + (column + _x))); + + return new ReadOnlyRefEnumerable(rc, _height, _stride); + } + } + + #endregion + + #region Constructors + + public ImageColumns(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + { + this._pixels = pixels; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImageColumnsEnumerator GetEnumerator() => new(this); + + #endregion + + public ref struct ImageColumnsEnumerator + { + #region Properties & Fields + + private readonly ImageColumns _columns; + private int _position; + + /// + public ReadOnlyRefEnumerable Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _columns[_position]; + } + + #endregion + + #region Constructors + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ImageColumnsEnumerator(ImageColumns columns) + { + this._columns = columns; + this._position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _columns._width; + + #endregion + } + } + + #endregion +} \ No newline at end of file diff --git a/HPPH/PixelHelper.Average.cs b/HPPH/PixelHelper.Average.cs new file mode 100644 index 0000000..1b2d6f5 --- /dev/null +++ b/HPPH/PixelHelper.Average.cs @@ -0,0 +1,89 @@ +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace HPPH; + +public static partial class PixelHelper +{ + #region Methods + + public static IColor Average(IImage image) + { + ArgumentNullException.ThrowIfNull(image); + + int dataLength = image.SizeInBytes; + byte[] array = ArrayPool.Shared.Rent(dataLength); + Span buffer = array.AsSpan()[..dataLength]; + try + { + image.CopyTo(buffer); + return image.ColorFormat.Average(buffer); + } + finally + { + ArrayPool.Shared.Return(array); + } + } + + public static T Average(RefImage image) + where T : struct, IColor + { + int dataLength = image.Width * image.Height; + T[] array = ArrayPool.Shared.Rent(dataLength); + Span buffer = array.AsSpan()[..(dataLength)]; + try + { + image.CopyTo(buffer); + return Average(buffer); + } + finally + { + ArrayPool.Shared.Return(array); + } + } + + public static T Average(ReadOnlySpan colors) + where T : struct, IColor + { + if (colors == null) throw new ArgumentNullException(nameof(colors)); + + return T.ColorFormat.BytesPerPixel switch + { + 3 => Unsafe.BitCast(Average(MemoryMarshal.Cast(colors))), + 4 => Unsafe.BitCast(Average(MemoryMarshal.Cast(colors))), + _ => throw new NotSupportedException("Data is not of a supported valid color-type.") + }; + } + + internal static Generic3ByteData Average(ReadOnlySpan data) + { + if (data == null) throw new ArgumentNullException(nameof(data)); + if (data.Length == 0) return default; + if (data.Length == 1) return data[0]; + + Generic4LongData sum = Sum(data); + + float count = data.Length; + return new Generic3ByteData((sum.L1 / count).GetByteValueFromPercentage(), + (sum.L2 / count).GetByteValueFromPercentage(), + (sum.L3 / count).GetByteValueFromPercentage()); + } + + internal static Generic4ByteData Average(ReadOnlySpan data) + { + if (data == null) throw new ArgumentNullException(nameof(data)); + if (data.Length == 0) return default; + if (data.Length == 1) return data[0]; + + Generic4LongData sum = Sum(data); + + float count = data.Length; + return new Generic4ByteData((sum.L1 / count).GetByteValueFromPercentage(), + (sum.L2 / count).GetByteValueFromPercentage(), + (sum.L3 / count).GetByteValueFromPercentage(), + (sum.L4 / count).GetByteValueFromPercentage()); + } + + #endregion +} diff --git a/HPPH/PixelHelper.MinMax.cs b/HPPH/PixelHelper.MinMax.cs new file mode 100644 index 0000000..39d1bf7 --- /dev/null +++ b/HPPH/PixelHelper.MinMax.cs @@ -0,0 +1,212 @@ +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace HPPH; + +public static unsafe partial class PixelHelper +{ + #region Methods + + public static IMinMax MinMax(IImage image) + { + ArgumentNullException.ThrowIfNull(image); + + int dataLength = image.SizeInBytes; + byte[] array = ArrayPool.Shared.Rent(dataLength); + Span buffer = array.AsSpan()[..dataLength]; + try + { + image.CopyTo(buffer); + return image.ColorFormat.MinMax(buffer); + } + finally + { + ArrayPool.Shared.Return(array); + } + } + + public static IMinMax MinMax(RefImage image) + where T : struct, IColor + { + int dataLength = image.Width * image.Height; + T[] array = ArrayPool.Shared.Rent(dataLength); + Span buffer = array.AsSpan()[..(dataLength)]; + try + { + image.CopyTo(buffer); + return MinMax(buffer); + } + finally + { + ArrayPool.Shared.Return(array); + } + } + + public static IMinMax MinMax(ReadOnlySpan colors) + where T : struct, IColor + => T.ColorFormat.MinMax(MemoryMarshal.AsBytes(colors)); + + internal static IMinMax MinMax(ReadOnlySpan colors) + where T : struct, IColor + where TMinMax : struct, IMinMax + { + if (colors == null) throw new ArgumentNullException(nameof(colors)); + + return T.ColorFormat.BytesPerPixel switch + { + 3 => Unsafe.BitCast(MinMax(MemoryMarshal.Cast(colors))), + 4 => Unsafe.BitCast(MinMax(MemoryMarshal.Cast(colors))), + _ => throw new NotSupportedException("Data is not of a supported valid color-type.") + }; + } + + internal static Generic3ByteMinMax MinMax(ReadOnlySpan data) + { + byte minB1 = byte.MaxValue, minB2 = byte.MaxValue, minB3 = byte.MaxValue; + byte maxB1 = byte.MinValue, maxB2 = byte.MinValue, maxB3 = byte.MinValue; + + const int BYTES_PER_COLOR = 3; + int elementsPerVector = Vector.Count / BYTES_PER_COLOR; + + int chunks; + if (Vector.IsHardwareAccelerated && ((chunks = data.Length / elementsPerVector) > 1)) + { + int bytesPerVector = elementsPerVector * BYTES_PER_COLOR; + int missingElements = data.Length - (chunks * elementsPerVector); + + Vector max = Vector.Zero; + Vector min = new(byte.MaxValue); + + ReadOnlySpan colorBytes = MemoryMarshal.AsBytes(data); + fixed (byte* colorPtr = colorBytes) + { + for (int i = 0; i < chunks; i++) + { + Vector vector = *(Vector*)(colorPtr + (i * bytesPerVector)); + + max = Vector.Max(max, vector); + min = Vector.Min(min, vector); + } + } + + for (int i = 0; i < bytesPerVector; i += BYTES_PER_COLOR) + { + minB1 = Math.Min(minB1, min[i]); + minB2 = Math.Min(minB2, min[i + 1]); + minB3 = Math.Min(minB3, min[i + 2]); + + maxB1 = Math.Max(maxB1, max[i]); + maxB2 = Math.Max(maxB2, max[i + 1]); + maxB3 = Math.Max(maxB3, max[i + 2]); + } + + for (int i = 0; i < missingElements; i++) + { + Generic3ByteData d = data[^(i + 1)]; + + minB1 = Math.Min(minB1, d.B1); + minB2 = Math.Min(minB2, d.B2); + minB3 = Math.Min(minB3, d.B3); + + maxB1 = Math.Max(maxB1, d.B1); + maxB2 = Math.Max(maxB2, d.B2); + maxB3 = Math.Max(maxB3, d.B3); + } + } + else + { + foreach (Generic3ByteData d in data) + { + minB1 = Math.Min(minB1, d.B1); + minB2 = Math.Min(minB2, d.B2); + minB3 = Math.Min(minB3, d.B3); + + maxB1 = Math.Max(maxB1, d.B1); + maxB2 = Math.Max(maxB2, d.B2); + maxB3 = Math.Max(maxB3, d.B3); + } + } + + return new Generic3ByteMinMax(minB1, maxB1, minB2, maxB2, minB3, maxB3); + } + + internal static Generic4ByteMinMax MinMax(ReadOnlySpan data) + { + byte minB1 = byte.MaxValue, minB2 = byte.MaxValue, minB3 = byte.MaxValue, minB4 = byte.MaxValue; + byte maxB1 = byte.MinValue, maxB2 = byte.MinValue, maxB3 = byte.MinValue, maxB4 = byte.MinValue; + + const int BYTES_PER_COLOR = 4; + int elementsPerVector = Vector.Count / BYTES_PER_COLOR; + + int chunks; + if (Vector.IsHardwareAccelerated && ((chunks = data.Length / elementsPerVector) > 1)) + { + int bytesPerVector = elementsPerVector * BYTES_PER_COLOR; + int missingElements = data.Length - (chunks * elementsPerVector); + + Vector max = Vector.Zero; + Vector min = new(byte.MaxValue); + + ReadOnlySpan colorBytes = MemoryMarshal.AsBytes(data); + fixed (byte* colorPtr = colorBytes) + { + for (int i = 0; i < chunks; i++) + { + Vector vector = *(Vector*)(colorPtr + (i * bytesPerVector)); + + max = Vector.Max(max, vector); + min = Vector.Min(min, vector); + } + } + + for (int i = 0; i < bytesPerVector; i += BYTES_PER_COLOR) + { + minB1 = Math.Min(minB1, min[i]); + minB2 = Math.Min(minB2, min[i + 1]); + minB3 = Math.Min(minB3, min[i + 2]); + minB4 = Math.Min(minB4, min[i + 3]); + + maxB1 = Math.Max(maxB1, max[i]); + maxB2 = Math.Max(maxB2, max[i + 1]); + maxB3 = Math.Max(maxB3, max[i + 2]); + maxB4 = Math.Max(maxB4, max[i + 3]); + } + + for (int i = 0; i < missingElements; i++) + { + Generic4ByteData d = data[^(i + 1)]; + + minB1 = Math.Min(minB1, d.B1); + minB2 = Math.Min(minB2, d.B2); + minB3 = Math.Min(minB3, d.B3); + minB4 = Math.Min(minB4, d.B4); + + maxB1 = Math.Max(maxB1, d.B1); + maxB2 = Math.Max(maxB2, d.B2); + maxB3 = Math.Max(maxB3, d.B3); + maxB4 = Math.Max(maxB4, d.B4); + } + } + else + { + foreach (Generic4ByteData d in data) + { + minB1 = Math.Min(minB1, d.B1); + minB2 = Math.Min(minB2, d.B2); + minB3 = Math.Min(minB3, d.B3); + minB4 = Math.Min(minB4, d.B4); + + maxB1 = Math.Max(maxB1, d.B1); + maxB2 = Math.Max(maxB2, d.B2); + maxB3 = Math.Max(maxB3, d.B3); + maxB4 = Math.Max(maxB4, d.B4); + } + } + + return new Generic4ByteMinMax(minB1, maxB1, minB2, maxB2, minB3, maxB3, minB4, maxB4); + } + + #endregion +} diff --git a/HPPH/PixelHelper.Quantize.cs b/HPPH/PixelHelper.Quantize.cs new file mode 100644 index 0000000..85f4618 --- /dev/null +++ b/HPPH/PixelHelper.Quantize.cs @@ -0,0 +1,91 @@ +using System.Buffers; +using System.Numerics; + +namespace HPPH; + +public static partial class PixelHelper +{ + #region Methods + + public static IColor[] CreateColorPalette(IImage image, int paletteSize) + { + ArgumentNullException.ThrowIfNull(image); + + int dataLength = image.SizeInBytes; + byte[] array = ArrayPool.Shared.Rent(dataLength); + Span buffer = array.AsSpan()[..dataLength]; + try + { + image.CopyTo(buffer); + return image.ColorFormat.CreateColorPalette(buffer, paletteSize); + } + finally + { + ArrayPool.Shared.Return(array); + } + } + + public static T[] CreateColorPalette(RefImage image, int paletteSize) + where T : unmanaged, IColor + { + int dataLength = image.Width * image.Height; + T[] array = ArrayPool.Shared.Rent(dataLength); + Span buffer = array.AsSpan()[..(dataLength)]; + try + { + image.CopyTo(buffer); + return CreateColorPalette(buffer, paletteSize); + } + finally + { + ArrayPool.Shared.Return(array); + } + } + + public static T[] CreateColorPalette(ReadOnlySpan colors, int paletteSize) + where T : unmanaged, IColor + { + T[] buffer = ArrayPool.Shared.Rent(colors.Length); + try + { + Span colorBuffer = buffer.AsSpan()[..colors.Length]; + colors.CopyTo(colorBuffer); + + return CreateColorPalette(colorBuffer, paletteSize); + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } + + public static T[] CreateColorPalette(Span colors, int paletteSize) + where T : unmanaged, IColor + { + int splits = BitOperations.Log2((uint)paletteSize); + + Span> cubes = new ColorCube[1 << splits]; + cubes[0] = new ColorCube(0, colors.Length, SortTarget.None); + + int currentIndex = 0; + for (int i = 0; i < splits; i++) + { + int currentCubeCount = 1 << i; + Span> currentCubes = cubes[..currentCubeCount]; + for (int j = 0; j < currentCubes.Length; j++) + { + currentCubes[j].Split(colors, out ColorCube a, out ColorCube b); + currentCubes[j] = a; + cubes[++currentIndex] = b; + } + } + + T[] result = new T[cubes.Length]; + for (int i = 0; i < cubes.Length; i++) + result[i] = Average(cubes[i].Slice(colors)); + + return result; + } + + #endregion +} diff --git a/HPPH/PixelHelper.Sort.cs b/HPPH/PixelHelper.Sort.cs new file mode 100644 index 0000000..8706653 --- /dev/null +++ b/HPPH/PixelHelper.Sort.cs @@ -0,0 +1,45 @@ +namespace HPPH; + +public static unsafe partial class PixelHelper +{ + #region Methods + + [ColorSortGenerator("T", "R")] + public static partial void SortByRed(Span colors) + where T : unmanaged, IColor; + + [ColorSortGenerator("T", "G")] + public static partial void SortByGreen(Span colors) + where T : unmanaged, IColor; + + [ColorSortGenerator("T", "B")] + public static partial void SortByBlue(Span colors) + where T : unmanaged, IColor; + + [ColorSortGenerator("T", "A")] + public static partial void SortByAlpha(Span colors) + where T : unmanaged, IColor; + + [ColorSortGenerator("Generic3ByteData", "B1")] + internal static partial void SortB1(Span colors); + + [ColorSortGenerator("Generic3ByteData", "B2")] + internal static partial void SortB2(Span colors); + + [ColorSortGenerator("Generic3ByteData", "B3")] + internal static partial void SortB3(Span colors); + + [ColorSortGenerator("Generic4ByteData", "B1")] + internal static partial void SortB1(Span colors); + + [ColorSortGenerator("Generic4ByteData", "B2")] + internal static partial void SortB2(Span colors); + + [ColorSortGenerator("Generic4ByteData", "B3")] + internal static partial void SortB3(Span colors); + + [ColorSortGenerator("Generic4ByteData", "B4")] + internal static partial void SortB4(Span colors); + + #endregion +} diff --git a/HPPH/PixelHelper.Sum.cs b/HPPH/PixelHelper.Sum.cs new file mode 100644 index 0000000..3fa0417 --- /dev/null +++ b/HPPH/PixelHelper.Sum.cs @@ -0,0 +1,258 @@ +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics.X86; +using System.Runtime.Intrinsics; +using System.Buffers; +using System.Runtime.CompilerServices; + +namespace HPPH; + +public static unsafe partial class PixelHelper +{ + #region Methods + + public static ISum Sum(IImage image) + { + ArgumentNullException.ThrowIfNull(image); + + int dataLength = image.SizeInBytes; + byte[] array = ArrayPool.Shared.Rent(dataLength); + Span buffer = array.AsSpan()[..dataLength]; + try + { + image.CopyTo(buffer); + return image.ColorFormat.Sum(buffer); + } + finally + { + ArrayPool.Shared.Return(array); + } + } + + public static ISum Sum(RefImage image) + where T : struct, IColor + { + int dataLength = image.Width * image.Height; + T[] array = ArrayPool.Shared.Rent(dataLength); + Span buffer = array.AsSpan()[..(dataLength)]; + try + { + image.CopyTo(buffer); + return Sum(buffer); + } + finally + { + ArrayPool.Shared.Return(array); + } + } + + public static ISum Sum(ReadOnlySpan colors) + where T : struct, IColor + => T.ColorFormat.Sum(MemoryMarshal.AsBytes(colors)); + + internal static ISum Sum(ReadOnlySpan colors) + where T : struct, IColor + where TSum : struct, ISum + { + if (colors == null) throw new ArgumentNullException(nameof(colors)); + + return T.ColorFormat.BytesPerPixel switch + { + // DarthAffe 05.07.2024: Important: The sum of 3-byte colors are result in 4 byte data! + 3 => Unsafe.BitCast(Sum(MemoryMarshal.Cast(colors))), + 4 => Unsafe.BitCast(Sum(MemoryMarshal.Cast(colors))), + _ => throw new NotSupportedException("Data is not of a supported valid color-type.") + }; + } + + internal static Generic4LongData Sum(ReadOnlySpan data) + { + long b1Sum = 0, b2Sum = 0, b3Sum = 0; + + const int ELEMENTS_PER_VECTOR = 32; + int chunks; + if (Avx2.IsSupported && ((chunks = data.Length / ELEMENTS_PER_VECTOR) > 0)) + { + ReadOnlySpan blendMask1 = + [ + 255, 0, 0, + 255, 0, 0, + 255, 0, 0, + 255, 0, 0, + 255, 0, 0, + 255, 0, 0, + 255, 0, 0, + 255, 0, 0, + 255, 0, 0, + 255, 0, 0, + 255, 0 + ]; + + ReadOnlySpan blendMask2 = + [ + 0, 255, 0, + 0, 255, 0, + 0, 255, 0, + 0, 255, 0, + 0, 255, 0, + 0, 255, 0, + 0, 255, 0, + 0, 255, 0, + 0, 255, 0, + 0, 255, 0, + 0, 255 + ]; + + ReadOnlySpan blendMask3 = + [ + 0, 0, 255, + 0, 0, 255, + 0, 0, 255, + 0, 0, 255, + 0, 0, 255, + 0, 0, 255, + 0, 0, 255, + 0, 0, 255, + 0, 0, 255, + 0, 0, 255, + 0, 0 + ]; + + Vector256 blend1MaskVector = Vector256.LoadUnsafe(ref MemoryMarshal.GetReference(blendMask1)); + Vector256 blend2MaskVector = Vector256.LoadUnsafe(ref MemoryMarshal.GetReference(blendMask2)); + Vector256 blend3MaskVector = Vector256.LoadUnsafe(ref MemoryMarshal.GetReference(blendMask3)); + + Vector256 b1SumVector = Vector256.Zero; + Vector256 b2SumVector = Vector256.Zero; + Vector256 b3SumVector = Vector256.Zero; + + int missingElements = data.Length - (chunks * ELEMENTS_PER_VECTOR); + + ReadOnlySpan dataBytes = MemoryMarshal.AsBytes(data); + fixed (byte* bytePtr = dataBytes) + { + for (int i = 0; i < chunks; i++) + { + byte* basePtr = bytePtr + (i * 96); + Vector256 data1 = *(Vector256*)(basePtr); + Vector256 data2 = *(Vector256*)(basePtr + 32); + Vector256 data3 = *(Vector256*)(basePtr + 64); + + Vector256 vectorB1Blend1 = Avx2.BlendVariable(data2, data1, blend1MaskVector); + Vector256 vectorB2Blend1 = Avx2.BlendVariable(data2, data1, blend2MaskVector); + Vector256 vectorB3Blend1 = Avx2.BlendVariable(data2, data1, blend3MaskVector); + + Vector256 vectorB1Blend2 = Avx2.BlendVariable(vectorB1Blend1, data3, blend3MaskVector); + Vector256 vectorB2Blend2 = Avx2.BlendVariable(vectorB2Blend1, data3, blend1MaskVector); + Vector256 vectorB3Blend2 = Avx2.BlendVariable(vectorB3Blend1, data3, blend2MaskVector); + + Vector256 sumB1 = Avx2.SumAbsoluteDifferences(vectorB1Blend2, Vector256.Zero).AsInt64(); + Vector256 sumB2 = Avx2.SumAbsoluteDifferences(vectorB2Blend2, Vector256.Zero).AsInt64(); + Vector256 sumB3 = Avx2.SumAbsoluteDifferences(vectorB3Blend2, Vector256.Zero).AsInt64(); + + b1SumVector = Avx2.Add(b1SumVector, sumB1); + b2SumVector = Avx2.Add(b2SumVector, sumB2); + b3SumVector = Avx2.Add(b3SumVector, sumB3); + } + } + + b1Sum = b1SumVector[0] + b1SumVector[1] + b1SumVector[2] + b1SumVector[3]; + b2Sum = b2SumVector[0] + b2SumVector[1] + b2SumVector[2] + b2SumVector[3]; + b3Sum = b3SumVector[0] + b3SumVector[1] + b3SumVector[2] + b3SumVector[3]; + + for (int i = 0; i < missingElements; i++) + { + Generic3ByteData d = data[^(i + 1)]; + b1Sum += d.B1; + b2Sum += d.B2; + b3Sum += d.B3; + } + } + else + { + foreach (Generic3ByteData d in data) + { + b1Sum += d.B1; + b2Sum += d.B2; + b3Sum += d.B3; + } + } + + return new Generic4LongData(b1Sum, b2Sum, b3Sum, data.Length * 255); + } + + internal static Generic4LongData Sum(ReadOnlySpan data) + { + long b1Sum, b2Sum, b3Sum, b4Sum; + int i = 0; + + if (Avx2.IsSupported && (data.Length >= 8)) + { + ReadOnlySpan avx2ShuffleMask = + [ + // Byte 1 + 15, 11, 7, 3, + // Byte 2 + 14, 10, 6, 2, + // Byte 3 + 13, 9, 5, 1, + // Byte 4 + 12, 8, 4, 0 + ]; + + ReadOnlySpan avx2ControlData = + [ + // Byte 1 + 7, 3, + // Byte 2 + 6, 2, + // Byte 3 + 5, 1, + // Byte 4 + 4, 0 + ]; + + Vector256 rgbaSum64 = Vector256.Zero; + + ReadOnlySpan dataBytes = MemoryMarshal.AsBytes(data); + fixed (byte* bytePtr = dataBytes) + fixed (int* controlPtr = avx2ControlData) + fixed (byte* maskPtr = avx2ShuffleMask) + { + Vector256 avx2ShuffleMaskVector = Avx2.BroadcastVector128ToVector256(maskPtr); + + for (int j = 0; j < (data.Length / 8); j++, i += 8) + { + Vector256 chunk = *(Vector256*)(bytePtr + (i * 4)); + Vector256 deinterleaved = Avx2.Shuffle(chunk, avx2ShuffleMaskVector); + Vector256 deinterleaved2 = Avx2.PermuteVar8x32(deinterleaved.AsInt32(), *(Vector256*)controlPtr); + Vector256 sum = Avx2.SumAbsoluteDifferences(deinterleaved2.AsByte(), Vector256.Zero).AsInt64(); + rgbaSum64 = Avx2.Add(rgbaSum64, sum); + } + } + + Vector128 b1B2Sum = rgbaSum64.GetLower(); + Vector128 b3B4Sum = rgbaSum64.GetUpper(); + + b1Sum = b1B2Sum.GetLower()[0]; + b2Sum = b1B2Sum.GetUpper()[0]; + b3Sum = b3B4Sum.GetLower()[0]; + b4Sum = b3B4Sum.GetUpper()[0]; + } + else + { + b1Sum = b2Sum = b3Sum = b4Sum = 0; + } + + for (; i < data.Length; i++) + { + b1Sum += data[i].B1; + b2Sum += data[i].B2; + b3Sum += data[i].B3; + b4Sum += data[i].B4; + } + + return new Generic4LongData(b1Sum, b2Sum, b3Sum, b4Sum); + } + + #endregion +} diff --git a/HPPH/PixelHelper.cs b/HPPH/PixelHelper.cs new file mode 100644 index 0000000..a15aaca --- /dev/null +++ b/HPPH/PixelHelper.cs @@ -0,0 +1,6 @@ +using System.Runtime.CompilerServices; + +namespace HPPH; + +[SkipLocalsInit] +public static partial class PixelHelper; diff --git a/HPPH/Quantization/ColorCube.cs b/HPPH/Quantization/ColorCube.cs new file mode 100644 index 0000000..e0ee60b --- /dev/null +++ b/HPPH/Quantization/ColorCube.cs @@ -0,0 +1,72 @@ +using System.Runtime.CompilerServices; + +namespace HPPH; + +[SkipLocalsInit] +internal struct ColorCube + where T : unmanaged, IColor +{ + #region Properties & Fields + + private readonly int _offset; + private readonly int _length; + private SortTarget _sortOrder; + + #endregion + + #region Constructors + + internal ColorCube(int offset, int length, SortTarget sortOrder) + { + this._offset = offset; + this._length = length; + this._sortOrder = sortOrder; + } + + #endregion + + #region Methods + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Span Slice(Span fullColorList) => fullColorList.Slice(_offset, _length); + + internal void Split(Span fullColorList, out ColorCube a, out ColorCube b) + { + OrderColors(Slice(fullColorList), _sortOrder); + + int median = _length / 2; + + a = new ColorCube(_offset, median, _sortOrder); + b = new ColorCube(_offset + median, _length - median, _sortOrder); + } + + private void OrderColors(Span colors, SortTarget preOrdered) + { + if (colors.Length < 2) return; + IMinMax colorRanges = PixelHelper.MinMax(colors); + + if ((colorRanges.RedRange > colorRanges.GreenRange) && (colorRanges.RedRange > colorRanges.BlueRange)) + { + if (preOrdered != SortTarget.Red) + PixelHelper.SortByRed(colors); + + _sortOrder = SortTarget.Red; + } + else if (colorRanges.GreenRange > colorRanges.BlueRange) + { + if (preOrdered != SortTarget.Green) + PixelHelper.SortByGreen(colors); + + _sortOrder = SortTarget.Green; + } + else + { + if (preOrdered != SortTarget.Blue) + PixelHelper.SortByBlue(colors); + + _sortOrder = SortTarget.Blue; + } + } + + #endregion +} \ No newline at end of file diff --git a/HPPH/Quantization/SortTarget.cs b/HPPH/Quantization/SortTarget.cs new file mode 100644 index 0000000..0547430 --- /dev/null +++ b/HPPH/Quantization/SortTarget.cs @@ -0,0 +1,6 @@ +namespace HPPH; + +internal enum SortTarget +{ + None, Red, Green, Blue +} \ No newline at end of file diff --git a/README.md b/README.md index 45b31da..05d2e25 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # HPPH -C# High Performance Pixel Handling + +**WIP** diff --git a/sample_data/1.png b/sample_data/1.png new file mode 100644 index 0000000..d366d84 Binary files /dev/null and b/sample_data/1.png differ diff --git a/sample_data/2.png b/sample_data/2.png new file mode 100644 index 0000000..6335c12 Binary files /dev/null and b/sample_data/2.png differ diff --git a/sample_data/3.png b/sample_data/3.png new file mode 100644 index 0000000..a15dd37 Binary files /dev/null and b/sample_data/3.png differ diff --git a/sample_data/4.png b/sample_data/4.png new file mode 100644 index 0000000..3656505 Binary files /dev/null and b/sample_data/4.png differ diff --git a/sample_data/5.png b/sample_data/5.png new file mode 100644 index 0000000..6f38f93 Binary files /dev/null and b/sample_data/5.png differ diff --git a/sample_data/6.png b/sample_data/6.png new file mode 100644 index 0000000..c6049ad Binary files /dev/null and b/sample_data/6.png differ diff --git a/sample_data/7.png b/sample_data/7.png new file mode 100644 index 0000000..a8c8a29 Binary files /dev/null and b/sample_data/7.png differ diff --git a/sample_data/8.png b/sample_data/8.png new file mode 100644 index 0000000..47637ad Binary files /dev/null and b/sample_data/8.png differ