I want to set contrast, saturation and hue in my image editor. for this i use fabric.js but it have only brightness option..
Here is the my fabric js code
(function() {
fabric.Object.prototype.transparentCorners = false;
var $ = function(id){return document.getElementById(id)};
console.log($);
function applyFilter(index, filter) {
console.log(filter);
var obj = canvas.getActiveObject();
obj.filters[index] = filter;
obj.applyFilters(canvas.renderAll.bind(canvas));
}
function applyFilterValue(index, prop, value) {
var obj = canvas.getActiveObject();
if (obj.filters[index]) {
obj.filters[index][prop] = value;
obj.applyFilters(canvas.renderAll.bind(canvas));
}
}
fabric.Object.prototype.padding = 5;
fabric.Object.prototype.transparentCorners = false;
var canvas = this.__canvas = new fabric.Canvas('c'),
f = fabric.Image.filters;
fabric.Image.fromURL('../lib/bg.png', function(img) {
canvas.backgroundImage = img;
canvas.backgroundImage.width = 400;
canvas.backgroundImage.height = 400;
});
canvas.on({
'object:selected': function() {
fabric.util.toArray(document.getElementsByTagName('input'))
.forEach(function(el){ el.disabled = false; })
var filters = ['brightness',];
// var filters = ['grayscale', 'invert', 'remove-white', 'sepia', 'sepia2',
// 'brightness', 'noise', 'gradient-transparency', 'pixelate',
// 'blur', 'sharpen', 'emboss', 'tint', 'multiply', 'blend'];
for (var i = 0; i < filters.length; i++) {
$(filters[i]).checked = !!canvas.getActiveObject().filters[i];
}
applyFilter(5, true && new f.Brightness({
brightness: parseInt($('brightness-value').value, 10)
}));
},
'selection:cleared': function() {
fabric.util.toArray(document.getElementsByTagName('input'))
.forEach(function(el){ el.disabled = true; })
}
});
fabric.Image.fromURL('../upload/Chrysanthemum.jpg', function(img) {
var oImg = img.set({ left: 50, top: 100, angle: 0 }).scale(0.9);
canvas.add(oImg).renderAll();
canvas.setActiveObject(oImg);
});
$('brightness').onclick = function () {
applyFilter(5, this.checked && new f.Brightness({
brightness: parseInt($('brightness-value').value, 10)
}));
};
$('brightness-value').onchange = function() {
applyFilterValue(5, 'brightness', parseInt(this.value, 10));
};
})();
Last year I wrote a HSL plugin which might be of use:
fabric.Image.filters.HSL = fabric.util.createClass(fabric.Image.filters.BaseFilter, {
type: 'HSL',
initialize: function(options) {
options || (options = {});
this.hue = options.hue || 0;
this.saturation = options.saturation || 0;
this.lightness = options.lightness || 0;
},
rgbToHsl: function(r, g, b) {
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;
} 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];
},
hslToRgb: function(h, s, l) {
var r, g, b;
if (s == 0) {
r = g = b = l;
} else {
function hue2rgb(p, q, t){
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [r * 255, g * 255, b * 255];
},
applyTo: function(canvasEl) {
var context = canvasEl.getContext('2d'),
imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
data = imageData.data;
for (var i=0; i<data.length; i+=4)
{
// Convert RGB to HSL
var hsl = this.rgbToHsl(data[i], data[i+1], data[i+2]);
// Apply HSL values
if (this.hue ) hsl[0] = this.hue;
if (this.saturation) hsl[1] = this.saturation;
if (this.lightness ) hsl[2] = this.lightness;
// Convert HSL back to RGB
var rgb = this.hslToRgb(hsl[0], hsl[1], hsl[2]);
// Update data
data[i] = rgb[0];
data[i+1] = rgb[1];
data[i+2] = rgb[2];
}
context.putImageData(imageData, 0, 0);
},
toObject: function() {
return extend(this.callSuper('toObject'), {
hue: this.hue,
saturation: this.saturation,
lightness: this.lightness
});
}
});
Add to your page after FabricJS has loaded and call like this:
var hue = 1; // Range: 0 to 1
var brightness = 1; // Range: 0 to 1
var lightness = 1; // Range: 0 to 1
var filterHSL = new img.filters.HSL({
hue: hue,
saturation: saturation,
lightness: lightness
});
img.filters = [filterHSL];
img.applyFilters(canvas.renderAll.bind(canvas));
...where canvas is your FabricJS canvas object.
I hope this helps.
You can now do this with the latest Fabricjs release, 1.6.6
fabric.Image.filters.Contrast and fabric.Image.filters.Saturate were added in this release.
I made a fiddle to show you how to achieve it (note: change the applyFilter() function to achieve different filters):
DEMO
Related
We coded a spinning 3d shape in js. There's a flicker in the render of the top triangle, we think it's because the z sorting is not working correctly. How do we resolve this?
Here's a jsfiddle.
Here's the z sorting code:
// z sorting
// dots_for_rendering.sort((a,b) => Math.sqrt((b.x)**2 + (b.y)**2) - Math.sqrt((a.x)**2 + (a.y)**2))
for (var i = 0; i < polygons.length; i++) {
polygons[i].maxz = -Infinity;
polygons[i].minz = Infinity;
polygons[i].midz = 0;
for (var j = 0; j < polygons[i].verticies.length; j++) {
var z = rotated_verticies[polygons[i].verticies[j]].vector[2];
if (z > polygons[i].maxz) {
polygons[i].maxz = z;
}
if (z < polygons[i].minz) {
polygons[i].minz = z;
}
polygons[i].midz += z;
}
polygons[i].midz /= polygons[i].verticies.length;
}
polygons.sort((a, b) => b.midz - a.midz)
// polygons.sort((a,b) => Math.max(b.maxz - a.minz, b.minz - a.maxz))
// polygons.sort((a,b) => {
// if (a.minz < b.maxz) {
// return 0;
// }
// if (b.minz < a.maxz) {
// return -1;
// }
// return 0;
// })
Here's a code snippet:
class Tensor {
constructor(){
var input = this.takeInput(...arguments);
this.vector = input;
}
takeInput() {
var a = true;
for (var arg of arguments) {
if (typeof arg !== "number"){
a = false
}
}
if (a && arguments[2] !== true){
return new Array(...arguments);
}
else {
if (arguments[0] instanceof Tensor){
return arguments[0].vector;
}
else {
if (typeof arguments[0] === "number" && typeof arguments[1] === "number" && arguments[2] === true) {
var res = [];
for (var i = 0; i < arguments[0]; i++) {
res.push(arguments[1]);
}
return res;
}
}
}
}
// used for + - * /
change(f, input){
for (var i in this.vector) {
this.vector[i] = f(this.vector[i], input[i]);
}
return this;
}
copy() {
return new Tensor(...this.vector);
}
dimentions() {
return this.vector.length;
}
//-----------
len() {
var s = 0;
for (var dim of this.vector) {
s += dim ** 2;
}
return Math.sqrt(s);
}
norm() {
return this.div(this.dimentions(), this.len(), true)
}
add() {
var input = this.takeInput(...arguments);
return this.change((x, y) => x + y, input);
}
sub() {
var input = this.takeInput(...arguments);
return this.change((x, y) => x - y, input);
}
mult() {
var input = this.takeInput(...arguments);
return this.change((x, y) => x * y, input);
}
div() {
var input = this.takeInput(...arguments);
return this.change((x, y) => x / y, input);
}
dot() {
var input = this.takeInput(...arguments);
var res = 0;
for (var i in this.vector) {
res += this.vector[i] * input[i]
}
return res;
}
rotate() {
// WARNING: only for 3D currently!!!
var input = this.takeInput(...arguments);
var [x, y, z] = this.vector;
// rotate Z
var t_x = x * Math.cos(input[2]) - y * Math.sin(input[2])
y = y * Math.cos(input[2]) + x * Math.sin(input[2])
x = t_x
// rotate X
var t_y = y * Math.cos(input[0]) - z * Math.sin(input[0])
z = z * Math.cos(input[0]) + y * Math.sin(input[0])
y = t_y
// rotate Y
t_x = x * Math.cos(input[1]) + z * Math.sin(input[1])
z = z * Math.cos(input[1]) - x * Math.sin(input[1])
x = t_x
this.vector = [x, y, z];
return this;
}
}
var canvas = document.getElementById('canvas')
var ctx = canvas.getContext("2d")
w = 300
h = 286
fov = 0.1
scale = 65;
offset = new Tensor(w / 2 - 5, h / 2 - 92, 0.1);
light = new Tensor(3.5, 0.5, 1).norm();
canvas.width = w;
canvas.height = h;
var verticies = [];
verticies.push(new Tensor(0.5, 1, 0))
verticies.push(new Tensor(0.5, -1, 0))
verticies.push(new Tensor(-1, 0, 0))
verticies.push(new Tensor(0, 0, 2))
var polygons = [];
polygons.push({
verticies: [0, 3, 1],
color: 'red',
nf: 1
});
polygons.push({
verticies: [2, 3, 0],
color: 'blue',
nf: 1
});
polygons.push({
verticies: [2, 3, 1],
color: 'green',
nf: -1
});
polygons.push({
verticies: [0, 1, 2],
color: 'yellow',
nf: -1
});
for (var i = 0; i < polygons.length; i++) {
polygons[i].id = i;
}
theta = new Tensor(1.5 * Math.PI, 0, 1.5 * Math.PI);
function loop() {
ctx.clearRect(0, 0, w, h);
rotated_verticies = [];
for (var i = 0; i < verticies.length; i++) {
rotated_verticies.push(verticies[i].copy().rotate(theta));
}
// z sorting
// dots_for_rendering.sort((a,b) => Math.sqrt((b.x)**2 + (b.y)**2) - Math.sqrt((a.x)**2 + (a.y)**2))
for (var i = 0; i < polygons.length; i++) {
polygons[i].maxz = -Infinity;
polygons[i].minz = Infinity;
polygons[i].midz = 0;
for (var j = 0; j < polygons[i].verticies.length; j++) {
var z = rotated_verticies[polygons[i].verticies[j]].vector[2];
// z += 1 * (Math.random() * 2 - 1)
if (z > polygons[i].maxz) {
polygons[i].maxz = z;
}
if (z < polygons[i].minz) {
polygons[i].minz = z;
}
polygons[i].midz += z;
}
polygons[i].midz /= polygons[i].verticies.length;
}
polygons.sort((a, b) => b.midz - a.midz)
// polygons.sort((a,b) => Math.max(b.maxz - a.minz, b.minz - a.maxz))
// polygons.sort((a,b) => {
// if (a.minz < b.maxz) {
// return 0;
// }
// if (b.minz < a.maxz) {
// return -1;
// }
// return 0;
// })
for (var i = 0; i < polygons.length; i++) {
var polygon_2 = [];
for (var j = 0; j < polygons[i].verticies.length; j++) {
var v = rotated_verticies[polygons[i].verticies[j]]
polygon_2.push(v.vector);
}
var norm = getNormal(polygon_2, polygons[i].nf);
// var rotated_light = light.copy().rotate(theta);
var brightness = Math.max(0, norm.dot(light))
//ctx.fillStyle = "hsl(31, "+100+"%, "+(Math.min(9.0*brightness + 40, 100))+"%)";
ctx.fillStyle = "hsl(190, "+100+"%, "+(Math.min(9.0*brightness + 40, 100))+"%)";
// ctx.fillStyle = polygons[i].color
ctx.beginPath();
for (var j = 0; j < polygons[i].verticies.length; j++) {
var vertex = rotated_verticies[polygons[i].verticies[j]].copy();
vertex.mult(scale, scale, 1);
vertex.add(offset);
var n = 1 + vertex.vector[2] * fov;
vertex.div(n, n, 1)
// console.log(vertex.vector)
if (j == 0) {
ctx.moveTo(vertex.vector[0], vertex.vector[1]);
} else {
ctx.lineTo(vertex.vector[0], vertex.vector[1]);
}
}
ctx.closePath();
ctx.fill()
// ctx.stroke()
polygons[i].mid = new Tensor(3, 0, true);
for (var k = 0; k < polygons[i].verticies.length; k++) {
var vertex = rotated_verticies[polygons[i].verticies[k]].copy();
vertex.mult(scale, scale, 1);
vertex.add(offset);
var n = 1 + vertex.vector[2] * fov;
vertex.div(n, n, 1)
polygons[i].mid.add(vertex);
}
polygons[i].mid.div(3, polygons[i].verticies.length, true);
ctx.fillStyle = "red"
ctx.font = '50px serif';
// ctx.fillText(polygons[i].id + ", " + polygons[i].nf, polygons[i].mid.vector[0], polygons[i].mid.vector[1])
}
// theta.add(theta.vector[0] + (0.01*mouseY - theta.vector[0]) * 0.1, 0, theta.vector[2] + (-0.01*mouseX - theta.vector[2]) * 0.1)
theta.add(0, -0.0375, 0);
// fov = (mouseX - w/2) * 0.001
requestAnimationFrame(loop);
}
loop();
// setInterval(loop, 1000 / 60)
function getNormal(polygon, nf) {
var Ax = polygon[1][0] - polygon[0][0];
var Ay = polygon[1][1] - polygon[0][1];
var Az = polygon[1][2] - polygon[0][2];
var Bx = polygon[2][0] - polygon[0][0];
var By = polygon[2][1] - polygon[0][1];
var Bz = polygon[2][2] - polygon[0][2];
var Nx = Ay * Bz - Az * By
var Ny = Az * Bx - Ax * Bz
var Nz = Ax * By - Ay * Bx
return new Tensor(nf * Nx, nf * Ny, nf * Nz);
}
function len(p1, p2) {
return Math.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2 + (p2[2] - p1[2]) ** 2);
}
mouseX = 0
mouseY = 0
onmousemove = (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name=”ad.size” content=”width=300,height=600”>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>...</title>
</head>
<body>
<canvas id="canvas" width="300" height="238"></canvas>
</body>
</html>
** Edit:
Ok, we've significantly edited the code, see this fiddle, and the following code snippet below. It's still not working correctly, we think it's something to do with the first line of this piece of code, any ideas?
if (polygons[i].mid.copy().sub(camera).dot(norm) < 0) {
var pathelem = document.createElementNS("http://www.w3.org/2000/svg", "path");
pathelem.setAttribute("d", path);
pathelem.setAttribute("fill", "hsl(31, "+100+"%, "+(Math.min(9.0*brightness + 40, 100))+"%)");
svg.appendChild(pathelem);
}
class Tensor {
constructor(){
var input = this.takeInput(...arguments);
this.vector = input;
}
takeInput() {
var a = true;
for (var arg of arguments) {
if (typeof arg !== "number"){
a = false
}
}
if (a && arguments[2] !== true){
return new Array(...arguments);
}
else {
if (arguments[0] instanceof Tensor){
return arguments[0].vector;
}
else {
if (typeof arguments[0] === "number" && typeof arguments[1] === "number" && arguments[2] === true) {
var res = [];
for (var i = 0; i < arguments[0]; i++) {
res.push(arguments[1]);
}
return res;
}
}
}
}
// used for + - * /
change(f, input){
for (var i in this.vector) {
this.vector[i] = f(this.vector[i], input[i]);
}
return this;
}
copy() {
return new Tensor(...this.vector);
}
dimentions() {
return this.vector.length;
}
//-----------
len() {
var s = 0;
for (var dim of this.vector) {
s += dim ** 2;
}
return Math.sqrt(s);
}
norm() {
return this.div(this.dimentions(), this.len(), true)
}
add() {
var input = this.takeInput(...arguments);
return this.change((x, y) => x + y, input);
}
sub() {
var input = this.takeInput(...arguments);
return this.change((x, y) => x - y, input);
}
mult() {
var input = this.takeInput(...arguments);
return this.change((x, y) => x * y, input);
}
div() {
var input = this.takeInput(...arguments);
return this.change((x, y) => x / y, input);
}
dot() {
var input = this.takeInput(...arguments);
var res = 0;
for (var i in this.vector) {
res += this.vector[i] * input[i]
}
return res;
}
rotate() {
// WARNING: only for 3D currently!!!
var input = this.takeInput(...arguments);
var [x, y, z] = this.vector;
// rotate Z
var t_x = x * Math.cos(input[2]) - y * Math.sin(input[2])
y = y * Math.cos(input[2]) + x * Math.sin(input[2])
x = t_x
// rotate X
var t_y = y * Math.cos(input[0]) - z * Math.sin(input[0])
z = z * Math.cos(input[0]) + y * Math.sin(input[0])
y = t_y
// rotate Y
t_x = x * Math.cos(input[1]) + z * Math.sin(input[1])
z = z * Math.cos(input[1]) - x * Math.sin(input[1])
x = t_x
this.vector = [x, y, z];
return this;
}
}
var svg = document.getElementById('svg')
w = 300
h = 286
fov = 0.1
scale = 65;
camera = new Tensor(-w / 2 + 5, -h / 2 + 92, 0.1);
light = new Tensor(3.5, 0.5, 1).norm();
svg.setAttribute('width', w);
svg.setAttribute('height', h);
var vertices = [
new Tensor(0.5, 1, 0),
new Tensor(0.5, -1, 0),
new Tensor(-1, 0, 0),
new Tensor(0, 0, 2)
];
var polygons = [];
polygons.push({
vertices: [0, 3, 1],
color: 'red',
nf: 1
});
polygons.push({
vertices: [2, 3, 0],
color: 'blue',
nf: 1
});
polygons.push({
vertices: [2, 3, 1],
color: 'green',
nf: -1
});
polygons.push({
vertices: [0, 1, 2],
color: 'yellow',
nf: 1
});
for (var i = 0; i < polygons.length; i++) {
polygons[i].id = i;
}
theta = new Tensor(1.5 * Math.PI, 0, 1.5 * Math.PI);
function loop() {
// ctx.clearRect(0, 0, w, h);
svg.innerHTML = "";
rotated_vertices = [];
for (var i = 0; i < vertices.length; i++) {
rotated_vertices.push(vertices[i].copy().rotate(theta));
}
// z sorting
// dots_for_rendering.sort((a,b) => Math.sqrt((b.x)**2 + (b.y)**2) - Math.sqrt((a.x)**2 + (a.y)**2))
for (var i = 0; i < polygons.length; i++) {
polygons[i].maxz = -Infinity;
polygons[i].minz = Infinity;
polygons[i].midz = 0;
for (var j = 0; j < polygons[i].vertices.length; j++) {
var z = rotated_vertices[polygons[i].vertices[j]].vector[2];
// z += 1 * (Math.random() * 2 - 1)
if (z > polygons[i].maxz) {
polygons[i].maxz = z;
}
if (z < polygons[i].minz) {
polygons[i].minz = z;
}
polygons[i].midz += z;
}
polygons[i].midz /= polygons[i].vertices.length;
polygons[i].mid = new Tensor(3, 0, true);
for (var k = 0; k < polygons[i].vertices.length; k++) {
var vertex = rotated_vertices[polygons[i].vertices[k]].copy();
vertex.mult(scale, scale, 1);
vertex.sub(camera);
var n = 1 + vertex.vector[2] * fov;
vertex.div(n, n, 1)
polygons[i].mid.add(vertex);
}
polygons[i].mid.div(3, polygons[i].vertices.length, true);
}
polygons.sort((a, b) => b.midz - a.midz)
// polygons.sort((a,b) => Math.max(b.maxz - a.minz, b.minz - a.maxz))
// polygons.sort((a,b) => {
// if (a.minz < b.maxz) {
// return 0;
// }
// if (b.minz < a.maxz) {
// return -1;
// }
// return 0;
// })
for (var i = 0; i < polygons.length; i++) {
var polygons_embedded_point_coords = [];
for (var j = 0; j < polygons[i].vertices.length; j++) {
var v = rotated_vertices[polygons[i].vertices[j]]
polygons_embedded_point_coords.push(v.vector);
}
var norm = getNormal(polygons_embedded_point_coords, polygons[i].nf);
// var rotated_light = light.copy().rotate(theta);
var brightness = Math.max(0, norm.dot(light))
// ctx.fillStyle = "hsl(31, "+100+"%, "+(Math.min(9.0*brightness + 40, 100))+"%)";
// ctx.fillStyle = polygons[i].color
// ctx.beginPath();
var path = [];
for (var j = 0; j < polygons[i].vertices.length; j++) {
var vertex = rotated_vertices[polygons[i].vertices[j]].copy();
vertex.mult(scale, scale, 1);
vertex.sub(camera);
var n = 1 + vertex.vector[2] * fov;
vertex.div(n, n, 1)
// console.log(vertex.vector)
if (j == 0) {
// ctx.moveTo(vertex.vector[0], vertex.vector[1]);
path.push("M "+vertex.vector[0]+" "+vertex.vector[1]);
} else {
path.push("L "+vertex.vector[0]+" "+vertex.vector[1]);
// ctx.lineTo(vertex.vector[0], vertex.vector[1]);
}
}
// that should work
if (polygons[i].mid.copy().sub(camera).dot(norm) < 0) {
var pathelem = document.createElementNS("http://www.w3.org/2000/svg", "path");
pathelem.setAttribute("d", path);
pathelem.setAttribute("fill", "hsl(31, "+100+"%, "+(Math.min(9.0*brightness + 40, 100))+"%)");
svg.appendChild(pathelem);
}
// ctx.fillStyle = "red"
// ctx.font = '15px serif';
//
// ctx.fillText(polygons[i].id + ", " + polygons[i].nf, polygons[i].mid.vector[0], polygons[i].mid.vector[1])
}
// theta.add(theta.vector[0] + (0.01*mouseY - theta.vector[0]) * 0.1, 0, theta.vector[2] + (-0.01*mouseX - theta.vector[2]) * 0.1)
theta.add(0, -0.0375, 0);
// fov = (mouseX - w/2) * 0.001
requestAnimationFrame(loop);
}
loop();
// setInterval(loop, 1000 / 60)
function getNormal(vertices, nf) {
var Ax = vertices[1][0] - vertices[0][0];
var Ay = vertices[1][1] - vertices[0][1];
var Az = vertices[1][2] - vertices[0][2];
var Bx = vertices[2][0] - vertices[0][0];
var By = vertices[2][1] - vertices[0][1];
var Bz = vertices[2][2] - vertices[0][2];
var Nx = Ay * Bz - Az * By
var Ny = Az * Bx - Ax * Bz
var Nz = Ax * By - Ay * Bx
return new Tensor(nf * Nx, nf * Ny, nf * Nz);
}
function len(p1, p2) {
return Math.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2 + (p2[2] - p1[2]) ** 2);
}
mouseX = 0
mouseY = 0
onmousemove = (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
<script src="Tensor.js"></script>
<script src="script-tensors-svg.js" async defer></script>
</head>
<body>
<!-- <canvas id="canvas"></canvas> -->
<svg id="svg" xmlns="http://www.w3.org/2000/svg"></svg>
</body>
</html>
Sorting by average Z just doesn't give you a reliable rendering order. Since your shape is convex, though, you don't need to sort at all.
Make sure the vertices of each triangle are sorted so that you can consistently get a surface normal that points outward. Then, just don't render any triangles with normals that point away from the camera, i.e.:
if (vector_from_camera_to_poly_midpoint \dot poly_normal < 0) {
//render the poly
}
Now you will only render the side of the object that is facing the camera -- none of the polygons will overlap, so you can render them in any order.
I just read this tutorial and tried this example. So I downloaded a video from web for my own testing. All I have to do is tweak rgb values in if conditions
HERE is the sample code from example
computeFrame: function() {
this.ctx1.drawImage(this.video, 0, 0, this.width, this.height);
let frame = this.ctx1.getImageData(0, 0, this.width, this.height);
let l = frame.data.length / 4;
for (let i = 0; i < l; i++) {
let r = frame.data[i * 4 + 0];
let g = frame.data[i * 4 + 1];
let b = frame.data[i * 4 + 2];
if (g > 100 && r > 100 && b < 43)
frame.data[i * 4 + 3] = 0;
}
this.ctx2.putImageData(frame, 0, 0);
return;
}
In the tutorial example its filtering out yellow(not yellow I guess) color. The sample video I downloaded uses green background. So I tweaked rgb value in if condition to get desired results
After multiple tries, I managed to get this.
Now what I want to know is how can I accurately filter out green screen (or any other screen)perfectly without guessing. Or randomly tweaking values.
With just guessing it take hours to get it perfectly right. And this is just a sample with a real world application. It can take maybe more.
NOTE: The example is working in Firefox for now..
You probably just need a better algorithm. Here's one, it's not perfect, but you can tweak it a lot easier.
Basically you'll just need a colorpicker, and pick the lightest and darkest values from the video (putting the RGB values in the l_ and d_ variables respectively). You can adjust the tolerance a little bit if you need to, but getting the l_ and r_ values just right by picking different areas with the color picker will give you a better key.
let l_r = 131,
l_g = 190,
l_b = 137,
d_r = 74,
d_g = 148,
d_b = 100;
let tolerance = 0.05;
let processor = {
timerCallback: function() {
if (this.video.paused || this.video.ended) {
return;
}
this.computeFrame();
let self = this;
setTimeout(function () {
self.timerCallback();
}, 0);
},
doLoad: function() {
this.video = document.getElementById("video");
this.c1 = document.getElementById("c1");
this.ctx1 = this.c1.getContext("2d");
this.c2 = document.getElementById("c2");
this.ctx2 = this.c2.getContext("2d");
let self = this;
this.video.addEventListener("play", function() {
self.width = self.video.videoWidth;
self.height = self.video.videoHeight;
self.timerCallback();
}, false);
},
calculateDistance: function(c, min, max) {
if(c < min) return min - c;
if(c > max) return c - max;
return 0;
},
computeFrame: function() {
this.ctx1.drawImage(this.video, 0, 0, this.width, this.height);
let frame = this.ctx1.getImageData(0, 0, this.width, this.height);
let l = frame.data.length / 4;
for (let i = 0; i < l; i++) {
let _r = frame.data[i * 4 + 0];
let _g = frame.data[i * 4 + 1];
let _b = frame.data[i * 4 + 2];
let difference = this.calculateDistance(_r, d_r, l_r) +
this.calculateDistance(_g, d_g, l_g) +
this.calculateDistance(_b, d_b, l_b);
difference /= (255 * 3); // convert to percent
if (difference < tolerance)
frame.data[i * 4 + 3] = 0;
}
this.ctx2.putImageData(frame, 0, 0);
return;
}
};
// :/
If performance does not matter, then you could work in another color space e.g. HSV. You could use the left top pixel as reference.
You compare the hue value of the reference point with hue value other pixels, and exclude all pixels that exceed a certain threshold and dark and light areas using saturation and value.
This how ever does not completely get rid of color bleeding, there you might need to do some color correct/desaturation.
function rgb2hsv () {
var rr, gg, bb,
r = arguments[0] / 255,
g = arguments[1] / 255,
b = arguments[2] / 255,
h, s,
v = Math.max(r, g, b),
diff = v - Math.min(r, g, b),
diffc = function(c){
return (v - c) / 6 / diff + 1 / 2;
};
if (diff == 0) {
h = s = 0;
} else {
s = diff / v;
rr = diffc(r);
gg = diffc(g);
bb = diffc(b);
if (r === v) {
h = bb - gg;
}else if (g === v) {
h = (1 / 3) + rr - bb;
}else if (b === v) {
h = (2 / 3) + gg - rr;
}
if (h < 0) {
h += 1;
}else if (h > 1) {
h -= 1;
}
}
return {
h: Math.round(h * 360),
s: Math.round(s * 100),
v: Math.round(v * 100)
};
}
let processor = {
timerCallback: function() {
if (this.video.paused || this.video.ended) {
return;
}
this.computeFrame();
let self = this;
setTimeout(function () {
self.timerCallback();
}, 0);
},
doLoad: function() {
this.video = document.getElementById("video");
this.c1 = document.getElementById("c1");
this.ctx1 = this.c1.getContext("2d");
this.c2 = document.getElementById("c2");
this.ctx2 = this.c2.getContext("2d");
let self = this;
this.video.addEventListener("play", function() {
self.width = self.video.videoWidth / 2;
self.height = self.video.videoHeight / 2;
self.timerCallback();
}, false);
},
computeFrame: function() {
this.ctx1.drawImage(this.video, 0, 0, this.width, this.height);
let frame = this.ctx1.getImageData(0, 0, this.width, this.height);
let l = frame.data.length / 4;
let reference = rgb2hsv(frame.data[0], frame.data[1], frame.data[2]);
for (let i = 0; i < l; i++) {
let r = frame.data[i * 4 + 0];
let g = frame.data[i * 4 + 1];
let b = frame.data[i * 4 + 2];
let hsv = rgb2hsv(r, g, b);
let hueDifference = Math.abs(hsv.h - reference.h);
if( hueDifference < 20 && hsv.v > 50 && hsv.s > 50 ) {
frame.data[i * 4 + 3] = 0;
}
}
this.ctx2.putImageData(frame, 0, 0);
return;
}
};
I have an image where I need to change it's background color, but keep the "effects" on it (on the image the black dots, white lines etc.)
Here's the orginal image:
I managed to change the color, but also I keep removing those "effects". Preview:
Here's the code:
//let's say I want it to be red
var r = 255;
var g = 0;
var b = 0;
var imgElement = document.getElementById('img');
var canvas = document.getElementById('canvas');
canvas.width = imgElement.width;
canvas.height = imgElement.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(imgElement, 0, 0);
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var data = imageData.data;
for (var i = 0; i < data.length; i += 4) {
if (data[i + 3] !== 0) {
data[i] = r;
data[i + 1] = g;
data[i + 2] = b;
data[i + 3] = data[i + 3];
}
}
ctx.putImageData(imageData, 0, 0);
<img src="foo" id="img" />
<canvas id="canvas"></canvas>
How to prevent that?
For modern browsers except Internet Explorer, you can use compositing to change the hue of your original image while leaving the saturation & lightness unchanged. This will "recolor" your original image while leaving the contours intact.
Example code that works in modern browsers except Internet Explorer
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/M449a.png";
function start(){
// create an overlay with solid #00d9c6 color
var tempCanvas=document.createElement('canvas');
var tempctx=tempCanvas.getContext('2d');
canvas.width=tempCanvas.width=img.width;
canvas.height=tempCanvas.height=img.height;
tempctx.drawImage(img,0,0);
tempctx.globalCompositeOperation='source-atop';
tempctx.fillStyle='#00d9c6';
tempctx.fillRect(0,0,tempCanvas.width,tempCanvas.height);
//
canvas.width=img.width;
canvas.height=img.height;
// use compositing to change the hue of the original image
ctx.drawImage(img,0,0);
ctx.globalCompositeOperation='hue';
ctx.drawImage(tempCanvas,0,0);
// always clean up: reset compositing to its default
ctx.globalCompositeOperation='source-over';
}
#canvas{border:1px solid red; }
<canvas id="canvas" width=300 height=300></canvas>
Since Internet Explorer does not support Blend Compositing, you will have to do it manually.
Read the RGBA value of each pixel.
Convert that RGBA value to HSL.
Shift the hue value (the "H" in HSL) by the difference between your blue hue and your desired green hue.
Convert the hue-shifted HSL value to RGBA value.
Write the hue-shifted RGBA value back to the pixel.
Here's example code of manually shifting the hue:
Important note: This manual method works by manipulating pixels with .getImageData. Therefore you must make sure the original image is hosted on the same domain as the webpage. Otherwise, the canvas will become tainted for security reasons and you will not be able to use .getImageData.
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = new Image();
img.crossOrigin = "anonymous";
img.onload = start;
img.src = "https://dl.dropboxusercontent.com/u/139992952/multple/marioStanding.png";
function start() {
ctx.drawImage(img, 0, 0);
ctx.drawImage(img, 150, 0);
// shift blueish colors to greenish colors
recolorPants(-.33);
}
function recolorPants(colorshift) {
var imgData = ctx.getImageData(150, 0, canvas.width, canvas.height);
var data = imgData.data;
for (var i = 0; i < data.length; i += 4) {
red = data[i + 0];
green = data[i + 1];
blue = data[i + 2];
alpha = data[i + 3];
// skip transparent/semiTransparent pixels
if (alpha < 200) {
continue;
}
var hsl = rgbToHsl(red, green, blue);
var hue = hsl.h * 360;
// change blueish pixels to the new color
if (hue > 200 && hue < 300) {
var newRgb = hslToRgb(hsl.h + colorshift, hsl.s, hsl.l);
data[i + 0] = newRgb.r;
data[i + 1] = newRgb.g;
data[i + 2] = newRgb.b;
data[i + 3] = 255;
}
}
ctx.putImageData(imgData, 150, 0);
}
function rgbToHsl(r, g, b) {
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: h,
s: s,
l: l,
});
}
function hslToRgb(h, s, l) {
var r, g, b;
if (s == 0) {
r = g = b = l; // achromatic
} else {
function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return ({
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255),
});
}
<p>Example shifting color Hue with .getImageData</p>
<p>(Original: left, Recolored: right)</p>
<canvas id="canvas" width=300 height=300></canvas>
You need to convert each pixel to the LSH colour space (lightness/luminance, hue, saturation). Then you set the Hue to the colour you want and keep the calculated luminance and saturation, then convert back to RGB and set the imageData to the new RGB value.
I have added my own code for conversions. There may be faster versions out there.
// returns RGB in an array on 3 numbers 0-255
var lshToRGB = function(ll,ss,hh){ //ll 0-255,ss 0-255, hh 0-360
var l = ll/255;
var s = ss/255;
var hhh = (hh/255)*360;
var C = (1 - Math.abs(2*l - 1)) * s;
var X = C*(1 - Math.abs(((hhh / 60)%2) - 1));
var m = l - C/2;
if(hhh < 60){
var r = C;
var g = X;
var b = 0;
}else
if(hhh < 120){
var r = X;
var g = C;
var b = 0;
}else
if(hhh < 180){
var r = 0;
var g = C;
var b = X;
}else
if(hhh < 240){
var r = 0;
var g = X;
var b = C;
}else
if(hhh < 300){
var r = X;
var g = 0;
var b = C;
}else{
var r = C;
var g = 0;
var b = X;
}
r += m;
g += m;
b += m;
// is there a need to clamp these ????)
r = Math.round(Math.min(255,Math.max(0,r*255)));
g = Math.round(Math.min(255,Math.max(0,g*255)));
b = Math.round(Math.min(255,Math.max(0,b*255)));
return [r,g,b];
}
// returns array of 3 numbers 0-255,0-255,0-360
var rgbToLSH = function(rr,gg,bb){ // could do without the conversion from 360 to 255 on hue
var r,
g,
b,
h,
s,
l,
min,
max,
d;
r = rr / 255;
g = gg / 255;
b = bb / 255;
max = Math.max(r, g, b);
min = Math.min(r, g, b);
l = (max + min) / 2;
if (max == min) {
h = 0;
s = 0; // achromatic
} else {
d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = (g - b) / d;
break;
case g:
h = 2 + ((b - r) / d);
break;
case b:
h = 4 + ((r - g) / d);
break;
}
h *= 60;
if (h < 0) {
h += 360;
}
h = Math.round(h);
}
return [
Math.min(Math.round(l*255),255),
Math.min(Math.round(s*255),255),
Math.min(Math.round((h/360)*255),255)
];
}
I've been asked to create the same ripple effect as the one found with the bit.ly 404 page: http://bit.ly/khgefiyueagf734
Javascript and canvas are pretty new to me and I am having this problem. When I change the image to one that I want. Not the fish one that is on the 404 page it renders the image as the same size as the fish and wont change.
Can you tell me how to change the size of the ship (bit.ly fish) image?
Here is the JS Fiddle: http://jsfiddle.net/UzpAw/8/
and Here is the javascript that comes with it:
(function() {
function F() {
if (g.getContext) {
var a, b = a = 0;
if (document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight)) a = document.documentElement.clientWidth, b = document.documentElement.clientHeight;
if (document.body && (document.body.clientWidth || document.body.clientHeight)) a = Math.max(a, document.body.clientWidth), b = Math.max(b, document.body.clientHeight);
a = [a, b];
e = a[1];
d = a[0] + 1;
r = Math.floor(0.5 * e);
i = e - r;
j = i - G;
g.setAttribute("width", d);
g.setAttribute("height",
e);
g.style.width = d + "px";
g.style.height = e + "px";
s = [Math.floor(0.75 * d), 0];
c = g.getContext("2d");
c.fillStyle = "#00022f"; //rgba(104,168,220,.8)
v()
}
}
function v() {
c.fillRect(0, i, d, e - j)
}
function m(a, b) {
if (!w) k.style.top = "-9999px", k.style.left = "-9999px",k.style.height = "20px", c.clearRect(0, j, d, e - j);
c.clearRect(s[0], s[1] - 5, n[0], n[1]);
a = a || Math.floor(0.6 * d);
b = b || 0;
b += r - 0.5 * n[1];
c.drawImage(k, a, b, n[0], n[1]);
s = [a, b];
w || (v(), w = !0)
}
function O() {
midPointY = Math.sin(h * 10 * P) * (o - h) * Q;
h <= o && (f[p] = [0, midPointY], h % 9 == 0 && h % 2 == 1 && (f[z] = [-1, midPointY], z++));
c.clearRect(0, j, d, e - j);
m(Math.floor(0.6 * d), h < o ? Math.floor(midPointY) : 0);
c.beginPath();
c.moveTo(0, i);
for (var a = [0, i], b = 0; b < t; b++) if (f[b]) {
b < p && (f[b][0] = f[b][0] * R - H, f[p - b + p] = [-f[b][0], f[b][1]]);
var q = f[b][0] + I,
A = f[b][1] + i;
c.bezierCurveTo((q - a[0]) / 2 + a[0], a[1], q - (q - a[0]) / 2, A, q, A);
a[0] = q;
a[1] = A
}
c.lineTo(d, i);
c.lineTo(d, e);
c.lineTo(0, e);
c.closePath();
c.fill();
h++;
h == o && (B = !1);
h >= o * 2 && clearInterval(C)
}
function D(a, b) {
if (!J) return !1;
b && c.clearRect(x, y, b.w, b.h);
a = a || E[0];
x = K[0];
y = K[1] + a.mt;
c.drawImage(l, Math.abs(a.l),
Math.abs(a.t), a.w, a.h, x, y, a.w, a.h)
}
function S() {
var a = -1,
b = 0,
c = E.length,
d;
clearInterval(L);
L = setInterval(function() {
b = ++a > c - 1 ? c * 2 - 2 - a : a;
d = E[b];
D(d, M);
M = d;
a >= c * 2 - 2 && (a = 0)
}, 200)
}
var u = function(a, b, c) {
var d = c,
c = function(b) {
d.call(a, b)
};
return a.attachEvent ? a.attachEvent("on" + b, c) : a.addEventListener(b, c, !1)
};
if (!window.getComputedStyle) window.getComputedStyle = function(a) {
return a.currentStyle
};
var t = 13,
Q = 0.3,
d = 960,
e = 600,
G = 30,
r = Math.floor(0.5 * e),
R = 1.01,
H = 20,
i = e - r,
j = i - G,
o = (t - 3) * 9,
p = Math.floor(t / 2),
I, g,
c, C, B = !1,
P = Math.PI / 180,
f, z, h, k, s = [Math.floor(0.75 * d), 0],
n = [236, 195],
w, l, L, J, M, K = [10, 10], // 75, 150
E = [{
h: 58,
w: 140,
t: 0,
l: 0,
mt: 10
}, {
h: 72,
w: 150,
t: -64,
l: 0,
mt: 0
}, {
h: 61,
w: 150,
t: -151,
l: 0,
mt: 30
}],
N = !1;
g = document.createElement("canvas");
g.getContext && (N = !0, document.body.removeChild(document.getElementById("ripple")), g.setAttribute("id", "ripple"), document.body.appendChild(g), k = document.getElementById("ship"));
//, l = document.getElementById("gull"), l.setAttribute("src", "/static/graphics/gulls-404.png")
N && (function() {
try {
if (fish.complete) m();
else if (parseInt(15, 5)) setTimeout(m, 1E3); // 15 IS: getComputedStyle(k).height
else throw "no ship";
} catch (a) {
u(k, "load", function() {
m()
})
}
u(document.getElementById("ripple-control"), "mouseover", function(a) {
if (g.getContext && !B) clearInterval(C), p = Math.floor(t / 2), I = a.pageX, z = 1, h = 0, f = [], f[0] = [H, 0], C = setInterval(O, 30), B = !0
});
u(window, "resize", function() {
F();
m();
v();
D()
})
}(), F());
(function() {
var a = 0,
b = document.getElementById("cloud1"),
c = document.getElementById("cloud2"),
e = parseInt(getComputedStyle(b).left, 10),
f = parseInt(getComputedStyle(c).left, 10);
setInterval(function() {
if (++a == 2) a = 0, e += 1, b.style.left = e + "px";
f += 1;
c.style.left = f + "px";
e > d + 50 && (e = -200);
f > d + 50 && (f = -100)
}, 50)
})()
})();
You have to hover over the blue to get the image to show but as you can see, the size of the ship is way too large. I need it to be: 183x116.
Thanks for the help :)
The size of the ship is controlled by the variable on line#110.
I'm currently trying to add this fun little crawling fly script to my website, and want to customize it so that you can kill it when it's clicked. The script automatically places the image right above the tag, and I can't seem to get my jQuery .click function below it.
This is the script that I put in the head (sorry it's minified, but the unminified version doesn't work for some reason)
/* WWW: http://screen-bug.googlecode.com/git/index.html Released under WTFPL license. */
(function () {
function y() {
0 >= --s && t();
if (!j) {
var b;
if (b = 0 >= --u) d = 0, 50 > a.top ? d |= 1 : a.top > document.documentElement.clientHeight - 50 && (d |= 2), 50 > a.left ? d |= 4 : a.left > document.documentElement.clientWidth - 50 && (d |= 8), b = d;
if (b && (c %= 360, 0 > c && (c += 360), 15 < g(q[d] - c))) {
b = q[d] - c;
var k = 360 - c + q[d];
f = g(b) < g(k) ? b : k;
u = 10;
l = 100;
m = 30
}
0 >= --l && (f = e(1, 150), l = h(40 * Math.random()));
if (0 >= --m) c += e(1, 10), m = h(10 * Math.random());
else {
b = e(1, 5);
if (0 < f && 0 > b || 0 > f && 0 < b) b = -b;
f -= b;
c += b
}
r = c * v;
b = 2 * Math.cos(r);
k = 2 * -Math.sin(r);
a.style.top = (a.top += k) + "px";
a.style.left = (a.left += b) + "px";
n("rotate(" + (90 - c) + "deg)")
}
}
function e(b, a) {
var c = h(b + Math.random() * a);
return 0.5 < Math.random() ? c : -c
}
function t() {
j = !j;
s = e(50, 300);
j ? (a.src = "https://screen-bug.googlecode.com/git/fruitfly.png", a.id = "bedbug", document.addEventListener ? document.addEventListener("mousemove", p, !1) : document.attachEvent && document.attachEvent("onmousemove", p)) : (a.src = "https://screen-bug.googlecode.com/git/fruitfly.png", w())
}
function w() {
document.removeEventListener ? document.removeEventListener("mousemove", p, !1) : document.detachEvent && document.detachEvent("onmousemove", p)
}
function p(b) {
b = b || window.event;
b.clientX ? (posx = b.clientX, posy = b.clientY) : b.pageX && (posx = b.pageX - (document.body.scrollLeft + document.documentElement.scrollLeft), posy = b.pageY - (document.body.scrollTop + document.documentElement.scrollTop));
40 > g(a.top - posy) + g(a.left - posx) && (t(), w())
}
var c = 90,
r = c * v,
f = 0,
q = {
1: 270,
2: 90,
4: 0,
8: 180,
5: 315,
9: 225,
6: 45,
10: 135
}, d = 0,
u = 10,
m = 0,
l = 0,
s = 50 * Math.random(),
j = !1,
a = null,
n = void 0,
x = {
Moz: function (b) {
a.style.MozTransform = b
},
Webkit: function (b) {
a.style.WebkitTransform = b
},
O: function (b) {
a.style.OTransform = b
},
Ms: function (b) {
a.style.msTransform = b
},
Khtml: function (b) {
a.style.KhtmlTransform = b
},
w3c: function (b) {
a.style.transform = b
}
}, h = Math.round,
g = Math.abs,
v = Math.PI / 180;
setTimeout(function () {
a = document.createElement("img");
a.src = "https://screen-bug.googlecode.com/git/fruitfly.png";
(new Image).src = "https://screen-bug.googlecode.com/git/fruitfly.png";
a.style.position = "fixed";
a.style.zIndex = "99";
a.top = e(50, document.documentElement.clientHeight - 50);
a.top = 0 < a.top ? a.top : -a.top;
a.left = e(50, document.documentElement.clientWidth - 50);
a.left = 0 < a.left ? a.left : -a.left;
a.style.top = a.top + "px";
a.style.left = a.left + "px";
document.body.appendChild(a);
m = h(10 * Math.random());
l = h(40 * Math.random());
"transform" in a.style && (n = x.w3c);
var b = ["Moz", "Webkit", "O", "Ms", "Khtml"];
for (i in b) if (b[i] + "Transform" in a.style) {
n = x[b[i]];
break
}
c = e(0, 360);
n && setInterval(y, 100)
}, 2E3)
})();
And I want to put this below it:
<script type="text/javascript">
$("#bedbug").click(function() {
alert("ARGHHH!!!!");
});
</script>
Eventually once it works, I'm going to have the click function do a lot more, but I just need to get it working first.
Thanks to all in advance, I appreciate all the assistance you guys (and gals) provide. Cheers!
It is not loaded when you try to reference it. Use on() to your advantage
$(document).on("click", "#bedbug", function() {
alert("ARGHHH!!!!");
});