Menú

Mostrar Mensajes

Esta sección te permite ver todos los mensajes escritos por este usuario. Ten en cuenta que sólo puedes ver los mensajes escritos en zonas a las que tienes acceso en este momento.

Mostrar Mensajes Menú

Mensajes - z3nth10n

#41
BatchedCoroutines: Iterar coroutinas una a una

Queréis que vuestras ConcurrentQueues se ejecuten una por una? No problemo, con esta implementación to hard-codeada lo conseguiréis:

Código (csharp) [Seleccionar]
using GTAMapper.Extensions.Threading;
using System;
using System.Collections;
using UnityEngine;

namespace GTAMapper.Extensions
{
   public static class BatchedCoroutines
   {
       public static IEnumerator BatchCoroutines(
           MonoBehaviour monoBehaviour,
           Action finish,
           Func<int, bool>[] waitUntil = null,
           params Tuple<Action<object>, ConcurrentQueuedCoroutines<object>>[] tuple) // Tuple<Action<T>, ConcurrentQueuedCoroutines<T>> || dynamic
                                                                                     // Fix for: https://stackoverflow.com/questions/15417174/using-the-params-keyword-for-generic-parameters-in-c-sharp
       {
           int i = 0;

           foreach (var val in tuple)
           {
               if (waitUntil != null && waitUntil[i] != null)
                   yield return new WaitUntil(() => waitUntil[i](i));

               yield return val.Item2.GetCoroutine(monoBehaviour, val.Item1);

               ++i;
           }

           finish?.Invoke();
       }
   }
}


Un ejemplo de implementación:

Código (csharp) [Seleccionar]
       protected ConcurrentQueuedCoroutines<object> debuggingCoroutine = new ConcurrentQueuedCoroutines<object>(),
                                                    colorCoroutine = new ConcurrentQueuedCoroutines<object>();

namespace GTAMapper.Core {
   public class Program : MonoBehaviour {
       public void Start() {
       StartCoroutine(BatchedCoroutines.BatchCoroutines(
               this,
               () => areCoroutinesCollected = true,
               F.GetFuncs(null, (_ii) => debuggingCoroutine.Queue.Count > 0),
               new Tuple<Action<object>, ConcurrentQueuedCoroutines<object>>((obj) =>
               {
                   Tuple<int, Color> tuple = (Tuple<int, Color>)obj;

                   int i = tuple.Item1,
                           _x = i % width,
                           _y = i / width;

                   UnityEngine.Color actualColor = debugTexture.GetPixel(_x, _y),
                                         mixedColor = UnityEngine.Color.Lerp(actualColor, tuple.Item2, .5f);

                   if (actualColor != mixedColor)
                   {
                       debugTexture.SetPixel(_x, _y, mixedColor);
                       debugTexture.Apply();
                   }
               }, colorCoroutine),
               new Tuple<Action<object>, ConcurrentQueuedCoroutines<object>>((obj) =>
               {
                   Color[] colors = (Color[])obj;

                   debugTexture.SetPixels32(colors.CastBack().ToArray());
                   debugTexture.Apply();
               }, debuggingCoroutine)));
         }
    }
}


Básicamente, en las dos Queues vamos haciendo Enqueue donde sea necesario (en otro thread).

Cuando todo haya acabado, desde el primer thread, llamamos a que se ejecute lo que acabo de mostrar.

Y en mi caso por ejemplo, esto sirve para mostrar pixel a pixel donde se ha iterado una imagen.

Y lo siguiente que ocurre es que la imagen se rellena con el algoritmo de flood-fill que enseñe el otro día. (Básicamente, para saber si se ha hecho bien)

Nota: Si queréis el código de GetFuncs es este:

Código (csharp) [Seleccionar]
using System;

public static class F {
       public static Func<int, bool>[] GetFuncs(params Func<int, bool>[] waitUntil)
       {
           return waitUntil;
       }
}
#42
ConcurrentQueuedCoroutines: Implementación Thread-Safe de ConcurrentQueues dentro de Coroutinas

La idea de esta utilidad es que cuando tu almacenas items desde otro thread, puedas acceder desde el thread principal.

Mezclando esta idea, con coroutinas, que básicamente, es un sistema del prehistorico que implementó Unity en su momento, que funciona de la siguiente forma, se crea un IEnumerator (con yields), el cual cada MoveNext se ejecuta en cada frame (yield return null) o cuando el programador especifique (yield return new WaitForSeconds(3) equivalente a Thread.Sleep(3000)) (por tal de no atorar el Main Thread, sí, Unity se ejecuta en un solo hilo).

Entonces, teniendo estas 2 cosas, porque no hacer Dequeue en cada MoveNext?

Código (csharp) [Seleccionar]
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using UnityEngine;

namespace GTAMapper.Extensions.Threading
{
   public class ConcurrentQueuedCoroutines<T>
   {
       private List<Coroutine> coroutines;
       private Coroutine coroutine;
       private MonoBehaviour Mono;

       public ConcurrentQueue<object> Queue { get; private set; }

       public Action<T> Action { get; private set; }
       public bool Debugging { get; set; }
       public float SecondsToWait { get; private set; }

       private ConcurrentQueuedCoroutines()
       {
       }

