i am trying to create image filters using WebGL. i have found this library WebGLImageFilter.The filter effect I am trying to get is Rise Effect
from CSSGram. Here is the source code for that filter.
it basically does this:
apply brightness(1.05)
apply sepia(0.2)
apply contrast(0.9)
apply saturate(0.9)
In the give order, so i replicate this using the library i mentioned.
const image = new Image();
image.crossOrigin = "anonymous";
image.src = "https://i.imgur.com/TSiyiJv.jpg";
image.onload = function() {
try {
var filter = new WebGLImageFilter();
}
catch( err ) {
console.log(err)
}
filter.addFilter('brightness',1.05);
filter.addFilter('sepia',0.2);
filter.addFilter('contrast',0.9);
filter.addFilter('saturation',0.9);
var filteredImage = filter.apply(image);
document.body.appendChild(filteredImage);
}
<script src="https://cdn.rawgit.com/phoboslab/WebGLImageFilter/e0eee0cd/webgl-image-filter.js"></script>
But this gives a very different image effect than the mentioned one.so I tried to apply each effect individually and each of them works perfectly but when I combine them I get a different effect than the one I am trying to achieve. what could be the reason?
The library you're using doesn't take the same values as input.
For example CSS saturation takes a value 0 to 1 for how saturated. In other words 0 = no saturation, 1 = full saturation where as the library you linked to takes a positive or negative value for how much to change the saturation. In other words 0 = don't change the saturation. 1 = apply 1 amount of a saturation (amount being however much the library uses by default) and -1 remove one unit of saturation
Also AFAIK the sepia filter doesn't take an input. No matter what value you pass in for sepia it just makes the picture the same amount of sepia.
const image = new Image();
image.crossOrigin = "anonymous";
image.src = "https://i.imgur.com/TSiyiJv.jpg";
image.onload = function() {
for (var i = 0; i < 1; i += 0.25) {
var filter = new WebGLImageFilter();
filter.addFilter('sepia', i);
var filteredImage = filter.apply(image);
document.body.appendChild(filteredImage);
}
}
canvas { width: 100px; margin: 5px }
<script src="https://cdn.rawgit.com/phoboslab/WebGLImageFilter/e0eee0cd/webgl-image-filter.js"></script>
Looking inside the library most of the filters are based on a 5x4 color matrix where the identity (the matrix that leaves the colors as they are) is
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0
For sepia it's just hard coded to
0.393, 0.7689999, 0.18899999, 0, 0,
0.349, 0.6859999, 0.16799999, 0, 0,
0.272, 0.5339999, 0.13099999, 0, 0,
0,0,0,1,0
The shader being used looks like this
precision highp float;
varying vec2 vUv;
uniform sampler2D texture;
uniform float m[20];
void main(void) {
vec4 c = texture2D(texture, vUv);
gl_FragColor.r = m[0] * c.r + m[1] * c.g + m[2] * c.b + m[3] * c.a + m[4];
gl_FragColor.g = m[5] * c.r + m[6] * c.g + m[7] * c.b + m[8] * c.a + m[9];
gl_FragColor.b = m[10] * c.r + m[11] * c.g + m[12] * c.b + m[13] * c.a + m[14];
gl_FragColor.a = m[15] * c.r + m[16] * c.g + m[17] * c.b + m[18] * c.a + m[19];
}
Which if I understand correctly, reading cross the rows for sepia, means
new red = 39% red, 77% green, 19% blue
new green = 35% red, 69% green, 17% blue
new blue = 27% red, 53% green, 13% blue
new alpha = alpha
So to actually be able to set the amount you need have it be the identity matrix when amount = 0 and the sepia matrix when amount = 1. Fortunately it looks like there's a colorMatrix filter where you can pass in your own matrix. Let's try it
const identity = [
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
];
const sepia = [
0.393, 0.7689999, 0.18899999, 0, 0,
0.349, 0.6859999, 0.16799999, 0, 0,
0.272, 0.5339999, 0.13099999, 0, 0,
0,0,0,1,0,
];
const image = new Image();
image.crossOrigin = "anonymous";
image.src = "https://i.imgur.com/TSiyiJv.jpg";
image.onload = function() {
for (var i = 0; i <= 1; i += 0.25) {
var filter = new WebGLImageFilter();
filter.addFilter('colorMatrix', mix(identity, sepia, i));
var filteredImage = filter.apply(image);
document.body.appendChild(filteredImage);
}
}
function mix(m1, m2, amount) {
return m1.map((a, ndx) => {
const b = m2[ndx];
return a + (b - a) * amount;
});
}
canvas { width: 100px; margin: 5px }
<script src="https://cdn.rawgit.com/phoboslab/WebGLImageFilter/e0eee0cd/webgl-image-filter.js"></script>
Which seems to work?
const identity = [
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
];
const sepia = [
0.393, 0.7689999, 0.18899999, 0, 0,
0.349, 0.6859999, 0.16799999, 0, 0,
0.272, 0.5339999, 0.13099999, 0, 0,
0,0,0,1,0,
];
const image = new Image();
image.crossOrigin = "anonymous";
image.src = "https://cdn.rawgit.com/una/CSSgram/6f21810a/site/img/atx.jpg";
image.onload = function() {
try {
var filter = new WebGLImageFilter();
}
catch( err ) {
console.log(err)
}
filter.addFilter('brightness',-0.05); // 1.05);
// filter.addFilter('sepia',0.2);
filter.addFilter('colorMatrix', mix(identity, sepia, 0.2));
filter.addFilter('contrast', -0.1); // 0.9);
filter.addFilter('saturation', -0.1); //0.9);
var filteredImage = filter.apply(image);
document.body.appendChild(image);
document.body.appendChild(filteredImage);
}
function mix(m1, m2, amount) {
return m1.map((a, ndx) => {
const b = m2[ndx];
return a + (b - a) * amount;
});
}
img, canvas {
width: 300px;
margin: 5px;
}
<script src="https://cdn.rawgit.com/phoboslab/WebGLImageFilter/e0eee0cd/webgl-image-filter.js"></script>
Related
I have an image like this:
and like to find a specific color in the image (#ffa400 / #ffffff) and get their percentage occurrence value (orange maybe: 75%, white: 25%)
NOTE: Keep in mind that I don't like to get the average color (in this case: orange) like Color Thief does, but I'D like to find a SPECIFIC color.
Below is a snippet, so this is all that I got so far, but the code doesn't seem working & my idea also doesn't seem like an efficient & fast way.
function extract_colors(img) {
var canvas = document.createElement("canvas");
var c = canvas.getContext('2d');
c.width = canvas.width = img.width;
c.height = canvas.height = img.height;
c.clearRect(0, 0, c.width, c.height);
c.drawImage(img, 0, 0, img.width, img.height);
return getColors(c);
}
function getColors(c) {
var col, colors = {};
var pixels, r, g, b, a;
r = g = b = a = 0;
pixels = c.getImageData(0, 0, c.width, c.height);
for (var i = 0, data = pixels.data; i < data.length; i + = 4) {
r = data[i];
g = data[i + 1];
b = data[i + 2];
a = data[i + 3];
if (a < (255 / 2))
continue;
col = rgbToHex(r, g, b);
if (col == 'ffa400') { // find color #ffa400
if (!colors[col])
colors[col] = 0;
colors[col] + +;
}
}
return colors;
}
function rgbToHex(r, g, b) {
return ((r << 16) | (g << 8) | b).toString(16);
}
var img = document.getElementById('img');
out.innerHTML = extract_colors(img)
<img id='img' width=100 src='https://i.stack.imgur.com/EPDlQ.png'>
<p id='out'>...</p>
EDIT 1.0
I'm trying to use a code like this, but it doesn't seems to work either.
var orangeMatches=0, whiteMatches=0
Jimp.read("https://i.stack.imgur.com/EPDlQ.png").then(function (image) {
image.scan(0, 0, image.bitmap.width, image.bitmap.height, function (x, y, idx) {
var red = this.bitmap.data[ idx + 0 ];
var green = this.bitmap.data[ idx + 1 ];
var blue = this.bitmap.data[ idx + 2 ];
var alpha = this.bitmap.data[ idx + 3 ];
if (red == 255 && green == 164 && blue == 0 && alpha == 1){
orangeMatches++;
}
if (red == 255 && green == 255 && blue == 255 && alpha == 1){
whiteMatches++;
}
});
console.log(orangeMatches, whiteMatches)
});
Edit 2.0
I'd like to have some kind of tolerant value (10%) so that there is not every color to be counted, but the colors (e.g #ffa400 #ffa410) that are matching together are counted together.
A few notes
The main color in your picture is actually ffa500, not ffa400. There are in fact no occurances of ffa400 at all.
Also keep in mind that this is an anti-aliased bitmap graphic. Visually we see only two colors, but to smoothen the transition from one color to another a set of "in-between-colors" are generated. In other words, the sum of ffffff and ffa500 will not be exactly 100%. (This is why the output from my function below will display a lot more colors than the two in question)
Solution 1
The basic idea of this solution is to get every color in the picture, store these in one common object where the occurrence of each color is counted. When this object is returned, we can later count the occurrence of some specific color from the output.
This will return something like:
{
ffa500: 7802,
ffa501: 4,
ffa502: 2,
...,
ffffff: 1919,
total: 10000
}
Now you can access the occurences of a given color this way:
var imageData = extract_colors(img),
white = imageData.ffffff,
total = imageData.total;
And to display how many percent of the image a given color covers, do something like this (toFixed() is simply to limit the value to two decimals):
whitePct = (white / total * 100).toFixed(2);
Working sample
function getColors(ctx) {
// Get the canvas data
var pixels = ctx.getImageData(0, 0, ctx.width, ctx.height),
data = pixels.data,
// Set up our output object to collect color data
output = {};
// For each color we encounter, check the
// output object. If the color already exists
// there, simply increase its counted value.
// If it does not, create a new key.
for (var i = 0; i < data.length; i+=4) {
var r = data[i],
g = data[i + 1],
b = data[i + 2],
col = rgbToHex(r, g, b);
if( output[col] )
output[col]++
else
output[col] = 1
}
// Count total
var total = 0;
for(var key in output) {
total = total + parseInt(output[key])
}
output.total = total;
// Return the color data as an object
return output;
}
// Our elements
var img = document.getElementById('img'),
out = document.getElementById('out');
// Retrieve the image data
var imageData = extract_colors(img),
// Count our given colors
white = imageData.ffffff,
orange = imageData.ffa500,
total = imageData.total,
// Calculate percentage value
whitePct = (white / total * 100).toFixed(2),
orangePct = (orange / total * 100).toFixed(2);
out.innerHTML = `White: ${whitePct}%<br/>Orange: ${orangePct}%`;
// See the console for all colors identified
console.log(extract_colors(img))
// ----- These functions are left untouched ----- \\
function extract_colors(img) {
var canvas = document.createElement("canvas");
var c = canvas.getContext('2d');
c.width = canvas.width = img.width;
c.height = canvas.height = img.height;
c.clearRect(0, 0, c.width, c.height);
c.drawImage(img, 0, 0, img.width, img.height);
return getColors(c);
}
function rgbToHex(r, g, b) {
return ((r << 16) | (g << 8) | b).toString(16);
}
p, img {
margin: 10px 5px;
float: left
}
<!-- Image converted to base64 to keep it locally availible -->
<img id='img' width=100 src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAZdEVYdFNvZnR3YXJlAEFkb2JlIEltYWdlUmVhZHlxyWU8AAADImlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo4QUUwNzk4N0I2QjIxMUUzQUQzMkIzMTA4REQ0Nzc1MiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo4QUUwNzk4OEI2QjIxMUUzQUQzMkIzMTA4REQ0Nzc1MiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkI1NDU5MjAyQjZCMTExRTNBRDMyQjMxMDhERDQ3NzUyIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjhBRTA3OTg2QjZCMjExRTNBRDMyQjMxMDhERDQ3NzUyIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwv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'>
<p id='out'>...</p>
Solution 2
(Consider this solution a draft. It might need some tweaking if to be used for a live website, but at least it should work!)
The idea here is to call extract_colors() with the colors we want to map as arguments. A common object is built to hold the output, and this is populated with one object for each color. The color objects consists of a counter and an rgb value. Using the isNeighborColor() function, the rgb value is used to match not only the specific color but also any color close to it. Adjust the tolerance level as you want. (Ideally this would be another argument I guess).
Call the function specifying the image and what colors to count the occurrence of:
var imageData = extract_colors(img, 'ffffff', 'ffa500'),
This will output an object for each color:
{
"ffffff": {
"counter": 1979,
"rgb": {
"r": 255,
"g": 255,
"b": 255
}
},
"ffa500": {
"counter": 7837,
"rgb": {
"r": 255,
"g": 165,
"b": 0
}
},
"total": 9816
}
What we want is to access the counter values:
var white = imageData.ffffff.counter;
Working sample
// Added a rest parameter for unlimited color input
function extract_colors(img, ...colors) {
var canvas = document.createElement("canvas");
var c = canvas.getContext('2d');
c.width = canvas.width = img.width;
c.height = canvas.height = img.height;
c.clearRect(0, 0, c.width, c.height);
c.drawImage(img, 0, 0, img.width, img.height);
return getColors(c, ...colors);
}
// Unchanged
function rgbToHex(r, g, b) {
return ((r << 16) | (g << 8) | b).toString(16);
}
// From: https://stackoverflow.com/a/5624139/2311559
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
// From: https://stackoverflow.com/a/11506531/2311559
// Slightly modified
function isNeighborColor(color1, color2, tolerance) {
tolerance = tolerance || 32;
return Math.abs(color1.r - color2.r) <= tolerance
&& Math.abs(color1.g - color2.g) <= tolerance
&& Math.abs(color1.b - color2.b) <= tolerance;
}
function getColors(ctx, ...colors) {
var pixels = ctx.getImageData(0, 0, ctx.width, ctx.height),
data = pixels.data,
output = {};
// Build an 'output' object. This will hold one object for
// each color we want to check. The color objects will
// each consist of a counter and an rgb value. The counter
// is used to count the occurrence of each color. The rgb
// value is used to match colors with a certain tolerance
// using the 'isNeighborColor()' function above.
for (var i = 0; i < colors.length; i++) {
output[colors[i]] = {
counter: 0,
rgb: hexToRgb(colors[i])
};
}
// For each pixel, match its color against the colors in our
// 'output' object. Using the 'isNeighborColor()' function
// we will also match colors close to our input, given the
// tolerance defined.
for (var i = 0; i < data.length; i+=4) {
var r = data[i],
g = data[i + 1],
b = data[i + 2],
colobj = {r, g, b},
col = rgbToHex(r, g, b);
Object.keys(output).map(function(objectKey, index) {
var rgb = output[objectKey].rgb,
count = output[objectKey].counter;
if(isNeighborColor(rgb, colobj)) {
output[objectKey].counter = count+1;
}
});
}
// Count total
var total = 0;
for(var key in output) {
total = total + parseInt(output[key].counter)
}
output.total = total;
// Return the color data as an object
return output;
}
// Our elements
var img = document.getElementById('img'),
out = document.getElementById('out');
// Retrieve the image data
var imageData = extract_colors(img,'ffffff','ffa500'),
// Count our given colors
white = imageData.ffffff.counter,
orange = imageData.ffa500.counter,
total = imageData.total,
// Calculate percentage value
whitePct = (white / total * 100).toFixed(2),
orangePct = (orange / total * 100).toFixed(2);
out.innerHTML = `White: ${whitePct}%<br/>Orange: ${orangePct}%`;
// Optional console output
console.log(extract_colors(img,'ffffff','ffa500'));
p, img {
margin: 10px 5px;
float: left
}
<!-- Image converted to base64 to keep it locally availible -->
<img id='img' width=100 src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAZdEVYdFNvZnR3YXJlAEFkb2JlIEltYWdlUmVhZHlxyWU8AAADImlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo4QUUwNzk4N0I2QjIxMUUzQUQzMkIzMTA4REQ0Nzc1MiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo4QUUwNzk4OEI2QjIxMUUzQUQzMkIzMTA4REQ0Nzc1MiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkI1NDU5MjAyQjZCMTExRTNBRDMyQjMxMDhERDQ3NzUyIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjhBRTA3OTg2QjZCMjExRTNBRDMyQjMxMDhERDQ3NzUyIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwv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'>
<p id='out'>...</p>
I've been playing around with vertexshaderart.com and I'd like to use what I learned on a separate website. While I have used shaders before, some effects achieved on the site depend on having access to vertices/lines/triangles. While passing vertices is easy enough (at least it was with THREE.js, though it is kind of an overkill for simple shaders, but in some cases in need shader materials too), creating triangles seems a bit more complex.
I can't figure it out from the source, how exactly are triangles created there, when you switch the mode here?
I'd like to replicate that behavior but I honestly have no idea how to approach it. I could just create a number of triangles through THREE but with so many individual objects performance takes a hit rapidly. Are the triangles created here separate entities or are they a part of one geometry?
vertexshaderart.com is more of a puzzle, toy, art box, creative coding experiment than an example of the good WebGL. The same is true of shadertoy.com. An example like this is beautiful but it runs at 20fps in it's tiny window and about 1fps fullscreen on my 2014 Macbook Pro and yet my MBP can play beautiful games with huge worlds rendered fullscreen at 60fps. In other words, the techniques are more for art/fun/play/mental exercise and for the fun of trying to make things happen with extreme limits than to actually be good techniques.
The point I'm trying to make is both vertexshaderart and shadertoy are fun but impractical.
The way vertexshaderart works is it provides a count vertexId that counts vertices. 0 to N where N is the count setting the top of the UI. For each count you output gl_Position and a v_color (color).
So, if you want to draw something you need to provide the math to generate vertex positions based on the count. For example let's do it using Canvas 2D first
Here's a fake JavaScript vertex shader written in JavaScript that given nothing but vertexId will draw a grid 1 unit high and N units long where N = the number of vertices (vertexCount) / 6.
function ourPseudoVertexShader(vertexId, time) {
// let's compute an infinite grid of points based off vertexId
var x = Math.floor(vertexId / 6) + (vertexId % 2);
var y = (Math.floor(vertexId / 2) + Math.floor(vertexId / 3)) % 2;
// color every other triangle red or green
var triangleId = Math.floor(vertexId / 3);
var color = triangleId % 2 ? "#F00" : "#0F0";
return {
x: x * 0.2,
y: y * 0.2,
color: color,
};
}
We call it from a loop supplying vertexId
for (var count = 0; count < vertexCount; count += 3) {
// get 3 points
var position0 = ourPseudoVertexShader(count + 0, time);
var position1 = ourPseudoVertexShader(count + 1, time);
var position2 = ourPseudoVertexShader(count + 2, time);
// draw triangle
ctx.beginPath();
ctx.moveTo(position0.x, position0.y);
ctx.lineTo(position1.x, position1.y);
ctx.lineTo(position2.x, position2.y);
ctx.fillStyle = position0.color;
ctx.fill();
}
If you run it here you'll see a grid 1 unit high and N units long. I've set the canvas origin so 0,0 is in the center just like WebGL and so the canvas is addressed +1 to -1 across and +1 to -1 down
var vertexCount = 100;
function ourPseudoVertexShader(vertexId, time) {
// let's compute an infinite grid of points based off vertexId
var x = Math.floor(vertexId / 6) + (vertexId % 2);
var y = (Math.floor(vertexId / 2) + Math.floor(vertexId / 3)) % 2;
// color every other triangle red or green
var triangleId = Math.floor(vertexId / 3);
var color = triangleId % 2 ? "#F00" : "#0F0";
return {
x: x * 0.2,
y: y * 0.2,
color: color,
};
}
var ctx = document.querySelector("canvas").getContext("2d");
requestAnimationFrame(render);
function render(time) {
time *= 0.001;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.save();
ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
ctx.scale(ctx.canvas.width / 2, -ctx.canvas.height / 2);
// lets assume triangles
for (var count = 0; count < vertexCount; count += 3) {
// get 3 points
var position0 = ourPseudoVertexShader(count + 0, time);
var position1 = ourPseudoVertexShader(count + 1, time);
var position2 = ourPseudoVertexShader(count + 2, time);
// draw triangle
ctx.beginPath();
ctx.moveTo(position0.x, position0.y);
ctx.lineTo(position1.x, position1.y);
ctx.lineTo(position2.x, position2.y);
ctx.fillStyle = position0.color;
ctx.fill();
}
ctx.restore();
requestAnimationFrame(render);
}
canvas { border: 1px solid black; }
<canvas width="500" height="200"></canvas>
Doing the same thing in WebGL means making a buffer with the count
var count = [];
for (var i = 0; i < vertexCount; ++i) {
count.push(i);
}
Then putting that count in a buffer and using that as an attribute for a shader.
Here's the equivalent shader to the fake shader above
attribute float vertexId;
uniform float time;
varying vec4 v_color;
void main() {
// let's compute an infinite grid of points based off vertexId
float x = floor(vertexId / 6.) + mod(vertexId, 2.);
float y = mod(floor(vertexId / 2.) + floor(vertexId / 3.), 2.);
// color every other triangle red or green
float triangleId = floor(vertexId / 3.);
v_color = mix(vec4(0, 1, 0, 1), vec4(1, 0, 0, 1), mod(triangleId, 2.));
gl_Position = vec4(x * 0.2, y * 0.2, 0, 1);
}
If we run that we'll get the same result
var vs = `
attribute float vertexId;
uniform float vertexCount;
uniform float time;
varying vec4 v_color;
void main() {
// let's compute an infinite grid of points based off vertexId
float x = floor(vertexId / 6.) + mod(vertexId, 2.);
float y = mod(floor(vertexId / 2.) + floor(vertexId / 3.), 2.);
// color every other triangle red or green
float triangleId = floor(vertexId / 3.);
v_color = mix(vec4(0, 1, 0, 1), vec4(1, 0, 0, 1), mod(triangleId, 2.));
gl_Position = vec4(x * 0.2, y * 0.2, 0, 1);
}
`;
var fs = `
precision mediump float;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
`;
var vertexCount = 100;
var gl = document.querySelector("canvas").getContext("webgl");
var count = [];
for (var i = 0; i < vertexCount; ++i) {
count.push(i);
}
var bufferInfo = twgl.createBufferInfoFromArrays(gl, {
vertexId: { numComponents: 1, data: count, },
});
var programInfo = twgl.createProgramInfo(gl, [vs, fs]);
var uniforms = {
time: 0,
vertexCount: vertexCount,
};
requestAnimationFrame(render);
function render(time) {
uniforms.time = time * 0.001;
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, gl.TRIANGLES, bufferInfo);
requestAnimationFrame(render);
}
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<canvas width="500" height="200"></canvas>
Everything else on vertexshartart is just creative math to make interesting patterns. You can use time to do animation. a texture with sound data is also provided.
There are some tutorials here
So, in answer to your question, when you switch modes (triangles/lines/points) on vertexshaderart.com all that does is change what's passed to gl.drawArrays (gl.POINTS, gl.LINES, gl.TRIANGLES). The points themselves are generated in the vertex shader like the example above.
So that leaves the question, what specific effect are you trying to achieve. Then we can know what to suggest to achieve it. You might want to ask a new question for that (so that this answer still matches the question above)
Is there a algorithm to get the line strokes of a image (ignore curves, circles, etc., everything will be treated as lines, but still similiar to vectors), from their pixels? Then get a result of them, like a Array?
This is how it'd basically work to read
In this way, each row of pixel would be read as 1 horizontal line and I'd like to handle vertical lines also; but if there's a round fat line that takes more than 1 row
it'll be considered one line. Its line width is the same height of pixels it has.
For instance, let's suppose we've a array containing rows of pixels in the (red, green, blue, alpha) format (JavaScript):
/* formatted ImageData().data */
[
new Uint8Array([
/* first pixel */
255, 0, 0, 255,
/* second pixel */
255, 0, 0, 255
]),
new Uint8Array([
/* first pixel */
0, 0, 0, 0,
/* second pixel */
0, 0, 0, 0
])
]
This would be a 2x2px image data, with a straight horizontal red line. So, from this array, I want to get a array containing data of lines, like:
[
// x, y: start point
// tx, ty: end point
// w: line width
// the straight horizontal red line of 1 pixel
{ x: 0, y: 0, tx: 2, ty: 0, w: 1, rgba: [255, 0, 0, 255] }
]
Note: I'd like to handle anti-aliasing.
This is my function to read pixels in the above format:
var getImagePixels = function(img){
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
var imgData = ctx.getImageData(0, 0, img.width, img.height).data;
var nImgData = [];
var offWidth = img.width * 4;
var dataRow = (nImgData[0] = new Uint8Array(offWidth));
for (var b = 0, i = 0; b++ < img.height;) {
nImgData[b] = new Uint8Array(offWidth);
for (var arrI = 0, len = i + offWidth; i < len; i += 4, arrI += 4) {
nImgData[b][arrI] = imgData[i];
nImgData[b][arrI + 1] = imgData[i + 1];
nImgData[b][arrI + 2] = imgData[i + 2];
nImgData[b][arrI + 3] = imgData[i + 3];
}
}
return nImgData;
};
You can find all lines using Hough transform. It will find only lines, no curves or circles. You may need to run edge detection before finding lines. Here is example:
Here you can find opencv example of implementation.
I had a similar image processing question once, you can read it here. But you can basically take the same idea for anything you want to do with an image.
The basic data can be seen as follows:
var img = new Image,
w = canvas.width,
h = canvas.height,
ctx = canvas.getContext('2d');
img.onload = imgprocess;
img.src = 'some.png';
function imgprocess() {
ctx.drawImage(this, 0, 0, w, h);
var idata = ctx.getImageData(0, 0, w, h),
buffer = idata.data,
buffer32 = new Uint32Array(buffer.buffer),
x, y,
x1 = w, y1 = h, x2 = 0, y2 = 0;
//You now have properties of the image from the canvas data. You will need to write your own loops to detect which pixels etc... See the example in the link for some ideas.
}
UPDATE:
Working example of finding color data;
var canvas = document.getElementById('canvas');
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var ctx = canvas.getContext('2d');
var imageData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
var buf = new ArrayBuffer(imageData.data.length);
var buf8 = new Uint8ClampedArray(buf);
var data = new Uint32Array(buf);
for (var y = 0; y < canvasHeight; ++y) {
for (var x = 0; x < canvasWidth; ++x) {
var value = x * y & 0xff;
data[y * canvasWidth + x] =
(255 << 24) | // alpha
(value << 16) | // blue
(value << 8) | // green
value; // red
}
}
More examples can be seen here
The author above outlines pixel and line data:
The ImageData.data property referenced by the variable data is a one-dimensional array of integers, where each element is in the range 0..255. ImageData.data is arranged in a repeating sequence so that each element refers to an individual channel. That repeating sequence is as follows:
data[0] = red channel of first pixel on first row
data[1] = green channel of first pixel on first row
data[2] = blue channel of first pixel on first row
data[3] = alpha channel of first pixel on first row
data[4] = red channel of second pixel on first row
data[5] = green channel of second pixel on first row
data[6] = blue channel of second pixel on first row
data[7] = alpha channel of second pixel on first row
data[8] = red channel of third pixel on first row
data[9] = green channel of third pixel on first row
data[10] = blue channel of third pixel on first row
data[11] = alpha channel of third pixel on first row
I am trying to set the colors of all the pixels on the canvas. I'm pretty sure I know how it works: first 4 values in canvas.getContext("2d").getImageData.data refer to the r, g, b, and a of the first pixel, second 4 go to second pixel, and so on. I would like to know what is wrong with this code:
function draw()
{
var cnvs = document.getElementById("CanvasFlynn"); //Assume it's 2x2 pixels
var cont = cnvs.getContext("2d");
var imdt = cont.getImageData(0,0,2,2);
var r = [ 255 , 0 , 0 , 255 ];
var g = [ 0 , 255 , 0 , 255 ];
var b = [ 0 , 0 , 255 , 255 ];
var a = [ 1 , 1 , 1 , 1 ];
var index = 0;
for ( var i = 0 ; i < imdt.data.length ; i++ )
{
index = 4*i;
imdt.data[index] = r[i];
imdt.data[index+1] = g[i];
imdt.data[index+2] = b[i];
imdt.data[index+3] = a[i];
}
}
If anyone could tell me why this doesn't change the canvases pixel colors, that would rock (I don't actually use a 2x2 cavas, that was just to serve as an example).
There's a bunch of issues
cont.getImageData is a function not a property
imdt isn't the canvas's image data, it's a copy of the canvas's image data.
You need to call cont.putImageData to copy the data back into the canvas
imdt.data.length is a count of bytes not pixels. There are 4 bytes per pixel.
alpha is in the range of 0 to 255 in image data
function draw()
{
var cnvs = document.getElementById("CanvasFlynn"); //Assume it's 2x2 pixels
var cont = cnvs.getContext("2d");
var imdt = cont.getImageData(0, 0, 2, 2);
var r = [ 255 , 0 , 0 , 255 ];
var g = [ 0 , 255 , 0 , 255 ];
var b = [ 0 , 0 , 255 , 255 ];
var a = [ 255 , 255 ,255 ,255 ];
var index = 0;
for ( var i = 0 ; i < imdt.data.length / 4 ; i++ )
{
index = 4*i;
imdt.data[index] = r[i];
imdt.data[index+1] = g[i];
imdt.data[index+2] = b[i];
imdt.data[index+3] = a[i];
}
cont.putImageData(imdt, 0, 0);
}
draw();
<p>Note the 2x2 canvas will be stretched to 100x100 and may be bilinear filtered</p>
<canvas id="CanvasFlynn" width="2" height="2"
style="width: 100px; height: 100px; border: 1px solid black; image-rendering: pixelated;"></canvas>
note that it would arguable be faster to treat the data as 32bit values so then there is one value per pixel
function draw()
{
var cnvs = document.getElementById("CanvasFlynn"); //Assume it's 2x2 pixels
var cont = cnvs.getContext("2d");
var imdt = cont.getImageData(0, 0, 2, 2);
// make a Uint32 view of the image data
var pixels = new Uint32Array(imdt.data.buffer);
var r = 0xFF0000FF;
var g = 0xFF00FF00;
var b = 0xFFFF0000;
var y = 0xFF00FFFF;
pixels[0] = r;
pixels[1] = g;
pixels[2] = b;
pixels[3] = y;
cont.putImageData(imdt, 0, 0);
}
draw();
<p>Note the 2x2 canvas will be stretched to 100x100 and may be bilinear filtered</p>
<canvas id="CanvasFlynn" width="2" height="2"
style="width: 100px; height: 100px; border: 1px solid black; image-rendering: pixelated;"></canvas>
I am trying to achieve following effect using css3 and javascript when we move mouse to center div (MouseOver effect)
I have created small library which accepts 3 arguments element,sourcePoints,destination points and returns css3D matrix and update element. here is my javascript code.
var BLEND = BLEND || {};
BLEND.Util = BLEND.Util || {};
function print(msg) {
console.log(msg);
}
BLEND.Util.VendorPrefix = "";
BLEND.Util.DetectVendorPrefix = function() {
var styles = window.getComputedStyle(document.documentElement, ''),
pre = (Array.prototype.slice.call(styles).join('').match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o']))[1];
BLEND.Util.VendorPrefix = pre[0].toUpperCase() + pre.substr(1) + "Transform";
}
BLEND.Util.DetectVendorPrefix();
BLEND.TransformElement = function(elm, src, dest) {
var L = [[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]];
var R = [0, 0, 0, 0, 0, 0, 0, 0];
for (var i = 0; i < 4; i++) {
L[i] = [];
L[i][0] = L[i + 4][3] = src[i].x;
L[i][2] = L[i + 4][4] = src[i].y;
L[i][2] = L[i + 4][5] = 1;
L[i][3] = L[i][4] = L[i][5] = L[i + 4][0] = L[i + 4][3] = L[i + 4][2] = 0;
L[i][6] = -src[i].x * dest[i].x;
L[i][7] = -src[i].y * dest[i].x;
L[i + 4][6] = -src[i].x * dest[i].y;
L[i + 4][7] = -src[i].y * dest[i].y;
R[i] = dest[i].x;
R[i + 4] = dest[i].y;
}
var RM = [];
for (i = 0; i < R.length; i++) {
RM[i] = [R[i]];
}
var Left = Matrix.create(L);
var Right = Matrix.create(R);
var res = Matrix.calculate(Left, Right);
print (res);
if (BLEND.Util.VendorPrefix == 'WebkitTransform') {
var matrix3D = new CSSMatrix();
matrix3D.m11 = res.get(0,0);
matrix3D.m12 = res.get(3,0);
matrix3D.m13 = 0;
matrix3D.m14 = res.get(6,0);
matrix3D.m21 = res.get(1,0);
matrix3D.m22 = res.get(4,0);
matrix3D.m23 = 0;
matrix3D.m24 = res.get(7,0);
matrix3D.m31 = 0;
matrix3D.m32 = 0;
matrix3D.m33 = 1;
matrix3D.m34 = 0;
matrix3D.m41 = res.get(2,0);
matrix3D.m42 = res.get(5,0);
matrix3D.m43 = 0;
matrix3D.m44 = 1;
elm.style.webkitTransform = matrix3D;
} else {
if (BLEND.Util.VendorPrefix === "")
BLEND.Util.DetectVendorPrefix();
elm.style[BLEND.Util.VendorPrefix] = "matrix3d(" + res.get(0,0) + "," + res.get(3,0) + ", 0," + res.get(6,0) + "," + res.get(1,0) + "," + res.get(4,0) + ", 0," + res.get(7,0) + ",0, 0, 1, 0," + res.get(2,0) + "," + res.get(5,0) + ", 0, 1)";
}
}
UPDATE: Here is JSFiddle
I am calling TransformElement method for each of 9 div with proper source and destination coordinates. But its not working as expected. Please suggest the possible solution.
can we do it using three.js in anyway (just asking may be its silly idea)?
UPDATE: Can we do it with CSS3D renderer and Three.js.
Idea is to create plane and slice it in 3x3 grid and on mouse over of each face of plane we can scale that div upside and respectivly we have to scale others div according to current div? Is it possible?
I didn't try to use your library but here is my solution to your problem: http://codepen.io/bali_balo/pen/87b980a2acf722b1c9feb35f3fcb1c65/ (I used Haml and SCSS, you can see compiled code by clicking the little eye on the top right corner of each panel)
I used this article to write this code.
9 matrices are computed first (corresponding to the hovered tile and every surrounding ones), using numericjs and fomulas available on the article linked before. Then on mouseover these matrices are applied to the corresponding tiles. Here is the code to get the transform matrix knowing the locations of 4 points before and after transformation:
//from and to are arrays of 4 objects containing a property x and a property y
//describing the 4 points of the quadrilateral to transform
function getTransform(from, to)
{
var A = [], i;
for(i = 0; i < 4; i++)
{
A.push([from[i].x, from[i].y, 1, 0, 0, 0, -from[i].x * to[i].x, -from[i].y * to[i].x]);
A.push([0, 0, 0, from[i].x, from[i].y, 1, -from[i].x * to[i].y, -from[i].y * to[i].y]);
}
var b = [];
for(i = 0; i < 4; i++)
{
b.push(to[i].x);
b.push(to[i].y);
}
//Uses numericjs to solve matrices equations
//returns h knowing A and b where A.h = b
var h = numeric.solve(A, b);
//Column major representation
return [[h[0], h[3], 0, h[6]],
[h[1], h[4], 0, h[7]],
[ 0 , 0 , 1, 0 ],
[h[2], h[5], 0, 1 ]];
}
Note that, as mentionned in my code, if you want to animate transitions, you can't just use CSS (as it would just transition each number of the matrix, but this would rarely give an appropriate result). You could try doing it using javascript but it might be a bit slow (I didn't test it), because you could not cache matrices and would have to compute them at every frame.