draw preloaded image into canvas - javascript

Once again, completely out of my depth but I need to preload some images and then add them to the page when 'all elements (including xml files etc.)' are loaded. The images and references are stored in an array for later access. Trying to draw and image from that array throws an error yet I know it is available as I can just appendTo the page:
preloadImages: function (loadList, callback) {
var img;
var loadedFiles = [];
var remaining = loadList.length;
$(loadList).each(function(index, address ) {
img = new Image();
img.onload = function() {
--remaining;
if (remaining <= 0) {
callback(loadedFiles);
}
};
img.src = loadList[index];
loadedFiles.push({file: 'name of image to be loaded', image: img }); //Store the image name for later refernce and the image
});
}
//WHEN CERTAIN OTHER CONDITIONS EXIST I CALL THE FUNCTION BELOW
buildScreen: function ( imageLocs, image){
//THIS FUNCTION LOOPS THROUGH imageLocs (XML) AND CREATES CANVAS ELEMENTS, ADDING CLASSES ETC AND DRAWS PART OF A SPRITE (image)
//INTO THE CANVASES CREATED
var ctx = $('ID of CANVAS').get(0).getContext("2d");
var x = 'position x in imageLocs'
var y = 'position y in imageLocs'
var w = 'width in imageLocs'
var h = 'position x in imageLocs'
ctx.drawImage(image, x,y, w, h, 0, 0, w, h); //THIS THROWS AN ERROR 'TypeError: Value could not be converted to any of: HTMLImageElement, HTMLCanvasElement, HTMLVideoElement'
//$(image).appendTo("#innerWrapper") //YET I KNOW THAT IT IS AVAILABE AS THIS LINE ADDS THE IMAGE TO THE PAGE
}

Problem
The issue is caused because you are passing a jQuery object to a native function, in this case ctx.drawImage, drawImage will only support native objects.
startSequence : function(){
$('#innerWrapper').empty();
var screenImageRef = $.grep(ST.imageFilesLoaded, function(e){
return e.file == 'AtlasSheet'
});
var screenImage = $(screenImageRef[0].image);
var imageLocsRef = $.grep(ST.xmlFilesLoaded, function(e){
return e.file == 'IMAGELOCS'
});
var imageLocs = $(imageLocsRef[0].xml);
//$(screenImage).appendTo("#innerWrapper") //appends screenImage
Utilis.buildScreen('1', imageLocs, screenImage, ST.didYouSeeIt, 'ST')
}
Your screenImage var is created by $(screenImageRef[0].image), this will return a jQuery object that wrappers the native image object. To get back to the original native image object use the following:
screenImage.get(0)
or
screenImage[0]
The former is the jQuery supported way.
Solution
So the fix to your code should be either, changing the following line:
Utilis.buildScreen('1', imageLocs, screenImage.get(0), ST.didYouSeeIt, 'ST');
Or changing the line in the buildScreen method:
ctx.drawImage(image.get(0), x,y, w, h, 0, 0, w, h);
... Whichever you prefer.
Confusion when debugging
The reason why everything appears to work when you append the image, is because you are using jQuery to append the image, and jQuery supports being passed jQuery wrapped elements. If you had tried to append your screenImage using native functions i.e. Element.appendChild() you would have got similar errors.
Just to help in future, it's always best to use console.log to find out what type/structure a variable actually has. Using console.log on your previous image var would have given a strange object dump of the jQuery wrapper (which might have rang alarm bells), rather than the expected [object HTMLImageElement] or some other image/console related output (depending on the browser).

I think your image preloader isn't quite correct as it uses the same img variable for all images.
Here is one that I know works well: https://gist.github.com/eikes/3925183

Related

How to see if PNG has a transparent background with Javascript [duplicate]