       public ConcurrentQueuedCoroutines(float secondsToWait = -1)
       {
           Queue = new ConcurrentQueue<object>();
           coroutines = new List<Coroutine>();
           SecondsToWait = secondsToWait;
       }

       public Coroutine StartCoroutine(MonoBehaviour monoBehaviour, Action<T> action)
       {
           coroutines.Add(monoBehaviour.StartCoroutine(InternalCoroutine()));
           Mono = monoBehaviour;
           Action = action;

           return coroutine;
       }

       public Coroutine StartCoroutineOnce(MonoBehaviour monoBehaviour, Action<T> action)
       {
           if (Debugging)
               Debug.Log("Starting dequeing!");

           if (coroutine == null)
           {
               coroutine = monoBehaviour.StartCoroutine(InternalCoroutine());
               Mono = monoBehaviour;
               Action = action;
           }

           return coroutine;
       }

       public void StopCoroutine()
       {
           if (coroutine != null && Mono != null)
               Mono.StopCoroutine(coroutine);
       }

       public void StopAllCoroutines()
       {
           if (Mono != null && coroutines != null && coroutines.Count > 0)
               coroutines.ForEach((c) => Mono.StopCoroutine(c));
       }

       public IEnumerator GetCoroutine(MonoBehaviour mono, Action<T> action)
       {
           Mono = mono;
           Action = action;
           return InternalCoroutine();
       }

       private IEnumerator InternalCoroutine()
       {
           if (Debugging)
               Debug.Log($"Starting dequeing {Queue.Count} values!");

           while (Queue.Count > 0)
           {
               object value = null;
               bool dequeued = Queue.TryDequeue(out value);

               if (!dequeued)
               {
                   if (SecondsToWait == -1)
                       yield return new WaitForEndOfFrame();
                   else
                       yield return new WaitForSeconds(SecondsToWait);

                   continue;
               }

               Action?.Invoke((T)value);

               if (SecondsToWait == -1)
                   yield return new WaitForEndOfFrame();
               else
                   yield return new WaitForSeconds(SecondsToWait);
           }
       }
   }
}


Y diréis, y para que sirve esta chorra, pues por ejemplo, lo que se puede conseguir, es visualizar como se recorre una textura.





En el próximo post o enseñaré un caso de uso.
#43
Thread Safe Bool: Implementación Thread-Safe de bools

El otro día me pasaba que al asignar una variable que estaba declarada en ambito de la clase desde otro thread, al leerla desde otro thread no me devolvía el resultado esperado, por eso os traigo esta utilidad.

Código (csharp) [Seleccionar]
using System.Threading;

namespace GTAMapper.Extensions.Threading
{
    /// <summary>
    /// Thread safe enter once into a code block:
    /// the first call to CheckAndSetFirstCall returns always true,
    /// all subsequent call return false.
    /// </summary>
    public class ThreadSafeBool
    {
        private static int NOTCALLED = 0,
                           CALLED = 1;

        private int _state = NOTCALLED;

        /// <summary>Explicit call to check and set if this is the first call</summary>
        public bool Value
        {
            get
            {
                return Interlocked.Exchange(ref _state, CALLED) == NOTCALLED;
            }
        }

        /// <summary>usually init by false</summary>
        public static implicit operator ThreadSafeBool(bool called)
        {
            return new ThreadSafeBool() { _state = called ? CALLED : NOTCALLED };
        }

        public static implicit operator bool(ThreadSafeBool cast)
        {
            if (cast == null)
                return false;

            return cast.Value;
        }
    }
}


Extraído de: https://www.codeproject.com/Tips/375559/Implement-Thread-Safe-One-shot-Bool-Flag-with-Inte
#45
Básicamente, esa "j" no pinta nada ahí :rolleyes:
#46
Programación General / Re: Tengo un error en C#
21 Octubre 2018, 06:50 AM
En vez de usar =, usa Substring(...).Replace(..., ...)

Con esto ya valdría:

Código (csharp) [Seleccionar]
etiqueta = etiqueta.Replace(equiteta.Substring(0, 1), banco_palabras[numero_palabra].Substring(0, 1));

Básicamente, estás asignando un valor a una función (método del tipo nativo string) cosa que no se puede.
#47
Te cuento de forma rápida lo que pretendo.

En el mapa hay x cantidad de colores predefinidos, tantos como enumeraciones tengas.

En este caso: Building, Asphalt, LightPavement, Pavement, Grass, DryGrass, Sand, Dirt, Mud, Water, Rails, Tunnel, BadCodingDark, BadCodingLight, BuildingLight, son 15.

Lo que pasa con esa imagen es hay micro variaciones de color. Quizás hay 100 tonos distintos de Grass con variaciones de pares en la escala RGB (es decir, nunca te vas a encontrar tonos que tengan un (0, 241, 0, 255), para el caso de un verde), y quizás con un rango total de ±10. Es decir, 5 posibilidades entre los 3 componentes: 5^3=125 tonos de verde.

Estos tonos son inperceptibles al ojo humano. Quizás se hizo por algun motivo (ya le metere saturación para ver si sigue algún patrón o algo. Estos de Rockstar te meten easter eggs hasta en los mapas).

