Dibujar una imagen con una serpiente

28

Imagine un camino bidimensional continuo que solo puede girar a la izquierda, a la derecha o en línea recta, no puede intersectarse y debe llenar una cuadrícula rectangular como la cuadrícula de píxeles en una imagen. Llamaremos a este tipo de camino una serpiente .

Ejemplo de serpiente

Este ejemplo ampliado muestra un camino de serpiente en una cuadrícula de 10 × 4 que comienza en rojo y aumenta el tono en aproximadamente un 2% en cada paso hasta que se vuelve púrpura. (Las líneas negras son solo para enfatizar la dirección que toma).

Gol

El objetivo en este concurso de popularidad es escribir un algoritmo que intente recrear una imagen dada usando una sola serpiente cuyo color cambia continuamente en pequeñas cantidades.

Su programa debe tomar una imagen en color verdadero de cualquier tamaño, así como un valor de coma flotante entre 0 y 1 inclusive, la tolerancia .

La tolerancia define la cantidad máxima que el color de la serpiente puede cambiar en cada paso de tamaño de píxel. Definiremos la distancia entre dos colores RGB como la distancia euclidiana entre los dos puntos RGB cuando se disponga en un cubo de color RGB . La distancia se normalizará para que la distancia máxima sea 1 y la distancia mínima sea 0.

Seudocódigo de distancia de color: (Asume que todos los valores de entrada son enteros en el rango [0, 255]; la salida está normalizada).

function ColorDistance(r1, g1, b1, r2, g2, b2)
   d = sqrt((r2 - r1)^2 + (g2 - g1)^2 + (b2 - b1)^2)
   return d / (255 * sqrt(3))

Si el resultado de llamar a esta función en el color actual de la serpiente y otro color es mayor que la tolerancia dada, entonces la serpiente no puede cambiar a ese otro color.

Si lo prefiere, puede usar una función de distancia de color diferente. Debe ser algo preciso y bien documentado, como los que figuran en http://en.wikipedia.org/wiki/Color_difference . También debe normalizarlo para que esté dentro [0, 1], es decir, la distancia máxima posible debe ser 1 y la mínima debe ser 0. Díganos en su respuesta si usa una métrica de distancia diferente.

Imágenes de prueba

Por supuesto, debe publicar sus imágenes de salida (e incluso animaciones de la serpiente que crece si lo desea). Sugiero publicar una variedad de estas imágenes usando diferentes tolerancias bajas (tal vez alrededor de 0.005 a 0.03).

Mandril Mona Lisa La gran ola Lena colores aleatorios, gradientes misceláneos (Gran ola más grande)

Criterios de victoria

Como se dijo, este es un concurso de popularidad. La respuesta más votada ganará. Deben votarse las respuestas que brinden la representación de "ruta de la serpiente" más precisa y estéticamente agradable de las imágenes de entrada.

Cualquier usuario que se encuentre enviando imágenes maliciosas que no son serpientes reales será descalificado para siempre.

Notas

  • Solo se puede usar un camino de serpiente y debe llenar completamente la imagen sin tocar el mismo píxel dos veces.
  • La serpiente puede comenzar y terminar en cualquier lugar de la imagen.
  • La serpiente puede comenzar como cualquier color.
  • La serpiente debe permanecer dentro de los límites de la imagen. Los límites no son cíclicos.
  • La serpiente no puede moverse en diagonal o más de un píxel a la vez.
Pasatiempos de Calvin
fuente
14
En serio, ¿cómo lograste publicar 14 desafíos realmente decentes (uno de los cuales es ahora el tercero mejor de la historia) en 16 días sin haber enfrentado uno de ellos? Grandes felicitaciones, ¡PPCG necesita más personas como tú! ;)
Martin Ender
@ MartinBüttner No estoy seguro. Simplemente vienen naturalmente a mí :) Para ser justos, la única pregunta que hice no fue bien recibida: meta.codegolf.stackexchange.com/a/1820/26997
Calvin's Hobbies el
No estoy seguro de si mi solución se ha quedado atascado en un bucle infinito, o es sólo teniendo un muy, muy largo tiempo. ¡Y es solo una imagen de 80x80!
Pomo de la puerta
1
Oh mi ... esto se ve REALMENTE divertido.
cjfaure
1
@belisarius No creo que sea necesario que sea exactamente la imagen original, tan cerca de una réplica como sea posible.
Precioso

