Farba MS jest niedoceniana

48

MS Paint zawsze był świetnym niszczycielem czasu, ale został odrzucony przez większość grafików. Być może ludzie stracili zainteresowanie z powodu wstrząsającej palety kolorów lub z powodu ograniczonych poziomów cofania. Niezależnie od tego nadal można generować piękne obrazy za pomocą standardowego pędzla i domyślnej palety kolorów.

Wyzwanie

Używając tylko domyślnego pędzla (kwadrat 4x4 bez narożników) i domyślnej palety kolorów (28 kolorów poniżej), spróbuj odtworzyć obraz źródłowy za pomocą techniki opartej na stochastycznym wspinaniu się na wzgórze .

wprowadź opis zdjęcia tutaj

Algorytm

Każda odpowiedź musi być zgodna z tym samym podstawowym algorytmem (stochastyczna wspinaczka). Szczegóły można modyfikować na każdym etapie. Ruch jest uważany za pociągnięciem pędzla (np. Kliknięcie farby).

  1. Zgadnij następne ruchy. Odgadnij (współrzędne i kolory) dla następnego ruchu (ruchów), jak chcesz. Jednak zgadywanie nie może odwoływać się do obrazu źródłowego.
  2. Zgadnij. Nanieś pędzel na obraz, aby wykonać ruch.
  3. Zmierz korzyści płynące z ruchu (ruchów). Odwołując się do obrazu źródłowego, określ, czy ruch (y) przyniosły korzyść obrazowi (tzn. Obraz bardziej przypomina obraz źródłowy). Jeśli jest to korzystne, zachowaj ruch (y), w przeciwnym razie odrzuć ruch (y).
  4. Powtarzaj aż do konwergencji. Przejdź do kroku 1 i spróbuj zgadnąć, aż algorytm wystarczająco się zbiegnie. W tym momencie obraz powinien mocno przypominać obraz źródłowy.

Jeśli twój program nie pasuje do tych czterech kroków, prawdopodobnie nie jest to stochastyczna wspinaczka. Oznacziłem to jako konkurs popularności, ponieważ celem jest stworzenie interesujących algorytmów malarskich opartych na ograniczonej palecie kolorów i pędzlu.

Przeciwwskazania

  • Algorytm powinien być w jakiś sposób stochastyczny .
  • Obraz źródłowy nie powinien mieć wpływu na następne przypuszczenia. Zgadujesz każdy nowy ruch, a następnie sprawdzasz, czy to pomogło, czy nie. Na przykład nie można określić, gdzie umieścić pędzel na podstawie kolorów obrazu źródłowego (jest to podobne do ditheringu obrazu źródłowego, co nie jest celem).

  • Możesz wpływać na miejsce docelowe, modyfikując kroki algorytmu w dowolny sposób. Na przykład możesz rozpocząć zgadywanie od krawędzi i przesunąć się do wewnątrz, przeciągnąć pędzel, aby utworzyć linie dla każdego odgadnięcia, lub zdecydować się najpierw pomalować ciemne kolory. Możesz odwoływać się do poprzednich obrazów iteracji (ale nie obrazu źródłowego) w celu obliczenia następnego pożądanego ruchu. Mogą być restrykcyjne, jeśli chcesz (tzn. Zgaduj tylko w lewym górnym kwadrancie dla bieżącej iteracji).

  • Miarę „różnicy” między obrazem źródłowym a bieżącą iteracją można zmierzyć w dowolny sposób, o ile nie oblicza ona innych potencjalnych ruchów w celu ustalenia, czy ruch ten jest uważany za „najlepszy”. Nie powinien wiedzieć, czy aktualny ruch jest „najlepszy”, tylko czy pasuje do tolerancji kryteriów akceptacji. Na przykład może być tak prosty jak abs(src.R - current.R) + abs(src.G - current.G) + abs(src.B - current.B)dla każdego dotkniętego piksela lub dowolnej z dobrze znanych technik różnic kolorów .

Paleta

Możesz pobrać paletę jako obraz 28 x 1 lub utworzyć ją bezpośrednio w kodzie.

Szczotka

Szczotka ma kwadrat 4x4 bez narożników. To jest jego skalowana wersja:

wprowadź opis zdjęcia tutaj

(Twój kod musi używać wersji 4x4)

Przykład

Wejście:

Van Gogh - Gwiaździsta noc

Wynik:

Wygenerowano Gwiaździstą Noc

Możesz zobaczyć, jak postępuje podstawowy algorytm w krótkim filmie, który zrobiłem (każda klatka ma 500 iteracji): The Starry Night . Początkowe etapy są interesujące do obejrzenia:

wprowadź opis zdjęcia tutaj

gajeNL
źródło
1
@ vihan1086: Obraz źródłowy nie ma przezroczystości. Bieżący obraz może zależeć od poprzedniej iteracji (jak mój przykład, w którym nowe punkty są dodawane na początku poprzedniego), jeśli to masz na myśli.
gajeNL
Nie rozumiem, co w ogóle dodaje stochastyczna wspinaczka po wzgórzach ... biorąc pod uwagę, że możesz zgadywać, ile chcesz i odrzucać je, jeśli nie są dobre, to faktycznie tak samo, jak po prostu przebrnąć przez kilka domysłów podczas sprawdzania i wybranie najlepszego.
Sp3000,
@ Sp3000: Chodzi o to, że nie znasz „najlepszego” ruchu, dopóki nie zostanie wykonany potencjalny ruch, w którym to momencie możesz go zaakceptować, jeśli pasuje on do twoich własnych kryteriów akceptacji (tj. Jest „wystarczająco blisko” „). Kryteria akceptacji nie powinny obejmować wszystkich możliwych ruchów (być może będę musiał to wyjaśnić dalej). Zasadniczo nie powinieneś być w stanie określić „najlepszego” ruchu przed czasem, zamiast tego powinieneś stopniowo poprawiać obraz.
gajeNL
Czy przy tym kształcie pędzla nie można pomalować pikseli narożnych (z twojego przykładu mam takie wrażenie), czy też pędzel można umieścić częściowo poza granicami obrazu?
Oliphaunt
Problem Sp3000 może obejmować następujący algorytm deterministyczny: dla każdego piksela z kolei wypróbuj każdy kolor. Brak stochastyczności i bardzo podobne do ditheringu, ale wydaje się, że pasuje do zasad.
Oliphaunt