Is there a way to read transparent pixels from a picture using javascript?
I think, that it could be something similar to what PNG fixes does for IE (reading transparent pixels and applying some stuff, lol). But yes, for every browser..
Ah, would be awesome if it could be achieved without HTML5.
Well this question is actually answered by the dude from GoogleTechTalks in this video on javascript-based game engines.
http://www.youtube.com/watch?feature=player_detailpage&v=_RRnyChxijA#t=1610s
It should start at the point where it is explained.
Edit:
So I will summarize what is told in the video and provide a code-example.
It was a lot tougher than I had expected. The trick is to load your image onto a canvas and then check each pixel if it is transparent. The data is put into a two dimension array. Like alphaData[pixelRow][pixelCol]. A 0 is representing transparency while a 1 is not. When the alphaData array is completed it is put in global var a.
var a;
function alphaDataPNG(url, width, height) {
var start = false;
var context = null;
var c = document.createElement("canvas");
if(c.getContext) {
context = c.getContext("2d");
if(context.getImageData) {
start = true;
}
}
if(start) {
var alphaData = [];
var loadImage = new Image();
loadImage.style.position = "absolute";
loadImage.style.left = "-10000px";
document.body.appendChild(loadImage);
loadImage.onload = function() {
c.width = width;
c.height = height;
c.style.width = width + "px";
c.style.height = height + "px";
context.drawImage(this, 0, 0, width, height);
try {
try {
var imgDat = context.getImageData(0, 0, width, height);
} catch (e) {
netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
var imgDat = context.getImageData(0, 0, width, height);
}
} catch (e) {
throw new Error("unable to access image data: " + e);
}
var imgData = imgDat.data;
for(var i = 0, n = imgData.length; i < n; i += 4) {
var row = Math.floor((i / 4) / width);
var col = (i/4) - (row * width);
if(!alphaData[row]) alphaData[row] = [];
alphaData[row][col] = imgData[i+3] == 0 ? 0 : 1;
}
a=alphaData;
};
loadImage.src = url;
} else {
return false;
}
}
I got errors when running local in Firefox and the try catch statement solved it. Oh I gotta eat...
Edit 2:
So I finished my dinner, I'd like to add some sources I used and wich can be helpful.
https://developer.mozilla.org/En/HTML/Canvas/Pixel_manipulation_with_canvas
Info about the imageData object.
http://blog.nihilogic.dk/2008/05/compression-using-canvas-and-png.html
Even more info about the imageData object and it's use.
http://www.nihilogic.dk/labs/canvascompress/pngdata.js
A really helpful example of the use of imageData, the code I provided resembles this one for a big part.
http://www.youtube.com/watch?v=_RRnyChxijA
Infos on scripting game-engines in javascript, really really interesting.
http://blog.project-sierra.de/archives/1577
Infos about the use of enablePrivilege in firefox.
This is a bit tricky problem, since the only way to access files directly from Javascript is by using FileReader, which is a relatively new feature and not yet supported in most browsers.
However, you could get the desired result by using a canvas. If you have a canvas, you could assign it some distinctive color (such as neon green used in green screens). Then you could insert the image onto canvas and use the method mentioned here to get each individual pixel. Then you could check each pixel's color and see whether that point corresponds to your background color (ergo it's transparent) or does it have some other color (not transparent).
Kind of hackish, but don't think there's anything else you can do with pure JS.
It appears that GameJS can do this and much, much more. I am referencing this SO question for any/all of my knowledge, as I don't claim to actually have any about this topic.
Of course, this is HTML5, and uses the canvas element.

SVG to canvas Image working half of the time

