MS Paint bị đánh giá thấp


48

MS Paint luôn là một người dọn dẹp thời gian tuyệt vời, nhưng nó đã bị hầu hết các nhà thiết kế đồ họa xa lánh. Có lẽ mọi người mất hứng thú vì bảng màu chói tai, hoặc do mức độ hoàn tác hạn chế. Bất kể, vẫn có thể tạo ra hình ảnh đẹp chỉ bằng cọ tiêu chuẩn và bảng màu mặc định.

Thử thách

Chỉ sử dụng cọ mặc định (hình vuông 4 x 4 không có góc) và bảng màu mặc định (28 màu bên dưới), cố gắng sao chép hình ảnh nguồn bằng kỹ thuật dựa trên leo đồi ngẫu nhiên .

nhập mô tả hình ảnh ở đây

Thuật toán

Mỗi câu trả lời phải tuân theo cùng một thuật toán cơ bản (stochastic hillclimb). Chi tiết có thể được điều chỉnh trong từng bước. Một chuyển động được coi là một nét của bàn chải (tức là nhấp vào sơn).

  1. Đoán chuyển động tiếp theo (s). Đoán (tọa độ và màu sắc) cho (các) chuyển động tiếp theo theo cách bạn muốn. Tuy nhiên, dự đoán không được tham chiếu hình ảnh nguồn.
  2. Áp dụng dự đoán. Áp dụng cọ vào bức tranh để thực hiện các chuyển động.
  3. Đo lường lợi ích của phong trào. Bằng cách tham chiếu hình ảnh nguồn, xác định xem (các) chuyển động có lợi cho bức tranh hay không (tức là bức tranh gần giống với hình ảnh nguồn hơn). Nếu nó có lợi, hãy giữ (các) phong trào, nếu không thì loại bỏ (các) phong trào.
  4. Lặp lại cho đến khi hội tụ. Chuyển đến Bước 1 và thử đoán tiếp theo cho đến khi thuật toán đã hội tụ đủ. Bức tranh nên rất giống với hình ảnh nguồn vào thời điểm này.

Nếu chương trình của bạn không phù hợp với bốn bước này, thì đó có thể không phải là một ngọn đồi ngẫu nhiên. Tôi đã gắn thẻ đây là một cuộc thi phổ biến vì mục tiêu là tạo ra các thuật toán vẽ thú vị dựa trên bảng màu và cọ vẽ hạn chế.

Chống chỉ định

  • Thuật toán nên được ngẫu nhiên theo một cách nào đó.
  • Dự đoán tiếp theo không nên bị ảnh hưởng bởi hình ảnh nguồn. Bạn đang đoán từng chuyển động mới, và sau đó kiểm tra xem liệu nó có giúp ích hay không. Ví dụ: bạn không được phép xác định vị trí đặt cọ dựa trên màu sắc của hình ảnh nguồn (tương tự như phối màu hình ảnh nguồn, không phải là mục tiêu).

  • Bạn được phép ảnh hưởng đến vị trí bằng cách điều chỉnh các bước của thuật toán theo cách bạn muốn. Ví dụ: bạn có thể bắt đầu dự đoán của mình ở các cạnh và di chuyển vào trong, kéo cọ để tạo các đường cho mỗi lần đoán hoặc quyết định tô màu tối trước. Bạn được phép tham chiếu các hình ảnh lặp trước đó (nhưng không phải hình ảnh nguồn) để tính toán chuyển động mong muốn tiếp theo. Chúng có thể bị hạn chế như bạn muốn (nghĩa là chỉ đoán trong góc phần tư phía trên bên trái cho lần lặp hiện tại).

  • Đo lường "sự khác biệt" giữa hình ảnh nguồn và lần lặp hiện tại có thể được đo theo cách bạn muốn, miễn là nó không tính toán các chuyển động tiềm năng khác để xác định xem chuyển động này có được coi là "tốt nhất" hay không. Không nên biết liệu phong trào hiện tại có phải là "tốt nhất" hay không, chỉ khi nó phù hợp với dung sai của các tiêu chí chấp nhận. Ví dụ, nó có thể đơn giản như abs(src.R - current.R) + abs(src.G - current.G) + abs(src.B - current.B)đối với từng pixel bị ảnh hưởng hoặc bất kỳ kỹ thuật khác biệt màu sắc nổi tiếng nào .