Odpowiedzi:

35

JavaScript

To rozwiązanie wykorzystuje element canvas HTML5 do wyodrębnienia danych obrazu, ale bez potrzeby używania HTML, co oznacza, że ​​można go uruchomić w konsoli. Uzyskuje dostęp do obrazu palety kolorów jako tablicy; Zapisałem wszystkie kolory z obrazu palety w tablicy). Wysyła dane do konsoli (po zakończeniu), a także zapisuje wynik w zmiennej.

Najbardziej zaktualizowana wersja kodu znajduje się w skrzypcach . Skrzypce wykorzystują również lepszy algorytm do redukcji szumów na zdjęciach. Ulepszenie algorytmu polega głównie na ustaleniu funkcji (maks. Do min.), Która spowodowała wybór odwrotnego koloru.

Kod w kształcie ikony MS Paint! (sformatowany kod w skrzypcach lub stosie fragmentu)

eval(`                                                   function                  
                                                        Paint(t){fun              
                                                         ction n(t){va            
                                                         r n=t.toString(          
                                                         16);return 1==n.         
                                                         length?"0"+n:n}fu        
                                                         nction e(t){return       
                                                         "#"+n(t[0])+n(t[1]       
                                                          )+n(t[2])}var a=ne      
                                                          w Image,i=document.     
                                                          createElement("canv     
                                                          as"),h=null,o=docum     
                                                          ent.createElement(      
                                    "canvas"),r=          o.getContext("2d        
                               ")     ,l=[],u=this,c      =[[0,0,0],[255          
                            ,2       55,255],[ 192,192,   192],[128,12            
                          8     ,128],[126,3,8],[252,13,27] ,[255,25              
                       3,    56],[128,127,23],[15,127,18],[ 41,253                
                      ,    46],[45,255,254],[17,128,127],[2 ,12,1                 
                    2    6],[ 11,36,2 51],[252,40,252],[12 7,15,1                 
                  2    6],[  128,127 ,68],[255,253,136],[4 2,253,                 
                 1   33],   [4,64,64],[23 ,131,251],[133,255,254],                
               [    129   ,132,252],[6,6 6,126],[127,37,2 51],[127,               
              6   4,1    3],[253,128,73],[252,22,129]];a.crossOrigin              
             =   "",   a.src=t,this.done=this.done||function(){},a.o              
            n   load=function(){function t(t){var n=0,e=0,a=0;return              
           t  .forEach(function(t){n+=t[0],e+=t[1],a+=t[2]}),[n/t.leng            
          t  h,e /t.length,a/t.length]}function n(t){for(var n=[],e=0;e           
         <  t.l  ength;e+=1)n.push(t[e]);return n}function g(t,n){retur           
        n  (Ma  th.abs(t[0]-n[0])/255+Math.abs(t[1]-n[1])/255+Math.abs(t          
       [  2]- n[2])/255)/3}function f(t,n){for(var e=Math.floor(Math.ran          
          do m()*n.length),a=n[e],i=(g(t,a),1-.8),h=56,o=[];o.length<=h&          
         &g (t,a)>i;)if(o.push(a),a=n[Math.floor(Math.random()*n.length)]         
     ,  o.length==h){var r=o.map(function(n){return g(t,n)});a=o[r.indexO         
       f(Math.max.apply(Math,r))],o.push(a)}return a}function s(t,n){for(         
    v  ar e=[];t.length>0;)e.push(t.splice(0,n).slice(0,-1));return e}i.w         
   i  dth=a.width,i.height=2*a.height,h=i.getContext("2d"),h.drawImage(a,0        
   ,0,a.width,a.height);for(var d=(function(t){reduce=t.map(function(t){re        
  turn(t[ 0]+t[1]+t[2])/3})}(c),0),m=0,v=i.width*i.height/4,p=0;v>p;p+=1)d        
  >2*Mat h.ceil(a.width/2)&&(d=0,m+=1),l.push(f(t(s(n(h.getImageData(2*d,2        
  *m,4,4).data),4)),c)),d+=1;o.width=i.width,o.height=i.height;for(var d=0        
 ,m=0,v=i.width*i.height/4,p=0;v>p;p+=1)d>2*Math.ceil(a.width/2)&&(d=0,m+=        
 1),console.log("Filling point ("+d+", "+m+") : "+e(l[p])),r.fillStyle=e(l        
 [p]),r.fillRect(2*d+1,2*m,2,1)  ,r.fillRect(2*d,2*m+1,4,2),r.fillRect(2*d        
+1,2*m+3,2,1),d+=1;u.result=o      .toDataURL("image/png"),u.resultCanvas         
=o,u.imageCanvas=i,u.image=a       ,u.done(),console.log(u.result)},a.one         
rror=function(t){console.log       ("The image failed to load. "+t)}}/*..         
............................       ......................................         
. ..........................       .....................................          
............................      ......................................          
.............................    .......................................          
.......................................................................           
.......................................................................           
..................  ..................................................            
................     .................................................            
..............       ................................................             
.............       ................................................              
...........        .................................................              
 .........         ................................................               
 .......          ................................................                
  ....           ................................................                 
                ................................................                  
                ...............................................                   
               ...............................................                    
              ..............................................                      
              .............................................                       
             ............................................                         
             ..........................................                           
              .......................................                             
              .....................................                               
               .................................                                  
                .............................                                     
                  ......................                                          
                                   .....                                          
                                  .....                                           
                                  .....                                           
                                  ....                                            
                                   */`
.replace(/\n/g,''))                                             

Stosowanie:

Paint('DATA URI');

Fiddle .

Skrzypce używa crossorigin.me, więc nie musisz się martwić dzieleniem zasobów między źródłami.

Zaktualizowałem również skrzypce, abyś mógł dostosować niektóre wartości, aby uzyskać najlepiej wyglądający obraz. Kolory niektórych zdjęć mogą być wyłączone, aby tego uniknąć, dostosuj wartość accept_rate, aby dostosować algorytm. Niższa liczba oznacza lepsze gradienty, wyższa liczba zapewni ostrzejsze kolory.


