Фарба MS недооцінена


48

MS Paint завжди була чудовою витратою часу, але її уникали більшість графічних дизайнерів. Можливо, люди втратили інтерес через яскраву колірну палітру або через обмежений рівень скасування. Незважаючи на це, все ще можливо генерувати красиві зображення за допомогою лише стандартної пензлика та кольорової палітри за замовчуванням.

Виклик

Використовуючи лише пензлик за замовчуванням (квадрат 4х4 без кутів) та кольорову палітру за замовчуванням (28 кольорів нижче), спробуйте скопіювати вихідне зображення за допомогою техніки, заснованої на стохастичному сходження на гірку .

введіть тут опис зображення

Алгоритм

Кожна відповідь повинна дотримуватися того самого основного алгоритму (стохастичний підйом на гірку). Деталі можна налаштувати на кожному кроці. Рух вважається розчерком пензля (тобто .. клацати фарбою).

  1. Вгадайте наступний рух (и). Зробіть здогадку (про координати та кольори) для наступних рухів, як би ви хотіли. Однак здогад не повинен посилатися на вихідне зображення.
  2. Застосуйте здогадку. Нанесіть пензлик на картину, щоб зробити рух (і).
  3. Виміряйте вигоду від руху (-ів). Посилаючись на вихідне зображення, визначте, чи принесли користь руху (і) зображення (тобто картина більше нагадує вихідне зображення). Якщо це вигідно, тримайте рух (и), інакше відмовтеся від руху.
  4. Повторюйте до зближення. Перейдіть до кроку 1 і спробуйте наступну здогадку, поки алгоритм не зблизиться достатньо. Картина повинна сильно нагадувати вихідне зображення в цей момент.

Якщо ваша програма не відповідає цим чотирма крокам, це, мабуть, не стохастичний підйом на гірку. Я позначив це як конкурс популярності, оскільки мета полягає у створенні цікавих алгоритмів малювання на основі обмеженої кольорової палітри та пензлика.

Протипоказання

  • Алгоритм повинен бути певним чином стохастичним .
  • На наступну здогадку не має впливати вихідне зображення. Ви вгадуєте кожен новий рух, а потім перевіряєте, чи допомогло це чи ні. Наприклад, вам не дозволяється визначати, де розмістити пензлик на основі кольорів вихідного зображення (це схоже на відтіняння вихідного зображення, що не є метою).

  • Ви можете впливати на місце розташування, перебираючи кроки алгоритму, як би ви хотіли. Наприклад, ви можете почати здогадки по краях і просуватися всередину, перетягніть пензлик, щоб створити лінії для кожної здогадки, або вирішите спочатку пофарбувати темні кольори. Вам дозволяється посилатися на попередні зображення ітерації (але не на вихідне зображення), щоб обчислити наступний потрібний рух. Вони можуть бути обмежувальними за вашим бажанням (наприклад, лише вгадуйте у верхньому лівому квадранті поточну ітерацію).

  • Міру "різниці" між вихідним зображенням та поточною ітерацією можна виміряти, як би ви хотіли, доки він не обчислює інші потенційні рухи, щоб визначити, чи вважається цей рух "найкращим". Він не повинен знати, чи є поточний рух "найкращим", лише чи відповідає він допущенню критеріїв прийняття. Наприклад, він може бути таким же простим, як abs(src.R - current.R) + abs(src.G - current.G) + abs(src.B - current.B)для кожного пікселя, що впливає, або будь-який з відомих методів різниці кольорів .

Палітра

Ви можете завантажити палітру як зображення розміром 28x1 або створити її безпосередньо в коді.

Кисть

Кисть - квадрат 4х4 без кутів. Це масштабована версія його:

введіть тут опис зображення

(Ваш код повинен використовувати версію 4x4)

Приклад

Вхід:

Ван Гог - Зоряна ніч

Вихід:

Згенерована Зоряна ніч

Ви можете бачити, як просувається базовий алгоритм у короткому відео, яке я зробив (кожен кадр - 500 ітерацій): Зоряна ніч . Початкові етапи цікаво спостерігати:

введіть тут опис зображення


1
@ vihan1086: вихідне зображення не має прозорості. Поточне зображення може залежати від попередньої ітерації (як, наприклад, мій приклад, коли нові точки додаються поверх попереднього), якщо це саме ви маєте на увазі.
grovesNL

