Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cross-Browser Canvas using nodeJS no signature found for the function

I'm trying to build a canvas on a page which updates itself whenever a user draws something on it. However it looks as if the following piece of code ( from index.html) is causing an error

  socket.on("drawn_complete",function(data){
    ctx.putImageData(data.image,0,0);
    console.log("Everthing worked technically");
});

Here is the error that gets outputted in my console:

Uncaught TypeError: Failed to execute 'putImageData' on 'CanvasRenderingContext2D': No function was found that matched the signature provided

my data.image is what gets returned from the following code:

ctx.getImageData(0,0,200,100); // ctx is canvas.getContext("2d")

Here is my canvas in my html:

 <canvas id="myCanvas" width="200" height="100"
style="border:1px solid #000000;">
</canvas>

Here is the full nodeJS:

    var http = require("http")
var server = http.createServer(handler).listen(1337);
console.log("Server created at 127.0.0.1:1337");
var io = require("socket.io").listen(server);


function handler(req,res)
{
    console.log("Client Connected");
    res.writeHead(200,{"Content-type": "text/plain"});
    res.end("Hello World");
}


io.sockets.on("connection",function(socket){
    socket.on("drawing",function(data){
        var image = data["image"];
        io.sockets.emit("drawn_complete",image);
    });
});

Drawing is emmited when the user holds and moves the mouse inside my canvas:

Here is the code:

  c.onmousedown = function(evt){
moving = true
};
c.onmousemove = function(evt){
    if(moving == true)
    {
        console.log("holding and moving");

        var x = evt.clientX - rect.left;
        var y = evt.clientY - rect.top;
        console.log("X: " + x + " Y: " + y);
        ctx.fillRect(x,y,1,1);
        socket.emit("drawing",{"image": ctx.getImageData(0,0,200,100)});

    }
};
c.onmouseup = function(evt){
    moving = false;
};

UPDATE

Here is the enitre script:

<script>

window.onload = function(){
var socket = io.connect("http://localhost:1337");
socket.on("drawn_complete",function(data){
    ctx.putImageData(data.image,0,0);
    console.log("Everthing worked technically");
});


var c = document.getElementById("myCanvas");
var moving = false;
console.log(c);
 var ctx = c.getContext("2d");
 var rect = c.getBoundingClientRect();
c.onmousedown = function(evt){
moving = true
};
c.onmousemove = function(evt){
    if(moving == true)
    {
        console.log("holding and moving");

        var x = evt.clientX - rect.left;
        var y = evt.clientY - rect.top;
        console.log("X: " + x + " Y: " + y);
        ctx.fillRect(x,y,1,1);
        socket.emit("drawing",{"image": ctx.getImageData(0,0,200,100)});

    }
};
c.onmouseup = function(evt){
    moving = false;
};
};



</script>

UPDATE As Palanik suggested I have trieed to serialize the image before emitting it:

 var stringfy = JSON.stringify(ctx.getImageData(0,0,200,100));
        socket.emit("drawing",{"image": stringfy});

It looks as if the console does still throw the same error

UPDATE ( 2 ) As @Palanik suggested I tried to serialise the object. I know the following code is not exactly what he recommended however I just want to highlight something very peculiar:

Sender:

var stringfy = ctx.getImageData(0,0,200,100);
        console.log(stringfy)
        var sent = JSON.stringify(stringfy);
        socket.emit("drawing",{"image": sent});

Reciever

socket.on("drawn_complete",function(data){
imageData = JSON.parse(data.image);
console.log(imageData);
ctx.putImageData(imageData,0,0);
console.log("Everthing worked technically");

});

The interesting bit here is that the console.log of imageData prints out an oject with the same exact properties that ImageData has. Height, Width and raw. Take a look here:

{"height":100,"width":200,"data":{"0":0,"1":0,"2":0,"3":0,"4":0,"5 etc... }

So as I can see from here I am able to get the object back. If I was in java a cast would be enough;

ImageData object = (ImageData) imageData // Is there a way to do this?
like image 287
Bula Avatar asked Dec 19 '22 17:12

Bula


2 Answers

ImageData may not be portable. Try serializing imagedata before sending and deserialize and create imagedata on the receiving end.

=== Update ===

Something like :

Sender

canvasData = ctx.getImageData(0,0,200,100)}
myData = {
        height : canvasData.height,
        width : canvasData.width,
        raw : canvasData.data
};
socket.emit("drawing",{"image": myData});

Receiver

imgData = ctx.getImageData(myData.width, myData.height);
for (var i = 0; i < myData.raw.length) {
        imgData.data[i] = myData.raw[i];
}
ctx.putImageData(imgData, 0, 0);

This is untested code. Just an idea for you to try it out.

like image 33
palanik Avatar answered Jan 19 '23 00:01

palanik


The solution here is to use canvas.toDataURL();.

This will return a Base64-encoded image-string (with MIME-Type and encoding in the head).

var canvas = getMyCanvas(),
    ctx = canvas.getContext("2d"),
    encodedImageData = canvas.toDataURL(),
    image = new Image();

image.src = encodedImageData;
ctx.drawImage(image, x, y, w, h...);

You can pass these encoded strings to and from the server, happily.
You can use them in regular HTML <img src="data:image/png;base64,...">, as well.

Also, you can pass toDataURL a couple of arguments:

 canvas.toDataURL("image/jpeg", 0.5);`

Where, the first argument is the requested file type, and the second is the requested image-quality level (between 0 and 1).

There is no guarantee that any browser is going to support what you asked for, though, and you might just get a .png file back, anyway (which is likely what you'd want for a game, for transparencies, regardless).

Also, the Android 2.2 browser and lower, and IE9, and I think Mobile Safari 5.1 (original iPads), don't support .toDataURL.
I might be wrong about the iPads, but they don't support other HTML5 APIs which are useful in conjunction.

like image 120
Norguard Avatar answered Jan 18 '23 22:01

Norguard