Oto skrzypce jako fragment kodu stosu (NIE jest aktualizowany, jeśli skrzypce nie działa):


Aby upamiętnić przelot Plutona w Nowym Horyzoncie, wprowadziłem obraz Plutona:

Oryginał Pociągnięty

Oryginał Pociągnięty

Dla następujących ustawiłem go tak, aby przypominały oryginał tak blisko, jak to możliwe:

Uruchomiłem to z domyślną tapetą OS X Yosemite. Po pozostawieniu go na chwilę wyniki są absolutnie oszałamiające. Oryginalny plik był ogromny (26 MB), więc zmieniłem jego rozmiar i skompresowałem:

wprowadź opis zdjęcia tutaj

Gwiaździsta noc ( dla uzyskania lepszych rezultatów użyłem obrazu w wyższej rozdzielczości )

Zdjęcie, które znalazłem w Google: wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

Downgoat
źródło
12

JavaScript + HTML

Losowy:

Losowy punkt

Wyrównany losowo:

Dzieli płótno na kwadraty 4x4 i wybiera losowo punkt w jednym z kwadratów. Przesunięcia przesuną siatkę, dzięki czemu możesz wypełnić małe luki.

Pętla:

Tworzy siatkę i Pętle przez wszystkie punkty. Przesunięcia przesuwa siatkę. Odstępy określają wielkość każdej komórki. (Zaczną się nakładać)

Różnica barw:

  • RGB
  • HSL
  • HSV

var draw = document.getElementById("canvas").getContext("2d");
var data = document.getElementById("data").getContext("2d");
colors = [
    [0, 0, 0],
    [255, 255, 255],
    [192, 192, 192],
    [128, 128, 128],
    [126, 3, 8],
    [252, 13, 27],
    [255, 253, 56],
    [128, 127, 23],
    [15, 127, 18],
    [41, 253, 46],
    [45, 255, 254],
    [17, 128, 127],
    [2, 12, 126],
    [11, 36, 251],
    [252, 40, 252],
    [127, 15, 126],
    [128, 127, 68],
    [255, 253, 136],
    [42, 253, 133],
    [4, 64, 64],
    [23, 131, 251],
    [133, 255, 254],
    [129, 132, 252],
    [6, 66, 126],
    [127, 37, 251],
    [127, 64, 13],
    [253, 128, 73],
    [252, 22, 129]
];
iteration = 0;
fails = 0;
success = 0;
x = 0;
y = 0;
//Init when the Go! button is pressed
document.getElementById("file").onchange = function (event) {
    document.getElementById("img").src = URL.createObjectURL(event.target.files[0]);
    filename = document.getElementById("file").value;
    /*if (getCookie("orginal") == filename) {
        console.log("Loading from Cookie");
        reload = true;
        document.getElementById("reload").src = getCookie("picture");
    }*/
};

/*function getCookie(cname) {
    var name = cname + "=";
    var ca = document.cookie.split(';');
    for (var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') c = c.substring(1);
        if (c.indexOf(name) == 0) return c.substring(name.length, c.length);
    }
    return "";
}*/

//Run when the image has been loaded into memory
document.getElementById("img").onload = function () {
    document.getElementById("file").disable = "true";
    document.getElementById("canvas").hidden = "";
    document.getElementById("canvas").height = document.getElementById("img").height;
    document.getElementById("data").height = document.getElementById("img").height;
    document.getElementById("canvas").width = document.getElementById("img").width;
    document.getElementById("data").width = document.getElementById("img").width;

    var imgData = draw.createImageData(document.getElementById("img").width, document.getElementById("img").height);
    for (var i = 0; i < imgData.data.length; i += 4) {
        imgData.data[i + 0] = 0;
        imgData.data[i + 1] = 0;
        imgData.data[i + 2] = 0;
        imgData.data[i + 3] = 255;
    }
    draw.putImageData(imgData, 0, 0);
    data.putImageData(imgData, 0, 0);
    if (reload == true) {
        draw.drawImage(document.getElementById("reload"), 0, 0);
    }
    data.drawImage(document.getElementById("img"), 0, 0);
    setInterval(function () {
        for (var u = 0; u < document.getElementById("selectColor").value; u++) {
            doThing();
        }
    }, 0);
};

//The core function. Every time this function is called, is checks/adds a dot.
function doThing() {
    getCoords();
    paintBucket();
    console.count("Iteration");
    if (compare(x, y)) {
        draw.putImageData(imgData, x, y);
    }
}

function getCoords() {
    switch (document.getElementById("selectCord").value) {
        case "1":
            x = Math.floor(Math.random() * (document.getElementById("img").width + 4));
            y = Math.floor(Math.random() * (document.getElementById("img").height + 4));
            break;
        case "2":
            x = Math.floor(Math.random() * ((document.getElementById("img").width + 4) / 4)) * 4;
            console.log(x);
            x += parseInt(document.getElementById("allignX").value);
            console.log(x);
            y = Math.floor(Math.random() * ((document.getElementById("img").height + 4) / 4)) * 4;
            y += parseInt(document.getElementById("allignY").value);
            break;
        case "3":
            x += parseInt(document.getElementById("loopX").value);
            if (x > document.getElementById("img").width + 5) {
                x = parseInt(document.getElementById("allignX").value);
                y += parseInt(document.getElementById("loopY").value);
            }
            if (y > document.getElementById("img").height + 5) {
                y = parseInt(document.getElementById("allignY").value);
            }
    }
}

function compare(arg1, arg2) {
    var arg3 = arg1 + 4;
    var arg4 = arg2 + 4;
    imgData2 = data.getImageData(arg1, arg2, 4, 4);
    imgData3 = draw.getImageData(arg1, arg2, 4, 4);
    N = 0;
    O = 0;
    i = 4;
    addCompare();
    addCompare();
    i += 4;
    for (l = 0; l < 8; l++) {
        addCompare();
    }
    i += 4;
    addCompare();
    addCompare();
    i += 4;
    //console.log("New Score: " + N + " Old Score: " + O);
    iteration++;
    /*if(iteration>=1000){
        document.cookie="orginal="+filename;
        document.cookie="picture length="+document.getElementById("canvas").toDataURL().length;
        document.cookie="picture="+document.getElementById("canvas").toDataURL();
        
    }*/
    if (N < O) {
        return true;
    } else {
        return false;
    }
}

