Draw an Image with a Snake

Imagine a continuous 2-dimensional path that can only turn left, right, or go straight, cannot intersect itself, and must fill a rectangular grid such as the grid of pixels in an image. We'll call this kind of path a snake.

This enlarged example shows a snake path in a 10×4 grid that starts out red and increases in hue by about 2% at every step until it is purple. (The black lines are only to emphasize the direction it takes.)

Goal

The goal in this popularity contest is to write an algorithm that attempts to recreate a given image using a single snake whose color continuously changes by small amounts.

Your program must take in a true-color image of any size as well as a floating point value between 0 and 1 inclusive, the tolerance.

Tolerance defines the maximum amount the color of the snake is allowed to change in each pixel sized step. We'll define the distance between two RGB colors as the Euclidean distance between the two RGB points when arranged on an RGB color cube. The distance will then be normalized so the maximum distance is 1 and the minimum distance is 0.

Color distance pseudocode: (Assumes all input values are integers in the range [0, 255]; output is normalized.)

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


If the result of calling this function on the snake's current color and another color is greater than the given tolerance then the snake may not change into that other color.

If you prefer, you may use a different color distance function. It must be something accurate and well documented such as those listed at http://en.wikipedia.org/wiki/Color_difference. You also must normalize it to be in within [0, 1], i.e. the maximum possible distance must be 1 and the minimum must be 0. Tell us in your answer if you use a different distance metric.

Test Images

You should of course post your output images (and even animations of the snake growing if you want). I suggest posting a variety of these images using different low tolerances (maybe around 0.005 to 0.03).

Win Criteria

As stated, this is a popularity contest. The highest voted answer will win. Answers that provide the most accurate and aesthetically pleasing "snake path" depiction of the input images should be voted up.

Any user who is found to be maliciously submitting images that are not actual snakes will be disqualified forever.

Notes

• Only one snake path may be used and it must completely fill the image without touching the same pixel twice.
• The snake may start and end anywhere in the image.
• The snake may start as any color.
• The snake must stay in the bounds of the image. The bounds are not cyclic.
• The snake cannot move diagonally or more than one pixel at a time.
• Seriously, how did you manage to post 14 really decent challenges (one of which is now the third best ever) in 16 days without ever sandboxing one of them? Big kudos, PPCG needs more people like you! ;) – Martin Ender Jul 23 '14 at 8:06
• @MartinBüttner Not sure. They just come naturally to me :) To be fair the one question I did sandbox was not received too well: meta.codegolf.stackexchange.com/a/1820/26997 – Calvin's Hobbies Jul 23 '14 at 8:14
• I'm not sure whether my solution is stuck in an infinite loop, or it's just taking a really, really long time. And it's only an 80x80 image! – Doorknob Jul 23 '14 at 12:11
• Oh my...this looks REALLY fun. – cjfaure Jul 23 '14 at 19:39
• @belisarius I don't think it's required to be exactly the original image, just as close a replica as possible. – Οurous Jul 24 '14 at 6:00

Python

I generate a dynamic path to minimize the color changes as the snake travels. Here are some images:

tolerance = 0.01

Cyclic color paths for the above images (blue to red, getting greener as it repeats):

The path is generated by starting with some initial path, then adding 2x2 loops onto it until the image is filled. The advantage of this method is that the loops can be added anywhere on the path, so you can't paint yourself into a corner and have more freedom to build the path you want. I keep track of the possible loops adjacent to the current path and store them in a heap, weighted by the color change along the loop. I then pop off the loop with the least color change and add it to the path, and repeat until the image is filled.

I actually track the loops alone ('DetourBlock' in the code), then reconstruct the path; this was a mistake as there are some special cases for odd width/height and I spent several hours debugging the reconstruction method. Oh well.

The path generation metric needs tuning and I also have an idea for better colorization, but I thought I'd get this out first since it works quite well. Except for this one, which seems better in some of the fixed paths:

Here's the Python code, with apologies for my atrocious coding habits:

# 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

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


And some more images at a very low tolerance of 0.001:

And also the great wave path because it's neat:

EDIT

The path generation seems better when minimizing the color distance between the average colors of adjacent blocks, rather than minimizing the sum of the color distances between their adjacent pixels. Also, it turns out you can average the colors of any two tolerance-compliant snake paths and end up with another tolerance-compliant snake path. So I traverse the path both ways and average them, which smooths out a lot of the artifacts. Zombie Lena and Scary Hands Mona look much better. Final versions:

