Gambarlah dengan Ular

28

Bayangkan jalur 2 dimensi terus menerus yang hanya bisa belok kiri, kanan, atau lurus, tidak dapat berpotongan sendiri, dan harus mengisi kisi persegi seperti kisi piksel dalam gambar. Kami akan menyebut jalur semacam ini ular .

Contoh ular

Contoh yang diperbesar ini menunjukkan jalur ular di kisi 10 × 4 yang dimulai merah dan meningkat rona sekitar 2% di setiap langkah sampai berwarna ungu. (Garis hitam hanya untuk menekankan arah yang dibutuhkan.)

Tujuan

Tujuan dalam kontes popularitas ini adalah untuk menulis sebuah algoritma yang mencoba untuk membuat ulang gambar yang diberikan menggunakan ular tunggal yang warnanya terus berubah dalam jumlah kecil.

Program Anda harus mengambil gambar warna sebenarnya dari ukuran apa pun serta nilai titik apung antara 0 dan 1 termasuk toleransi .

Toleransi mendefinisikan jumlah maksimum warna ular diizinkan untuk berubah di setiap langkah berukuran piksel. Kami akan menentukan jarak antara dua warna RGB sebagai jarak Euclidean antara dua titik RGB saat disusun pada kubus warna RGB . Jarak kemudian akan dinormalisasi sehingga jarak maksimum adalah 1 dan jarak minimum adalah 0.

Kode jarak warna: (Asumsikan semua nilai input adalah bilangan bulat dalam kisaran [0, 255]; output dinormalisasi.)

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

Jika hasil memanggil fungsi ini pada warna ular saat ini dan warna lain lebih besar dari toleransi yang diberikan maka ular tidak dapat berubah menjadi warna lain.

Jika Anda suka, Anda dapat menggunakan fungsi jarak warna yang berbeda. Itu harus sesuatu yang akurat dan didokumentasikan dengan baik seperti yang terdaftar di http://en.wikipedia.org/wiki/Color_difference . Anda juga harus menormalkannya agar berada di dalam [0, 1], yaitu jarak maksimum yang mungkin harus 1 dan minimum harus 0. Beri tahu kami dalam jawaban Anda jika Anda menggunakan metrik jarak yang berbeda.

Gambar Uji

Tentu saja Anda harus memposting gambar output Anda (dan bahkan animasi ular tumbuh jika Anda mau). Saya sarankan memposting berbagai gambar ini menggunakan toleransi rendah yang berbeda (mungkin sekitar 0,005 hingga 0,03).

Mandrill Mona lisa Gelombang Besar Lena warna acak, gradien misc (Gelombang Besar Yang Lebih Besar)

Menangkan Kriteria

Seperti yang dinyatakan, ini adalah kontes popularitas. Jawaban dengan suara tertinggi akan menang. Jawaban yang memberikan gambaran "jalur ular" yang paling akurat dan menyenangkan dari gambar input harus dipilih.

Setiap pengguna yang diketahui mengirimkan gambar-gambar jahat yang bukan ular sebenarnya akan didiskualifikasi selamanya.

Catatan

  • Hanya satu jalur ular yang dapat digunakan dan itu harus sepenuhnya mengisi gambar tanpa menyentuh piksel yang sama dua kali.
  • Ular dapat memulai dan mengakhiri di mana saja pada gambar.
  • Ular bisa mulai dengan warna apa saja.
  • Ular harus tetap berada di batas gambar. Batas-batasnya bukan siklik.
  • Ular tidak dapat bergerak secara diagonal atau lebih dari satu piksel pada satu waktu.
Hobi Calvin
sumber
14
Serius, bagaimana Anda bisa memposting 14 tantangan yang benar-benar layak (salah satunya sekarang menjadi yang terbaik ketiga) dalam 16 hari tanpa pernah meninju salah satu dari mereka? Pujian besar, PPCG membutuhkan lebih banyak orang seperti Anda! ;)
Martin Ender
@ MartinBüttner Tidak yakin. Mereka hanya datang secara alami kepada saya :) Agar adil satu pertanyaan yang saya lakukan sandbox tidak diterima dengan baik: meta.codegolf.stackexchange.com/a/1820/26997
Calvin Hobbies
Saya tidak yakin apakah solusi saya macet dalam loop tak terbatas, atau hanya butuh waktu yang sangat, sangat lama. Dan itu hanya gambar 80x80!
Gagang pintu
1
Oh my ... ini terlihat SANGAT menyenangkan.
cjfaure
1
@belisarius Saya tidak berpikir itu harus persis gambar aslinya, hanya sebagai replika sedekat mungkin.
Surous

Jawaban:

24

Python

Saya menghasilkan jalur dinamis untuk meminimalkan perubahan warna saat ular bergerak. Berikut ini beberapa gambar:

