Skip to content

Instantly share code, notes, and snippets.

@Cryptoc1
Created December 31, 2022 18:27
Show Gist options
  • Save Cryptoc1/155dd447bde9fb68777858f16ae008b5 to your computer and use it in GitHub Desktop.
Save Cryptoc1/155dd447bde9fb68777858f16ae008b5 to your computer and use it in GitHub Desktop.
C# implementation of @kimasendorf's ASDFPixelSort algorithm using Magick.NET
#!/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