function addCompare() {
    if (document.getElementById("colorDif").value == "HSL") {
        HSLCompare();
        i += 4;
        return;
    }
    if (document.getElementById("colorDif").value == "HSV") {
        HSVCompare();
        i += 4;
        return;
    }
    N += Math.abs(imgData.data[i] - imgData2.data[i]);
    N += Math.abs(imgData.data[i + 1] - imgData2.data[i + 1]);
    N += Math.abs(imgData.data[i + 2] - imgData2.data[i + 2]);
    O += Math.abs(imgData3.data[i] - imgData2.data[i]);
    O += Math.abs(imgData3.data[i + 1] - imgData2.data[i + 1]);
    O += Math.abs(imgData3.data[i + 2] - imgData2.data[i + 2]);
    i += 4;
}

function HSVCompare() {
    var NewHue = rgbToHsv(imgData.data[i], imgData.data[i + 1], imgData.data[i + 2])[0];
    var PicHue = rgbToHsv(imgData2.data[i], imgData2.data[i + 1], imgData2.data[i + 2])[0];
    var OldHue = rgbToHsv(imgData3.data[i], imgData3.data[i + 1], imgData3.data[i + 2])[0];

    var NScore = [Math.abs(NewHue - PicHue), ((NewHue < PicHue) ? NewHue + (1 - PicHue) : PicHue + (1 - NewHue))];
    var OScore = [Math.abs(OldHue - PicHue), ((OldHue < PicHue) ? OldHue + (1 - PicHue) : PicHue + (1 - OldHue))];
    
    
    NScore = Math.min(NScore[0], NScore[1]);
    OScore = Math.min(OScore[0], OScore[1]);
    
    NewHue = rgbToHsv(imgData.data[i], imgData.data[i + 1], imgData.data[i + 2])[1];
    PicHue = rgbToHsv(imgData2.data[i], imgData2.data[i + 1], imgData2.data[i + 2])[1];
    OldHue = rgbToHsv(imgData3.data[i], imgData3.data[i + 1], imgData3.data[i + 2])[1];
    
    NScore += Math.abs(NewHue-PicHue);
    OScore += Math.abs(OldHue-PicHue);
    
    NewHue = rgbToHsv(imgData.data[i], imgData.data[i + 1], imgData.data[i + 2])[2];
    PicHue = rgbToHsv(imgData2.data[i], imgData2.data[i + 1], imgData2.data[i + 2])[2];
    OldHue = rgbToHsv(imgData3.data[i], imgData3.data[i + 1], imgData3.data[i + 2])[2];
    
    N += Math.abs(NewHue-PicHue) + NScore;
    O += Math.abs(OldHue-PicHue) + OScore;
}

function rgbToHsv(r, g, b){
    r = r/255, g = g/255, b = b/255;
    var max = Math.max(r, g, b), min = Math.min(r, g, b);
    var h, s, v = max;

    var d = max - min;
    s = max == 0 ? 0 : d / max;

    if(max == min){
        h = 0; // achromatic
    }else{
        switch(max){
            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
            case g: h = (b - r) / d + 2; break;
            case b: h = (r - g) / d + 4; break;
        }
        h /= 6;
    }

    return [h, s, v];
}

function HSLCompare() {
    var result = 0;
    rgb = false;

    var NewHue = rgbToHue(imgData.data[i], imgData.data[i + 1], imgData.data[i + 2])[0];
    var PicHue = rgbToHue(imgData2.data[i], imgData2.data[i + 1], imgData2.data[i + 2])[0];
    var OldHue = rgbToHue(imgData3.data[i], imgData3.data[i + 1], imgData3.data[i + 2])[0];
    if (rgb == true) {
        N += Math.abs(imgData.data[i] - imgData2.data[i]);
        N += Math.abs(imgData.data[i + 1] - imgData2.data[i + 1]);
        N += Math.abs(imgData.data[i + 2] - imgData2.data[i + 2]);
        O += Math.abs(imgData3.data[i] - imgData2.data[i]);
        O += Math.abs(imgData3.data[i + 1] - imgData2.data[i + 1]);
        O += Math.abs(imgData3.data[i + 2] - imgData2.data[i + 2]);
        return;
    }
    var NScore = [Math.abs(NewHue - PicHue), ((NewHue < PicHue) ? NewHue + (1 - PicHue) : PicHue + (1 - NewHue))];
    var OScore = [Math.abs(OldHue - PicHue), ((OldHue < PicHue) ? OldHue + (1 - PicHue) : PicHue + (1 - OldHue))];
    
    
    NScore = Math.min(NScore[0], NScore[1]);
    OScore = Math.min(OScore[0], OScore[1]);
    
    NewHue = rgbToHue(imgData.data[i], imgData.data[i + 1], imgData.data[i + 2])[1];
    PicHue = rgbToHue(imgData2.data[i], imgData2.data[i + 1], imgData2.data[i + 2])[1];
    OldHue = rgbToHue(imgData3.data[i], imgData3.data[i + 1], imgData3.data[i + 2])[1];
    
    NScore += Math.abs(NewHue-PicHue);
    OScore += Math.abs(OldHue-PicHue);
    
    NewHue = rgbToHue(imgData.data[i], imgData.data[i + 1], imgData.data[i + 2])[2];
    PicHue = rgbToHue(imgData2.data[i], imgData2.data[i + 1], imgData2.data[i + 2])[2];
    OldHue = rgbToHue(imgData3.data[i], imgData3.data[i + 1], imgData3.data[i + 2])[2];
    
    N += Math.abs(NewHue-PicHue) + NScore;
    O += Math.abs(OldHue-PicHue) + OScore;
}

