Jump to content
How to Install Plugins ×

Pixel Sort [v2 2023-05-18]


Recommended Posts

Pixel Sort

 

This plugin can be used to create a glitchy effect by sorting pixels.

- Use the interval parameters to change the intensity of the effect.

- It can sort in either horizontal or vertical direction.

- It can sort based on multiple properties, such as value, hue, saturation or individual color channels.

Note: if multiple pixels have the same property, the order might appear to be random.

 

demo2.jpg.261c8a383d14d07b9c6d8bad9a0e0fd0.jpg

 

Download

Located in: Effects > Distort > Pixel Sort

PixelSort.zip

 

Code

Spoiler
#region UICodes
ListBoxControl<Sorter> SORTER = 0; // Sorter|RGB
ListBoxControl<Direction> DIRECTION = 0; // Direction|Row
IntSliderControl MIN = 60; // [0,255] Interval min
IntSliderControl MAX = 180; // [0,255] Interval max
ListBoxControl<Interval> INTERVAL = 0; // Interval|Threshold|Edge detection
CheckboxControl INVERT = false; // Invert interval
CheckboxControl FLIP = false; // {!RANDOM} Flip sorting order
CheckboxControl RANDOM = false; // Random sorting order
ReseedButtonControl SEED = 0; // {RANDOM} Reseed
#endregion

private enum Sorter { RGB, Red, Green, Blue, Alpha, Hue, Saturation, Value, Lightness, Luma, Minimum, Random }
private enum Direction { Row, Column }
private enum Interval { Threshold, Edge }
private readonly Comparer<Vector2Int32> comparer = Comparer<Vector2Int32>.Create((a, b) => a.Y.CompareTo(b.Y));
private bool[,] mask = null;
private int[,] sortMap = null;
private Random random = null;

public void PreRender(Surface dst, Surface src)
{
    Rectangle selection = EnvironmentParameters.SelectionBounds;
    Surface wrk = new Surface(selection.Size);
    Surface wrkDst = new Surface(wrk.Size);
    wrk.CopySurface(src, selection);
    wrkDst.CopySurface(wrk);

    this.random = new Random(SEED);
    this.mask = GetIntervalMask(wrk, MIN, MAX, INTERVAL);
    Func<ColorBgra, int> mapper = GetMapper(SORTER);
    this.sortMap = GetSortMap(wrk, mapper, FLIP);
    Action<Surface, Surface>[] actions = DIRECTION switch {
        Direction.Row => GetSortActionsX(wrk.Height),
        _ => GetSortActionsY(wrk.Width)
    };
    
    System.Threading.Tasks.Parallel.ForEach(actions, action =>
    {
        if (IsCancelRequested) return;
        action(wrk, wrkDst);
    });

    dst.CopySurface(wrkDst, selection.Location);
}


private Func<ColorBgra, int> GetMapper(Sorter sorter)
{
    Func<ColorBgra, int> result = sorter switch
    {
        Sorter.Red => c => c.R,
        Sorter.Green => c => c.G,
        Sorter.Blue => c => c.B,
        Sorter.Hue => c => HsvColor.FromColor(c.ToColor()).Hue,
        Sorter.Saturation => c => HsvColor.FromColor(c.ToColor()).Saturation,
        Sorter.Alpha => c => c.A,
        Sorter.Value => Value,
        Sorter.Lightness => Lightness,
        Sorter.Luma => Luma,
        Sorter.Minimum => Minimum,
        Sorter.Random => c => random.Next(),
        _ => Average
    };
    return result;
}


private Action<Surface, Surface>[] GetSortActionsX(int rows)
{
    var actions = new Action<Surface, Surface>[rows];
    for (int y = 0; y < rows; y++)
    {
        int tmp = y;
        actions[y] = (s, d) => SortRow(s, d, tmp);
    }
    return actions;
}


