MS 페인트가 과소 평가되었습니다


48

MS Paint는 항상 좋은 시간 낭비 였지만 대부분의 그래픽 디자이너들은이를 피했습니다. 어색한 색상 팔레트 또는 실행 취소 수준이 제한되어 사람들이 관심을 잃었을 수 있습니다. 어쨌든 표준 브러시와 기본 색상 팔레트만으로도 아름다운 이미지를 생성 할 수 있습니다.

도전

기본 브러시 (코너가없는 4x4 정사각형)와 기본 색상 팔레트 (아래 28 색) 만 사용하여 확률 적 언덕 등반을 기반으로하는 기술을 사용하여 소스 이미지를 복제하십시오 .

여기에 이미지 설명을 입력하십시오

연산

모든 답변은 동일한 기본 알고리즘을 따라야합니다 (stochastic hillclimb). 각 단계 내에서 세부 사항을 조정할 수 있습니다. 운동은 브러시 (예. 페인트 클릭)의 스트로크로 간주됩니다.

  1. 다음 운동을 맞춰보세요. 그러나 원하는 다음 움직임에 대해 (좌표와 색상으로) 추측하십시오. 그러나 추측은 소스 이미지를 참조해서는 안됩니다.
  2. 추측을 적용하십시오. 브러시를 그림에 적용하여 움직임을 만듭니다.
  3. 운동의 이점을 측정하십시오. 소스 이미지를 참조하여 움직임 (들)이 페인팅에 도움이되었는지 여부를 결정하십시오 (즉, 페인팅이 소스 이미지와 더 유사 함). 유익한 경우에는 운동을 유지하고 그렇지 않으면 운동을 폐기하십시오.
  4. 수렴 될 때까지 반복하십시오. 1 단계로 이동하여 알고리즘이 충분히 수렴 될 때까지 다음 추측을 시도하십시오. 이 시점에서 그림은 소스 이미지와 매우 유사해야합니다.

만약 당신의 프로그램이이 네 단계와 맞지 않는다면, 확률론적인 언덕이 아닐 것입니다. 제한된 색상 팔레트와 브러시를 기반으로 재미있는 페인팅 알고리즘을 만드는 것이 목표이기 때문에 인기 콘테스트로 태그를 지정했습니다.

금기

  • 알고리즘은 어떤 방식 으로 확률 론적 이어야합니다 .
  • 다음 추측은 소스 이미지의 영향을받지 않아야합니다. 당신은 각각의 새로운 움직임을 추측하고 그것이 도움이되는지 아닌지를 점검하고 있습니다. 예를 들어 소스 이미지 색상 (목표가 아닌 소스 이미지 디더링과 유사)을 기반으로 브러시를 배치 할 위치를 결정할 수 없습니다.

  • 원하는 알고리즘 단계를 조정하여 배치에 영향을 줄 수 있습니다. 예를 들어, 가장자리에서 추측을 시작하고 안쪽으로 이동하거나 브러시를 드래그하여 각 추측에 대한 선을 만들거나 어두운 색상을 먼저 페인트하기로 결정할 수 있습니다. 다음 원하는 움직임을 계산하기 위해 이전 반복 이미지 (소스 이미지는 아님)를 참조 할 수 있습니다. 이들은 원하는대로 제한적일 수 있습니다 (즉, 현재 반복에 대해 왼쪽 상단 사분면 내에서만 추측).

  • 소스 이미지와 현재 반복 간의 "차이"측정 값은 이 움직임이 "최고"로 간주되는지 여부를 결정하기 위해 다른 잠재적 움직임을 계산하지 않는 한 원하는 대로 측정 할 수 있습니다 . 현재의 움직임이 "최고"인지 여부는 알 수 없으며 수락 기준의 허용 범위 내에 맞는지 여부 만 알 수 있습니다. 예를 들어, abs(src.R - current.R) + abs(src.G - current.G) + abs(src.B - current.B)영향을받는 각 픽셀 또는 잘 알려진 색상 차이 기술 만큼 간단 할 수 있습니다 .

팔레트

당신은 할 수 28x1 이미지로 팔레트를 다운로드 하거나 코드에서 직접 만들 수 있습니다.

브러시

브러시는 모서리가없는 4x4 정사각형입니다. 이것은 확장 버전입니다.

여기에 이미지 설명을 입력하십시오

(코드는 4x4 버전을 사용해야합니다)

입력:

반 고흐-별이 빛나는 밤

산출:

별이 빛나는 밤 생성

내가 만든 짧은 비디오 (각 프레임은 500 회 반복) : Starry Night 에서 기본 알고리즘이 어떻게 진행되는지 확인할 수 있습니다 . 초기 단계는 다음과 같습니다.

