Created
December 31, 2022 18:27
-
-
Save Cryptoc1/155dd447bde9fb68777858f16ae008b5 to your computer and use it in GitHub Desktop.
C# implementation of @kimasendorf's ASDFPixelSort algorithm using Magick.NET
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env dotnet-script | |
#r "nuget: Magick.NET-Q8-AnyCPU, 12.2.2" | |
using ImageMagick; | |
using( var image = new MagickImage( Path.GetFullPath( @".\source.jpg" ) ) ) | |
{ | |
Console.WriteLine( "loaded, starting sort." ); | |
PixelSorter.Sort( image, new PixelSortMode.White() ); | |
var filepath = Path.GetFullPath( @$".\{Path.GetFileNameWithoutExtension( Path.GetRandomFileName() )}.jpg" ); | |
await image.WriteAsync( filepath ); | |
Console.WriteLine( $"wrote image to '{filepath}'." ); | |
} | |
public static class PixelSorter | |
{ | |
public static void Sort( MagickImage image, PixelSortMode mode ) | |
{ | |
ArgumentNullException.ThrowIfNull( image ); | |
using var context = PixelSortContext.Create( image ); | |
SortColumns( context, mode ); | |
SortRows( context, mode ); | |
} | |
private static void SortColumns( PixelSortContext context, PixelSortMode mode ) | |
{ | |
int x = 0; | |
while( x < context.Width - 1 ) | |
{ | |
var (y, nextY) = (0, 0); | |
while( nextY < context.Height - 1 ) | |
{ | |
(y, nextY) = mode.Column( context, x, y ); | |
if( y < 0 ) | |
{ | |
break; | |
} | |
int length = nextY - y; | |
if( length > 0 ) | |
{ | |
var colors = new IMagickColor<byte>[ length ]; | |
for( int i = 0; i < colors.Length; i++ ) | |
{ | |
var color = context.Pixels.GetPixel( x, y + i ).ToColor()!; | |
colors[ i ] = color; | |
} | |
Array.Sort( colors ); | |
for( int i = 0; i < colors.Length; i++ ) | |
{ | |
byte[] color = colors[ i ].ToByteArray(); | |
context.Pixels.SetPixel( x, y + i, color ); | |
} | |
} | |
y = nextY + 1; | |
} | |
x++; | |
} | |
} | |
private static void SortRows( PixelSortContext context, PixelSortMode mode ) | |
{ | |
int y = 0; | |
while( y < context.Height - 1 ) | |
{ | |
var (x, nextX) = (0, 0); | |
while( nextX < context.Width - 1 ) | |
{ | |
(x, nextX) = mode.Row( context, x, y ); | |
if( x < 0 ) | |
{ | |
break; | |
} | |
int length = nextX - x; | |
if( length > 0 ) | |
{ | |
var colors = new IMagickColor<byte>[ length ]; | |
for( int i = 0; i < colors.Length; i++ ) | |
{ | |
var color = context.Pixels.GetPixel( x + i, y ).ToColor()!; | |
colors[ i ] = color; | |
} | |
Array.Sort( colors ); | |
for( int i = 0; i < colors.Length; i++ ) | |
{ | |
byte[] color = colors[ i ].ToByteArray(); | |
context.Pixels.SetPixel( x + i, y, color ); | |
} | |
} | |
x = nextX + 1; | |
} | |
y++; | |
} | |
} | |
} | |
public readonly ref struct PixelSortContext | |
{ | |
public readonly int Height { get; init; } | |
public readonly IPixelCollection<byte> Pixels { get; init; } | |
public readonly int Width { get; init; } | |
public static PixelSortContext Create( MagickImage image ) | |
=> new() { Height = image.Height, Pixels = image.GetPixels(), Width = image.Width }; | |
// NOTE: ref struct cannot implement interfaces (IDisposable), but can still implement the "disposable pattern" | |
public void Dispose( ) => Pixels?.Dispose(); | |
} | |
public abstract class PixelSortMode | |
{ | |
public abstract (int FirstY, int NextY) Column( PixelSortContext context, int x, int y ); | |
public abstract (int FirstX, int NextX) Row( PixelSortContext context, int x, int y ); | |
public sealed class White : PixelSortMode | |
{ | |
public const int THRESHOLD = -12345678; | |
public override (int FirstY, int NextY) Column( PixelSortContext context, int x, int y ) | |
{ | |
return (FirstY( context, x, y ), NextY( context, x, y )); | |
static int FirstY( PixelSortContext context, int x, int y ) | |
{ | |
if( y < context.Height ) | |
{ | |
while( context.Pixels.GetPixel( x, y ).ToColor()!.ToHexValue() < THRESHOLD ) | |
{ | |
y++; | |
if( y >= context.Height ) | |
{ | |
return -1; | |
} | |
} | |
} | |
return y; | |
} | |
static int NextY( PixelSortContext context, int x, int y ) | |
{ | |
y++; | |
if( y < context.Height ) | |
{ | |
while( context.Pixels.GetPixel( x, y ).ToColor()!.ToHexValue() > THRESHOLD ) | |
{ | |
y++; | |
if( y >= context.Height ) | |
{ | |
return context.Height - 1; | |
} | |
} | |
} | |
return y - 1; | |
} | |
} | |
public override (int FirstX, int NextX) Row( PixelSortContext context, int x, int y ) | |
{ | |
return (FirstX( context, x, y ), NextX( context, x, y )); | |
static int FirstX( PixelSortContext context, int x, int y ) | |
{ | |
if( x < context.Width ) | |
{ | |
while( context.Pixels.GetPixel( x, y ).ToColor()!.ToHexValue() < THRESHOLD ) | |
{ | |
x++; | |
if( x >= context.Width ) | |
{ | |
return -1; | |
} | |
} | |
} | |
return x; | |
} | |
static int NextX( PixelSortContext context, int x, int y ) | |
{ | |
x++; | |
if( x < context.Width ) | |
{ | |
while( context.Pixels.GetPixel( x, y ).ToColor()!.ToHexValue() > THRESHOLD ) | |
{ | |
x++; | |
if( x >= context.Width ) | |
{ | |
return context.Width - 1; | |
} | |
} | |
} | |
return x - 1; | |
} | |
} | |
} | |
} | |
static int ToHexValue( this IMagickColor<byte> color ) => ( 255 << 24 ) | ( color.R << 16 ) | ( color.G << 8 ) | ( color.B << 0 ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment