link64
link64

Reputation: 1976

Drawing an image on a canvas and returning as a texture

Firstly, I'd like to make it clear that I am an absolute beginner when it comes to canvas & three.js

I've taken an existing project and am trying to make some minor modifications, namely, drawing an image on to a texture instead of just writing text. I would like the texture to have a set background color and will be attempting to draw a transparent png image on top of it.

The method that I am attempting to modify returns a texture for use in another function which is currently doing a lot more stuff that I probably won't be able to explain here.

I've tried a few approaches and I can't seem to get the behaviour that I want.

Approach #1

In this approach, I am getting the background color but I am not getting the image displayed

                var ts = calc_texture_size(size + size * 2 * margin) * 2;
                canvas.width = canvas.height = ts;
                context.fillStyle = back_color;
                context.fillRect(0, 0, canvas.width, canvas.height);
                var img = new Image();
                function drawIcon() {
                    context.drawImage(img,0,0);
                }
                img.onload = drawIcon();
                img.src = "img/test.png";

                var texture = new THREE.Texture(canvas);
                texture.needsUpdate = true;
                return texture;

Approach #2

After doing some research, it seemed I was supposed to use a TextureLoader but I couldn't figure out how to draw the color/shape on the canvas. In this case, the image appears on its own without any background color.

 var imgTex = new new THREE.TextureLoader().load( "img/test.png",function(t){
                    texture.map = imgTex;
                });
                texture.needsUpdate = true;
                return texture;

How can I successfully draw an image on top of a coloured background and return it as a texture for use by other functions?

Upvotes: 0

Views: 2976

Answers (1)

Leeft
Leeft

Reputation: 3837

Your problem is much related to JavaScript event handling, like a question I answered yesterday: How to make a Json model auto-rotates in the scene?. You can never have an "onload" and then expect that to return the variable for you - the variable is just not going to be defined until the callback is executed. Instead you need to defer the work to the callback, or elsewhere keep polling the variable until it's defined.

More specific to your question though:

With approach #1, you're sending the backfilled texture to the GPU, then rendering additional content to it, but not updating the texture that was sent to the GPU after the image was loaded. Moving the texture.needsUpdate = true; to your drawIcon() would probably solve it here. Untested example:

var ts = calc_texture_size(size + size * 2 * margin) * 2;
var img = new Image();
var texture = new THREE.Texture(canvas);

canvas.width = canvas.height = ts;
context.fillStyle = back_color;
context.fillRect(0, 0, canvas.width, canvas.height);
img.onload = function() {
    context.drawImage(img,0,0);
    texture.needsUpdate = true;
};
img.src = "img/test.png";
return texture;

NB: You should always define your var variables at the start of a scope in JavaScript to make things clearer ... drawIcon() has access to that texture variable, though it doesn't look like it from your example code because it appears to be defined after it, but that's not how JavaScript works: var variables are always global to the scope they're defined in.

With approach #2, you need a little extra work to paint on the texture, turning the texture back in to a canvas and sending the resulting canvas to the GPU. The ImageLoader and Texture documentation combined hint at how you need to paint to the image you get from the TextureLoader.

Combining the two approaches, it should be something like this (also utterly untested):

var ts = calc_texture_size(size + size * 2 * margin) * 2;
canvas.width = canvas.height = ts;
context.fillStyle = back_color;
context.fillRect(0, 0, canvas.width, canvas.height);

var textureLoader = new THREE.TextureLoader().load(
    "img/test.png",
    function( texture ) {
        var image = texture.image;
        context.drawImage( texture.image, 0, 0 0 );
        var texture = new THREE.Texture(canvas);
        texture.needsUpdate = true;
        // Now do something with texture, like assigning it
        // to your material
    }
);

NB: Don't forget about using data URL's, if the image you need to paint with is small then this can be included in your code as a "data:" URL and this avoids the need to go to the server and wait for it to load in an event handler. Example at MDN.

Upvotes: 1

Related Questions