Я не бачу, що взагалі додає стохастичне сходження на пагорби ... враховуючи, що ви можете здогадуватися, але хочете, і відкинути їх, якщо вони непогані, це фактично те саме, що просто перебирати купу здогадок під час перевірки та вибір найкращого.
Sp3000

@ Sp3000: Справа в тому, що ви не знаєте "найкращого" руху, поки не буде зроблено потенційний рух, і тоді ви зможете прийняти його, якщо він відповідає вашим власним критеріям прийняття (тобто "достатньо близько" "). Критерії прийняття не повинні знати всіх можливих кроків (можливо, мені потрібно буде це уточнити далі). По суті, ви не повинні бути в змозі визначити "найкращий" рух достроково, натомість слід поступово покращувати зображення.
grovesNL

За допомогою цієї форми пензля неможливо намалювати кутові пікселі (з вашого прикладу я створюю таке враження), або кисть може бути розміщена частково поза межами зображення?
Оліфант

Занепокоєння Sp3000, можливо, включає наступний детермінований алгоритм: для кожного пікселя, по черзі, спробуйте кожен колір. Ніякої стохастичності і дуже подобається дихання, але, здається, це відповідає правилам.
Оліфант

Відповіді:


35

JavaScript

Це рішення використовує елемент полотна HTML5 для отримання даних про зображення, але без необхідності використання HTML, це означає, що його можна запускати у вашій консолі. Він отримує доступ до зображення кольорової палітри як масив; Я зберігав усі кольори з зображення палітри в масиві). Він виводить на консоль (після її закінчення), а також зберігає результат у змінній.

Найновіша версія коду знаходиться у скрипці . Загадка також використовує кращий алгоритм для зменшення шуму в картинках. Удосконалення алгоритму здебільшого фіксує функцію (max до min), яка спричинила вибір зворотного кольору.

Код у формі піктограми MS Paint! (відформатований код у скрипці або фрагменті стека)

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

Використання:

Paint('DATA URI');

Скрипка .

У скрипці використовується crossorigin.me, тому вам не потрібно турбуватися про спільний розподіл ресурсів.

Я також оновив скрипку, щоб ви могли скорегувати деякі значення для отримання найкращого вигляду картини. Деякі кольори зображень можуть бути вимкнені, щоб уникнути цього, налаштуйте accept_rate, щоб налаштувати алгоритм. Менше число означає кращі градієнти, а більша кількість призведе до отримання більш чітких кольорів.


Ось загадка як фрагмент стека (не оновлюється, якщо скрипт не працює):


Щоб відзначити проліт Плутона "New Horizon", я ввів зображення Плутона:

Оригінал Намальовано

Оригінал Намальовано

Для наступного я встановив це, щоб вони були максимально схожими на оригінал:

Я запускав це за допомогою стандартних шпалер OS X Yosemite. Залишивши його трохи пробігти, результати абсолютно приголомшливі. Оригінальний файл був величезним (26 Мб), тому я змінив розмір і стиснув його:

введіть тут опис зображення

Зоряна ніч (я використовував більш високу роздільну здатність зображення для кращого результату)

Зображення, яке я знайшов у google: введіть тут опис зображення введіть тут опис зображення


12

JavaScript + HTML

Випадкові:

Випадкова точка

Випадкове вирівнювання:

Підрозділяє полотно на квадрати 4х4 і вибирає крапку випадково всередині одного з квадратів. Зсуви перемістять сітку, тож ви зможете заповнити невеликі прогалини.

Петля:

Створюється сітка і прокручується через усі точки. Зсув переміщує сітку. Проміжки визначають розмір кожної комірки. (Вони почнуть перетинатися)

Різниця в кольорі:

  • RGB
  • HSL
  • ВПГ

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>

введіть тут опис зображення
RGB: введіть тут опис зображення
HSL: введіть тут опис зображення
HSV: введіть тут опис зображення


Дуже круто. Фрагмент "Виконати код коду" для мене порушується, коли він намагається встановити document.cookie(після 1000 ітерацій), оскільки документ є пісочницею. Чи потрібне печиво?
grovesNL

Ні, одного разу я кілька годин запускав програму, але потім мій браузер розбився. Тож я спекла печиво як резервну річ. Але я видалю його, бо, здається, обмін стеками не любить файли cookie.
Грант Девіс

