Disegna un'immagine con un serpente


28

Immagina un percorso bidimensionale continuo che può solo girare a sinistra, a destra o dritto, non può intersecarsi e deve riempire una griglia rettangolare come la griglia di pixel in un'immagine. Chiameremo questo tipo di percorso un serpente .

Esempio di serpente

Questo esempio ingrandito mostra un percorso del serpente in una griglia 10 × 4 che inizia in rosso e aumenta di tonalità di circa il 2% ad ogni passaggio fino a quando non diventa viola. (Le linee nere servono solo per enfatizzare la direzione che prende.)

Obbiettivo

L'obiettivo in questo concorso di popolarità è quello di scrivere un algoritmo che tenti di ricreare una data immagine usando un singolo serpente il cui colore cambia continuamente di piccole quantità.

Il programma deve includere un'immagine a colori reali di qualsiasi dimensione, nonché un valore in virgola mobile compreso tra 0 e 1 incluso, il tolleranza .

La tolleranza definisce la quantità massima che il colore del serpente può cambiare in ogni passo di dimensioni pixel. Definiremo la distanza tra due colori RGB come distanza euclidea tra i due punti RGB quando disposti su un cubo di colore RGB . La distanza verrà quindi normalizzata in modo che la distanza massima sia 1 e la distanza minima sia 0.

Pseudocodice distanza colore: (presuppone che tutti i valori di input siano numeri interi nell'intervallo [0, 255]; l'output è normalizzato.)

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

Se il risultato della chiamata di questa funzione sul colore corrente del serpente e un altro colore è maggiore della tolleranza data, il serpente potrebbe non cambiare in quell'altro colore.

Se si preferisce, è possibile utilizzare una funzione di distanza del colore diversa. Deve essere qualcosa di preciso e ben documentato come quelli elencati su http://en.wikipedia.org/wiki/Color_difference . Devi anche normalizzarlo per essere all'interno [0, 1], ovvero la distanza massima possibile deve essere 1 e il minimo deve essere 0. Comunicaci nella tua risposta se usi una metrica della distanza diversa.

Immagini di prova

Ovviamente dovresti pubblicare le tue immagini in uscita (e anche le animazioni del serpente che crescono se vuoi). Suggerisco di pubblicare una varietà di queste immagini usando diverse tolleranze basse (forse tra 0,005 e 0,03).

Mandrillo Monna Lisa La grande onda Lena colori casuali, sfumature varie (Grande onda maggiore)

Vincere i criteri

Come detto, questo è un concorso di popolarità. La risposta più votata vincerà. Le risposte che forniscono la rappresentazione più accurata ed esteticamente piacevole del "percorso del serpente" delle immagini di input dovrebbero essere votate.

Qualsiasi utente che si presenta per inviare in modo dannoso immagini che non sono veri serpenti sarà squalificato per sempre.

Gli appunti

  • È possibile utilizzare solo un percorso a serpente e deve riempire completamente l'immagine senza toccare due volte lo stesso pixel.
  • Il serpente può iniziare e finire ovunque nell'immagine.
  • Il serpente può iniziare come qualsiasi colore.
  • Il serpente deve rimanere nei limiti dell'immagine. I limiti non sono ciclici.
  • Il serpente non può muoversi in diagonale o più di un pixel alla volta.

14
Scherzi a parte, come sei riuscito a pubblicare 14 sfide davvero decenti (una delle quali è ora la terza migliore in assoluto) in 16 giorni senza mai sandboxare una di esse? Complimenti, PPCG ha bisogno di più persone come te! ;)
Martin Ender,

@ MartinBüttner Non sono sicuro. Sono venuti naturalmente da me :) Per essere onesti l'unica domanda che ho fatto sandbox non è stata ricevuta troppo bene: meta.codegolf.stackexchange.com/a/1820/26997
Calvin's Hobbies

Non sono sicuro che la mia soluzione sia bloccata in un ciclo infinito, o che ci voglia solo molto, molto tempo. Ed è solo un'immagine 80x80!
Maniglia della porta

1
Oh mio ... sembra davvero divertente.
cjfaure,

1
@belisarius Non credo che debba essere esattamente l'immagine originale, il più vicino possibile a una replica.
Οuroso

Risposte:


24

Pitone

Genero un percorso dinamico per ridurre al minimo i cambiamenti di colore mentre il serpente viaggia. Ecco alcune immagini:

tolleranza = 0,01