function rgbToHue(r, g, b) {
    if (Math.max(r, g, b) - Math.min(r, g, b) < 50) {
        rgb = true
    }
    r /= 255, g /= 255, b /= 255;
    var max = Math.max(r, g, b),
        min = Math.min(r, g, b);
    var h, s, l = (max + min) / 2;

    if (max == min) {
        h = s = 0; // achromatic
    } else {
        var d = max - min;
        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
        switch (max) {
            case r:
                h = (g - b) / d + (g < b ? 6 : 0);
                break;
            case g:
                h = (b - r) / d + 2;
                break;
            case b:
                h = (r - g) / d + 4;
                break;
        }
        h /= 6;
    }
    return [h,s,l];
}

//Create a 4x4 ImageData object, random color selected from the colors var, transparent corners.
function paintBucket() {
    color = Math.floor(Math.random() * 28);
    imgData = draw.createImageData(4, 4);
    imgData2 = draw.getImageData(x, y, 4, 4);
    i = 0;
    createCorn();
    createColor();
    createColor();
    createCorn();
    for (l = 0; l < 8; l++) {
        createColor();
    }
    createCorn();
    createColor();
    createColor();
    createCorn();
}

function createCorn() {
    imgData.data[i] = imgData2.data[i];
    imgData.data[i + 1] = imgData2.data[i + 1];
    imgData.data[i + 2] = imgData2.data[i + 2];
    imgData.data[i + 3] = 255;
    i += 4;
}

function createColor() {
    imgData.data[i] = colors[color][0];
    imgData.data[i + 1] = colors[color][1];
    imgData.data[i + 2] = colors[color][2];
    imgData.data[i + 3] = 255;
    i += 4;
}
<canvas id="canvas" hidden></canvas>
<br>
<canvas id="data" hidden></canvas>
<br>
<input type="file" id="file"></input>
<br>
<img id="img">
<img id="reload" hidden>
<p>Algorithms:</p>
<select id="selectCord">
    <option value="1">Random</option>
    <option value="2">Random Alligned</option>
    <option value="3" selected>Loop</option>
</select>
<select id="selectColor">
    <option value="2000">Super Speedy</option>
    <option value="1000">Very Speedy</option>
    <option value="500" selected>Speedy</option>
    <option value="1">Visual</option>
</select>
<select id="colorDif">
    <option value="RGB" selected>RGB</option>
    <option value="HSL">HSL</option>
    <option value="HSV">HSV</option>
</select>
<p>Algorithm Options:
    <br>
</p>
<p>X Offset:
    <input id="allignX" type="range" min="0" max="3" value="0"></input>
</p>
<p>Y Offset:
    <input id="allignY" type="range" min="0" max="3" value="0"></input>
</p>
<p>Spacing X:
    <input id="loopX" type="range" min="1" max="4" value="2"></input>
</p>
<p>Spacing Y:
    <input id="loopY" type="range" min="1" max="4" value="2"></input>
</p>

wprowadź opis zdjęcia tutaj
RGB: wprowadź opis zdjęcia tutaj
HSL: wprowadź opis zdjęcia tutaj
HSV: wprowadź opis zdjęcia tutaj

Grant Davis
źródło
Bardzo fajny. „Uruchom fragment kodu” pęka dla mnie, gdy próbuje się ustawić document.cookie(po 1000 iteracjach), ponieważ dokument jest w trybie piaskownicy. Czy plik cookie jest konieczny?
gajeNL
Nie, pewnego razu uruchomiłem program na kilka godzin, ale potem moja przeglądarka uległa awarii. Więc upiekłem ciasteczko jako kopię zapasową. Ale usunę go, ponieważ wydaje się, że wymiana stosów nie lubi ciasteczek.
Grant Davis,
1
Patrząc na twój kod, myślę, że może skorzystać z tego samego przyspieszenia, które zasugerowałem w odpowiedzi wolfhammer , z wyjątkiem zastosowanego doThingzamiast loop. Zwiększenie prędkości może być warte dodatkowej linii ...
trichoplax
1
@trichoplax Dziękuję bardzo, Twoja poprawka nie tylko zwiększyła szybkość mojego programu, podczas naprawy znalazłem i naprawiłem błąd matematyczny, który popełniłem, a mój program nie generuje już tych małych czarnych kropek.
Grant Davis,
To wspaniale! Nowy obraz wyjściowy wygląda znacznie lepiej.
trichoplax
8

C # (implementacja referencyjna)

Jest to kod używany do generowania obrazów w pytaniu. Pomyślałem, że użyteczne byłoby podanie niektórym osobom odniesienia do organizacji ich algorytmu. Zupełnie losowa współrzędna i kolor są wybierane przy każdym ruchu. Działa zaskakująco dobrze, biorąc pod uwagę ograniczenia nałożone przez rozmiar pędzla / kryteria akceptacji.

Używam algorytmu CIEDE2000 do pomiaru różnic kolorów, z biblioteki ColorMine open source . Powinno to dać dokładniejsze dopasowanie kolorów (z ludzkiej perspektywy), ale wydaje się, że nie jest to zauważalna różnica w przypadku użycia z tą paletą.

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using ColorMine.ColorSpaces;
using ColorMine.ColorSpaces.Comparisons;

namespace Painter
{
    public class Painter
    {
        private readonly Bitmap _source;
        private readonly Bitmap _painting;
        private readonly int _width;
        private readonly int _height;
        private readonly CieDe2000Comparison _comparison = new CieDe2000Comparison();
        private const int BRUSHSIZE = 4;
        private readonly Random _random = new Random();
        private readonly ColorPalette _palette;

        private static readonly int[][] BRUSH = {
            new[] {1, 0}, new[] {2, 0},
            new[] {0, 1}, new[] {1, 1}, new[] {2, 1}, new[] {3, 1}, 
            new[] {0, 2}, new[] {1, 2}, new[] {2, 2}, new[] {3, 2}, 
            new[] {1, 3}, new[] {2, 3}
        };

        public Painter(string sourceFilename, string paletteFilename)
        {
            _source = (Bitmap)Image.FromFile(sourceFilename);
            _width = _source.Width;
            _height = _source.Height;

            _palette = Image.FromFile(paletteFilename).Palette;
            _painting = new Bitmap(_width, _height, PixelFormat.Format8bppIndexed) {Palette = _palette};

            // search for black in the color palette
            for (int i = 0; i < _painting.Palette.Entries.Length; i++)
            {
                Color color = _painting.Palette.Entries[i];
                if (color.R != 0 || color.G != 0 || color.B != 0) continue;
                SetBackground((byte)i);
            }
        }