Entonces lo que hago primero es iterar todos los colores. Mientras itero, voy comparando pixel a pixel, con los colores definidos en el diccionario, pero no los comparo literalmente (==), si no que saco un porcentaje de similitud. Y estás microvariaciones, como digo, como máximo su diferencia puede ser de ±10.

El porcentaje (con el mayor offset posible) sera en este caso: (255, 255, 255, 255) --> (245, 245, 245, 255) = 0.9609375 = 96,1% (un 3,9% de diferencia), vamos bien, ya que yo comparo con hasta un 10%, es decir una variación de ±25, es decir 25/2=12,5^3=1953 posibilidades, imagina.

Teniendo ese porcentaje, pues ya al debugear lo unico que hago es agrupar todos los colores (antes lo que hacia era posterizarlos, pero no me moló la idea, por eso hay un método de posterización) y sumar sus respectivas agrupaciones, pasamos de +1600 colores a unos 15 o menos (algunos no los detecta bien, otros directamente, no están presentes).

Un saludo.
#48
Pon la fuente y lo sabremos. ;D
#49
Leer los pixeles de una imagen y contarlos siguiendo un diccionario estático de colores

Básicamente, la funcionalidad que tiene esto, es definir un diccionario estático de colores (con una enumeración donde se especifiquen los apartados que hay (si fuese necesario)), se itera todo pixel a pixel, y cada color se compara con la muestra sacando el porcentaje de similitud, si la similitud es del 90% o mayor se da por hecho que ese color pertenece a x enumeración del diccionario.

Para más INRI, le he añadido la utilidad de que se pueda leer desde Internet, lo que cambia si queremos leerlo desde el disco es que tenemos que llamar únicamente a System.IO.File.ReadAllBytes.

Aquí el codigo: https://github.com/z3nth10n/GTA-ColorCount/blob/master/CountColors/Program.cs

Nota: Tiene una versión compilada (para el que lo quiera probar).
Nota2: No está optimizado (memory leak & no se ha mirado si se puede optimizar desde el punto de vista de procesamiento de cpu), asi que, si se elige guardar puede llegar a ocupar 1GB en memoria (la imagen tiene 7000x5000, en bruto son unos 140MB (7000x5000x4 (ARGB)) en memoria.)

Codigo en VB.NET:

Código (vbnet) [Seleccionar]

Imports System
Imports System.Net
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices
Imports System.IO
Imports System.Collections.Generic
Imports System.Linq
Imports Color = zenthion.Color
Imports System.Diagnostics
Imports System.Reflection

Public Enum GroundType
Building
Asphalt
LightPavement
Pavement
Grass
DryGrass
Sand
Dirt
Mud
Water
Rails
Tunnel
BadCodingDark
BadCodingLight
BuildingLight
End Enum

Public Enum OrderingType
ByColor
[ByVal]
ByName
End Enum

Public Class Program
Public Shared colorToCompare As Color = Color.white
Public Shared orderingType As OrderingType = OrderingType.ByVal
Public Shared isDarkened As Boolean = False, isPosterized As Boolean = False, isOrdered As Boolean = True, saveTexture As Boolean = False

Private Shared ReadOnly Property SavingPath() As String
Get
Return Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "texture.png")
End Get
End Property

Public Shared Sub Main()
Dim imageBytes() As Byte = Nothing

' OriginalTexture: http://i.imgur.com/g9fRYbm.png
' TextureColor: https://image.ibb.co/dP3Nvf/texture-Color.png

Dim url As String = "https://image.ibb.co/dP3Nvf/texture-Color.png"

Using webClient = New WebClient()
imageBytes = webClient.DownloadData(url)
End Using

Dim sw As Stopwatch = Stopwatch.StartNew()

isDarkened = url = "https://image.ibb.co/dP3Nvf/texture-Color.png"


Dim colors As IEnumerable(Of Color) = Nothing

Dim bitmap As Bitmap = Nothing
Dim dict = GetColorCount(bitmap, imageBytes, (If(isDarkened, F.DarkenedMapColors, F.mapColors)).Values.AsEnumerable(), colors, isPosterized)

Console.WriteLine(DebugDict(dict))
Console.WriteLine("Num of colors: {0}", dict.Keys.Count)

If saveTexture Then
colors.ToArray().SaveBitmap(7000, 5000, SavingPath)
End If

bitmap.Dispose()
sw.Stop()

Console.WriteLine("Ellapsed: {0} s", (sw.ElapsedMilliseconds / 1000F).ToString("F2"))

Console.Read()
End Sub

Private Shared Function DebugDict(ByVal dict As Dictionary(Of Color, Integer)) As String
Dim num = dict.Select(Function(x) New With {Key .Name = x.Key.GetGroundType(isPosterized), Key .Similarity = x.Key.ColorSimilaryPerc(colorToCompare), Key .Val = x.Value, Key .ColR = x.Key.r, Key .ColG = x.Key.g, Key .ColB = x.Key.b}).GroupBy(Function(x) x.Name).Select(Function(x) New With {Key .Name = x.Key, Key .Similarity = x.Average(Function(y) y.Similarity), Key .Val = x.Sum(Function(y) y.Val), Key .Col = New Color(CByte(x.Average(Function(y) y.ColR)), CByte(x.Average(Function(y) y.ColG)), CByte(x.Average(Function(y) y.ColB)))})