Mona Lisa tolleranza 0,01 Mandrill tolleranza 0,01

Percorsi di colore ciclici per le immagini sopra (da blu a rosso, diventando più verdi man mano che si ripete):

Mona Lisa Snake Path in Cyclic Colors Mandrill Snake Path in Cyclic Colors

Il percorso viene generato iniziando con un percorso iniziale, quindi aggiungendo loop 2x2 su di esso fino a riempire l'immagine. Il vantaggio di questo metodo è che i loop possono essere aggiunti in qualsiasi punto del percorso, quindi non puoi dipingerti in un angolo e avere più libertà di costruire il percorso che desideri. Tengo traccia dei possibili loop adiacenti al percorso corrente e li memorizzo in un heap, ponderato dal cambio di colore lungo il loop. Quindi apro il loop con il minor cambio di colore e lo aggiungo al percorso e lo ripeto fino a riempire l'immagine.

In realtà seguo i loop da solo ('DetourBlock' nel codice), quindi ricostruisco il percorso; questo è stato un errore in quanto vi sono alcuni casi speciali per larghezza / altezza dispari e ho trascorso diverse ore a eseguire il debug del metodo di ricostruzione. Oh bene.

La metrica di generazione del percorso deve essere ottimizzata e ho anche un'idea per una migliore colorazione, ma ho pensato di farcela prima perché funziona abbastanza bene. Tranne questo, che sembra migliore in alcuni dei percorsi fissi:

Misc Stuff 0,01 Tolleranza

Ecco il codice Python, con scuse per le mie atroci abitudini di codifica:

# 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()

E alcune altre immagini con una tolleranza molto bassa di 0,001 :

Tolleranza 0.001 alla grande onda Mona Lisa tolleranza 0,001 Tolleranza Lena 0.001

E anche il grande percorso dell'onda perché è pulito:

inserisci qui la descrizione dell'immagine

MODIFICARE

La generazione del percorso sembra migliore quando si minimizza la distanza del colore tra i colori medi dei blocchi adiacenti, piuttosto che minimizzare la somma delle distanze del colore tra i loro pixel adiacenti. Inoltre, si scopre che è possibile calcolare la media dei colori di due percorsi del serpente conformi alla tolleranza e finire con un altro percorso del serpente conforme alla tolleranza. Quindi percorro il percorso in entrambi i modi e li medio, il che leviga molti artefatti. Zombie Lena e Scary Hands Mona sembrano molto meglio. Versioni finali:

Tolleranza 0,01 :

Final Mona 0.01 Final Lena 0.01

Final Great Wave 0,01

Tolleranza 0,001 :

Final Mona Final Lena

Grande onda finale


4
Il migliore ancora! Adoro come appare la Grande Onda!
Hobby di Calvin il

Mi piace la risposta a questa sfida pitone eh
Albert Renshaw,

17

Giava

Il mio programma genera un percorso a serpente per una data larghezza e altezza, usando un algoritmo simile a quello che genera la curva di Hilbert.

inserisci qui la descrizione dell'immagine

(piccolo gioco: nella foto sopra, il serpente inizia nell'angolo in alto a sinistra. Riesci a trovare dove finisce? Buona fortuna :)

Ecco i risultati per vari valori di tolleranza:

Tolleranza = 0,01

Tolleranza = 0.01

Tolleranza = 0,05

Tolleranza = 0.05

Tolleranza = 0,1

Tolleranza = 0.01

Tolleranza = 0,01

Onda

Con blocchi di pixel 4x4 e il percorso visibile

inserisci qui la descrizione dell'immagine

Calcolo del percorso del serpente

Un percorso del serpente è memorizzato in un array intero a doppia dimensione. Il serpente entra sempre nella griglia dall'angolo in alto a sinistra. Esistono 4 operazioni di base che il mio programma può eseguire su un determinato percorso del serpente:

  • creare un nuovo percorso del serpente per una griglia di larghezza 1 o altezza 1. Il percorso è solo una semplice linea che va da sinistra a destra o verso il basso, a seconda del caso.

  • aumentare l'altezza della griglia, aggiungendo in alto un percorso del serpente da sinistra a destra, quindi rispecchiando la griglia (il serpente deve sempre entrare nella griglia nell'angolo in alto a sinistra)

  • aumentare la larghezza della griglia, aggiungendo a sinistra un percorso del serpente dall'alto verso il basso, quindi capovolgendo la griglia (il serpente deve sempre entrare nella griglia dall'angolo in alto a sinistra)

  • raddoppia la dimensione della griglia usando un algoritmo "stile Hilbert" (vedi descrizione sotto)