        public void Paint()
        {
            // pick a color from the palette
            int brushIndex = _random.Next(0, _palette.Entries.Length);
            Color brushColor = _palette.Entries[brushIndex];

            // choose coordinate
            int x = _random.Next(0, _width - BRUSHSIZE + 1);
            int y = _random.Next(0, _height - BRUSHSIZE + 1);

            // determine whether to accept/reject brush
            if (GetBrushAcceptance(brushColor, x, y))
            {
                BitmapData data = _painting.LockBits(new Rectangle(0, y, _width, BRUSHSIZE), ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);
                byte[] bytes = new byte[data.Height * data.Stride];
                Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);

                // apply 4x4 brush without corners
                foreach (int[] offset in BRUSH)
                {
                    bytes[offset[1] * data.Stride + offset[0] + x] = (byte)brushIndex;
                }
                Marshal.Copy(bytes, 0, data.Scan0, bytes.Length);
                _painting.UnlockBits(data);
            }
        }

        public void Save(string filename)
        {
            _painting.Save(filename, ImageFormat.Png);
        }

        private void SetBackground(byte index)
        {
            BitmapData data = _painting.LockBits(new Rectangle(0, 0, _width, _height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
            byte[] bytes = new byte[data.Height * data.Stride];
            for (int i = 0; i < data.Height; i++)
            {
                for (int j = 0; j < data.Stride; j++)
                {
                    bytes[i*data.Stride + j] = index;
                }
            }
            Marshal.Copy(bytes, 0, data.Scan0, bytes.Length);
            _painting.UnlockBits(data);
        }

        private bool GetBrushAcceptance(Color brushColor, int x, int y)
        {
            double currentDifference = 0.0;
            double brushDifference = 0.0;
            foreach (int[] offset in BRUSH)
            {
                Color sourceColor = _source.GetPixel(x + offset[0], y + offset[1]);
                Rgb sourceRgb = new Rgb {R = sourceColor.R, G = sourceColor.G, B = sourceColor.B};
                Color currentColor = _painting.GetPixel(x + offset[0], y + offset[1]);

                currentDifference += sourceRgb.Compare(new Rgb {R = currentColor.R, G = currentColor.G, B = currentColor.B}, _comparison);
                brushDifference += sourceRgb.Compare(new Rgb {R = brushColor.R, G = brushColor.G, B = brushColor.B}, _comparison);
            }
            return brushDifference < currentDifference;
        }
    }
}

Następnie możesz wygenerować serię obrazów (jak mój film), wywołując instancję w sposób podobny do poniższego kodu (poprawianie w zależności od liczby żądanych iteracji / klatek / nazwy). Pierwszy argument to ścieżka do pliku źródłowego, drugi argument to ścieżka do palety (połączona w pytaniu), a trzeci argument to ścieżka do plików wyjściowych.

namespace Painter
{
    class Program
    {
        private static void Main(string[] args)
        {
            int i = 0;
            int counter = 1;
            Painter painter = new Painter(args[0], args[1]);
            while (true)
            {
                painter.Paint();
                if (i%500000 == 0)
                {
                    counter++;
                    painter.Save(string.Format("{0}{1:D7}.png", args[2], counter));
                }
                i++;
            }
        }
    }
}

Szukałem w Internecie kolorowych obrazów na płótnie i natknąłem się na poniższe zdjęcia, które wydają się być świetnymi (skomplikowanymi) obrazami testowymi. Wszelkie prawa autorskie należą do ich właścicieli.

wprowadź opis zdjęcia tutajwprowadź opis zdjęcia tutaj Źródło

wprowadź opis zdjęcia tutajwprowadź opis zdjęcia tutaj

Źródło

wprowadź opis zdjęcia tutajwprowadź opis zdjęcia tutaj

Źródło

gajeNL
źródło
W ten sposób wiem, że nic o niczym nie wiem. Ładne rozwiązanie
Brandon
6

JavaScript Canvas

Aktualizacja

Doskonałe sugestie w komentarzach. Jest teraz szybszy i nie spowalnia interfejsu użytkownika!

function previewFile() {
  var srcImage = document.getElementById('srcImage');
  var file = document.querySelector('input[type=file]').files[0];
  var reader = new FileReader();

  reader.onloadend = function() {
    srcImage.src = reader.result;
  }

  if (file) {
    reader.readAsDataURL(file);
  } else {
    srcImage.src = "";
  }
}

var buckets = []; // buckets / iterations
var iter_per_focus = 5000;

var pal = "00FFFF,0000FF,00FF00,FFFF00,\
C0C0C0,FF0000,FF00FF,FFFF78,\
FF0078,FF7848,7878FF,78FFFF,\
00FF78,784800,007800,\
007878,787800,780000,787878,\
000078,780078,004878,7800FF,\
0078FF,004848,787848,000000,FFFFFF".split(",");
var pLen = pal.length;
var p = 0;
var _R = 0;
var _G = 1;
var _B = 2;
var _CAN = 3;

// Create fast access palette with r,g,b values and
// brush image for color.
function initPal() {

  for (var i = 0; i < pal.length; i++) {
    var r = parseInt(pal[i].substr(0, 2), 16);
    var g = parseInt(pal[i].substr(2, 2), 16);
    var b = parseInt(pal[i].substr(4, 2), 16);
    var pcan = document.createElement('canvas');
    pcan.width = 4;
    pcan.height = 4;
    var pctx = pcan.getContext('2d');
    pctx.fillStyle = '#' + pal[i];
    pctx.beginPath();
    pctx.rect(1, 0, 2, 4);
    pctx.rect(0, 1, 4, 2);
    pctx.fill();

    pal[i] = [r,g,b,pcan];

  }
}
initPal();

var score = [];
var can = document.getElementById("canB");
var ctx = can.getContext('2d');
var mainDiv = document.getElementById("main");
var bCan = document.createElement('canvas');
bCan.width = can.width;
bCan.height = can.height;
var bCtx = bCan.getContext('2d');

var canA = document.getElementById("canA");
can.width = can.height = canA.width = canA.height = 200;
var ctxA = canA.getContext('2d');
var imageData;
var data;

function getSrcImage() {
  var img = document.getElementById('srcImage');
  can.width = canA.width = img.width;
  can.height = canA.height = img.height;
  ctxA.drawImage(img, 0, 0);
  imageData = ctxA.getImageData(0, 0, img.width, img.height);
  data = imageData.data;
  
  // adjust for brush offset
  var w = can.width - 2;
  var h = can.height - 2;
  
  var n = Math.floor((w * h) / iter_per_focus);
  buckets = [];
  for (var i = 0; i < n; i++) {
    var bucket = [];
    bucket.r = Math.floor(Math.random() * pLen);
    buckets.push(bucket);
  }
  var b = 0;
  var pt = 0;
  for (var y = 0; y < h; y++) {
    for (var x = 0; x < w; x++, pt+=4) {
      var r = Math.floor((Math.random() * n));
      buckets[r].push([x,y,pt,256 * 12,Math.floor(Math.random()*pLen)]);
      b %= n;
    }
    pt += 8; // move past brush offset.
  }
    
}

var loopTimeout = null;

function loopInit() {
  var r, y, x, pt, c, s;
  var row = can.width * 4;
  
  var b = 0;

  function loop() {
    clearTimeout(loopTimeout);
    var bucket = buckets[b++];
    var len = bucket.length;
    // Stepping color
    //c = pal[p];
    // Pulsing color;
    //c = pal[Math.floor(Math.random()*pLen)]
    // Pulsting step
    c = pal[bucket.r++];
    bucket.r%=pLen;
    b %= buckets.length;
    if (b === 0) {
      p++;
      p%=pLen;
    }
    
    for (var i = 0; i < len; i++) {

      var x = bucket[i][0]
      var y = bucket[i][1];
      var pt = bucket[i][2];
      // Random color
      //c = pal[bucket[i][4]++];
      //bucket[i][4]%=pLen;
      
     
      s = Math.abs(data[pt] - c[_R]) +
        Math.abs(data[pt + 1] - c[_G]) +
        Math.abs(data[pt + 2] - c[_B]) +
        Math.abs(data[pt + 4] - c[_R]) +
        Math.abs(data[pt + 5] - c[_G]) +
        Math.abs(data[pt + 6] - c[_B]) +
        Math.abs(data[pt + row] - c[_R]) +
        Math.abs(data[pt + row + 1] - c[_G]) +
        Math.abs(data[pt + row + 2] - c[_B]) +
        Math.abs(data[pt + row + 4] - c[_R]) +
        Math.abs(data[pt + row + 5] - c[_G]) +
        Math.abs(data[pt + row + 6] - c[_B]);
      if (bucket[i][3] > s) {
        bucket[i][3] = s;
        bCtx.drawImage(c[_CAN], x - 1, y - 1);
      }

    }
    loopTimeout = setTimeout(loop, 0);
  }

  loop();
}

// Draw function is separate from rendering. We render
// to a backing canvas first.
function draw() {
  ctx.drawImage(bCan, 0, 0);
  setTimeout(draw, 100);
}

function start() {

  getSrcImage();
  imageData = ctxA.getImageData(0, 0, can.width, can.height);
  data = imageData.data;
  bCan.width = can.width;
  bCan.height = can.height;
  bCtx.fillStyle = "black";
  bCtx.fillRect(0, 0, can.width, can.height);
  loopInit();

  draw();
}
body {
  background-color: #444444;
  color: #DDDDEE;
}
#srcImage {
  display: none;
}
#testBtn {
  display: none;
}
#canA {
  display:none;
}
<input type="file" onchange="previewFile()">
<br>
<img src="" height="200" alt="Upload Image for MS Painting">