1
Дивлячись на ваш код, я думаю, що це може отримати користь від тієї самої прискореності, яку я запропонував у відповіді вовка на молоті , за винятком застосованих doThingзамість цього loop. Ви можете виявити збільшення швидкості, що вартує додаткової лінії ...
trichoplax

1
@trichoplax Дякую багато, Ви не тільки виправили збільшення швидкості моєї програми, виправляючи, я виявив і виправив математичну помилку, яку я зробив, і моя програма більше не генерує ці крихітні чорні точки.
Грант Девіс

Це чудово! Новий вихідний образ виглядає набагато краще.
трихоплакс

8

C # (реалізація посилань)

Це код, який використовується для створення зображень у запитанні. Я подумав, що було б корисно дати деяким людям довідку щодо організації їх алгоритму. Кожен рух вибирається абсолютно випадковою координатою та кольором. Він спрацьовує напрочуд добре, враховуючи обмеження, встановлені розміром / критеріями щітки.

Я використовую алгоритм CIEDE2000 для вимірювання різниці кольорів, з бібліотеки ColorMine з відкритим кодом . Це повинно наближати кольорові відповідники (з людської точки зору), але це не здається помітною різницею при використанні з цією палітрою.

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

Тоді ви можете генерувати серію зображень (як-от моє відео), зателефонувавши до екземпляра таким чином, як у наведеному нижче коді. Перший аргумент - шлях до файлу до вихідного зображення, другий аргумент - шлях до файлу до палітри (пов'язаний у запитанні), а третій аргумент - шлях до файлу для вихідних зображень.

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

Я шукав барвисті картини полотна в Інтернеті і натрапив на зображення нижче, які здаються чудовими (складними) тестовими зображеннями. Усі авторські права належать їх власникам.

введіть тут опис зображеннявведіть тут опис зображення Джерело

введіть тут опис зображеннявведіть тут опис зображення

Джерело

введіть тут опис зображеннявведіть тут опис зображення

Джерело


Ось так я знаю, що нічого нічого не знаю. Приємне рішення
Брендон

6

Полотно JavaScript

Оновлення

Відмінні пропозиції в коментарях. Зараз це швидше і не сповільнює інтерфейс користувача!

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>


@trichoplax У мене виникли проблеми між завантаженням веб-сайтів із завантаженням зображення. Я побачу, чи зможу щось придумати.
вовчий молот

1
@trichoplax Ні темного не було навмисно. Це була помилка з прозорістю у створеному зображенні. Код порівняння, який вважається прозорим, повинен бути чорним.
вовчий молот

@trichoplax Я змінив його лише для порівняння випадкового кольору.
вовчий молот

1
Я скопіював ваш код у jsfiddle і спробував експеримент. Це зробило конвергенцію дещо швидше. Ви можете спробувати це ... Все, що я зробив, це оточити вміст функції циклу за допомогою циклу for, щоб повторити вміст 1000 разів. Це означає, що події миші та клавіатури перевіряються лише на кожні 1000 ітерацій замість кожної ітерації. Ваш цикл досить швидкий, що кожні 1000 ітерацій все ще залишають мишу та клавіатуру чутливими, і це економить години очікування на конвергенцію :)
trichoplax

1
@tricholplax Ого, ці пропозиції зробили все набагато швидше. Я думаю, що s / = 4 потрібен, я не отримую прохолодну кольорову анімацію.
вовчий молот

3

Математика

Це насправді не все так швидко, але, принаймні, це робить нечітко впізнавані образи, тому я щасливий.

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

Вихід:

вхід Вихід

вхід вихід

Результат, можливо, може бути трохи кращим з більшою кількістю ітерацій, і все ще є багато, що я можу спробувати прискорити його / покращити конвергенцію, але наразі це здається досить гарним.


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

MSPAINT зображення% [] , ширина% , висота% , кроки% OUT вихід% []

  • image% - 2D [y, x] цілочисельний масив із зображеннями даних (32-бітний формат ARGB (альфа ігнорується))
  • width% - ширина зображення
  • висота% - висота зображення
  • кроки% - кількість повторень
  • вихід% - вихідний масив, такий самий, як зображення%.

введіть тут опис зображення


ви можете додати кілька прикладів?
драм

Так, незабаром я додам (Хоча дуже багато роботи над передачею зображень, тому мені доведеться наразі просто сфотографувати екран)
12Me21
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.