toleransi = 0,01

Mona Lisa 0,01 toleransi Toleransi 0,01 mandrill

Jalur warna siklik untuk gambar di atas (biru menjadi merah, semakin hijau saat diulang):

Jalur Ular Mona Lisa dalam Warna Siklik Jalur Ular Mandrill dalam Warna Siklik

Jalur dibuat dengan memulai dengan beberapa jalur awal, kemudian menambahkan 2x2 loop ke atasnya sampai gambar diisi. Keuntungan dari metode ini adalah bahwa loop dapat ditambahkan di mana saja di jalan, sehingga Anda tidak dapat melukis diri sendiri ke sudut dan memiliki lebih banyak kebebasan untuk membangun jalur yang Anda inginkan. Saya melacak kemungkinan loop yang berdekatan dengan jalur saat ini dan menyimpannya di tumpukan, tertimbang oleh perubahan warna sepanjang loop. Saya kemudian mematikan loop dengan perubahan warna paling sedikit dan menambahkannya ke jalan, dan ulangi sampai gambar diisi.

Saya benar-benar melacak loop sendirian ('DetourBlock' dalam kode), kemudian merekonstruksi path; ini adalah kesalahan karena ada beberapa kasus khusus untuk aneh lebar / tinggi dan saya menghabiskan beberapa jam men-debug metode rekonstruksi. Baiklah.

Metrik jalur pembuatan perlu disetel dan saya juga memiliki ide untuk pewarnaan yang lebih baik, tetapi saya pikir saya akan mendapatkan ini terlebih dahulu karena berfungsi dengan baik. Kecuali untuk yang ini, yang tampaknya lebih baik di beberapa jalur tetap:

Misc Stuff 0.01 Toleransi

Ini kode Python, dengan permintaan maaf atas kebiasaan pengkodean saya yang mengerikan:

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

Dan beberapa gambar lainnya dengan toleransi sangat rendah 0,001 :

Great Wave 0,001 toleransi Mona Lisa toleransi 0,001 Lena toleransi 0,001

Dan juga jalur gelombang yang bagus karena rapi:

masukkan deskripsi gambar di sini

EDIT

Pembuatan jalur tampak lebih baik ketika meminimalkan jarak warna antara warna rata-rata blok yang berdekatan, daripada meminimalkan jumlah jarak warna antara piksel yang berdekatan. Selain itu, ternyata Anda dapat membuat rata-rata warna dari dua jalur ular yang sesuai toleransi dan berakhir dengan jalur ular lain yang sesuai toleransi. Jadi saya melintasi jalan baik cara dan rata-rata, yang merapikan banyak artefak. Zombie Lena dan Scary Hands Mona terlihat jauh lebih baik. Versi terakhir:

Toleransi 0,01 :

Final Mona 0,01 Final Lena 0,01

Final Great Wave 0.01

Toleransi 0,001 :

Mona terakhir Lena terakhir

Gelombang Besar Terakhir

adipy
sumber
4
Yang terbaik! Saya suka bagaimana Great Wave terlihat!
Calvin Hobi
Saya suka jawaban untuk tantangan ini dibuat dengan python heh
Albert Renshaw
17

Jawa

Program saya menghasilkan jalur ular untuk lebar & tinggi tertentu, menggunakan algoritma yang mirip dengan yang menghasilkan kurva Hilbert.

masukkan deskripsi gambar di sini

(permainan kecil: pada gambar di atas, ular mulai di sudut kiri atas. Bisakah Anda menemukan di mana ia berakhir? Semoga beruntung :)

Berikut adalah hasil untuk berbagai nilai toleransi:

Toleransi = 0,01

toleransi = 0,01

Toleransi = 0,05

toleransi = 0,05

Toleransi = 0,1

toleransi = 0,01

Toleransi = 0,01

Gelombang

Dengan blok piksel 4x4 & jalur terlihat

masukkan deskripsi gambar di sini

Menghitung jalur ular

Jalur ular disimpan dalam array integer dimensi ganda. Ular selalu memasuki kotak di sudut kiri atas. Ada 4 operasi dasar yang bisa dilakukan program saya pada jalur ular yang diberikan:

  • buat jalur ular baru untuk kisi dengan lebar 1 atau tinggi 1. Jalur ini hanya garis sederhana yang bergerak dari kiri ke kanan atau ke atas ke bawah, tergantung pada kasingnya.

  • menambah tinggi kisi, dengan menambahkan di atas jalur ular dari kiri ke kanan, lalu dengan memantulkan kisi (ular harus selalu masuk ke kisi dengan sudut kiri atas)

  • menambah lebar kisi, dengan menambahkan di sebelah kiri jalur ular dari atas ke bawah, kemudian dengan membalik kisi (ular harus selalu memasuki kisi dengan sudut kiri atas)

  • gandakan dimensi grid menggunakan algoritma "Hilbert style" (lihat deskripsi di bawah)

