I'm working on another tunnel effect demo. This time I'm trying to make the tunnel move within the image.
However, the function that handles rendering the tunnel always throws an error, and I'm not entirely sure why:
function draw(time) {
let animation = time / 1000.0;
let shiftX = ~~(texWidth * animation);
let shiftY = ~~(texHeight * 0.25 * animation);
let shiftLookX = (screenWidth / 2) + ~~(screenWidth / 2 * Math.sin(animation))
let shiftLookY = (screenHeight / 2) + ~~(screenHeight / 2 * Math.sin(animation))
for (y = 0; y < buffer.height; y++) {
for (x = 0; x < buffer.width; x++) {
let id = (y * buffer.width + x) * 4;
let d = ~~(distanceTable[y + shiftLookY][x + shiftLookX] + shiftX) % texWidth;
let a = ~~(angleTable[y + shiftLookY][x + shiftLookX] + shiftY) % texHeight;
let tex = (a * texture.width + d) * 4;
buffer.data[id] = texture.data[tex];
buffer.data[id+1] = texture.data[tex+1];
buffer.data[id+2] = texture.data[tex+2];
buffer.data[id+3] = texture.data[tex+3];
}
}
ctx.putImageData(buffer, 0, 0);
window.requestAnimationFrame(draw);
}
The rest of the code is viewable here, just in case the problem happens to be somewhere else.
I have identified a possible cause -- if the first index used to read from distanceTable or
angleTable is anything other than y, the error appears, even if it's simply a value being added to y. Unfortunately, I haven't figured out what causes it, or why the second index isn't affected by this.
I've also searched for similar questions, but it seems like the people asking them all got this error for different reasons, so I'm kind of stuck.
It appears that setting the for loops to use the canvas' height and width as the upper limit instead of the pixel buffer's width and height was enough to fix it.
I have absolutely no idea why, though. Was it because the buffer was twice the size of the canvas?
var texWidth = 256;
var texHeight = 256;
var screenWidth = 640;
var screenHeight = 480;
var canvas = document.createElement('canvas');
canvas.width = screenWidth;
canvas.height = screenHeight;
var ctx = canvas.getContext("2d");
var texture = new ImageData(texWidth, texHeight);
var distanceTable = [];
var angleTable = [];
var buffer = new ImageData(canvas.width * 2, canvas.height * 2);
for (let y = 0; y < texture.height; y++) {
for (let x = 0; x < texture.width; x++) {
let id = (y * texture.width + x) * 4;
let c = x ^ y;
texture.data[id] = c;
texture.data[id+1] = c;
texture.data[id+2] = c;
texture.data[id+3] = 255;
}
}
for (let y = 0; y < buffer.height; y++) {
distanceTable[y] = [];
angleTable[y] = [];
let sqy = Math.pow(y - canvas.height, 2);
for (let x = 0; x < buffer.width; x++) {
let sqx = Math.pow(x - canvas.width, 2);
let ratio = 32.0;
let distance = ~~(ratio * texHeight / Math.sqrt(sqx + sqy)) % texHeight;
let angle = Math.abs(~~(0.5 * texWidth * Math.atan2(y - canvas.height, x - canvas.width)) / Math.PI);
distanceTable[y][x] = distance;
angleTable[y][x] = angle;
}
}
function draw(time) {
let animation = time / 1000.0;
let shiftX = ~~(texWidth * animation);
let shiftY = ~~(texHeight * 0.25 * animation);
let shiftLookX = (screenWidth / 2) + ~~(screenWidth / 2 * Math.sin(animation))
let shiftLookY = (screenHeight / 2) + ~~(screenHeight / 2 * Math.sin(animation * 2.0))
for (y = 0; y < canvas.height; y++) {
for (x = 0; x < canvas.width; x++) {
let id = (y * buffer.width + x) * 4;
let d = ~~(distanceTable[y + shiftLookY][x + shiftLookX] + shiftX) % texWidth;
let a = ~~(angleTable[y + shiftLookY][x + shiftLookX] + shiftY) % texHeight;
let tex = (a * texture.width + d) * 4;
buffer.data[id] = texture.data[tex];
buffer.data[id+1] = texture.data[tex+1];
buffer.data[id+2] = texture.data[tex+2];
buffer.data[id+3] = texture.data[tex+3];
}
}
ctx.putImageData(buffer, 0, 0);
window.requestAnimationFrame(draw);
}
document.body.appendChild(canvas);
window.requestAnimationFrame(draw);
Alright. I'm new to this, I'm not coder, just trying something for fun. And I'm confused.
I've found a tutorial about making a duotone image with canvas, and I'm so new to this that I can't figure out what I'm doing wrong. Maybe someone can help.
Here is my code. It displays the original image (demo_small.png), but doesn't show any of the effects on it. I guess that maybe I've to overwrite it after the last "return pixels", but I've not idea of what I'm doing so..
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
<script src="https://www.mattkandler.com/assets/application-9cbca3f8879431193adab436bd8e0cf7629ecf3752685f49f997ed4469f42826.js" type="text/javascript"></script>
</head>
<body>
<canvas id="idOfCanvasToDrawImageOn" width="img.width" height="img.height"></canvas>
<script>
//Getting the image pixels
var canvasId = 'idOfCanvasToDrawImageOn';
var imageUrl = 'demo_small.png';
var canvas = document.getElementById(canvasId);
var context = canvas.getContext('2d');
var img = new Image();
// img.crossOrigin = 'Anonymous';
img.onload = function() {
// Perform image scaling if desired size is given
var scale = 1;
context.canvas.width = img.width;
context.canvas.height = img.height;
context.scale(scale, scale);
// Draw image on canvas
context.drawImage(img, 0, 0);
// Perform filtering here
};
img.src = imageUrl;
//Then we'll need to grab the pixels from this newly created canvas image using the following function
Filters.getPixels = function(img) {
var c = this.getCanvas(img.width, img.height);
var ctx = c.getContext('2d');
ctx.drawImage(img, 0, 0);
return ctx.getImageData(0, 0, c.width, c.height);
};
//Converting to grayscale
Filters.grayscale = function(pixels) {
var d = pixels.data;
var max = 0;
var min = 255;
for (var i = 0; i < d.length; i += 4) {
// Fetch maximum and minimum pixel values
if (d[i] > max) {
max = d[i];
}
if (d[i] < min) {
min = d[i];
}
// Grayscale by averaging RGB values
var r = d[i];
var g = d[i + 1];
var b = d[i + 2];
var v = 0.3333 * r + 0.3333 * g + 0.3333 * b;
d[i] = d[i + 1] = d[i + 2] = v;
}
for (var i = 0; i < d.length; i += 4) {
// Normalize each pixel to scale 0-255
var v = (d[i] - min) * 255 / (max - min);
d[i] = d[i + 1] = d[i + 2] = v;
}
return pixels;
};
//Building a color gradient
Filters.gradientMap = function(tone1, tone2) {
var rgb1 = hexToRgb(tone1);
var rgb2 = hexToRgb(tone2);
var gradient = [];
for (var i = 0; i < (256 * 4); i += 4) {
gradient[i] = ((256 - (i / 4)) * rgb1.r + (i / 4) * rgb2.r) / 256;
gradient[i + 1] = ((256 - (i / 4)) * rgb1.g + (i / 4) * rgb2.g) / 256;
gradient[i + 2] = ((256 - (i / 4)) * rgb1.b + (i / 4) * rgb2.b) / 256;
gradient[i + 3] = 255;
}
return gradient;
};
//Applying the gradient
Filters.duotone = function(img, tone1, tone2) {
var pixels = this.getPixels(img);
pixels = Filters.grayscale(pixels);
var gradient = this.gradientMap(tone1, tone2);
var d = pixels.data;
for (var i = 0; i < d.length; i += 4) {
d[i] = gradient[d[i] * 4];
d[i + 1] = gradient[d[i + 1] * 4 + 1];
d[i + 2] = gradient[d[i + 2] * 4 + 2];
}
return pixels;
};
</script>
</body>
</html>
How to achieve a similar effect on an image instead of raw canvas?
/**
* Water ripple effect.
* Original code (Java) by Neil Wallis
* #link http://www.neilwallis.com/java/water.html
*
* #author Sergey Chikuyonok (serge.che#gmail.com)
* #link http://chikuyonok.ru
*/
(function(){
var canvas = document.getElementById('c'),
/** #type {CanvasRenderingContext2D} */
ctx = canvas.getContext('2d'),
width = 400,
height = 400,
half_width = width >> 1,
half_height = height >> 1,
size = width * (height + 2) * 2,
delay = 30,
oldind = width,
newind = width * (height + 3),
riprad = 3,
mapind,
ripplemap = [],
last_map = [],
ripple,
texture,
line_width = 20,
step = line_width * 2,
count = height / line_width;
canvas.width = width;
canvas.height = height;
/*
* Water ripple demo can work with any bitmap image
* (see example here: http://media.chikuyonok.ru/ripple/)
* But I need to draw simple artwork to bypass 1k limitation
*/
with (ctx) {
fillStyle = '#a2ddf8';
fillRect(0, 0, width, height);
fillStyle = '#07b';
save();
rotate(-0.785);
for (var i = 0; i < count; i++) {
fillRect(-width, i * step, width * 3, line_width);
}
restore();
}
texture = ctx.getImageData(0, 0, width, height);
ripple = ctx.getImageData(0, 0, width, height);
for (var i = 0; i < size; i++) {
last_map[i] = ripplemap[i] = 0;
}
/**
* Main loop
*/
function run() {
newframe();
ctx.putImageData(ripple, 0, 0);
}
/**
* Disturb water at specified point
*/
function disturb(dx, dy) {
dx <<= 0;
dy <<= 0;
for (var j = dy - riprad; j < dy + riprad; j++) {
for (var k = dx - riprad; k < dx + riprad; k++) {
ripplemap[oldind + (j * width) + k] += 512;
}
}
}
/**
* Generates new ripples
*/
function newframe() {
var i, a, b, data, cur_pixel, new_pixel, old_data;
i = oldind;
oldind = newind;
newind = i;
i = 0;
mapind = oldind;
// create local copies of variables to decrease
// scope lookup time in Firefox
var _width = width,
_height = height,
_ripplemap = ripplemap,
_mapind = mapind,
_newind = newind,
_last_map = last_map,
_rd = ripple.data,
_td = texture.data,
_half_width = half_width,
_half_height = half_height;
for (var y = 0; y < _height; y++) {
for (var x = 0; x < _width; x++) {
data = (
_ripplemap[_mapind - _width] +
_ripplemap[_mapind + _width] +
_ripplemap[_mapind - 1] +
_ripplemap[_mapind + 1]) >> 1;
data -= _ripplemap[_newind + i];
data -= data >> 5;
_ripplemap[_newind + i] = data;
//where data=0 then still, where data>0 then wave
data = 1024 - data;
old_data = _last_map[i];
_last_map[i] = data;
if (old_data != data) {
//offsets
a = (((x - _half_width) * data / 1024) << 0) + _half_width;
b = (((y - _half_height) * data / 1024) << 0) + _half_height;
//bounds check
if (a >= _width) a = _width - 1;
if (a < 0) a = 0;
if (b >= _height) b = _height - 1;
if (b < 0) b = 0;
new_pixel = (a + (b * _width)) * 4;
cur_pixel = i * 4;
_rd[cur_pixel] = _td[new_pixel];
_rd[cur_pixel + 1] = _td[new_pixel + 1];
_rd[cur_pixel + 2] = _td[new_pixel + 2];
}
++_mapind;
++i;
}
}
mapind = _mapind;
}
canvas.onmousemove = function(/* Event */ evt) {
disturb(evt.offsetX || evt.layerX, evt.offsetY || evt.layerY);
};
setInterval(run, delay);
// generate random ripples
var rnd = Math.random;
setInterval(function() {
disturb(rnd() * width, rnd() * height);
}, 700);
})();
<canvas id="c"></canvas>
Just look at this majestic unicorn: codepen. Data URI used to avoid CORS problems, it'll work with any number of images (all images by default).
JS:
window.addEventListener('load',()=>{
document.querySelectorAll('img').forEach((img)=>{
var cont = document.createElement('div');
cont.style.position = 'relative'
cont.style.display = 'inline-block'
img.parentNode.insertBefore(cont,img);
img.style.verticalAlign = 'top';
cont.appendChild(img)
console.dir(img)
var c = document.createElement('canvas');
c.width = img.clientWidth
c.height = img.clientHeight
c.style.position = 'absolute'
c.style.top = '0px'
c.style.left = '0px'
cont.appendChild(c)
console.log(c)
makeRipple(c,img)
})
})
function makeRipple(el,img){
var canvas = el,
/** #type {CanvasRenderingContext2D} */
ctx = canvas.getContext('2d'),
width = img.clientWidth,
height = img.clientHeight,
half_width = width >> 1,
half_height = height >> 1,
size = width * (height + 2) * 2,
delay = 30,
oldind = width,
newind = width * (height + 3),
riprad = 3,
mapind,
ripplemap = [],
last_map = [],
ripple,
texture,
line_width = 20,
step = line_width * 2,
count = height / line_width;
canvas.width = width;
canvas.height = height;
/*
* Water ripple demo can work with any bitmap image
* (see example here: http://media.chikuyonok.ru/ripple/)
* But I need to draw simple artwork to bypass 1k limitation
*/
ctx.drawImage(img,0,0,img.clientWidth,img.clientHeight)
texture = ctx.getImageData(0, 0, width, height);
ripple = ctx.getImageData(0, 0, width, height);
for (var i = 0; i < size; i++) {
last_map[i] = ripplemap[i] = 0;
}
/**
* Main loop
*/
function run() {
console.log('bbb')
newframe();
ctx.putImageData(ripple, 0, 0);
}
/**
* Disturb water at specified point
*/
function disturb(dx, dy) {
dx <<= 0;
dy <<= 0;
for (var j = dy - riprad; j < dy + riprad; j++) {
for (var k = dx - riprad; k < dx + riprad; k++) {
ripplemap[oldind + (j * width) + k] += 512;
}
}
}
/**
* Generates new ripples
*/
function newframe() {
var i, a, b, data, cur_pixel, new_pixel, old_data;
i = oldind;
oldind = newind;
newind = i;
i = 0;
mapind = oldind;
// create local copies of variables to decrease
// scope lookup time in Firefox
var _width = width,
_height = height,
_ripplemap = ripplemap,
_mapind = mapind,
_newind = newind,
_last_map = last_map,
_rd = ripple.data,
_td = texture.data,
_half_width = half_width,
_half_height = half_height;
for (var y = 0; y < _height; y++) {
for (var x = 0; x < _width; x++) {
data = (
_ripplemap[_mapind - _width] +
_ripplemap[_mapind + _width] +
_ripplemap[_mapind - 1] +
_ripplemap[_mapind + 1]) >> 1;
data -= _ripplemap[_newind + i];
data -= data >> 5;
_ripplemap[_newind + i] = data;
//where data=0 then still, where data>0 then wave
data = 1024 - data;
old_data = _last_map[i];
_last_map[i] = data;
if (old_data != data) {
//offsets
a = (((x - _half_width) * data / 1024) << 0) + _half_width;
b = (((y - _half_height) * data / 1024) << 0) + _half_height;
//bounds check
if (a >= _width) a = _width - 1;
if (a < 0) a = 0;
if (b >= _height) b = _height - 1;
if (b < 0) b = 0;
new_pixel = (a + (b * _width)) * 4;
cur_pixel = i * 4;
_rd[cur_pixel] = _td[new_pixel];
_rd[cur_pixel + 1] = _td[new_pixel + 1];
_rd[cur_pixel + 2] = _td[new_pixel + 2];
}
++_mapind;
++i;
}
}
mapind = _mapind;
}
canvas.onmousemove = function(/* Event */ evt) {
console.log('XXXX',evt.offsetX)
disturb(evt.offsetX || evt.layerX, evt.offsetY || evt.layerY);
};
setInterval(run, delay);
// generate random ripples
var rnd = Math.random;
setInterval(function() {
console.log('aaa')
disturb(rnd() * width, rnd() * height);
}, 700);
};
I made a compress or resize the image on the client side with canvas, but I am wondering how to fill the image into canvas with a FileUpload control from c #.
then each took a picture of how that canvas cleaned first and then filled with the new picture?
this is my source:
$(document).ready(function() {
var canvas = document.getElementById("cc");
var ctx = canvas.getContext("2d");
var img = new Image();
img.crossOrigin = "Anonymous";
img.onload = function() {
var W = img.width;
var H = img.height;
canvas.width = W;
canvas.height = H;
ctx.drawImage(img, 0, 0);
resample_hermite(canvas, W, H, 439, 222);
}
img.src = 'http://i.imgur.com/8VsK7gS.png';
});
function resample_hermite(canvas, W, H, W2, H2) {
var time1 = Date.now();
W2 = Math.round(W2);
H2 = Math.round(H2);
var img = canvas.getContext("2d").getImageData(0, 0, W, H);
var img2 = canvas.getContext("2d").getImageData(0, 0, W2, H2);
var data = img.data;
var data2 = img2.data;
var ratio_w = W / W2;
var ratio_h = H / H2;
var ratio_w_half = Math.ceil(ratio_w / 2);
var ratio_h_half = Math.ceil(ratio_h / 2);
for (var j = 0; j < H2; j++) {
for (var i = 0; i < W2; i++) {
var x2 = (i + j * W2) * 4;
var weight = 0;
var weights = 0;
var weights_alpha = 0;
var gx_r = gx_g = gx_b = gx_a = 0;
var center_y = (j + 0.5) * ratio_h;
for (var yy = Math.floor(j * ratio_h); yy < (j + 1) * ratio_h; yy++) {
var dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half;
var center_x = (i + 0.5) * ratio_w;
var w0 = dy * dy //pre-calc part of w
for (var xx = Math.floor(i * ratio_w); xx < (i + 1) * ratio_w; xx++) {
var dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half;
var w = Math.sqrt(w0 + dx * dx);
if (w >= -1 && w <= 1) {
//hermite filter
weight = 2 * w * w * w - 3 * w * w + 1;
if (weight > 0) {
dx = 4 * (xx + yy * W);
//alpha
gx_a += weight * data[dx + 3];
weights_alpha += weight;
//colors
if (data[dx + 3] < 255)
weight = weight * data[dx + 3] / 250;
gx_r += weight * data[dx];
gx_g += weight * data[dx + 1];
gx_b += weight * data[dx + 2];
weights += weight;
}
}
}
}
data2[x2] = gx_r / weights;
data2[x2 + 1] = gx_g / weights;
data2[x2 + 2] = gx_b / weights;
data2[x2 + 3] = gx_a / weights_alpha;
}
}
console.log("hermite = " + (Math.round(Date.now() - time1) / 1000) + " s");
canvas.getContext("2d").clearRect(0, 0, Math.max(W, W2), Math.max(H, H2));
canvas.width = W2;
canvas.height = H2;
canvas.getContext("2d").putImageData(img2, 0, 0);
}
thanks for all your help
I've borrowed this code to try and suit my needs http://www.html5canvastutorials.com/advanced/html5-canvas-get-image-data-tutorial/ since it does the minimum task of it renders a image pixel by pixel (i believe).
What I'm trying to do is create an array of each pixel's RGB information, and display the information in plain text.
To test I am trying this with small images, 5x5 pixels, also I have this in an .html file i've opened with chrome.
The lightly adapted JS
<script>
function drawImage(imageObj) {
var codepanel = document.getElementById('code');
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var imageX = 69;
var imageY = 50;
var imageWidth = imageObj.width;
var imageHeight = imageObj.height;
var pixels = new Array(); //my addition
var pixel = 0; //my addition
context.drawImage(imageObj, imageX, imageY);
var imageData = context.getImageData(imageX, imageY, imageWidth, imageHeight);
var data = imageData.data;
// iterate over all pixels
for(var i = 0, n = data.length; i < n; i += 4) {
var red = data[i];
var green = data[i + 1];
var blue = data[i + 2];
var alpha = data[i + 3];
pixels[pixel] = red + " " + green + " " + blue + " "; //my addition
pixel++; //my addition
}
codepanel.innerHTML = pixels.join(); //my addition
var x = 20;
var y = 20;
var red = data[((imageWidth * y) + x) * 4];
var green = data[((imageWidth * y) + x) * 4 + 1];
var blue = data[((imageWidth * y) + x) * 4 + 2];
var alpha = data[((imageWidth * y) + x) * 4 + 3];
for(var y = 0; y < imageHeight; y++) {
for(var x = 0; x < imageWidth; x++) {
var red = data[((imageWidth * y) + x) * 4];
var green = data[((imageWidth * y) + x) * 4 + 1];
var blue = data[((imageWidth * y) + x) * 4 + 2];
var alpha = data[((imageWidth * y) + x) * 4 + 3];
}
}
}
var imageObj = new Image();
imageObj.onload = function() {
drawImage(this);
};
imageObj.src = 'pallet.gif';
</script>
HTML
<!DOCTYPE HTML>
<html>
<head> </head>
<body>
<canvas id="myCanvas" width="100%" height="100%"></canvas>
<div id="code"> </div>
</body>
</html>
It means the image you drew to canvas came from a different origin than your page (file:// is considered a different origin too if you are testing with local pages).
The easiest way to solve this is:
If local, install a light-weight server to load the pages off (localhost) such as Mongoose.
If online, move the images to your own server or try to request cross-origin use. For this latter the external server need to be configured to allow this.
To request cross-origin do the following before setting src:
imageObj.crossOrigin = '';
imageObj.src = 'pallet.gif';
It the external server do not accept this will fail.