private Action<Surface, Surface>[] GetSortActionsY(int columns)
{
    var actions = new Action<Surface, Surface>[columns];
    for (int x = 0; x < columns; x++)
    {
        int tmp = x;
        actions[x] = (s, d) => SortColumn(s, d, tmp);
    }
    return actions;
}


private void SortRow(Surface src, Surface dst, int y)
{
    for (int x = 0; x < src.Width;)
    {
        int start = NextTrueX(x, y, src.Width);
        int end = NextFalseX(start, y, src.Width);
        if (IsCancelRequested) return;
        if (start < end - 1)
        {
            SortRowSection(y, start, end, src, dst);
        }
        x = end + 1;
    }
}


private void SortColumn(Surface src, Surface dst, int x)
{
    for (int y = 0; y < src.Height;)
    {
        int start = NextTrueY(x, y, src.Height);
        int end = NextFalseY(x, start, src.Height);
        if (IsCancelRequested) return;
        if (start < end - 1)
        {
            SortColumnSection(x, start, end, src, dst);
        }
        y = end + 1;
    }
}


private int NextTrueX(int x, int y, int maxX)
{
    while (!mask[x,y])
    {
        x++;
        if (x >= maxX)
        {
            return maxX - 1;
        }
    }
    return x;
}


private int NextFalseX(int x, int y, int maxX)
{
    while (mask[x,y])
    {
        x++;
        if (x >= maxX)
        {
            return maxX;
        }
    }
    return x;
}


private int NextTrueY(int x, int y, int maxY)
{
    while (!mask[x,y])
    {
        y++;
        if (y >= maxY)
        {
            return maxY - 1;
        }
    }
    return y;
}


private int NextFalseY(int x, int y, int maxY)
{
    while (mask[x,y])
    {
        y++;
        if (y >= maxY)
        {
            return maxY;
        }
    }
    return y;
}


private bool[,] GetIntervalMask(Surface src, int min, int max, Interval interval)
{
    return interval switch {
        Interval.Edge => GetEdgeMask(src, min, max),
        _ => GetThresholdMask(src, min, max)
    };
}

private bool[,] GetThresholdMask(Surface src, int min, int max)
{
    bool[,] result = new bool[src.Width, src.Height];
    min *= 3; max *= 3;

    for (int y = 0; y < src.Height; y++)
    {
        for (int x = 0; x < src.Width; x++)
        {
            float l = Average(src[x,y]);
            result[x,y] = (min <= l && l <= max) ^ INVERT;
        }
    }

    return result;
}

private bool[,] GetEdgeMask(Surface src, int min, int max)
{
    bool[,] result = new bool[src.Width, src.Height];

    for (int y = 0; y < src.Height; y++)
    {
        for (int x = 0; x < src.Width; x++)
        {
            if (!src.Bounds.Contains(x + 1, y + 1))
            {
                result[x,y] = (min <= 127 && 127 <= max) ^ INVERT;
                continue;
            }

            ColorBgra cp = src[x,y];
            ColorBgra np = src[x + 1, y + 1];
            int r = np.R - cp.R;
            int g = np.G - cp.G;
            int b = np.B - cp.B;
            int mx = Math.Max(Math.Max(r, g), b);
            int mn = Math.Min(Math.Min(r, g), b);
            int m = Math.Abs(mn) > Math.Abs(mx) ? mn : mx;
            int l = Math.Clamp(127 + m * 3, 0, 255);

            result[x,y] = (min <= l && l <= max) ^ INVERT;
        }
    }

    return result;
}


private void SortRowSection(int y, int x0, int x1, Surface src, Surface dst)
{
    int size = x1 - x0;
    Vector2Int32[] array = new Vector2Int32[size];
    for (int x = x0; x < x1; x++)
    {
        array[x - x0] = new Vector2Int32(x, this.sortMap[x, y]);
    }

    if (RANDOM && random.Next(0, 2) == 0) Array.Sort(array, Reverse(comparer));
    else Array.Sort(array, comparer);

    if (IsCancelRequested) return;

    for (int x = x0; x < x1; x++)
    {
        Vector2Int32 p = array[x - x0];
        dst[x, y] = src[p.X, y];
    }
}