Dim num1 = num

If isOrdered Then
num1 = If(orderingType = OrderingType.ByName, num.OrderBy(Function(x) x.Name), num.OrderByDescending(Function(x)If(orderingType = OrderingType.ByColor, x.Col.ColorSimilaryPerc(colorToCompare), x.Val)))
End If

Dim num2 = num1.Select(Function(x) String.Format("[{2}] {0}: {1}", x.Name, x.Val.ToString("N0"), x.Similarity.ToString("F2")))

Return String.Join(Environment.NewLine, num2)
End Function

Public Shared Function GetColorCount(ByRef image As Bitmap, ByVal arr() As Byte, ByVal colors As IEnumerable(Of Color), <System.Runtime.InteropServices.Out()> ByRef imageColors As IEnumerable(Of Color), Optional ByVal isPosterized As Boolean = False) As Dictionary(Of Color, Integer)
Dim count As New Dictionary(Of Color, Integer)()

Using stream As Stream = New MemoryStream(arr)
image = CType(System.Drawing.Image.FromStream(stream), Bitmap)
End Using

'Color[]
imageColors = image.ToColor() '.ToArray();

'Parallel.ForEach(Partitioner.Create(imageColors, true).GetOrderableDynamicPartitions(), colorItem =>
For Each colorItem As Color In imageColors
' .Value
Dim thresholedColor As Color = If((Not isPosterized), colorItem.GetSimilarColor(colors), colorItem) '.RoundColorOff(65);

If Not count.ContainsKey(thresholedColor) Then
count.Add(thresholedColor, 1)
Else
count(thresholedColor) += 1
End If
Next colorItem

Dim posterizedColors As Dictionary(Of Color, Integer) = If(isPosterized, New Dictionary(Of Color, Integer)(), count)

If isPosterized Then
For Each kv In count
Dim pColor As Color = kv.Key.Posterize(16)

If Not posterizedColors.ContainsKey(pColor) Then
posterizedColors.Add(pColor, kv.Value)
Else
posterizedColors(pColor) += kv.Value
End If
Next kv
End If

Return posterizedColors
End Function
End Class

Public Module F
Public mapColors As New Dictionary(Of GroundType, Color)() From {
{ GroundType.Building, Color.white },
{ GroundType.Asphalt, Color.black },
{ GroundType.LightPavement, New Color(206, 207, 206, 255) },
{ GroundType.Pavement, New Color(156, 154, 156, 255) },
{ GroundType.Grass, New Color(57, 107, 41, 255) },
{ GroundType.DryGrass, New Color(123, 148, 57, 255) },
{ GroundType.Sand, New Color(231, 190, 107, 255) },
{ GroundType.Dirt, New Color(156, 134, 115, 255) },
{ GroundType.Mud, New Color(123, 101, 90, 255) },
{ GroundType.Water, New Color(115, 138, 173, 255) },
{ GroundType.Rails, New Color(74, 4, 0, 255) },
{ GroundType.Tunnel, New Color(107, 105, 99, 255) },
{ GroundType.BadCodingDark, New Color(127, 0, 0, 255) },
{ GroundType.BadCodingLight, New Color(255, 127, 127, 255) }
}

Private _darkened As Dictionary(Of GroundType, Color)

Public ReadOnly Property DarkenedMapColors() As Dictionary(Of GroundType, Color)
Get
If _darkened Is Nothing Then
_darkened = GetDarkenedMapColors()
End If

Return _darkened
End Get
End Property

Private BmpStride As Integer = 0

Private Function GetDarkenedMapColors() As Dictionary(Of GroundType, Color)
' We will take the last 2 elements

Dim last2 = mapColors.Skip(mapColors.Count - 2)

Dim exceptLast2 = mapColors.Take(mapColors.Count - 2)

Dim dict As New Dictionary(Of GroundType, Color)()

dict.AddRange(exceptLast2.Select(Function(x) New KeyValuePair(Of GroundType, Color)(x.Key, x.Value.Lerp(Color.black,.5F))))

dict.Add(GroundType.BuildingLight, Color.white)

dict.AddRange(last2)

Return dict
End Function

<System.Runtime.CompilerServices.Extension> _
Public Sub AddRange(Of TKey, TValue)(ByVal dic As Dictionary(Of TKey, TValue), ByVal dicToAdd As IEnumerable(Of KeyValuePair(Of TKey, TValue)))
dicToAdd.ForEach(Sub(x) dic.Add(x.Key, x.Value))
End Sub

<System.Runtime.CompilerServices.Extension> _
Public Sub ForEach(Of T)(ByVal source As IEnumerable(Of T), ByVal action As Action(Of T))
For Each item In source
action(item)
Next item
End Sub

'INSTANT VB NOTE: The parameter color was renamed since it may cause conflicts with calls to static members of the user-defined type with this name:
<System.Runtime.CompilerServices.Extension> _
Public Function Posterize(ByVal color_Renamed As Color, ByVal level As Byte) As Color
Dim r As Byte = 0, g As Byte = 0, b As Byte = 0

