我想使用javascript从照片开始模拟人物绘制效果。
我一直在搜索几个进行图像处理的js库(主要是在画布上)。 但似乎没有人尝试过我正在寻找的东西。
我认为用javascript实现这样的效果是不可能的。所以我想知道为什么我找不到任何已经完成的事情。
在本机方面,有几种替代photoshop来实现这样的效果,如App Store中的几个应用程序所示:
以下是可能结果的其他示例(来自Artist的Touch App):
答案 0 :(得分:12)
好的,所以我找到了对 here 使用的算法的一个很好的解释,并将其改编为JS和画布。
<强> Live Demo 强>
<强> CodePen Demo with controls to mess with the effect 强>
它是如何工作的是你平均你的中心像素周围的所有像素,然后你将该平均值乘以你想要的强度等级,然后将它除以255.然后你增加r / g / b相关的到强度水平。然后检查像素邻居中最常见的强度等级,并为目标像素分配强度等级。
编辑对它进行了更多的处理并重写了很多内容,获得了一些非常巨大的性能提升,现在可以很好地处理尺寸合适的图像。
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);
}
答案 1 :(得分: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>