I want to compare two svg paths (user and model) at some point. The idea is to transform each of them onto ImageData to be able to make pixel comparisons. The problem I have is using the drawImage which leads me to an empty canvas half of the time.
let modelCanvas = document.createElement("canvas");
let modelContext = modelCanvas.getContext("2d");
modelCanvas.width = 898;
modelCanvas.height = 509;
document.body.appendChild(modelCanvas);
let modelImg = new Image(898, 509);
modelImg.src = 'data:image/svg+xml;base64,PHN2ZyBjbGFzcz0ic3ZnLW[....]';
modelContext.drawImage(modelImg, 0, 0, 898, 509);
The code is pretty straightforward and always run without producing error. Still drawImage seems to fail silently times to times.
Here is the JSFiddle (with the full data string) :
https://jsfiddle.net/Ldgpuo03/
Thank you very much for your help.
Image loading by web browser is an asynchronous operation.
You are trying to call modelContext.drawImage when the image is not guaranteed to be loaded.
You must place your drawing code inside the image.onload callback function
This function will be called once when the image loading is fully finished.
let modelCanvas = document.createElement("canvas");
let modelContext = modelCanvas.getContext("2d");
modelCanvas.width = 40;
modelCanvas.height = 40;
document.body.appendChild(modelCanvas);
let modelImg = new Image();
modelImg.src = 'https://i.stack.imgur.com/EK1my.png?s=48';
modelImg.onload = function(){
modelContext.drawImage(modelImg, 0, 0, 40, 40);
}

Creating an object copies old