Dim value As Double = color_Renamed.r \ 255.0
value *= level - 1
value = Math.Round(value)
value /= level - 1

r = CByte(value * 255)
value = color_Renamed.g \ 255.0
value *= level - 1
value = Math.Round(value)
value /= level - 1

g = CByte(value * 255)
value = color_Renamed.b \ 255.0
value *= level - 1
value = Math.Round(value)
value /= level - 1

b = CByte(value * 255)

Return New Color(r, g, b, 255)
End Function

<System.Runtime.CompilerServices.Extension> _
Public Function GetGroundType(ByVal c As Color, ByVal isPosterized As Boolean) As String
Dim mapToUse = If(Program.isDarkened, DarkenedMapColors, mapColors)
Dim kvColor As KeyValuePair(Of GroundType, Color) = mapToUse.FirstOrDefault(Function(x)If(isPosterized, x.Value.ColorSimilaryPerc(c) >.9F, x.Value = c))

If Not kvColor.Equals(Nothing) Then
Return kvColor.Key.ToString()
Else
Return c.ToString()
End If
End Function

<System.Runtime.CompilerServices.Extension> _
Public Function GetSimilarColor(ByVal c1 As Color, ByVal cs As IEnumerable(Of Color)) As Color
Return cs.OrderBy(Function(x) x.ColorThreshold(c1)).FirstOrDefault()
End Function

<System.Runtime.CompilerServices.Extension> _
Public Function ColorThreshold(ByVal c1 As Color, ByVal c2 As Color) As Integer
Return (Math.Abs(c1.r - c2.r) + Math.Abs(c1.g - c2.g) + Math.Abs(c1.b - c2.b))
End Function

<System.Runtime.CompilerServices.Extension> _
Public Function ColorSimilaryPerc(ByVal a As Color, ByVal b As Color) As Single
Return 1F - (a.ColorThreshold(b) / (256F * 3))
End Function

<System.Runtime.CompilerServices.Extension> _
Public Function RoundColorOff(ByVal c As Color, Optional ByVal roundTo As Byte = 5) As Color
Return New Color(c.r.RoundOff(roundTo), c.g.RoundOff(roundTo), c.b.RoundOff(roundTo), 255)
End Function

<System.Runtime.CompilerServices.Extension> _
Public Function RoundOff(ByVal i As Byte, Optional ByVal roundTo As Byte = 5) As Byte
Return CByte(CByte(Math.Ceiling(i / CDbl(roundTo))) * roundTo)
End Function

<System.Runtime.CompilerServices.Extension> _
Public Iterator Function ToColor(ByVal bmp As Bitmap) As IEnumerable(Of Color)
Dim rect As New Rectangle(0, 0, bmp.Width, bmp.Height)
Dim bmpData As BitmapData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat)

Dim ptr As IntPtr = bmpData.Scan0

Dim bytes As Integer = bmpData.Stride * bmp.Height
Dim rgbValues(bytes - 1) As Byte

' Copy the RGB values into the array.
Marshal.Copy(ptr, rgbValues, 0, bytes)

BmpStride = bmpData.Stride

For column As Integer = 0 To bmpData.Height - 1
For row As Integer = 0 To bmpData.Width - 1
' Little endian
Dim b As Byte = CByte(rgbValues((column * BmpStride) + (row * 4)))
Dim g As Byte = CByte(rgbValues((column * BmpStride) + (row * 4) + 1))
Dim r As Byte = CByte(rgbValues((column * BmpStride) + (row * 4) + 2))

Yield New Color(r, g, b, 255)
Next row
Next column

' Unlock the bits.
bmp.UnlockBits(bmpData)
End Function

<System.Runtime.CompilerServices.Extension> _
Public Sub SaveBitmap(ByVal bmp() As Color, ByVal width As Integer, ByVal height As Integer, ByVal path As String)
Dim stride As Integer = BmpStride
Dim rgbValues((BmpStride * height) - 1) As Byte

For column As Integer = 0 To height - 1
For row As Integer = 0 To width - 1
Dim i As Integer = Pn(row, column, width)

' Little endian
rgbValues((column * BmpStride) + (row * 4)) = bmp(i).b
rgbValues((column * BmpStride) + (row * 4) + 1) = bmp(i).g
rgbValues((column * BmpStride) + (row * 4) + 2) = bmp(i).r
rgbValues((column * BmpStride) + (row * 4) + 3) = bmp(i).a
Next row
Next column

Using image As New Bitmap(width, height, width * 4, PixelFormat.Format32bppArgb, Marshal.UnsafeAddrOfPinnedArrayElement(rgbValues, 0))
image.Save(path)
End Using
End Sub

Public Function Pn(ByVal x As Integer, ByVal y As Integer, ByVal w As Integer) As Integer
Return x + (y * w)
End Function
End Module

Public Module Mathf
<System.Runtime.CompilerServices.Extension> _
Public Function Clamp(Of T As IComparable(Of T))(ByVal val As T, ByVal min As T, ByVal max As T) As T
If val.CompareTo(min) < 0 Then
Return min
ElseIf val.CompareTo(max) > 0 Then
Return max
Else
Return val
End If
End Function