여기에 이미지 설명을 입력하십시오


1
@ vihan1086 : 소스 이미지에 투명도가 없습니다. 현재 이미지는 이전 반복에 따라 달라질 수 있습니다 (예 : 이전 포인트 위에 새 포인트가 추가되는 예).
grovesNL

확률 적 인 언덕 등반이 무엇을 추가하는지 전혀 알지 못합니다 ... 당신이 원하는 것을 추측 할 수 있다고 생각하지만 그들이 좋지 않은 경우 버릴 수 있다고 생각하면, 실제로 확인 하면서 많은 추측을하는 것과 동일합니다. 가장 좋은 것을 고르세요.
Sp3000

@ Sp3000 : 요점은 잠재적 인 움직임이 이루어질 때까지 "최상의"움직임을 모른다는 것입니다.이 시점에서 자신의 수락 기준에 맞는 경우 받아 들일 수 있습니다 (즉, "충분히 근접" "). 수락 기준에 모든 가능한 움직임에 대한 지식이 없어야합니다 (자세한 사항을 명확히해야 할 수도 있습니다). 기본적으로 "최상의"움직임을 미리 결정할 수 없어 이미지를 점진적으로 개선해야합니다.
grovesNL

이 브러시 모양을 사용하면 모퉁이 픽셀을 페인트하는 것이 불가능합니까 (예 :이 인상을 얻음) 브러시를 이미지 경계 외부에 부분적으로 배치 할 수 있습니까?
Oliphaunt

Sp3000의 관심사에는 다음과 같은 결정 론적 알고리즘이 포함될 수 있습니다. 각 픽셀마다 차례로 모든 색상을 시도하십시오. 확률론은없고 디더링과 매우 유사하지만 규칙에 맞는 것 같습니다.
Oliphaunt

답변:


35

자바 스크립트

이 솔루션은 HTML5 캔버스 요소를 사용하여 이미지 데이터를 추출하지만 HTML을 사용하지 않아도 콘솔에서 실행할 수 있습니다. 색상 표 이미지를 배열로 액세스합니다. 팔레트 이미지의 모든 색상을 배열에 저장했습니다). 완료된 후 콘솔에 출력하고 결과를 변수에 저장합니다.

코드의 가장 최신 버전은 바이올린에 있습니다. 바이올린은 또한 더 나은 알고리즘을 사용하여 사진의 노이즈를 줄입니다. 알고리즘의 개선은 대부분 역색을 선택하게하는 기능 (최대에서 최소)을 수정하는 것입니다.

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를 사용 하므로 cross-origin-resource-sharing에 대해 걱정할 필요가 없습니다.

또한 바이올린을 업데이트하여 가장 멋진 그림을 만들기 위해 일부 값을 조정할 수 있습니다. 이를 방지하기 위해 일부 그림의 색상이 꺼져있을 수 있습니다. 알고리즘을 조정하려면 accept_rate를 조정하십시오. 숫자가 작을수록 그라디언트가 좋아지고 숫자가 클수록 색상이 더 선명 해집니다.


다음은 스 택스 니펫으로 바이올린을 보여줍니다 (바이올린이 작동하지 않는 경우 업데이트되지 않음).


뉴 호라이즌의 명왕성 비행을 기념하기 위해 명왕성의 이미지를 입력했습니다.

기발한 그어진

기발한 그어진

다음을 위해 가능한 한 원본과 비슷하게 설정했습니다.

나는 OS X Yosemite의 기본 배경 화면으로 이것을 실행했습니다. 잠시 동안 그대로두면 결과가 정말 놀랍습니다. 원본 파일은 크기가 크므로 (26MB) 크기를 조정하고 압축했습니다.

여기에 이미지 설명을 입력하십시오

별이 빛나는 밤 ( 더 나은 결과를 위해 고해상도 이미지 를 사용했습니다 )

Google에서 찾은 사진 : 여기에 이미지 설명을 입력하십시오 여기에 이미지 설명을 입력하십시오


12

자바 스크립트 + HTML

무작위 :

랜덤 포인트

무작위 정렬 :

캔버스를 4x4 사각형으로 세분화하고 사각형 중 하나 안에서 무작위로 점을 선택합니다. 오프셋은 그리드를 이동하므로 약간의 간격을 채울 수 있습니다.

고리:

그리드를 작성하고 모든 점을 반복합니다. 오프셋은 그리드를 이동시킵니다. 간격은 각 셀의 크기를 결정합니다. (그들은 겹치기 시작합니다)

색차 :

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

여기에 이미지 설명을 입력하십시오
RGB : 여기에 이미지 설명을 입력하십시오
HSL : 여기에 이미지 설명을 입력하십시오
HSV : 여기에 이미지 설명을 입력하십시오