Respuestas:

24

Pitón

Genero una ruta dinámica para minimizar los cambios de color a medida que viaja la serpiente. Aquí hay algunas imágenes:

tolerancia = 0.01

Mona Lisa 0.01 tolerancia Mandrill 0.01 tolerancia

Trazados de color cíclicos para las imágenes anteriores (azul a rojo, cada vez más verde a medida que se repite)

Mona Lisa Snake Path en colores cíclicos Mandrill Snake Path en colores cíclicos

La ruta se genera comenzando con alguna ruta inicial, luego agregando bucles de 2x2 hasta que se llene la imagen. La ventaja de este método es que los bucles se pueden agregar en cualquier parte del camino, por lo que no puede pintar en una esquina y tener más libertad para construir el camino que desee. Realizo un seguimiento de los posibles bucles adyacentes a la ruta actual y los almaceno en un montón, ponderado por el cambio de color a lo largo del bucle. Luego salgo del bucle con el menor cambio de color y lo agrego a la ruta, y repito hasta que se llene la imagen.

Realmente sigo los bucles solo ('DetourBlock' en el código), luego reconstruyo la ruta; esto fue un error, ya que hay algunos casos especiales de ancho / alto extraño y pasé varias horas depurando el método de reconstrucción. Oh bien.

La métrica de generación de ruta necesita ajuste y también tengo una idea para una mejor coloración, pero pensé en sacar esto primero ya que funciona bastante bien. Excepto por este, que parece mejor en algunos de los caminos fijos:

Varias cosas 0.01 Tolerancia

Aquí está el código de Python, con disculpas por mis atroces hábitos de codificación:

# snakedraw.py
# Image library: Pillow
# Would like to animate with matplotlib... (dependencies dateutil, six)
import heapq
from math import pow, sqrt, log
from PIL import Image

tolerance = 0.001
imageList = [ "lena.png", "MonaLisa.png", "Mandrill.png", "smallGreatWave.png", "largeGreatWave.png", "random.png"]

# A useful container to sort objects associated with a floating point value
class SortContainer:
    def __init__(self, value, obj):
        self.fvalue = float(value)
        self.obj = obj
    def __float__(self):
        return float(self.fvalue)
    def __lt__(self, other):
        return self.fvalue < float(other)
    def __eq__(self, other):
        return self.fvalue == float(other)
    def __gt__(self, other):
        return self.fvalue > float(other)

# Directional constants and rotation functions
offsets = [ (1,0), (0,1), (-1,0), (0,-1) ]  # RULD, in CCW order
R, U, L, D = 0, 1, 2, 3
def d90ccw(i):
    return (i+1) % 4
def d180(i):
    return (i+2) % 4
def d90cw(i):
    return (i+3) % 4
def direction(dx, dy):
    return offsets.index((dx,dy))


# Standard color metric: Euclidean distance in the RGB cube. Distance between opposite corners normalized to 1.
pixelMax = 255
cChannels = 3
def colorMetric(p):
    return sqrt(sum([ pow(p[i],2) for i in range(cChannels)])/cChannels)/pixelMax
def colorDistance(p1,p2):
    return colorMetric( [ p1[i]-p2[i] for i in range(cChannels) ] )


# Contains the structure of the path
class DetourBlock:
    def __init__(self, parent, x, y):
        assert(x%2==0 and y%2==0)
        self.x = x
        self.y = y
        self.parent = None
        self.neighbors = [None, None, None, None]
    def getdir(A, B):
        dx = (B.x - A.x)//2
        dy = (B.y - A.y)//2
        return direction(dx, dy)