Bảng màu

Bạn có thể tải xuống bảng màu dưới dạng hình ảnh 28x1 hoặc tạo trực tiếp trong mã.

Chải

Bàn chải là một hình vuông 4 x 4 không có góc. Đây là phiên bản thu nhỏ của nó:

nhập mô tả hình ảnh ở đây

(Mã của bạn phải sử dụng phiên bản 4x4)

Thí dụ

Đầu vào:

Van Gogh - Đêm đầy sao

Đầu ra:

Đêm đầy sao tạo ra

Bạn có thể thấy tiến trình thuật toán cơ bản tiến triển trong một video ngắn mà tôi đã thực hiện (mỗi khung hình là 500 lần lặp): Đêm đầy sao . Các giai đoạn ban đầu rất thú vị để xem:

nhập mô tả hình ảnh ở đây


1
@ vihan1086: Hình ảnh nguồn không có độ trong suốt. Hình ảnh hiện tại có thể phụ thuộc vào lần lặp trước (như ví dụ của tôi, trong đó điểm mới được thêm vào đầu lần trước) nếu đó là ý bạn.
GrovesNL

Tôi không thấy những gì leo đồi ngẫu nhiên thêm vào ... xem xét rằng bạn có thể đoán theo cách bạn muốn và loại bỏ chúng nếu chúng không tốt, điều đó thực sự giống như chỉ cần trải qua một loạt các dự đoán trong khi kiểm tra và chọn một cái tốt nhất
Sp3000

@ Sp3000: Vấn đề là bạn không biết phong trào "tốt nhất" cho đến khi nó được thực hiện một phong trào tiềm năng, tại thời điểm đó bạn có thể chọn chấp nhận nó nếu nó phù hợp với tiêu chí chấp nhận của riêng bạn (nghĩa là "nó đủ gần "). Các tiêu chí chấp nhận không nên có kiến ​​thức về tất cả các động thái có thể (tôi có thể cần phải làm rõ điều này hơn nữa). Về cơ bản, bạn không nên xác định chuyển động "tốt nhất" trước thời hạn, thay vào đó bạn nên tăng dần hình ảnh.
GrovesNL

Với hình dạng bàn chải này, không thể vẽ các pixel góc (từ ví dụ của bạn tôi có ấn tượng này), hoặc bàn chải có thể được đặt một phần bên ngoài ranh giới hình ảnh không?
Oliphistic

Mối quan tâm của Sp3000 có lẽ bao gồm thuật toán xác định sau: lần lượt cho từng pixel, thử mọi màu. Không ngẫu nhiên và rất giống như hoà sắc, nhưng nó dường như phù hợp với các quy tắc.
Oliphistic

Câu trả lời:


35

JavaScript

Giải pháp này sử dụng phần tử canvas HTML5 để trích xuất dữ liệu hình ảnh, nhưng không cần sử dụng HTML, điều đó có nghĩa là nó có thể được chạy trong bảng điều khiển của bạn. Nó truy cập hình ảnh bảng màu như một mảng; Tôi lưu trữ tất cả các màu từ hình ảnh bảng màu trong một mảng). Nó xuất ra bàn điều khiển (sau khi kết thúc) và cũng lưu kết quả vào một biến.

Phiên bản cập nhật nhất của mã là trong fiddle . Fiddle cũng sử dụng một thuật toán tốt hơn để giảm nhiễu trong ảnh. Sự cải tiến trong thuật toán chủ yếu là sửa một hàm (tối đa đến tối thiểu) khiến cho màu ngược được chọn.

Mã trong hình dạng của Biểu tượng MS Paint! (mã được định dạng trong fiddle hoặc Stack Snippet)

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

Sử dụng:

Paint('DATA URI');

Fiddle .

Fiddle sử dụng crossorigin.me vì vậy bạn không cần phải lo lắng về việc chia sẻ tài nguyên nguồn gốc chéo.

Tôi cũng đã cập nhật fiddle để bạn có thể điều chỉnh một số giá trị để tạo ra bức tranh đẹp nhất. Màu sắc của một số hình ảnh có thể bị tắt, để tránh điều này, hãy điều chỉnh accept_rate để điều chỉnh thuật toán. Số thấp hơn có nghĩa là độ dốc tốt hơn, số cao hơn sẽ cho màu sắc sắc nét hơn.


