使用Javascript将油画颜料/素描效果应用于照片

问题描述 投票:4回答:2

我想使用javascript从照片开始模拟人类绘画效果。

我一直在通过几个进行图像处理的js库(主要是在画布上)进行搜索。但是似乎没有人尝试过我要找的东西。

“

我认为用javascript不可能达到这样的效果。所以我想知道为什么我找不到已经完成的事情。

在本机方面,有多种Photoshop替代品可以实现这种效果,如App Store中的多个应用程序所示:

这里是可能结果的其他示例(来自Artist's Touch App:)

<< img src =“ https://image.soinside.com/eyJ1cmwiOiAiaHR0cHM6Ly9pLnN0YWNrLmltZ3VyLmNvbS9zNUhFZS5wbmcifQ==” alt =“艺术家的触摸应用”>

javascript html canvas filter photo
2个回答
13
投票

好,所以我找到了对here使用的算法的很好解释,并将其调整为适用于JS和canvas。

Live Demo

CodePen Demo with controls to mess with the effect

“效果结果”

[工作原理是对中心像素周围的所有像素求平均,然后将该平均值乘以所需的强度级别,然后将其除以255。然后增加与强度级别相关的r / g / b。然后检查相邻像素之间最常见的强度级别,然后为目标像素分配该强度级别。

edit进行了更多的工作,并重写了很多内容,获得了非常大的性能提升,现在可以很好地处理体面大小的图像了。

var canvas = document.getElementById("canvas"),
    ctx = canvas.getContext("2d"),
    img = new Image();

img.addEventListener('load', function () {
    canvas.width = this.width;
    canvas.height = this.height;
    ctx.drawImage(this, 0, 0, canvas.width, canvas.height);
    oilPaintEffect(canvas, 4, 55);
});

img.crossOrigin = "Anonymous";
img.src = "https://fbcdn-sphotos-h-a.akamaihd.net/hphotos-ak-xpa1/v/t1.0-9/1379992_10202357787410559_1075078295_n.jpg?oh=5b001e9848796dd942f47a0b2f3df6af&oe=542F3FEF&__gda__=1412145968_4dbb7f75b385770ecc3f4b88105cb0f8";

function oilPaintEffect(canvas, radius, intensity) {
    var width = canvas.width,
        height = canvas.height,
        imgData = ctx.getImageData(0, 0, width, height),
        pixData = imgData.data,
        destCanvas = document.createElement("canvas"),
        dCtx = destCanvas.getContext("2d"),
        pixelIntensityCount = [];

    destCanvas.width = width;
    destCanvas.height = height;

    // for demo purposes, remove this to modify the original canvas
    document.body.appendChild(destCanvas);

    var destImageData = dCtx.createImageData(width, height),
        destPixData = destImageData.data,
        intensityLUT = [],
        rgbLUT = [];

    for (var y = 0; y < height; y++) {
        intensityLUT[y] = [];
        rgbLUT[y] = [];
        for (var x = 0; x < width; x++) {
            var idx = (y * width + x) * 4,
                r = pixData[idx],
                g = pixData[idx + 1],
                b = pixData[idx + 2],
                avg = (r + g + b) / 3;

            intensityLUT[y][x] = Math.round((avg * intensity) / 255);
            rgbLUT[y][x] = {
                r: r,
                g: g,
                b: b
            };
        }
    }


    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {

            pixelIntensityCount = [];

            // Find intensities of nearest pixels within radius.
            for (var yy = -radius; yy <= radius; yy++) {
                for (var xx = -radius; xx <= radius; xx++) {
                    if (y + yy > 0 && y + yy < height && x + xx > 0 && x + xx < width) {
                        var intensityVal = intensityLUT[y + yy][x + xx];

                        if (!pixelIntensityCount[intensityVal]) {
                            pixelIntensityCount[intensityVal] = {
                                val: 1,
                                r: rgbLUT[y + yy][x + xx].r,
                                g: rgbLUT[y + yy][x + xx].g,
                                b: rgbLUT[y + yy][x + xx].b
                            }
                        } else {
                            pixelIntensityCount[intensityVal].val++;
                            pixelIntensityCount[intensityVal].r += rgbLUT[y + yy][x + xx].r;
                            pixelIntensityCount[intensityVal].g += rgbLUT[y + yy][x + xx].g;
                            pixelIntensityCount[intensityVal].b += rgbLUT[y + yy][x + xx].b;
                        }
                    }
                }
            }

            pixelIntensityCount.sort(function (a, b) {
                return b.val - a.val;
            });

            var curMax = pixelIntensityCount[0].val,
                dIdx = (y * width + x) * 4;

            destPixData[dIdx] = ~~ (pixelIntensityCount[0].r / curMax);
            destPixData[dIdx + 1] = ~~ (pixelIntensityCount[0].g / curMax);
            destPixData[dIdx + 2] = ~~ (pixelIntensityCount[0].b / curMax);
            destPixData[dIdx + 3] = 255;
        }
    }

    // change this to ctx to instead put the data on the original canvas
    dCtx.putImageData(destImageData, 0, 0);
}

0
投票

在此处将@Loktar's answer演示粘贴给正在寻找该算法的人。

var canvas = document.getElementById("canvas"),
    ctx = canvas.getContext("2d"),
    img = new Image(),
    effectEl = document.getElementById("effect"),
    settings = {
      radius : 4,
      intensity : 25,
      ApplyFilter : function(){
         doOilPaintEffect();
      }
    }

img.addEventListener('load', function () {
    // reduced the size by half for pen and performance.
    canvas.width = (this.width/2);
    canvas.height = (this.height/2);
    ctx.drawImage(this, 0, 0, canvas.width, canvas.height);
    doOilPaintEffect();
});

img.crossOrigin = "Anonymous";
img.src = "https://codropspz-tympanus.netdna-ssl.com/codrops/wp-content/uploads/2013/02/ImageTechniques.jpg";

var gui = new dat.GUI();
gui.add(settings, 'intensity');
gui.add(settings, 'radius');
gui.add(settings, 'ApplyFilter');

function doOilPaintEffect(){
  oilPaintEffect(canvas, settings.radius, settings.intensity);
}

function oilPaintEffect(canvas, radius, intensity) {
    var width = canvas.width,
        height = canvas.height,
        imgData = ctx.getImageData(0, 0, width, height),
        pixData = imgData.data,
        // change to createElement getting added element just for the demo
        destCanvas = document.getElementById("dest-canvas"),
        dCtx = destCanvas.getContext("2d"),
        pixelIntensityCount = [];
    
    destCanvas.width = width;
    destCanvas.height = height;
    
    var destImageData = dCtx.createImageData(width, height),
        destPixData = destImageData.data,
        intensityLUT = [],
        rgbLUT = [];
    
    for (var y = 0; y < height; y++) {
        intensityLUT[y] = [];
        rgbLUT[y] = [];
        for (var x = 0; x < width; x++) {
            var idx = (y * width + x) * 4,
                r = pixData[idx],
                g = pixData[idx + 1],
                b = pixData[idx + 2],
                avg = (r + g + b) / 3;
            
            intensityLUT[y][x] = Math.round((avg * intensity) / 255);
            rgbLUT[y][x] = {
                r: r,
                g: g,
                b: b
            };
        }
    }
    
    
    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            pixelIntensityCount = [];
            
            // Find intensities of nearest pixels within radius.
            for (var yy = -radius; yy <= radius; yy++) {
                for (var xx = -radius; xx <= radius; xx++) {
                  if (y + yy > 0 && y + yy < height && x + xx > 0 && x + xx < width) {
                      var intensityVal = intensityLUT[y + yy][x + xx];

                      if (!pixelIntensityCount[intensityVal]) {
                          pixelIntensityCount[intensityVal] = {
                              val: 1,
                              r: rgbLUT[y + yy][x + xx].r,
                              g: rgbLUT[y + yy][x + xx].g,
                              b: rgbLUT[y + yy][x + xx].b
                          }
                      } else {
                          pixelIntensityCount[intensityVal].val++;
                          pixelIntensityCount[intensityVal].r += rgbLUT[y + yy][x + xx].r;
                          pixelIntensityCount[intensityVal].g += rgbLUT[y + yy][x + xx].g;
                          pixelIntensityCount[intensityVal].b += rgbLUT[y + yy][x + xx].b;
                      }
                  }
                }
            }
            
            pixelIntensityCount.sort(function (a, b) {
                return b.val - a.val;
            });
            
            var curMax = pixelIntensityCount[0].val,
                dIdx = (y * width + x) * 4;
            
            destPixData[dIdx] = ~~ (pixelIntensityCount[0].r / curMax);
            destPixData[dIdx + 1] = ~~ (pixelIntensityCount[0].g / curMax);
            destPixData[dIdx + 2] = ~~ (pixelIntensityCount[0].b / curMax);
            destPixData[dIdx + 3] = 255;
        }
    }
    
    // change this to ctx to instead put the data on the original canvas
    dCtx.putImageData(destImageData, 0, 0);
}
body{text-align:center;background:#ececec;font-family:Tahoma, Geneva, sans-serif}
section{display:inline-block}
canvas{border:1px solid #000}
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script>
<section>
  <h2>Original</h2>
  <canvas id="canvas"></canvas>
</section>
<section>
  <h2>Oil Painting Effect</h2>
   <canvas id="dest-canvas"></canvas>
</section>
© www.soinside.com 2019 - 2024. All rights reserved.