<button onclick="genImage()" id="testBtn">Generate Image</button>

<div id="main">
  <img id="srcImage" src="" onload="start()">
  <canvas id="canA"></canvas>
  <canvas id="canB"></canvas>
</div>

wolfhammer
źródło
@trichoplax Miałem pewne problemy z krzyżowaniem witryn przy ładowaniu obrazu. Zobaczę, czy coś wymyślę.
wolfhammer,
1
@trichoplax Nie, ciemność nie była zamierzona. Był to błąd związany z przezroczystością generowanego obrazu. Kod porównawczy uważany za przezroczysty powinien być czarny.
wolfhammer,
@trichoplax Zmieniłem to, aby porównać tylko losowy kolor.
wolfhammer
1
Skopiowałem twój kod do jsfiddle i spróbowałem eksperymentu. Dzięki temu konwergencja była nieco szybsza. Możesz spróbować ... Wszystko, co zrobiłem, to otoczyłem zawartość funkcji pętli pętlą for, aby powtórzyć zawartość 1000 razy. Oznacza to, że zdarzenia myszy i klawiatury są sprawdzane tylko dla każdych 1000 iteracji zamiast dla każdej iteracji. Twoja pętla jest na tyle szybka, że ​​każde 1000 iteracji wciąż reaguje na działanie myszy i klawiatury, co oszczędza godziny oczekiwania na zbieżność :)
trichoplax
1
@tricholplax Wow, te sugestie przyspieszyły sprawę. Myślę, że s / = 4 jest potrzebne, nie otrzymuję fajnej animacji kolorów.
wolfhammer,
3

Matematyka

Nie jest to wcale tak szybkie, ale przynajmniej sprawia, że ​​obrazy są ledwo rozpoznawalne, więc jestem szczęśliwy.

img = Import["http://i.stack.imgur.com/P7X6g.jpg"]
squigglesize = 20;
squiggleterb = 35;
colors = Import["http://i.stack.imgur.com/u9JAD.png"];
colist = Table[RGBColor[PixelValue[colors, {x, 1}]], {x, 28}];
imgdim0 = ImageDimensions[img];
curimg = Image[ConstantArray[0, Reverse[imgdim0]]];

