HTML5 Canvas ImageData assignment acting weird - javascript

I can't figure out why this isn't working. I draw an image onto a canvas, use getImageData, manipulate the pixels and then store that array of pixels for later. When I'm ready to draw those pixels later, I use createImageData on another, identically sized canvas, set the resulting ImageData object's data property to the array of pixels I saved and then call putImageData. The result: the saved array data isnt assigned to the ImageData object. Below is the code for a test page:
<!DOCTYPE html>
<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en-US' lang='en-US'>
<head>
<meta http-equiv='content-type' content='text/html;charset=UTF-8' />
<link rel="stylesheet" type="text/css" href="styles.css" media="screen" />
<script type='text/javascript' src='http://code.jquery.com/jquery-1.4.2.min.js'></script>
<title>Audio Browser</title>
</head>
<body>
<script type='text/javascript'>
$(document).ready(function()
{
var copy = document.createElement('canvas');
var viewer = $('#viewer')[0];
var output = $('#output')[0];
var ctx = viewer.getContext('2d');
var outputContext = output.getContext('2d');
var img = $('#input')[0];
var imageData;
var songs = [];
output.width = copy.width = img.width;
output.height = copy.height = img.height;
copy.getContext('2d').drawImage(img, 0, 0);
viewer.width = 1500;
viewer.height = 800;
originalData = copy.getContext('2d').getImageData(0, 0, copy.width, copy.height);
imageData = originalData.data;
for(var i = 3; i < imageData.length; i+=4)
{
var row = Math.floor(i / copy.width / 4);
imageData[i] *= 1-((copy.height - row)/copy.height*2);
}
songs.push({'fadeImg' : imageData});
draw();
function draw()
{
originalData = copy.getContext('2d').createImageData(copy.width, copy.height);
originalData.data = songs[0].fadeImg;
outputContext.putImageData(originalData, 0, 0);
}
});
</script>
<img id='input' src='albumArt/small.png' />
<canvas id='output'></canvas>
<br />
<canvas id='viewer'></canvas>
</body>
</html>
EDIT:
I just found something interesting when trying this in the new IE9 beta. In IE, I get the following error: SCRIPT65535: Invalid set operation on read-only property on originalData.data = songs[0].fadeImg; I checked firefox before and there are no errors in the error console. Maybe firefox just silently fails. If thats the case, how can I copy the array back without an extremely wasteful for loop?

I ran into this problem recently as well.
Unfortunately, it seems that straight assignment of the data is impossible because it is an Uint8ClampedArray.
According to Kronos.com :
It behaves identically to the other typed array views, except that the setters and constructor use clamping [WEBIDL] rather than modulo arithmetic when converting incoming number values.
So a loop is the way to go...
This should do:
for (var i in songs[0].fadeImg){
originalData.data[i] = songs[0].fadeImg[i];
}
It is very inconvenient, but this is the only way that I have found.

Related

A-Frame/Javascript-Slideshow has wrong mapping