' Interpolates between /a/ and /b/ by /t/. /t/ is clamped between 0 and 1.
Public Function Lerp(ByVal a As Single, ByVal b As Single, ByVal t As Single) As Single
Return a + (b - a) * Clamp01(t)
End Function

' Clamps value between 0 and 1 and returns value
Public Function Clamp01(ByVal value As Single) As Single
If value < 0F Then
Return 0F
ElseIf value > 1F Then
Return 1F
Else
Return value
End If
End Function
End Module

Namespace zenthion
''' <summary>
''' Struct Color
''' </summary>
''' <seealso cref="System.ICloneable" />
<Serializable>
Public Structure Color
Implements ICloneable

''' <summary>
''' Clones this instance.
''' </summary>
''' <returns>System.Object.</returns>
Public Function Clone() As Object Implements ICloneable.Clone
Return MemberwiseClone()
End Function

''' <summary>
''' The r
''' </summary>
Public r, g, b, a As Byte

''' <summary>
''' Gets the white.
''' </summary>
''' <value>The white.</value>
Public Shared ReadOnly Property white() As Color
Get
Return New Color(255, 255, 255)
End Get
End Property

''' <summary>
''' Gets the red.
''' </summary>
''' <value>The red.</value>
Public Shared ReadOnly Property red() As Color
Get
Return New Color(255, 0, 0)
End Get
End Property

''' <summary>
''' Gets the green.
''' </summary>
''' <value>The green.</value>
Public Shared ReadOnly Property green() As Color
Get
Return New Color(0, 255, 0)
End Get
End Property

''' <summary>
''' Gets the blue.
''' </summary>
''' <value>The blue.</value>
Public Shared ReadOnly Property blue() As Color
Get
Return New Color(0, 0, 255)
End Get
End Property

''' <summary>
''' Gets the yellow.
''' </summary>
''' <value>The yellow.</value>
Public Shared ReadOnly Property yellow() As Color
Get
Return New Color(255, 255, 0)
End Get
End Property

''' <summary>
''' Gets the gray.
''' </summary>
''' <value>The gray.</value>
Public Shared ReadOnly Property gray() As Color
Get
Return New Color(128, 128, 128)
End Get
End Property

''' <summary>
''' Gets the black.
''' </summary>
''' <value>The black.</value>
Public Shared ReadOnly Property black() As Color
Get
Return New Color(0, 0, 0)
End Get
End Property

''' <summary>
''' Gets the transparent.
''' </summary>
''' <value>The transparent.</value>
Public Shared ReadOnly Property transparent() As Color
Get
Return New Color(0, 0, 0, 0)
End Get
End Property

''' <summary>
''' Initializes a new instance of the <see cref="Color"/> struct.
''' </summary>
''' <param name="r">The r.</param>
''' <param name="g">The g.</param>
''' <param name="b">The b.</param>
Public Sub New(ByVal r As Byte, ByVal g As Byte, ByVal b As Byte)
Me.r = r
Me.g = g
Me.b = b
a = Byte.MaxValue
End Sub

''' <summary>
''' Initializes a new instance of the <see cref="Color"/> struct.
''' </summary>
''' <param name="r">The r.</param>
''' <param name="g">The g.</param>
''' <param name="b">The b.</param>
''' <param name="a">a.</param>
Public Sub New(ByVal r As Byte, ByVal g As Byte, ByVal b As Byte, ByVal a As Byte)
Me.r = r
Me.g = g
Me.b = b
Me.a = a
End Sub

''' <summary>
''' Implements the ==.
''' </summary>
''' <param name="c1">The c1.</param>
''' <param name="c2">The c2.</param>
''' <returns>The result of the operator.</returns>
Public Shared Operator =(ByVal c1 As Color, ByVal c2 As Color) As Boolean
Return c1.r = c2.r AndAlso c1.g = c2.g AndAlso c1.b = c2.b AndAlso c1.a = c2.a
End Operator

''' <summary>
''' Implements the !=.
''' </summary>
''' <param name="c1">The c1.</param>
''' <param name="c2">The c2.</param>
''' <returns>The result of the operator.</returns>
Public Shared Operator <>(ByVal c1 As Color, ByVal c2 As Color) As Boolean
Return Not(c1.r = c2.r AndAlso c1.g = c2.g AndAlso c1.b = c2.b AndAlso c1.a = c2.a)
End Operator

''' <summary>
''' Returns a hash code for this instance.
''' </summary>
''' <returns>A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.</returns>
Public Overrides Function GetHashCode() As Integer
Return GetHashCode()
End Function