rp := RandomInteger[squigglesize, 2] - squigglesize/2;
i = 0; j = 0;
Module[{imgdim = imgdim0, randimg, points, randcol, squigmid, st, 
  randist, curdist = curdist0, i = 0, j = 0},

 While[j < 10,
  squigmid = {RandomInteger[imgdim[[1]]], RandomInteger[imgdim[[1]]]};      
  While[i < 20,
   randcol = RandomChoice[colist];
   st = RandomInteger[squiggleterb, 2] - squiggleterb/2;
   points = {rp + squigmid + st, rp + squigmid + st, rp + squigmid + st, rp + squigmid + st};

   randimg = 
    Rasterize[
     Style[Graphics[{Inset[curimg, Center, Center, imgdim],
        {randcol, BezierCurve[Table[{-1, 0}, {4}] + points]},
        {randcol, BezierCurve[Table[{-1, 1}, {4}] + points]},
        {randcol, BezierCurve[Table[{0, -1}, {4}] + points]},
        {randcol, BezierCurve[points]},
        {randcol, BezierCurve[Table[{0, 1}, {4}] + points]},
        {randcol, BezierCurve[Table[{0, 2}, {4}] + points]},
        {randcol, BezierCurve[Table[{1, -1}, {4}] + points]},
        {randcol, BezierCurve[Table[{1, 0}, {4}] + points]},
        {randcol, BezierCurve[Table[{1, 1}, {4}] + points]},
        {randcol, BezierCurve[Table[{1, 2}, {4}] + points]},
        {randcol, BezierCurve[Table[{2, 0}, {4}] + points]},
        {randcol, BezierCurve[Table[{2, 1}, {4}] + points]}
       }, ImageSize -> imgdim, PlotRange -> {{0, imgdim[[1]]}, {0, imgdim[[2]]}}], 
      Antialiasing -> False], RasterSize -> imgdim];
   randist = ImageDistance[img, randimg];
   If[randist < curdist, curimg = randimg; curdist = randist; i = 0; 
    j = 0;];
   i += 1;
   ]; j += 1; i = 0;];
 Print[curimg]]

Wynik:

Wejście Wynik

Wejście wynik

Wydajność mogłaby być prawdopodobnie nieco lepsza przy większej liczbie iteracji, a wciąż jest wiele rzeczy, które mogę spróbować przyspieszyć / poprawić konwergencję, ale na razie wydaje się to wystarczająco dobre.

Zestawienie
źródło
2

SmileBASIC

OPTION STRICT
OPTION DEFINT

DEF MSPAINT(IMAGE,WIDTH,HEIGHT,STEPS)
 'read color data
 DIM COLORS[28]
 COPY COLORS%,@COLORS
 @COLORS
 DATA &H000000,&H808080,&H800000
 DATA &H808000,&H008000,&H008080
 DATA &H000080,&H800080,&H808040
 DATA &H004040,&H0080FF,&H004080
 DATA &H8000FF,&H804000,&HFFFFFF
 DATA &HC0C0C0,&HFF0000,&HFFFF00
 DATA &H00FF00,&H00FFFF,&H0000FF
 DATA &HFF00FF,&HFFFF80,&H00FF80
 DATA &H80FFFF,&H8080FF,&HFF0080
 DATA &HFF8040

 'create output array and fill with white
 DIM OUTPUT[WIDTH,HEIGHT]
 FILL OUTPUT,&HFFFFFFFF

 VAR K
 FOR K=1 TO STEPS
  'Pick random position/color
  VAR X=RND(WIDTH -3)
  VAR Y=RND(HEIGHT-3)
  VAR COLOR=COLORS[RND(28)]

  'Extract average (really the sum) color in a 4x4 area.
  'this is less detailed than checking the difference of every pixel
  'but it's better in some ways...
  'corners are included so it will do SOME dithering
  'R1/G1/B1 = average color in original image
  'R2/G2/B2 = average color in current drawing
  'R3/G3/B3 = average color if brush is used
  VAR R1=0,G1=0,B1=0,R2=0,G2=0,B2=0,R3=0,G3=0,B3=0
  VAR R,G,B
  VAR I,J
  FOR J=0 TO 3
   FOR I=0 TO 3
    'original image average
    RGBREAD IMAGE[Y+J,X+I] OUT R,G,B
    INC R1,R
    INC G1,G
    INC B1,B
    'current drawing average
    RGBREAD OUTPUT[Y+J,X+I] OUT R,G,B
    INC R2,R
    INC G2,G
    INC B2,B
    'add the old color to the brush average if we're in a corner
    IF (J==0||J==3)&&(I==0||I==3) THEN
     INC R3,R
     INC G3,G
     INC B3,B
    ENDIF
   NEXT
  NEXT
  'brush covers 12 pixels
  RGBREAD COLOR OUT R,G,B
  INC R3,R*12
  INC G3,G*12
  INC B3,B*12

  'Compare
  BEFORE=ABS(R1-R2)+ABS(G1-G2)+ABS(B1-B2)
  AFTER =ABS(R1-R3)+ABS(G1-G3)+ABS(B1-B3)

  'Draw if better
  IF AFTER<BEFORE THEN
   FILL OUTPUT,COLOR, Y   *WIDTH+X+1,2 ' ##
   FILL OUTPUT,COLOR,(Y+1)*WIDTH+X  ,4 '####
   FILL OUTPUT,COLOR,(Y+2)*WIDTH+X  ,4 '####
   FILL OUTPUT,COLOR,(Y+3)*WIDTH+X+1,2 ' ##
  ENDIF
 NEXT

 RETURN OUTPUT
END

Obraz MSPAINT % [] , szerokość% , wysokość% , kroki% OUT wyjście% []

  • image% - tablica liczb całkowitych 2D [y, x] z danymi obrazu (32-bitowy format ARGB (alfa jest ignorowane))
  • szerokość% - szerokość obrazu
  • wzrost% - wysokość obrazu
  • kroki% - liczba iteracji
  • output% - tablica wyjściowa, taka sama jak image%.

wprowadź opis zdjęcia tutaj

12Me21
źródło
czy możesz dodać kilka przykładów?
drham
Tak, niedługo dodam trochę (przesyłanie zdjęć jest jednak bardzo pracochłonne, więc na razie będę musiał zrobić zdjęcia ekranu)
maja 21