I have a similar slideshow displayed a few times here! It works fine but I don't get the right mapping on an a-sky. I am not a coder but I guess drawImage is just made for rectangular objects instead of spherical? Is there an alternative to drawImage which works for spherical?
Here are my codes:
AFRAME.registerComponent('draw-canvas', {
schema: {
type: 'selector'
},
init: function() {
var canvas = this.canvas = this.data;
var ctx = this.ctx = canvas.getContext('2d');
var i = 0; // Start Point
var images = []; // Images Array
var time = 3000; // Time Between Switch
// Image List
images[0] = "Tulips.jpg";
images[1] = "Tulips2.jpg";
images[2] = "Tulips3.jpg";
// Change Image
function changeImg() {
document.getElementById('pic01').src = images[i];
ctx.drawImage(document.getElementById('pic01'), 0, 0, 300, 300);
// Check If Index Is Under Max
if (i < images.length - 1) {
// Add 1 to Index
i++;
} else {
// Reset Back To O
i = 0;
}
// Run function every x seconds
setTimeout(function() {
changeImg()
}, time);
}
// Run function when page loads
window.onload = changeImg;
console.log("Hello World!");
}
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Canvas Texture</title>
<meta name="description" content="Canvas Texture - A-Frame">
<script src="./components/aframe-v0.6.0.js"></script>
<script src="./components/slideshow.js"></script>
</head>
<body>
<a-scene>
<a-assets>
<img id="pic01" src="Tulips.jpg">
<img id="pic02" src="Tulips2.jpg">
<img id="pic03" src="Tulips3.jpg">
<canvas id="slide" name="slide" crossOrigin="anonymous"> </canvas>
</a-assets>
<a-sky material="shader: flat; src: #slide" draw-canvas="#slide">
<a-sky/>
</a-scene>
</body>
</html>
And if anybody knows how to nicely fade over the pictures, please feel free to share! I bet a lot of people would be happy about a nice A-Frame Slideshow.
I've got a solution, but I've altered quite a lot of Your stuff.
I've got rid of the canvas, You already have three image assets, no need to rewrite, or buffer them on each other.
Just store the asset id's and use setAttribute("material", "src", picID)
Furthermore I've added the a-animation component, so Your slideshow will have a nice smooth transition. You need to set the animation duration to the slideshow's time / 2, for it goes back and forth.
This said, check out my fiddle.
As for the drawImage part of the question, drawImage draws (writes) an image onto a canvas element. The mapping is fine, since You only need to make sure You have a spherical photo, otherwise it will get stretched all over the model.

JQuery canvas drawImage() not working

I've tried everything I can think of and I'm reaching my wit's end.
"tilesheet.png" should be drawn onto the canvas but nothing I try seems to work.
Occasionally, when I try some new method I found online, it will appear to work fine once, but if I refresh the page it stops again.
Here is the code I'm using:
<head>
<title>Test Code</title>
<script src='scripts/jquery.js'></script>
</head>
<body>
<img id='img' src='images/tilesheet.png' style='position:absolute; visibility:hidden;'/>
<div id="port"></div>
<script>
viewHeight = 10;
viewWidth = 10;
$("#port").append("<canvas id='mapview' width='"+(viewWidth*32)+"' height='"+(viewHeight*32)+"' style='border:1px solid black;'>Your browser doesn't support the canvas element.</canvas>");
var worldMap = new gameMap(viewHeight,viewWidth);
worldMap.redraw();
// These functions define a gameMap "CLASS" //
function gameMap (height,width){
this.tileSheet = $("#img");
this.canvas = $("#mapview");
this.ctx = this.canvas.getContext("2d");//<-- Error is referring to this line
}
//Draw Function
gameMap.prototype.redraw = function(){
this.tileSheet.onload = function(){
this.ctx.drawImage(this.tileSheet,10,10);
}
this.tileSheet.src = "apps/gamejournal/images/tilesheet.png";
for(i = this.viewY; i < this.viewY+this.viewHeight; i++){
for(j = this.viewX; j < this.viewX+this.viewWidth; j++){
}
}
this.mapcss();
};
//CSS
gameMap.prototype.mapcss = function(){
var h = this.viewHeight*this.tileSize;
var w = this.viewHeight*this.tileSize;
$('#port').css({
"height":h,
"width":w,
"position":"relative",
"margin":"0 auto"
});
};
</script>
</body>
Update 10/30/15 - After using Google Chrome's "Inspect Element" feature, I've noticed that an error is being reported:
TypeError: this.canvas.getContext is not a function (test.php:26)
Does anyone know what might be causing this?
Update 11/4/15 - I've created a JSFiddle so you can see what my code is doing.
https://jsfiddle.net/6ss8ygLd/3/
Any help would be greatly appreciated. Thanks!
Okay, after a lot of extra research I seem to have found the solution to my own problem.
The problems proved to be a mix of using jquery in places where it wouldn't work, and putting my "onload" function in the wrong place.
Here is the final code I used:
<head>
<title>Test Code</title>
<script src='scripts/jquery.js'></script>
</head>
<body>
<img id="tiles" src="images/tilesheet.png" style="position:absolute; left:-9999px;"/>
<div id="port"></div>
<script>
viewHeight = 10;
viewWidth = 10;
var mapw = viewWidth*32;
var maph = viewHeight*32;
$("#port").append("<canvas id='mapview' width='"+mapw+"' height='"+maph+"' style='border:1px solid black;'>Your browser doesn't support the canvas element.</canvas>");
// These functions define a gameMap "CLASS" //
function gameMap (height,width){
this.canvas = document.getElementById("mapview");
this.ctx = this.canvas.getContext("2d");
this.tileSheet = document.getElementById("tiles");
}
//Draw Function
gameMap.prototype.redraw = function(){
this.ctx.drawImage(this.tileSheet,0,0);
};
//Call Functions
document.getElementById("tiles").onload = function(){
var worldMap = new gameMap(viewHeight,viewWidth);
worldMap.redraw();
}
</script>
</body>

Dynamic Canvas DrawImage Function

I've got a problem filling 25 canvas elements automatically in a for loop. They are numbered like so: can01 to can25.
I've tried all I knew to draw different images on the canvas and I have spent a lot of time in searching a few articles which are about this problem but I haven't found any.
This is my working code to fill all canvas elements with the same image:
var imageGrass = new Image();
imageGrass.src = 'recources/imagesBG/grass.jpg';
imageGrass.onload = function() {
for (var i = 1; i < 26; i++)
{
if( i < 10 )
{
var task = "can0" + i + "_ctx.drawImage(imageGrass, 0, 0);";
eval(task);
}
else
{
var task = "can" + i + "_ctx.drawImage(imageGrass, 0, 0);";
eval(task);
}
}
}
But I really don't know how to make the imageGrass.src dynamic. For example, the canvas element no. 5 (can05) in this case shall look like stone texture.
I´m really looking forward to read your ideas. I just don't get it.
Here’s how to impliment Dave’s good idea of using arrays to organize your canvases:
Create an array that will hold references to all your 25 canvases (do the same for 25 contexts)
var canvases=[];
var contexts=[];
Next, fill the array with all your canvases and contexts:
for(var i=0;i<25;i++){
var canvas=document.getElementById("can"+(i<10?"0":""));
var context=canvas.getContext("2d");
canvases[i]=canvas;
contexts[i]=context;
}
If you haven't seen it before: i<10?"0":"" is an inline if/else used here to add a leading zero to your lower-numbered canvases.
Then you can fetch your “can05” canvas like this:
var canvas=canvases[4];
Why 4 and not 5? Arrays are zero based, so canvases[0] holds can01. Therefore array element 4 contains your 5th canvas “can05”.
So you can fetch the drawing context for your “can05” like this:
var context=contexts[4];
As Dave says, “evals are evil” so here’s how to fetch the context for “can05” and draw the stone image on it.
var context=contexts[4];
context.drawImage(stoneImage,0,0);
This stone drawing can be shortened to:
contexts[4].drawImage(stoneImage,0,0);
You can even put this shortened code into a function for easy reuse and modification:
function reImage( canvasIndex, newImage ){
contexts[ canvasIndex ].drawImage( newImage,0,0 );
}
Then you can change the image on any of your canvases by calling the function:
reimage( 4,stoneImage );
That’s it!
The evil-evals have been vanquished (warning: never invite them to your computer again!)
Here is example code and a Fiddle: http://jsfiddle.net/m1erickson/ZuU2e/
This code creates 25 canvases dynamically rather than hard-coding 25 html canvas elements.
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; padding:0px; margin:0px;border:0px; }
canvas{vertical-align: top; }
</style>
<script>
$(function(){
var canvases=[];
var contexts=[];
var grass=new Image();
grass.onload=function(){
// the grass is loaded
// now make 25 canvases and fill them with grass
// ALSO !!!
// keep track of them in an array
// so we can use them later!
make25CanvasesFilledWithGrass()
// just a test
// fill canvas#3 with gold
draw(3,"gold");
// fill canvas#14 with red
draw(14,"red");
}
//grass.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/grass.jpg";
//grass.src="grass.jpg";
function make25CanvasesFilledWithGrass(){
// get the div that we will fill with 25 canvases
var container=document.getElementById("canvasContainer");
for(var i=0;i<25;i++){
// create a new html canvas element
var canvas=document.createElement("canvas");
// assign the new canvas an id, width and height
canvas.id="can"+(i<10?"0":"")+i;
canvas.width=grass.width;
canvas.height=grass.height;
// get the context for this new canvas
var ctx=canvas.getContext("2d");
// draw the grass image in the new canvas
ctx.drawImage(grass,0,0);
// add this new canvas to the web page
container.appendChild(canvas);
// add this new canvas to the canvases array
canvases[i]=canvas;
// add the context for this new canvas to the contexts array
contexts[i]=ctx;
}
}
// test -- just fill the specified canvas with the specified color
function draw(canvasIndex,newColor){
var canvas=canvases[canvasIndex];
var ctx=contexts[canvasIndex];
ctx.beginPath();
ctx.fillStyle=newColor;
ctx.rect(0,0,canvas.width,canvas.height);
ctx.fill();
}
}); // end $(function(){});
</script>
</head>
<body>
<div id="canvasContainer"></div>
</body>
</html>

The Image doesn't show up on the Canvas

<html>
<head>
<meta charset="utf-8">
<title>Game</title>
<script src="http://code.createjs.com/easeljs-0.4.2.min.js"></script>
</head>
<body>
<div id ="beeld">
<canvas id ="stageCanvas" width="1024" height="576">
<script src ="javascript.js"></script>
</canvas>
</div>
</body>
</html>
This is my Html
//Javascript Document
var canvas = document.getElementById('stageCanvas');
var stage = new Stage(canvas);
var Background;
var imgBackground = new Image();
imgBackground.src ="images/bg.png";
Background = new Bitmap(imgBackground);
Background.x = 0
Background.y = 0
stage.addChild(Background);
stage.update();
And that's my js.
The image is 1024 by 576, so it should be fullscreen. The original is 2048 by 576. I was planning to do like the background would slide to left and repeat it self when it gets to the end.
So my question is, why doesnt the image show up on the canvas.
I'm a very new to this, so I'm sorry if I'm asking such an easy question.
PS: If I inspect the HTML, it DOESN'T show any errors and it DOES show that the image is being loaded. It's just not.. drawn.. I guess.
I'm getting frustrated, because I'm stuck here for a couple hours already.
I FOUND A WORKING CODE
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Easel simple game</title>
<script src="http://code.createjs.com/easeljs-0.4.2.min.js"></script>
<script>
var canvas;
var stage;
var bg;
var score;
var ghost;
function init() {
canvas = document.getElementById("StageCanvas");
stage = new Stage(canvas);
score = 0;
bg = new Image();
bg.src = "img/bg.png";
bg.onload = setBG;
}
function setBG(event){
var bgrnd = new Bitmap(bg);
stage.addChild(bgrnd);
stage.update();
}
</script>
</head>
<body onload="init();">
<canvas id="StageCanvas" width="1240" height="576"></canvas>
</body>
</html>
By the way this code is the work for someone else, so I don't take any credit.
This seems to work perfectly. But once I remove the
bg = new Image();
bg.src = "img/bg.png";
bg.onload = setBG;
it stops working.
Background = new Bitmap(imgBackground);
In HTML:
Try to move the script-tag of your javascript.js to right before the </body> or at least not into the <canvas>...</canvas>.
And another thing is: You have a space between src and = and id and =, that might cause problems in some browsers.
In JavaScript:
You need to execute the update-method with () otherwise that line will just be a listed reference to the update function.
stage.update();
And a second thing, that I noticed(though not part of the issue): You are using EaselJS 0.4.2, but 0.5.0 is already released, just in case you want to be up-to-date: http://code.createjs.com/easeljs-0.5.0.min.js ;-)
I'm having the same issue with EaselJS at the moment. While it might work for you, you can try and add a ticker which will automatically update the canvas:
createjs.Ticker.addListener(stage);
I was able to get the image to paint on the canvas after adding this, but as soon as I add any transforms to the image (scale, positioning, height, width) they are not applied prior to the image being drawn.
I've been scratching my head over this too.

Trouble with setInterval and undefined arguments

I'm trying to create a basic strobe light in the browser using the canvas element. I'm expecting setInterval to keep calling the changeBG function to change to a random background color. This function works fine on its own, but not when called by setInterval. I tried pulling up this page in firebug and it told me that colors was undefined. Here's the problematic code.
<html>
<head>
<title>Strobe!</title>
<link rel="stylesheet" type="text/css" href="reset.css" />
<script type="text/javascript">
function changeBG(colors,ctx,canvas) {
ctx.fillStyle = colors[Math.floor(Math.random()*colors.length)]
ctx.fillRect(0,0,canvas.width,canvas.height)
}
function eventLoop() {
var colors = ['#000000','#ff0000','#00ff00','#0000ff','#ffff00','#ff00ff','#00ffff']
var canvas = document.getElementById('mainCanvas')
var ctx = canvas.getContext('2d')
canvas.width = window.innerWidth
canvas.height = window.innerHeight
//changeBG(colors,ctx,canvas)
setInterval("changeBG(colors,ctx,canvas)", 1000);
}
</script>
</head>
<body onload="eventLoop()">
<canvas id="mainCanvas" width="800" height="600">
</canvas>
</body>
I'm new to javascript so any insight what so ever would be highly appreciated.
You code would work if you weren't passing a string to setInterval. Because it is in a string, it can't create a closure on the variables you are trying to use.
Try this instead:
setInterval(function() {
changeBG(colors,ctx,canvas);
}, 1000)​;​
Using this method, you are passing an anonymous function to setInterval. It will call this function once per interval, which is 1000 miliseconds in this example.
The function can use the colors, ctx, and canvas variables because they exist in the scope where the function is declared. This creates a closure so that those variables still exist (as far as our anonymous function is concerned) when it is called over and over again.
For now, you can probably just use this code. For further understanding, I suggest researching anonymous functions and closures.
You can pass directly a function, instead of a string to evaluate, as
setInterval(function(){changeBG(colors,ctx,canvas)}, 1000);
Good luck provoking epilepsy to someone
The root problem is variable scope when the interval code is executed, colors and the other variables are not in scope.
Try this:
<html>
<head>
<title>Strobe!</title>
<link rel="stylesheet" type="text/css" href="reset.css" />
<script type="text/javascript">
function eventLoop() {
var colors = ['#000000','#ff0000','#00ff00','#0000ff','#ffff00','#ff00ff','#00ffff']
var canvas = document.getElementById('mainCanvas')
var ctx = canvas.getContext('2d')
canvas.width = window.innerWidth
canvas.height = window.innerHeight
setInterval(function() {
ctx.fillStyle = colors[Math.floor(Math.random()*colors.length)]
ctx.fillRect(0,0,canvas.width,canvas.height)
}, 1000);
}
</script>
</head>
<body onload="eventLoop()">
<canvas id="mainCanvas" width="800" height="600">
</canvas>
</body>
try this
UPDATED
setInterval(function(){changeBG(colors,ctx,canvas)}, 1000);

Categories