Đây là fiddle dưới dạng Stack-Snippet (KHÔNG được cập nhật, trong trường hợp fiddle không hoạt động):


Để kỷ niệm sự bay bổng của Sao Diêm Vương, tôi đã nhập một hình ảnh của Sao Diêm Vương:

Nguyên Vẽ

Nguyên Vẽ

Đối với những điều sau đây, tôi đã đặt nó để làm cho chúng giống với bản gốc nhất có thể:

Tôi đã chạy nó với hình nền mặc định của OS X Yosemite. Sau khi để nó chạy một chút, kết quả hoàn toàn tuyệt vời. Tệp gốc rất lớn (26 MB) vì vậy tôi đã thay đổi kích thước và nén nó:

nhập mô tả hình ảnh ở đây

Đêm đầy sao (Tôi đã sử dụng hình ảnh có độ phân giải cao hơn để có kết quả tốt hơn)

Một hình ảnh tôi tìm thấy trên google: nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây


12

JavaScript + HTML

Ngẫu nhiên:

Điểm ngẫu nhiên

Sắp xếp ngẫu nhiên:

Chia khung vẽ thành các hình vuông 4 x 4 và chọn một điểm ngẫu nhiên bên trong một trong các hình vuông. Offsets sẽ di chuyển lưới, vì vậy bạn có thể điền vào những khoảng trống nhỏ.

Vòng:

Tạo lưới và Loops thông qua tất cả các điểm. Offsets di chuyển lưới. Khoảng cách xác định kích thước của mỗi ô. (Chúng sẽ bắt đầu chồng chéo)

Màu sắc khác nhau:

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

nhập mô tả hình ảnh ở đây
RGB: nhập mô tả hình ảnh ở đây
HSL: nhập mô tả hình ảnh ở đây
HSV: nhập mô tả hình ảnh ở đây


Rất tuyệt. "Đoạn mã chạy" bị hỏng đối với tôi khi nó cố gắng đặt document.cookie(sau 1000 lần lặp) vì tài liệu được đóng hộp cát. Là cookie cần thiết?
GrovesNL

Không, có một lần tôi chạy chương trình trong vài giờ, nhưng sau đó trình duyệt của tôi bị sập. Vì vậy, tôi nướng cookie như một điều dự phòng. Nhưng tôi sẽ xóa nó vì có vẻ như trao đổi ngăn xếp không thích cookie.
Cấp cho Davis

1
Nhìn vào mã của bạn, tôi nghĩ rằng nó có thể được hưởng lợi từ cùng một cách tăng tốc mà tôi đã đề xuất cho câu trả lời của wolfhammer , ngoại trừ áp dụng cho doThingthay vì loop. Bạn có thể thấy tốc độ tăng đáng giá hơn dòng ...
trichoplax

1
@trichoplax Cảm ơn rất nhiều, không chỉ sửa lỗi của bạn làm tăng tốc độ chương trình của tôi, trong khi sửa tôi đã tìm thấy và sửa một lỗi toán học mà tôi đã gây ra, và chương trình của tôi không còn tạo ra những chấm đen nhỏ bé đó nữa.
Cấp cho Davis

Thật tuyệt! Hình ảnh đầu ra mới trông tốt hơn nhiều.
trichoplax

8

C # (thực hiện tham khảo)

Đây là mã được sử dụng để tạo ra các hình ảnh trong câu hỏi. Tôi nghĩ rằng nó sẽ hữu ích để cung cấp cho một số người tham khảo để tổ chức thuật toán của họ. Một tọa độ và màu sắc hoàn toàn ngẫu nhiên được chọn mỗi chuyển động. Nó thực hiện tốt đáng ngạc nhiên khi xem xét các giới hạn áp đặt bởi các tiêu chí chấp nhận / kích thước bàn chải.

Tôi sử dụng thuật toán CIEDE2000 để đo sự khác biệt về màu sắc, từ thư viện nguồn mở ColorMine . Điều này sẽ cho màu sắc gần hơn (theo quan điểm của con người) nhưng dường như không phải là một sự khác biệt đáng chú ý khi được sử dụng với bảng màu này.

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