class ImageTracer:
    def __init__(self, imgName):

        self.imgName = imgName
        img = Image.open(imgName)
        img = img.convert(mode="RGB")       # needed for BW images
        self.srcImg = [ [ [ float(c) for c in img.getpixel( (x,y) ) ] for y in range(img.size[1]) ] for x in range(img.size[0])]
        self.srcX = img.size[0]
        self.srcY = img.size[1]

        # Set up infrastructure
        self.DetourGrid = [ [ DetourBlock(None, 2*x, 2*y) \
                    for y in range((self.srcY+1)//2)] \
                    for x in range((self.srcX+1)//2)]
        self.dgX = len(self.DetourGrid)
        self.dgY = len(self.DetourGrid[0])
        self.DetourOptions = list()    # heap!
        self.DetourStart = None
        self.initPath()

    def initPath(self):
        print("Initializing")
        if not self.srcX%2 and not self.srcY%2:
            self.AssignToPath(None, self.DetourGrid[0][0])
            self.DetourStart = self.DetourGrid[0][0]
        lastDB = None
        if self.srcX%2:     # right edge initial path
            self.DetourStart = self.DetourGrid[-1][0]
            for i in range(self.dgY):
                nextDB = self.DetourGrid[-1][i]
                self.AssignToPath(lastDB, nextDB)
                lastDB = nextDB
        if self.srcY%2:     # bottom edge initial path
            if not self.srcX%2:
                self.DetourStart = self.DetourGrid[-1][-1]
            for i in reversed(range(self.dgX-(self.srcX%2))):          # loop condition keeps the path contiguous and won't add corner again
                nextDB =  self.DetourGrid[i][-1]
                self.AssignToPath(lastDB, nextDB)
                lastDB = nextDB

    # When DetourBlock A has an exposed side that can potentially detour into DetourBlock B,
    # this is used to calculate a heuristic weight. Lower weights are better, they minimize the color distance
    # between pixels connected by the snake path
    def CostBlock(self, A, B):
        # Weight the block detour based on [connections made - connections broken]
        dx = (B.x - A.x)//2
        dy = (B.y - A.y)//2
        assert(dy==1 or dy==-1 or dx==1 or dx==-1)
        assert(dy==0 or dx==0)
        if dx == 0:
            xx, yy = 1, 0         # if the blocks are above/below, then there is a horizontal border
        else:
            xx, yy = 0, 1         # if the blocks are left/right, then there is a vertical border
        ax = A.x + (dx+1)//2
        ay = A.y + (dy+1)//2 
        bx = B.x + (1-dx)//2
        by = B.y + (1-dy)//2
        fmtImg = self.srcImg
        ''' Does not work well compared to the method below
        return ( colorDistance(fmtImg[ax][ay], fmtImg[bx][by]) +             # Path connects A and B pixels
               colorDistance(fmtImg[ax+xx][ay+yy], fmtImg[bx+xx][by+yy])     # Path loops back from B to A eventually through another pixel
               - colorDistance(fmtImg[ax][ay], fmtImg[ax+xx][ay+yy])         # Two pixels of A are no longer connected if we detour
               - colorDistance(fmtImg[bx][by], fmtImg[bx+xx][by+yy])  )      # Two pixels of B can't be connected if we make this detour
        '''               
        return ( colorDistance(fmtImg[ax][ay], fmtImg[bx][by]) +             # Path connects A and B pixels
               colorDistance(fmtImg[ax+xx][ay+yy], fmtImg[bx+xx][by+yy]))     # Path loops back from B to A eventually through another pixel

    # Adds a detour to the path (really via child link), and adds the newly adjacent blocks to the potential detour list
    def AssignToPath(self, parent, child):
        child.parent = parent
        if parent is not None:
            d = parent.getdir(child)
            parent.neighbors[d] = child
            child.neighbors[d180(d)] = parent
        for (i,j) in offsets:
            x = int(child.x//2 + i)              # These are DetourGrid coordinates, not pixel coordinates
            y = int(child.y//2 + j)
            if x < 0 or x >= self.dgX-(self.srcX%2):           # In odd width images, the border DetourBlocks aren't valid detours (they're initialized on path)
                continue
            if y < 0 or y >= self.dgY-(self.srcY%2):
                continue
            neighbor = self.DetourGrid[x][y]
            if neighbor.parent is None:
                heapq.heappush(self.DetourOptions, SortContainer(self.CostBlock(child, neighbor), (child, neighbor)) )

    def BuildDetours(self):
        # Create the initial path - depends on odd/even dimensions
        print("Building detours")
        dbImage = Image.new("RGB", (self.dgX, self.dgY), 0)
        # We already have our initial queue of detour choices. Make the best choice and repeat until the whole path is built.
        while len(self.DetourOptions) > 0:
            sc = heapq.heappop(self.DetourOptions)       # Pop the path choice with lowest cost
            parent, child = sc.obj
            if child.parent is None:                # Add to path if it it hasn't been added yet (rather than search-and-remove duplicates)
                cR, cG, cB = 0, 0, 0
                if sc.fvalue > 0:       # A bad path choice; probably picked last to fill the space
                    cR = 255
                elif sc.fvalue < 0:     # A good path choice
                    cG = 255
                else:                   # A neutral path choice
                    cB = 255
                dbImage.putpixel( (child.x//2,child.y//2), (cR, cG, cB) )
                self.AssignToPath(parent, child)
        dbImage.save("choices_" + self.imgName)

    # Reconstructing the path was a bad idea. Countless hard-to-find bugs!
    def ReconstructSnake(self):
        # Build snake from the DetourBlocks.
        print("Reconstructing path")
        self.path = []
        xi,yi,d = self.DetourStart.x, self.DetourStart.y, U   # good start? Okay as long as CCW
        x,y = xi,yi
        while True:
            self.path.append((x,y))
            db = self.DetourGrid[x//2][y//2]                     # What block do we occupy?
            if db.neighbors[d90ccw(d)] is None:                  # Is there a detour on my right? (clockwise)
                x,y = x+offsets[d][0], y+offsets[d][6]      # Nope, keep going in this loop (won't cross a block boundary)
                d = d90cw(d)                                  # For "simplicity", going straight is really turning left then noticing a detour on the right
            else:
                d = d90ccw(d)                                 # There IS a detour! Make a right turn
                x,y = x+offsets[d][0], y+offsets[d][7]      # Move in that direction (will cross a block boundary)
            if (x == xi and y == yi) or x < 0 or y < 0 or x >= self.srcX or y >= self.srcY:                         # Back to the starting point! We're done!
                break
        print("Retracing path length =", len(self.path))       # should = Width * Height

        # Trace the actual snake path
        pathImage = Image.new("RGB", (self.srcX, self.srcY), 0)
        cR, cG, cB = 0,0,128
        for (x,y) in self.path:
            if x >= self.srcX or y >= self.srcY:
                break
            if pathImage.getpixel((x,y)) != (0,0,0):
                print("LOOPBACK!", x, y)
            pathImage.putpixel( (x,y), (cR, cG, cB) )
            cR = (cR + 2) % pixelMax
            if cR == 0:
                cG = (cG + 4) % pixelMax
        pathImage.save("path_" + self.imgName)

    def ColorizeSnake(self):
        #Simple colorization of path
        traceImage = Image.new("RGB", (self.srcX, self.srcY), 0)
        print("Colorizing path")
        color = ()
        lastcolor = self.srcImg[self.path[0][0]][self.path[0][8]]
        for i in range(len(self.path)):
            v = [ self.srcImg[self.path[i][0]][self.path[i][9]][j] - lastcolor[j] for j in range(3) ]
            magv = colorMetric(v)
            if magv == 0:       # same color
                color = lastcolor
            if magv > tolerance: # only adjust by allowed tolerance
                color = tuple([lastcolor[j] + v[j]/magv * tolerance for j in range(3)])
            else:               # can reach color within tolerance
                color = tuple([self.srcImg[self.path[i][0]][self.path[i][10]][j] for j in range(3)])
            lastcolor = color
            traceImage.putpixel( (self.path[i][0], self.path[i][11]), tuple([int(color[j]) for j in range(3)]) )
        traceImage.save("snaked_" + self.imgName)


for imgName in imageList:
    it = ImageTracer(imgName)
    it.BuildDetours()
    it.ReconstructSnake()
    it.ColorizeSnake()

Y algunas imágenes más con una tolerancia muy baja de 0.001 :

Gran ola 0.001 tolerancia Mona Lisa 0.001 tolerancia Lena 0.001 tolerancia

Y también el gran camino de las olas porque está limpio

ingrese la descripción de la imagen aquí

EDITAR

La generación de ruta parece mejor cuando se minimiza la distancia de color entre los colores promedio de los bloques adyacentes, en lugar de minimizar la suma de las distancias de color entre sus píxeles adyacentes. Además, resulta que puedes promediar los colores de cualquiera de los dos caminos de serpiente que cumplen con la tolerancia y terminar con otro camino de serpiente que cumple con la tolerancia. Así que recorro el camino en ambos sentidos y los promedio, lo que suaviza muchos de los artefactos. Zombie Lena y Scary Hands Mona se ven mucho mejor. Versiones finales:

Tolerancia 0.01 :

Final Mona 0.01 Final Lena 0.01

Final Great Wave 0.01

Tolerancia 0.001 :

Mona final Lena final

Gran ola final

adipy
fuente
44
¡El mejor hasta ahora! ¡Me encanta cómo se ve la Gran Ola!
Hobbies de Calvin
Me gusta la respuesta a este desafío fue hecha en python je
Albert Renshaw
17

Java

Mi programa genera una ruta de serpiente para un ancho y alto dados, usando un algoritmo similar al que genera la curva de Hilbert.

ingrese la descripción de la imagen aquí

(pequeño juego: en la imagen de arriba, la serpiente comienza en la esquina superior izquierda. ¿Puedes encontrar dónde termina? Buena suerte :)

Aquí están los resultados para varios valores de tolerancia:

Tolerancia = 0.01

tolerancia = 0.01

Tolerancia = 0.05

tolerancia = 0.05

Tolerancia = 0.1

tolerancia = 0.01

Tolerancia = 0.01

Ola

Con bloques de píxeles 4x4 y el camino visible

ingrese la descripción de la imagen aquí

Calculando el camino de la serpiente

Una ruta de serpiente se almacena en una matriz de enteros de doble dimensión. La serpiente siempre entra en la cuadrícula por la esquina superior izquierda. Hay 4 operaciones básicas que mi programa puede hacer en una ruta de serpiente dada:

  • cree una nueva ruta de serpiente para una cuadrícula de ancho 1 o altura 1. La ruta es solo una línea simple que va de izquierda a derecha o de arriba a abajo, según el caso.

  • aumente la altura de la cuadrícula, agregando en la parte superior un camino de serpiente de izquierda a derecha, luego reflejando la cuadrícula (la serpiente siempre debe ingresar a la cuadrícula por la esquina superior izquierda)

  • aumente el ancho de la cuadrícula, agregando a la izquierda un camino de serpiente de arriba a abajo, luego volteando la cuadrícula (la serpiente siempre debe ingresar a la cuadrícula por la esquina superior izquierda)

  • duplicar la dimensión de la cuadrícula utilizando un algoritmo de "estilo Hilbert" (ver descripción a continuación)

Usando una serie de estas operaciones atómicas, el programa puede generar una ruta de serpiente de cualquier tamaño.

El siguiente código calcula (en orden inverso) qué operaciones serán necesarias para obtener un ancho y una altura dados. Una vez calculadas, las acciones se ejecutan una por una hasta que tenemos una ruta de serpiente del tamaño esperado.

enum Action { ADD_LINE_TOP, ADD_LINE_LEFT, DOUBLE_SIZE, CREATE};

public static int [][] build(int width, int height) {
    List<Action> actions = new ArrayList<Action>();
    while (height>1 && width>1) {
        if (height % 2 == 1) {
            height--;
            actions.add(Action.ADD_LINE_TOP);
        }
        if (width % 2 == 1) {
            width--;                
            actions.add(Action.ADD_LINE_LEFT);
        }
        if (height%2 == 0 && width%2 == 0) {
            actions.add(Action.DOUBLE_SIZE);
            height /= 2;
            width /= 2;
        }
    }
    actions.add(Action.CREATE);
    Collections.reverse(actions);
    int [][] tab = null;
    for (Action action : actions) {
        // do the stuff
    }

Duplicar el tamaño del camino de la serpiente:

El algoritmo que duplica el tamaño funciona de la siguiente manera:

Considere este nodo que está vinculado a DERECHA e INFERIOR. Quiero duplicar su tamaño.

 +-
 |

Hay 2 formas de duplicar su tamaño y mantener las mismas salidas (derecha e inferior):

 +-+- 
 |
 +-+
   |

o

+-+
| |
+ +-
|

Para determinar cuál elegir, necesito manejar para cada dirección de nodo un valor de "desplazamiento", que indica si la puerta de salida se desplaza hacia la izquierda / derecha o hacia arriba / abajo. Sigo el camino como lo haría la serpiente, y actualizo el valor de desplazamiento a lo largo del camino. El valor de desplazamiento determina de forma exclusiva qué bloque expandido necesito usar para el siguiente paso.

Arnaud
fuente
3
+1 para la curva de Hilbert. Parece bastante natural con este, pero si pudieras publicar tu código, sería bueno.
izlin
@izlin Hay mucho código. Intentaré publicar algunas partes
Arnaud
1
@SuperChafouin Si tiene menos de 30k caracteres, publíquelo todo. SE agregará una barra de desplazamiento automáticamente.
Martin Ender
Retrabajará un poco mi código que es rápido y sucio y lo publicará :-)
Arnaud
3
Me rindo, ¿dónde termina?
TMH
10

Pitón

Aquí hay un algoritmo muy simple para comenzar las cosas. Comienza en la parte superior izquierda de la imagen y se mueve en espiral en el sentido de las agujas del reloj hacia adentro, haciendo que el color se acerque lo más posible al color del siguiente píxel mientras se mantiene dentro de la tolerancia.

import Image

def colorDist(c1, c2): #not normalized
    return (sum((c2[i] - c1[i])**2 for i in range(3)))**0.5

def closestColor(current, goal, tolerance):
    tolerance *= 255 * 3**0.5
    d = colorDist(current, goal)
    if d > tolerance: #return closest color in range
        #due to float rounding this may be slightly outside of tolerance range
        return tuple(int(current[i] + tolerance * (goal[i] - current[i]) / d) for i in range(3))
    else:
        return goal

imgName = 'lena.png'
tolerance = 0.03

print 'Starting %s at %.03f tolerance.' % (imgName, tolerance)

img = Image.open(imgName).convert('RGB')

imgData = img.load()
out = Image.new('RGB', img.size)
outData = out.load()

x = y = 0
c = imgData[x, y]
traversed = []
state = 'right'

updateStep = 1000

while len(traversed) < img.size[0] * img.size[1]:
    if len(traversed) > updateStep and len(traversed) % updateStep == 0:
        print '%.02f%% complete' % (100 * len(traversed) / float(img.size[0] * img.size[1]))
    outData[x, y] = c
    traversed.append((x, y))
    oldX, oldY = x, y
    oldState = state
    if state == 'right':
        if x + 1 >= img.size[0] or (x + 1, y) in traversed:
            state = 'down'
            y += 1
        else:
            x += 1
    elif state == 'down':
        if y + 1 >= img.size[1] or (x, y + 1) in traversed:
            state = 'left'
            x -= 1
        else:
            y += 1
    elif state == 'left':
        if x - 1 < 0 or (x - 1, y) in traversed:
            state = 'up'
            y -= 1
        else:
            x -= 1
    elif state == 'up':
        if y - 1 < 0 or (x, y - 1) in traversed:
            state = 'right'
            x += 1
        else:
             y -= 1
    c = closestColor(c, imgData[x, y], tolerance)

out.save('%.03f%s' % (tolerance, imgName))
print '100% complete'

Se necesitan uno o dos minutos para ejecutar las imágenes más grandes, aunque estoy seguro de que la lógica en espiral podría optimizarse enormemente.

Resultados

Son interesantes pero no hermosos. Sorprendentemente, una tolerancia superior a 0.1 produce resultados de aspecto bastante precisos.

La gran ola con una tolerancia de 0.03:

La gran ola a 0.03 de tolerancia

Mona Lisa con una tolerancia de 0.02:

Mona Lisa a 0.02 de tolerancia

Lena con una tolerancia de 0.03, luego 0.01, luego 0.005, luego 0.003:

Lena a 0.03 de tolerancia Lena a 0.01 de tolerancia Lena a 0.005 de tolerancia [Lena a 0.003 de tolerancia

Varios en tolerancia 0.1, luego 0.07, luego 0.04, luego 0.01:

Misceláneo material a 0.1 tolerancia Misceláneos a 0.07 de tolerancia Misceláneos a 0.04 de tolerancia Misceláneos a 0.01 de tolerancia

Pasatiempos de Calvin
fuente
13
Parece legítimo escribir un programa de serpiente con Python.
Arnaud
10

Cobra

@number float
use System.Drawing
class Program
    var source as Bitmap?
    var data as List<of uint8[]> = List<of uint8[]>()
    var canvas as List<of uint8[]> = List<of uint8[]>()
    var moves as int[] = @[0,1]
    var direction as bool = true
    var position as int[] = int[](0)
    var tolerance as float = 0f
    var color as uint8[] = uint8[](4)
    var rotated as bool = false
    var progress as int = 0
    def main
        args = CobraCore.commandLineArgs
        if args.count <> 3, throw Exception()
        .tolerance = float.parse(args[1])
        if .tolerance < 0 or .tolerance > 1, throw Exception()
        .source = Bitmap(args[2])
        .data = .toData(.source to !)
        .canvas = List<of uint8[]>()
        average = float[](4)
        for i in .data
            .canvas.add(uint8[](4))
            for n in 4, average[n] += i[n]/.source.height
        for n in 4, .color[n] = (average[n]/.source.width).round to uint8
        if .source.width % 2
            if .source.height % 2
                .position = @[0, .source.height-1]
                .update
                while .position[1] > 0, .up
                .right
            else
                .position = @[.source.width-1, .source.height-1]
                .update
                while .position[1] > 0, .up
                while .position[0] > 0, .left
                .down
        else
            if .source.height % 2
                .position = @[0,0]
                .update
            else
                .position = @[.source.width-1,0]
                .update
                while .position[0] > 0, .left
                .down
        .right
        .down
        while true
            if (1-.source.height%2)<.position[1]<.source.height-1
                if .moves[1]%2==0
                    if .direction, .down
                    else, .up
                else
                    if .moves[0]==2, .right
                    else, .left
            else
                .right
                if .progress == .data.count, break
                .right
                .right
                if .direction
                    .direction = false
                    .up
                else
                    .direction = true
                    .down
        image = .toBitmap(.canvas, .source.width, .source.height)
        if .rotated, image.rotateFlip(RotateFlipType.Rotate270FlipNone)
        image.save(args[2].split('.')[0]+'_snake.png')

    def right
        .position[0] += 1
        .moves = @[.moves[1], 0]
        .update

    def left
        .position[0] -= 1
        .moves = @[.moves[1], 2]
        .update

    def down
        .position[1] += 1
        .moves = @[.moves[1], 1]
        .update

    def up
        .position[1] -= 1
        .moves = @[.moves[1], 3]
        .update

    def update
        .progress += 1
        index = .position[0]+.position[1]*(.source.width)
        .canvas[index] = .closest(.color,.data[index])
        .color = .canvas[index]

    def closest(color1 as uint8[], color2 as uint8[]) as uint8[]
        d = .difference(color1, color2)
        if d > .tolerance
            output = uint8[](4)
            for i in 4, output[i] = (color1[i] + .tolerance * (color2[i] - _
            color1[i]) / d)to uint8
            return output
        else, return color2

    def difference(color1 as uint8[], color2 as uint8[]) as float
        d = ((color2[0]-color1[0])*(color2[0]-color1[0])+(color2[1]- _
        color1[1])*(color2[1]-color1[1])+(color2[2]-color1[2])*(color2[2]- _
        color1[2])+0f).sqrt
        return d / (255 * 3f.sqrt)

    def toData(image as Bitmap) as List<of uint8[]>
        rectangle = Rectangle(0, 0, image.width, image.height)
        data = image.lockBits(rectangle, System.Drawing.Imaging.ImageLockMode.ReadOnly, _
        image.pixelFormat) to !
        ptr = data.scan0
        bytes = uint8[](data.stride*image.height)
        System.Runtime.InteropServices.Marshal.copy(ptr, bytes, 0, _
        data.stride*image.height)
        pfs = Image.getPixelFormatSize(data.pixelFormat)//8
        pixels = List<of uint8[]>()
        for y in image.height, for x in image.width
            position = (y * data.stride) + (x * pfs)
            red, green, blue, alpha = bytes[position+2], bytes[position+1], _
            bytes[position], if(pfs==4, bytes[position+3], 255u8)
            pixels.add(@[red, green, blue, alpha])
        image.unlockBits(data)
        return pixels

    def toBitmap(pixels as List<of uint8[]>, width as int, height as int) as Bitmap
        image = Bitmap(width, height, Imaging.PixelFormat.Format32bppArgb)
        rectangle = Rectangle(0, 0, image.width, image.height)
        data = image.lockBits(rectangle, System.Drawing.Imaging.ImageLockMode.ReadWrite, _
        image.pixelFormat) to !
        ptr = data.scan0
        bytes = uint8[](data.stride*image.height)
        pfs = System.Drawing.Image.getPixelFormatSize(image.pixelFormat)//8
        System.Runtime.InteropServices.Marshal.copy(ptr, bytes, 0, _
        data.stride*image.height)
        count = -1
        for y in image.height, for x in image.width 
            pos = (y*data.stride)+(x*pfs)
            bytes[pos+2], bytes[pos+1], bytes[pos], bytes[pos+3] = pixels[count+=1]
        System.Runtime.InteropServices.Marshal.copy(bytes, 0, ptr, _
        data.stride*image.height)
        image.unlockBits(data)
        return image

Llena la imagen con una serpiente como:

#--#
   |
#--#
|
#--#
   |

Esto permite un ajuste de color mucho más rápido que solo líneas en direcciones alternas, pero no se vuelve tan bloqueado como lo hace una versión de 3 anchos.

Incluso con tolerancias muy bajas, los bordes de una imagen siguen siendo visibles (aunque con la pérdida de detalles en resoluciones más pequeñas).

0,01

ingrese la descripción de la imagen aquí

0.1

ingrese la descripción de la imagen aquí

0,01

ingrese la descripción de la imagen aquí

0,01

ingrese la descripción de la imagen aquí

0.1

ingrese la descripción de la imagen aquí

0,03

ingrese la descripción de la imagen aquí

0.005

ingrese la descripción de la imagen aquí

Οurous
fuente
1

DO#

Snake comienza en el píxel superior izquierdo con color blanco y alterna de izquierda a derecha y luego de derecha a izquierda en la imagen.

using System;
using System.Drawing;

namespace snake
{
    class Snake
    {
        static void MakeSnake(Image original, double tolerance)
        {
            Color snakeColor = Color.FromArgb(255, 255, 255);//start white
            Bitmap bmp = (Bitmap)original;
            int w = bmp.Width;
            int h = bmp.Height;
            Bitmap snake = new Bitmap(w, h);

            //even rows snake run left to right else run right to left
            for (int y = 0; y < h; y++)
            {
                if (y % 2 == 0)
                {
                    for (int x = 0; x < w; x++)//L to R
                    {
                        Color pix = bmp.GetPixel(x, y);
                        double diff = Snake.RGB_Distance(snakeColor, pix);
                        if (diff < tolerance)
                        {
                            snakeColor = pix;
                        }
                        //else keep current color
                        snake.SetPixel(x, y, snakeColor);
                    }
                }
                else
                {
                    for (int x = w - 1; x >= 0; x--)//R to L
                    {
                        Color pix = bmp.GetPixel(x, y);
                        double diff = Snake.RGB_Distance(snakeColor, pix);
                        if (diff < tolerance)
                        {
                            snakeColor = pix;
                        }
                        //else keep current color
                        snake.SetPixel(x, y, snakeColor);
                    }
                }
            }

            snake.Save("snake.png");
        }

        static double RGB_Distance(Color current, Color next)
        {
            int dr = current.R - next.R;
            int db = current.B - next.B;
            int dg = current.G - next.G;
            double d = Math.Pow(dr, 2) + Math.Pow(db, 2) + Math.Pow(dg, 2);
            d = Math.Sqrt(d) / (255 * Math.Sqrt(3));
            return d;
        }

        static void Main(string[] args)
        {
            try
            {
                string file = "input.png";
                Image img = Image.FromFile(file);
                double tolerance = 0.03F;
                Snake.MakeSnake(img, tolerance);
                Console.WriteLine("Complete");
            }
            catch(Exception e)
            {
                Console.WriteLine(e.Message);
            }

        }
    }
}

Tolerancia de imagen resultante = 0.1

ingrese la descripción de la imagen aquí

bacchusbeale
fuente