pascal Posted May 16 Share Posted May 16 (edited) 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. 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 May 18 by pascal Version 2 2 1 2 Quote Link to comment Share on other sites More sharing options...
NinthDesertDude Posted May 16 Share Posted May 16 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 Quote Link to comment Share on other sites More sharing options...
null54 Posted May 16 Share Posted May 16 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. 1 Quote Plugin Pack | PSFilterPdn | Content Aware Fill | G'MIC | Paint 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 More sharing options...
pascal Posted May 17 Author Share Posted May 17 Version 1.1 I made some small tweaks for overall improvement. - Now uses Parallel.ForEach instead of tasks with locks, thanks to @NinthDesertDude and @null54. - Small UI tweaks. - Pre-calculates the pixel properties it has to sort on. - More efficient sorting using arrays and vectors. 1 1 Quote Link to comment Share on other sites More sharing options...
pascal Posted May 18 Author Share Posted May 18 (edited) 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 May 18 by pascal 2 2 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.