매우 시원합니다. document.cookie문서가 샌드 박스 처리되어 있기 때문에 (1000 회 반복 후) 설정하려고하면 "Run code snippet"이 나에게 도움이됩니다 . 쿠키가 필요합니까?
grovesNL

아니요, 옛날 옛적에 몇 시간 동안 프로그램을 실행했지만 브라우저가 다운되었습니다. 그래서 쿠키를 백업으로 구 웠습니다. 그러나 스택 교환이 쿠키를 싫어하는 것처럼 보이기 때문에 제거하겠습니다.
그랜트 데이비스

1
귀하의 코드를 살펴보면 wolfhammer의 답변 에서 제안한 것과 동일한 속도 향상 doThing대신에 적용되는 것을 제외하고 는 이점이 있다고 생각합니다 loop. 당신은 여분의 라인의 가치가 증가 속도를 찾을 수 있습니다 ...
trichoplax

1
@trichoplax 감사합니다. 많은 수정 사항으로 인해 프로그램의 속도가 빨라 졌을뿐 아니라 수정하는 동안 수학 오류를 발견하고 수정했으며 더 이상 작은 검은 점이 생성되지 않습니다.
Grant Davis

대단해! 새로운 출력 이미지가 훨씬 좋아 보입니다.
trichoplax

8

C # (참조 구현)

문제의 이미지를 생성하는 데 사용되는 코드입니다. 일부 사람들에게 알고리즘 구성에 대한 참조를 제공하는 것이 유용 할 것이라고 생각했습니다. 각 움직임마다 완전히 임의의 좌표와 색상이 선택됩니다. 브러시 크기 / 수락 기준에 의해 부과 된 제한을 고려하면 놀랍도록 잘 수행됩니다.

오픈 소스 라이브러리 ColorMine 에서 색상 차이를 측정하기 위해 CIEDE2000 알고리즘을 사용합니다 . 이것은 인간의 관점에서 더 가까운 색상 일치를 제공해야하지만이 팔레트와 함께 사용할 때 눈에 띄는 차이는없는 것 같습니다.

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

온라인에서 화려한 캔버스 그림을 검색하고 아래 이미지를 보았습니다.이 이미지는 훌륭한 (복잡한) 테스트 이미지 인 것 같습니다. 모든 저작권은 해당 소유자의 재산입니다.

여기에 이미지 설명을 입력하십시오여기에 이미지 설명을 입력하십시오 출처

여기에 이미지 설명을 입력하십시오여기에 이미지 설명을 입력하십시오

출처

여기에 이미지 설명을 입력하십시오여기에 이미지 설명을 입력하십시오

출처


이것이 내가 아무것도 모르는 방법입니다. 좋은 해결책
Brandon

6

자바 스크립트 캔버스

최신 정보

의견에서 훌륭한 제안. 이제 더 빨라지고 UI 속도가 느려지지 않습니다!

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 어둠은 의도적이지 않았습니다. 생성 된 이미지의 투명도가 버그였습니다. 투명하다고 생각한 비교 코드는 검은 색이어야합니다.
wolfhammer

@trichoplax 무작위 색상 만 비교하도록 변경했습니다.
wolfhammer

1
코드를 jsfiddle에 복사하고 실험을 시도했습니다. 수렴이 다소 빨라졌습니다. 시도해보고 싶을 것입니다 ... 루프 함수의 내용을 for 루프로 감싸서 내용을 1000 번 반복했습니다. 즉, 마우스 및 키보드 이벤트는 모든 반복이 아닌 1000 회 반복마다 점검됩니다. 루프는 1000 번 반복 할 때마다 마우스와 키보드가 여전히 반응 할 수있을
정도로 빠르며

1
@tricholplax 와우 제안이 훨씬 빨라졌습니다. 나는 s / = 4가 필요하다고 생각합니다. 멋진 컬러 애니메이션을 얻지 못했습니다.
wolfhammer

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

스마일 BASIC

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 image % [] , 너비 % , 높이 % , 단계 % OUT 출력 % []

  • image %-이미지 데이터가 포함 된 2D [y, x] 정수 배열 (32 비트 ARGB 형식 (알파는 무시 됨))
  • width %-이미지 너비
  • height %-이미지 높이
  • steps %-반복 횟수
  • output %-이미지 배열과 동일한 출력 배열입니다.

여기에 이미지 설명을 입력하십시오


몇 가지 예를 추가 할 수 있습니까?
drham

네, 곧 추가하겠습니다. (이미지를 전송하는 것은 많은 작업이므로 지금은 화면을 촬영해야합니다)
12Me21
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.