Utilizzando una serie di queste operazioni atomiche, il programma è in grado di generare un percorso a serpente di qualsiasi dimensione.

Il codice seguente calcola (in ordine inverso) quali operazioni saranno necessarie per ottenere una data larghezza e altezza. Una volta calcolate, le azioni vengono eseguite una ad una fino a quando non abbiamo ottenuto un percorso a serpente delle dimensioni previste.

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
    }

Raddoppiare le dimensioni del percorso del serpente:

L'algoritmo che raddoppia le dimensioni funziona come segue:

Considera questo nodo che è collegato a DESTRA e INFERIORE. Voglio raddoppiare le sue dimensioni.

 +-
 |

Esistono 2 modi per raddoppiare le sue dimensioni e mantenere le stesse uscite (destra e in basso):

 +-+- 
 |
 +-+
   |

o

+-+
| |
+ +-
|

Per determinare quale scegliere, devo gestire per ogni direzione del nodo un valore "shift", che indica se la porta di uscita viene spostata a sinistra / destra o su / giù. Seguo il percorso come farebbe il serpente e aggiorno il valore di spostamento lungo il percorso. Il valore di spostamento determina in modo univoco quale blocco espanso devo utilizzare per il passaggio successivo.


3
+1 per la curva di Hilbert. Sembra abbastanza naturale con questo, ma se potessi pubblicare il tuo codice sarebbe bello.
izlin,

@izlin C'è molto codice - cercherò di pubblicare alcune parti
Arnaud,

1
@SuperChafouin Se contiene meno di 30k caratteri, per favore pubblica tutto. SE aggiungerà automaticamente una barra di scorrimento.
Martin Ender,

Rielaborerò un po 'il mio codice che è veloce e sporco e lo pubblicherà :-)
Arnaud

3
Mi arrendo, dove finisce ?!
TMH,

10

Pitone

Ecco un algoritmo molto semplice per iniziare. Inizia nella parte superiore sinistra dell'immagine e si sposta a spirale in senso orario verso l'interno, rendendo il colore il più vicino possibile al colore del pixel successivo rimanendo all'interno della tolleranza.

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'

Ci vogliono un minuto o due per eseguire le immagini più grandi, anche se sono sicuro che la logica a spirale potrebbe essere notevolmente ottimizzata.

risultati

Sono interessanti ma non stupendi. Sorprendentemente una tolleranza superiore a 0,1 produce risultati dall'aspetto abbastanza accurato.

La grande onda con tolleranza 0,03:

La grande onda con tolleranza 0,03

Mona Lisa con tolleranza 0,02:

Mona Lisa con tolleranza 0,02

Lena con tolleranza 0,03, quindi 0,01, quindi 0,005, quindi 0,003:

Lena con tolleranza 0,03 Lena con tolleranza 0,01 Lena a tolleranza 0,005 [Lena con tolleranza 0,003

Varie cose a tolleranza 0,1, quindi 0,07, quindi 0,04, quindi 0,01:

Varie cose a tolleranza 0,1 Varie cose con tolleranza 0,07 Varie cose con tolleranza 0,04 Varie cose con tolleranza 0,01


13
Sembra legittimo scrivere un programma Snake 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

Riempie l'immagine con un serpente come:

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

Ciò consente una regolazione del colore molto più rapida rispetto alle semplici linee in direzioni alternate, ma non diventa così blocchi come una versione a 3 dimensioni.

Anche con tolleranze molto basse, i bordi di un'immagine sono ancora visibili (sebbene alla perdita di dettagli con risoluzioni più piccole).

0.01

inserisci qui la descrizione dell'immagine

0.1

inserisci qui la descrizione dell'immagine

0.01

inserisci qui la descrizione dell'immagine

0.01

inserisci qui la descrizione dell'immagine

0.1

inserisci qui la descrizione dell'immagine

0.03

inserisci qui la descrizione dell'immagine

0.005

inserisci qui la descrizione dell'immagine


1

C #

Snake inizia nel pixel in alto a sinistra con il colore bianco e alterna da sinistra a destra e poi da destra a sinistra sull'immagine.

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);
            }

        }
    }
}

Tolleranza dell'immagine del risultato = 0.1

inserisci qui la descrizione dell'immagine

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.