Dengan menggunakan serangkaian operasi atom ini, program ini dapat menghasilkan jalur ular dengan ukuran berapa pun.

Kode di bawah ini menghitung (dalam urutan terbalik) yang diperlukan untuk operasi untuk mendapatkan lebar & tinggi yang diberikan. Setelah dihitung, tindakan dieksekusi satu per satu hingga kami mendapatkan jalur ular dengan ukuran yang diharapkan.

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
    }

Menggandakan ukuran jalur ular:

Algoritma yang menggandakan ukuran berfungsi sebagai berikut:

Pertimbangkan simpul ini yang ditautkan dengan KANAN dan BAWAH. Saya ingin menggandakan ukurannya.

 +-
 |

Ada 2 cara untuk menggandakan ukurannya dan tetap keluar yang sama (kanan dan bawah):

 +-+- 
 |
 +-+
   |

atau

+-+
| |
+ +-
|

Untuk menentukan yang mana yang harus dipilih, saya perlu menangani nilai "shift" untuk setiap simpul, yang menunjukkan apakah pintu keluar digeser ke kiri / kanan atau atas / bawah. Saya mengikuti jalan seperti yang dilakukan ular, dan memperbarui nilai shift di sepanjang jalan. Nilai shift menentukan secara unik blok diperluas yang perlu saya gunakan untuk langkah berikutnya.

Arnaud
sumber
3
+1 untuk kurva Hilbert. Ini terlihat sangat alami dengan yang satu ini tetapi jika Anda dapat memposting kode Anda akan lebih baik.
izlin
@izlin Ada banyak kode - Saya akan mencoba memposting beberapa bagian
Arnaud
1
@SuperChafouin Jika kurang dari 30k karakter, silakan kirim semuanya. SE akan menambahkan scrollbar secara otomatis.
Martin Ender
Akan ulang sedikit kode saya yang cepat dan kotor dan posting :-)
Arnaud
3
Saya menyerah, di mana itu berakhir?
TMH
10

Python

Berikut ini adalah algoritma yang sangat sederhana untuk memulai. Itu dimulai di kiri atas gambar dan berputar searah jarum jam ke dalam, membuat warnanya sedekat mungkin dengan warna piksel berikutnya sambil tetap berada dalam toleransi.

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'

Butuh satu atau dua menit untuk menjalankan gambar yang lebih besar, meskipun saya yakin logika spiral bisa sangat dioptimalkan.

Hasil

Mereka menarik tetapi tidak cantik. Hebatnya toleransi di atas 0,1 menghasilkan hasil yang cukup akurat.

The Great Wave dengan toleransi 0,03:

The Great Wave dengan toleransi 0,03

Mona Lisa pada toleransi 0,02:

Mona Lisa pada toleransi 0,02

Lena pada toleransi 0,03, lalu 0,01, lalu 0,005, lalu 0,003:

Lena dengan toleransi 0,03 Lena pada toleransi 0,01 Lena pada toleransi 0,005 [Lena pada toleransi 0,003

Barang lain-lain dengan toleransi 0,1, lalu 0,07, lalu 0,04, lalu 0,01:

Barang lain-lain dengan toleransi 0,1 Barang lain-lain dengan toleransi 0,07 Barang lain-lain dengan toleransi 0,04 Barang lain-lain pada toleransi 0,01

Hobi Calvin
sumber
13
Tampaknya sah untuk menulis program ular dengan Python.
Arnaud
10

Kobra

@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

Isi gambar dengan ular seperti:

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

Hal ini memungkinkan penyesuaian warna yang jauh lebih cepat daripada hanya garis-garis dalam arah yang bergantian, tetapi tidak menjadi seperti yang diblokir seperti versi 3-lebar.

Bahkan pada toleransi yang sangat rendah, ujung-ujung gambar masih terlihat (meskipun kehilangan detail dalam resolusi yang lebih kecil).

0,01

masukkan deskripsi gambar di sini

0,1

masukkan deskripsi gambar di sini

0,01

masukkan deskripsi gambar di sini

0,01

masukkan deskripsi gambar di sini

0,1

masukkan deskripsi gambar di sini

0,03

masukkan deskripsi gambar di sini

0,005

masukkan deskripsi gambar di sini

Suram
sumber
1

C #

Ular dimulai dari piksel kiri atas dengan warna putih dan berganti dari kiri ke kanan dan kemudian kanan ke kiri bawah gambar.

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

        }
    }
}

Toleransi gambar hasil = 0,1

masukkan deskripsi gambar di sini

bacchusbeale
sumber