Sau đó, bạn có thể tạo một loạt hình ảnh (như video của tôi) bằng cách gọi một thể hiện theo cách tương tự như mã bên dưới (điều chỉnh dựa trên số lần lặp / khung / tên mong muốn). Đối số đầu tiên là đường dẫn tệp đến hình ảnh nguồn, đối số thứ hai là đường dẫn tệp đến bảng màu (được liên kết trong câu hỏi) và đối số thứ ba là đường dẫn tệp cho hình ảnh đầu ra.

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

Tôi đã tìm kiếm một số bức tranh vải đầy màu sắc trực tuyến và bắt gặp những hình ảnh dưới đây, dường như là những hình ảnh thử nghiệm (phức tạp) tuyệt vời. Mọi bản quyền thuộc về chủ sử hữu của chúng.

nhập mô tả hình ảnh ở đâynhập mô tả hình ảnh ở đây Nguồn

nhập mô tả hình ảnh ở đâynhập mô tả hình ảnh ở đây

Nguồn

nhập mô tả hình ảnh ở đâynhập mô tả hình ảnh ở đây

Nguồn


Đây là cách tôi biết tôi không biết gì về bất cứ điều gì. Giải pháp tốt đẹp
Brandon

6

Canvas Canvas

Cập nhật

Đề xuất tuyệt vời trong các ý kiến. Bây giờ nhanh hơn và không làm chậm 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 Tôi gặp một số vấn đề về trang web khi tải hình ảnh. Tôi sẽ xem nếu tôi có thể tìm ra một cái gì đó.
sói

1
@trichoplax Không có bóng tối không cố ý. Đó là một lỗi với độ trong suốt trong hình ảnh được tạo. Các mã so sánh nghĩ trong suốt nên có màu đen.
sói

@trichoplax Tôi đã thay đổi nó để chỉ so sánh một màu ngẫu nhiên.
sói

1
Tôi đã sao chép mã của bạn vào jsfiddle và thử một thử nghiệm. Nó làm cho sự hội tụ có phần nhanh hơn. Bạn có thể muốn thử nó ... Tất cả những gì tôi đã làm là bao quanh nội dung của hàm vòng lặp với một vòng lặp for để lặp lại nội dung 1000 lần. Điều này có nghĩa là các sự kiện chuột và bàn phím chỉ được kiểm tra cho mỗi 1000 lần lặp thay vì mỗi lần lặp. Vòng lặp của bạn đủ nhanh để cứ sau 1000 lần lặp lại vẫn khiến chuột và bàn phím phản ứng nhanh và nó giúp tiết kiệm thời gian chờ đợi để hội tụ :)
trichoplax

1
@tricholplax Wow những đề xuất đó làm mọi thứ nhanh hơn rất nhiều. Tôi nghĩ rằng s / = 4 là cần thiết, tôi không có được hình ảnh động màu sắc thú vị với nó.
sói

3

Toán học

Mặc dù nó không thực sự nhanh đến thế, nhưng ít nhất nó cũng tạo ra những hình ảnh mơ hồ, vì vậy tôi rất vui.

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

Đầu ra:

đầu vào Đầu ra

đầu vào đầu ra

Đầu ra có thể tốt hơn một chút với nhiều lần lặp hơn, và vẫn còn rất nhiều điều mà tôi có thể cố gắng tăng tốc / cải thiện sự hội tụ, nhưng bây giờ điều này có vẻ đủ tốt.


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

Hình ảnh MSPAINT % [] , width% , height% , các bước% OUT đầu ra% []

  • mảng số nguyên hình ảnh% - 2D [y, x] với dữ liệu hình ảnh (định dạng ARGB 32 bit (bỏ qua alpha))
  • chiều rộng% - chiều rộng hình ảnh
  • chiều cao% - chiều cao hình ảnh
  • bước% - số lần lặp
  • đầu ra% - mảng đầu ra, giống như hình ảnh%.

nhập mô tả hình ảnh ở đây


bạn có thể thêm một số ví dụ?
drham

Vâng, tôi sẽ sớm thêm một số (Tuy nhiên, rất nhiều công việc để chuyển hình ảnh, vì vậy tôi sẽ chỉ cần chụp ảnh màn hình ngay bây giờ)
12Me21
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.