I have multiple canvas setup in my HTML page. I access them via their respective ID in JS/jQuery.
In this canvas there is a "player"-character (a basic square), that has a constant size and a variable position.
I create the player object like this:
<script>
var player = {
xpos = 50,
ypos = 50
}
</script>
in the same <script></script> I have a function that looks like this:
async animate(cid2){
var c = Object.create(player);
cid = $(cid2)[0];
ctx = cid.getContext("2d");
c.xpos = 50;
c.ypos = 50;
while(c.xpos < 300){
await sleep(1100);
c.xpos = c.xpos + 50;
//draw a rectangle..
}
Once any <canvas id="test"> is clicked, (with a jQuery .click Function), animate is executed with the respective canvas id.
All of this works great, as long as there is one canvas on the page.
If I have say two canvas, the following happens in the console:
canvas1 is clicked!
X-Position: 50
X-Position: 100
canvas2 is clicked!
X-Position: 150
X-Position: 200
Although the second canvas is clicked and a Object.create should create a new object, it doesn't work: It still accesses the old object.
I am almost certain there is an answer to this on SO, but despite my best efforts, I can't find it, because I don't know what is actually going wrong.
Can anyone help me or refer me to a question I could ask?
Thank you in advance.
You could use the (I think ECMAscript 6) spread operator
const obj = { myProp: 'hi' };
const newObj = { ...obj };
newObj.myProp = 'ciao';
console.log(obj); // still { myProp: 'hi' }
Know you should have two separate objects instead of a copy.

Html2Canvas Screenshot clarity

Using html2canvas for taking screenshots. And that screenshot converted to image and attach it with email.
These screenshot includes highcharts. Some time x axis and y axis displayed with some shadow effect. How can I avoid it?
var tempcanvas=document.createElement('canvas');
tempcanvas.width=2000;
tempcanvas.height=980;
var context=tempcanvas.getContext('2d');
context.imageSmoothingQuality = "High";
context.drawImage(canvas,0,0,1000,750);
var link=tempcanvas.toDataURL('image/png',1);
$scope.alert.message = link;
var blobBin = atob(link.split(',')[1]);
var array = [];
for(var i = 0; i < blobBin.length; i++) {
array.push(blobBin.charCodeAt(i));
}
$scope.file=new Blob([new Uint8Array(array)], {type: 'image/png'});
May this helps you something
All that is necessary is to set the dpi or scale options when you use html2canvas, and the resulting canvas should have your chosen dpi/scale.
function myRenderFunction(canvas) {
destination.appendChild(canvas);
}
// Get source element and destination div.
var element = document.getElementById('element');
var destination = document.getElementById('destination');
// Normal html2canvas rendering.
html2canvas(element, {
onrendered: myRenderFunction
});
// With dpi: 144 (scale: 1.5).
html2canvas(element, {
dpi: 144,
onrendered: myRenderFunction
});
// With scale: 2 (dpi: 192).
html2canvas(element, {
scale: 2,
onrendered: myRenderFunction
});
let me try to assess my comments and also suggest some code:
When I ran into this issue what solved for me is when I started playing around the options of html2canvas. Here you can find the available options. When you simply want to call the function you use html2canvas(element, options). Just as emran suggested in their answer, you can pass an object as the second argument to the html2canvas function which can contain canvas creation options. Try passing the following:
var options = {
async : false, // setting it to false may slow the generation a bit down
allowTaint : false, // I am not sure whether it helped or not but I remember setting it
imageTimeout : 100, // this further delays loading, however this solved a similar issue for me
foreignObjectRendering : true // it depends on the browser used whether it is allowed or not
}
Then you can call your html2canvas function similarly to what emran suggested, however, onrendered is no longer necessary since newer versions of html2canvas work with .then()
var element = document.getElementById('element');
var destination = document.getElementById('destination');
html2canvas(element, options).then(function(canvas) {
destination.appendChild(canvas);
});
Please note that there are several other interesting options at that page that might be useful for you. I can also suggest this article on the topic, this explanation of the options and this library that combines html2canvas and jsPDF for a direct HTML to PDF conversion.

How to make pre-initialized array contents work, vs. array.push()?

Why can't the images be defined in an array as shown here.
Why is it necessary push a new Image object in the array every time?
var canvas = null;
var ctx = null;
var assets = [
'/media/img/gamedev/robowalk/robowalk00.png',
'/media/img/gamedev/robowalk/robowalk01.png',
'/media/img/gamedev/robowalk/robowalk02.png',
'/media/img/gamedev/robowalk/robowalk03.png',
'/media/img/gamedev/robowalk/robowalk04.png',
'/media/img/gamedev/robowalk/robowalk05.png',
'/media/img/gamedev/robowalk/robowalk06.png',
'/media/img/gamedev/robowalk/robowalk07.png',
'/media/img/gamedev/robowalk/robowalk08.png',
'/media/img/gamedev/robowalk/robowalk09.png',
'/media/img/gamedev/robowalk/robowalk10.png',
'/media/img/gamedev/robowalk/robowalk11.png',
'/media/img/gamedev/robowalk/robowalk12.png',
'/media/img/gamedev/robowalk/robowalk13.png',
'/media/img/gamedev/robowalk/robowalk14.png',
'/media/img/gamedev/robowalk/robowalk15.png',
'/media/img/gamedev/robowalk/robowalk16.png',
'/media/img/gamedev/robowalk/robowalk17.png',
'/media/img/gamedev/robowalk/robowalk18.png'
];
var frames = [];
var onImageLoad = function() {
console.log("IMAGE!!!");
};
var setup = function() {
j=0;
body = document.getElementById('body');
canvas = document.createElement('canvas');
ctx = canvas.getContext('2d');
canvas.width = 100;
canvas.height = 100;
body.appendChild(canvas);
for (i = 0; i <= assets.length - 1; ++i) {
frames[i].src = assets[i];
}
setInterval(animate,30);
}
var animate = function() {
ctx.clearRect(0,0,canvas.width,canvas.height);
if (j >= assets.length) {
j=0;
}
var image = new Image();
image.src = frames[j];
ctx.drawImage(image,0,0);
++j;
}
The first reason is to reduce latency. Putting only the URLs into an array means that images have not been pre-fetched before the animation starts. The first round of animation is going to be slow and jerky as each image is retrieved from the net. If the animation is repeated, the next round will be faster. This consideration mostly applies to animations which replaced image elements on the page (in the DOM) rather than by writing to a canvas.
The second reason is to remove overhead and improve efficiency in the animation loop. Using new Image() inside the loop means that drawing time for each frame includes the time taken to create a new Image object as well as draw it on the canvas. In addition the image content can only be written to the canvas after it has been fetched, making it necessary to write to the canvas from an onload handler attached to the image object. The posted code does not do this and could throw an error in some browsers trying to synchronously write an image with no data to the canvas. Even if otherwise successful, repeated animations would be creating a new Image object each time a frame is displayed and churning memory usage.
Note the original version probably used onImageLoad to check when the image has been fully loaded from the web before pushing the object into an array of preloaded image objects. This is the preferred method of prefetching animation images.
And don't forget to define j before use :-)

Categories