''' <summary>
''' Determines whether the specified <see cref="System.Object" /> is equal to this instance.
''' </summary>
''' <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
''' <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
Public Overrides Function Equals(ByVal obj As Object) As Boolean
Dim c As Color = DirectCast(obj, Color)
Return r = c.r AndAlso g = c.g AndAlso b = c.b
End Function

''' <summary>
''' Implements the -.
''' </summary>
''' <param name="c1">The c1.</param>
''' <param name="c2">The c2.</param>
''' <returns>The result of the operator.</returns>
Public Shared Operator -(ByVal c1 As Color, ByVal c2 As Color) As Color
Return New Color(CByte(Mathf.Clamp(c1.r - c2.r, 0, 255)), CByte(Mathf.Clamp(c2.g - c2.g, 0, 255)), CByte(Mathf.Clamp(c2.b - c2.b, 0, 255)))
End Operator

''' <summary>
''' Implements the +.
''' </summary>
''' <param name="c1">The c1.</param>
''' <param name="c2">The c2.</param>
''' <returns>The result of the operator.</returns>
Public Shared Operator +(ByVal c1 As Color, ByVal c2 As Color) As Color
Return New Color(CByte(Mathf.Clamp(c1.r + c2.r, 0, 255)), CByte(Mathf.Clamp(c2.g + c2.g, 0, 255)), CByte(Mathf.Clamp(c2.b + c2.b, 0, 255)))
End Operator

''' <summary>
''' Lerps the specified c2.
''' </summary>
''' <param name="c2">The c2.</param>
''' <param name="t">The t.</param>
''' <returns>Color.</returns>
Public Function Lerp(ByVal c2 As Color, ByVal t As Single) As Color
Return New Color(CByte(Mathf.Lerp(r, c2.r, t)), CByte(Mathf.Lerp(g, c2.g, t)), CByte(Mathf.Lerp(b, c2.b, t)))
End Function

''' <summary>
''' Inverts this instance.
''' </summary>
''' <returns>Color.</returns>
Public Function Invert() As Color
Return New Color(CByte(Mathf.Clamp(Byte.MaxValue - r, 0, 255)), CByte(Mathf.Clamp(Byte.MaxValue - g, 0, 255)), CByte(Mathf.Clamp(Byte.MaxValue - b, 0, 255)))
End Function

''' <summary>
''' Returns a <see cref="System.String" /> that represents this instance.
''' </summary>
''' <returns>A <see cref="System.String" /> that represents this instance.</returns>
Public Overrides Function ToString() As String
If Me = white Then
Return "white"
ElseIf Me = transparent Then
Return "transparent"
ElseIf Me = red Then
Return "red"
ElseIf Me = blue Then
Return "blue"
ElseIf Me = black Then
Return "black"
ElseIf Me = green Then
Return "green"
ElseIf Me = yellow Then
Return "yellow"
Else
Return String.Format("({0}, {1}, {2}, {3})", r, g, b, a)
End If
End Function

''' <summary>
''' Fills the specified x.
''' </summary>
''' <param name="x">The x.</param>
''' <param name="y">The y.</param>
''' <returns>Color[].</returns>
Public Shared Iterator Function Fill(ByVal x As Integer, ByVal y As Integer) As IEnumerable(Of Color)
For i As Integer = 0 To (x * y) - 1
Yield black
Next i
End Function
End Structure
End Namespace


Nota: A pesar de haber sido convertido con un conversor se ha comprobado en: https://dotnetfiddle.net/1vbkgG
Nota2: La idea era que se ejecutase de forma online y si le poneis una imagen más pequeña deberia sacar los pixeles, pero como digo no se puede, por tema de web clouds y recursos compartidos.
Nota3: Le he metido esta imagen (https://vignette.wikia.nocookie.net/gta-myths/images/8/80/Gtasa-blank.png/revision/latest?cb=20161204212845) pero me da un error que ahora mismo no me puedo parar a comprobar:

CitarRun-time exception (line -1): Arithmetic operation resulted in an overflow.

Stack Trace:

[System.OverflowException: Arithmetic operation resulted in an overflow.]
  at F.ColorThreshold(Color c1, Color c2)
  at F._Closure$__3._Lambda$__15(Color x)
  at System.Linq.EnumerableSorter`2.ComputeKeys(TElement[] elements, Int32 count)
  at System.Linq.EnumerableSorter`1.Sort(TElement[] elements, Int32 count)
  at System.Linq.OrderedEnumerable`1.<GetEnumerator>d__1.MoveNext()
  at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source)
  at F.GetSimilarColor(Color c1, IEnumerable`1 cs)
  at Program.GetColorCount(Bitmap& image, Byte[] arr, IEnumerable`1 colors, IEnumerable`1& imageColors, Boolean isPosterized)
  at Program.Main()

Y creo que eso es todo.

Un saludo.

PD: La razón de que el código esté mitad comentado y mitad sin comentar es porque la parte de la clase Color es una implementación propia de la clase Color que hice hace tiempo y la introducí en mi Lerp2API.
PD2: Este código (el del ColorThreshold y lo de GetSimilarity, todo lo demás lo he escrito esta mañana y tarde) realmente lo estaba usando en mi proyecto de San Andreas Unity (de los últimos commits que hice antes de irme de este y empezar uno nuevo a solas).
PD3: Todo esto es parte de un proceso de depuración un tanto largo que me ha servido para constrastar de donde me venían unos valores. Para ser más concretos, tengo un algoritmo que saca los contornos de los edificios que he estado optimizando (el cual empecé en 2016, y después de un año he retomado), y bueno, yo esperaba que me devolviese unos 2600 edificios, pero se me han devuelto unos 1027k  y hay unos 1029k pixeles en la última imagen que he puesto (lo podéis comprobar vosotros mismos), así que ya se por donde seguir. Espero que vosotros también hagáis lo mismo con lo que escribo. ;) :P
#50
Como rellenar un array siguiendo el algoritmo Flood Fill usando HashSet

https://es.wikipedia.org/wiki/Algoritmo_de_relleno_por_difusi%C3%B3n

Código (vbnet) [Seleccionar]
Imports System.Collections.Generic
Imports System.Linq
Imports System.Runtime.CompilerServices
Imports System.Runtime.InteropServices

Module F
   <Extension()>
   Sub FloodFill(Of T)(ByVal source As T(), ByVal x As Integer, ByVal y As Integer, ByVal width As Integer, ByVal height As Integer, ByVal target As T, ByVal replacement As T)
       Dim i As Integer = 0
       FloodFill(source, x, y, width, height, target, replacement, i)
   End Sub

   <Extension()>
   Sub FloodFill(Of T)(ByVal source As T(), ByVal x As Integer, ByVal y As Integer, ByVal width As Integer, ByVal height As Integer, ByVal target As T, ByVal replacement As T, <Out> ByRef i As Integer)
       i = 0
       Dim queue As HashSet(Of Integer) = New HashSet(Of Integer)()
       queue.Add(Pn(x, y, width))

       While queue.Count > 0
           Dim _i As Integer = queue.First(), _x As Integer = _i Mod width, _y As Integer = _i / width
           queue.Remove(_i)
           If source(_i).Equals(target) Then source(_i) = replacement

           For offsetX As Integer = -1 To 2 - 1

               For offsetY As Integer = -1 To 2 - 1
                   If offsetX = 0 AndAlso offsetY = 0 OrElse offsetX = offsetY OrElse offsetX = -offsetY OrElse -offsetX = offsetY Then Continue For
                   Dim targetIndex As Integer = Pn(_x + offsetX, _y + offsetY, width)
                   Dim _tx As Integer = targetIndex Mod width, _ty As Integer = targetIndex / width
                   If _tx < 0 OrElse _ty < 0 OrElse _tx >= width OrElse _ty >= height Then Continue For

                   If Not queue.Contains(targetIndex) AndAlso source(targetIndex).Equals(target) Then
                       queue.Add(targetIndex)
                       i += 1
                   End If
               Next
           Next
       End While
   End Sub

   Function Pn(ByVal x As Integer, ByVal y As Integer, ByVal w As Integer) As Integer
       Return x + (y * w)
   End Function
End Module


Código (csharp) [Seleccionar]
using System.Collections.Generic;
using System.Linq;

public static class F
{
   /// <summary>
          /// Floods the fill.
          /// </summary>
          /// <typeparam name="T"></typeparam>
          /// <param name="source">The source.</param>
          /// <param name="x">The x.</param>
          /// <param name="y">The y.</param>
          /// <param name="width">The width.</param>
          /// <param name="height">The height.</param>
          /// <param name="target">The target to replace.</param>
          /// <param name="replacement">The replacement.</param>
   public static void FloodFill<T>(this T[] source, int x, int y, int width, int height, T target, T replacement)
   {
       int i = 0;

       FloodFill(source, x, y, width, height, target, replacement, out i);
   }

   /// <summary>
          /// Floods the array following Flood Fill algorithm
          /// </summary>
          /// <typeparam name="T"></typeparam>
          /// <param name="source">The source.</param>
          /// <param name="x">The x.</param>
          /// <param name="y">The y.</param>
          /// <param name="width">The width.</param>
          /// <param name="height">The height.</param>
          /// <param name="target">The target to replace.</param>
          /// <param name="replacement">The replacement.</param>
          /// <param name="i">The iterations made (if you want to debug).</param>
   public static void FloodFill<T>(this T[] source, int x, int y, int width, int height, T target, T replacement, out int i)
   {
       i = 0;

        // Queue of pixels to process. :silbar:
       HashSet<int> queue = new HashSet<int>();

       queue.Add(Pn(x, y, width));

       while (queue.Count > 0)
       {
           int _i = queue.First(),
             _x = _i % width,
             _y = _i / width;

           queue.Remove(_i);

           if (source[_i].Equals(target))
               source[_i] = replacement;

           for (int offsetX = -1; offsetX < 2; offsetX++)
               for (int offsetY = -1; offsetY < 2; offsetY++)
               {
                   // do not check origin or diagonal neighbours
                   if (offsetX == 0 && offsetY == 0 || offsetX == offsetY || offsetX == -offsetY || -offsetX == offsetY)
                       continue;

                   int targetIndex = Pn(_x + offsetX, _y + offsetY, width);
                   int _tx = targetIndex % width,
                     _ty = targetIndex / width;

                   // skip out of bounds point
                   if (_tx < 0 || _ty < 0 || _tx >= width || _ty >= height)
                       continue;

                   if (!queue.Contains(targetIndex) && source[targetIndex].Equals(target))
                   {
                       queue.Add(targetIndex);
                       ++i;
                   }
               }
       }
   }

   public static int Pn(int x, int y, int w)
   {
       return x + (y * w);
   }
}


EDIT: Añadidos using + función PN + codigo en VB.NET que para eso son los snippets de VB

Prueba de concepto: https://dotnetfiddle.net/ZacRiB

Un saludo.