Tolerance 0.01:

Tolerance 0.001:

• Best one yet! I love how the Great Wave looks! – Calvin's Hobbies Aug 9 '14 at 4:26
• I like the the answer to this challenge was made in python heh – Albert Renshaw Feb 14 '17 at 8:11

Java

My program generate a snake path for a given width & height, using an algorithm similar to the one that generates the Hilbert curve.

(little game: in the above picture, the snake starts at the top left corner. Can you find where he ends? Good luck :)

Here are the results for various tolerance values :

Tolerance = 0.01

Tolerance = 0.05

Tolerance = 0.1

Tolerance = 0.01

With 4x4 pixel blocks & the path visible

Computing the snake path

A snake path is stored in a double dimension integer array. The snake always enters the grid by the upper left corner. There are 4 basic operation my program can do on a given snake path:

• create a new snake path for a grid of width 1 or height 1. The path is just a simple line that goes left to right or up to down, depending on the case.

• increase the grid height, by adding at the top a snake path from left to right, then by mirroring the grid (the snake must always enter the grid by the upper left corner)

• increate the grid width, by adding at the left a snake path from top to bottom, then by flipping the grid (the snake must always enter the grid by the upper left corner)

• double the dimension of the grid using an "Hilbert style" algorithm (see description below)

Using a series of these atomic operations, the program is able to generate a snake path of any given size.

The code below computes (in reverse order) which operations will be needed to obtain a given width & height. Once computed, the actions are executed one by one until we got a snake path of the expected size.

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--;
}
if (width % 2 == 1) {
width--;
}
if (height%2 == 0 && width%2 == 0) {
height /= 2;
width /= 2;
}
}
Collections.reverse(actions);
int [][] tab = null;
for (Action action : actions) {
// do the stuff
}


Doubling the snake path size:

The algorithm that double the size works as follow:

Consider this node which is linked to RIGHT and BOTTOM. I want to double its size.

 +-
|


There are 2 ways to double its size and keep the same exits (right and bottom):

 +-+-
|
+-+
|


or

+-+
| |
+ +-
|


To determine which one to choose, I need to handle for each node direction a "shift" value, indicating if the exit door is shifted left/right or up/down. I follow the path as the snake would do, and update the shift value along the path. The shift value determines uniquely which expanded block I need to use for the next step.

• +1 for the Hilbert curve. It looks pretty natural with this one but if you could post your code it would be nice. – izlin Jul 24 '14 at 8:17
• @izlin There is a lot of code - I'll try to post some parts – Arnaud Jul 24 '14 at 8:24
• @SuperChafouin If it's less than 30k characters, please post all of it. SE will add a scrollbar automatically. – Martin Ender Jul 24 '14 at 8:28
• Will rework a little bit my code that is quick and dirty and post it :-) – Arnaud Jul 24 '14 at 8:37
• I give up, where does it end?! – TMH Jul 25 '14 at 13:32

Python

Here's a very simple algorithm to get things started. It starts at the top left of the image and spirals clockwise inwards, making the color as close as possible to the next pixel's color while staying within the tolerance.

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')

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

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

while len(traversed) < img.size[0] * img.size[1]:
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'


It takes a minute or two to run the larger images, though I'm sure the spiraling logic could be vastly optimized.

Results

They're interesting but not gorgeous. Amazingly a tolerance above 0.1 produces quite accurate looking results.

The Great Wave at 0.03 tolerance:

Mona Lisa at 0.02 tolerance:

Lena at 0.03 tolerance, then 0.01, then 0.005, then 0.003:

Misc stuff at 0.1 tolerance, then 0.07, then 0.04, then 0.01:

• Seems legitimate to write a snake program with Python. – Arnaud Jul 24 '14 at 9:59

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


Fills the image with a snake like:

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


This allows for much faster color adjustment than just lines in alternating directions, but doesn't become as blocky as a 3-wide version does.

Even at very low tolerances, the edges of an image are still visible (although at the loss of detail in smaller resolutions).

0.01

0.1

0.01

0.01

0.1

0.03

0.005

C#

Snake starts at top left pixel with colour white and alternates left to right and then right to left down the image.

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

}
}
}


Result image tolerance = 0.1