private void SortColumnSection(int x, int y0, int y1, Surface src, Surface dst)
{
    int size = y1 - y0;
    Vector2Int32[] array = new Vector2Int32[size];
    for (int y = y0; y < y1; y++)
    {
        array[y - y0] = new Vector2Int32(y, this.sortMap[x, y]);
    }

    if (RANDOM && random.Next(0, 2) == 0) Array.Sort(array, Reverse(comparer));
    else Array.Sort(array, comparer);

    if (IsCancelRequested) return;

    for (int y = y0; y < y1; y++)
    {
        Vector2Int32 p = array[y - y0];
        dst[x, y] = src[x, p.X];
    }

}


private int[,] GetSortMap(Surface src, Func<ColorBgra, int> mapper, bool flip)
{
    var result = new int[src.Width, src.Height];
    for (int y = 0; y < src.Height; y++)
    {
        for (int x = 0; x < src.Width; x++)
        {
            int val = mapper(src[x,y]);
            if (flip) val = -val;
            result[x,y] = val;
        }
    }
    return result;
}


private int Average(ColorBgra c)
{
    return c.R + c.G + c.B;
}

private int Lightness(ColorBgra c)
{
    return Value(c) + Minimum(c);
}

private int Value(ColorBgra c)
{
    return Math.Max(c.R, Math.Max(c.G, c.B));
}

private int Minimum(ColorBgra c)
{
    return Math.Min(c.R, Math.Min(c.G, c.B));
}

private int Luma(ColorBgra c)
{
    return 2126 * c.R + 7152 * c.G + 722 * c.B;
}


private Comparer<T> Reverse<T>(Comparer<T> comp)
{
    return Comparer<T>.Create((a, b) => comp.Compare(b, a));
}


public void Render(Surface dst, Surface src, Rectangle rect) {}

 

Edited by pascal
Version 2
  • Like 2
  • Thanks 1
  • Upvote 2

pdnsignature.jpg.bb235358debfd06bf4d9023c840e8aa2.jpg

Link to comment
Share on other sites

This might benefit from using LockBits with Parallel.For, but that depends on if accessing bit data with an instance of Surface locks and unlocks for read access each time. If so, then LockBits would perform much much better. Parallel.For probably beats making 16 tasks manually since it takes into account what the user's number of cores are, as long as you use it carefully with LockBits to avoid conflicts

Link to comment
Share on other sites

3 hours ago, NinthDesertDude said:

Surface locks and unlocks for read access each time.

 

The Surface class does not require locking and unlocking the image data.

  • Upvote 1

PdnSig.png

Plugin Pack | PSFilterPdn | Content Aware Fill | G'MICPaint Shop Pro Filetype | RAW Filetype | WebP Filetype

The small increase in performance you get coding in C++ over C# is hardly enough to offset the headache of coding in the C++ language. ~BoltBait

 

Link to comment
Share on other sites

  • pascal changed the title to Pixel Sort [v1.1 2023-05-17]
  • pascal changed the title to Pixel Sort [v2 2023-05-18]

Version 2 available!

Added some extra features.

- Now contains more properties to sort on, including complete randomness.  It includes all of the following: RGB, Red, Green, Blue, Alpha, Hue, Saturation, Value, Lightness, Luma, Minimum and Random. The previous Value setting is now RGB, with the current Value being the value of the pixel's HSV color.

- Added Edge detection as an extra interval function besides Threshold. This can be used to keep the overall shapes of objects in the image.

- Re-ordered the UI

Edited by pascal
  • Like 2
  • Upvote 2

pdnsignature.jpg.bb235358debfd06bf4d9023c840e8aa2.jpg

Link to comment
Share on other sites

  • 1 month later...
  • 1 month later...

Hi @DecadeFever - welcome to the forum :)

 

  1. Have you unzipped it?
  2. What version of PDN are you using?
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...