Better way to create CanvasTexture with image from canvas

I wrote short creating CanvasTexture code.

    new ImageLoader().load(src, (image) => {
        const canvas = document.createElement("canvas")
        const context = canvas.getContext("2d")
        
        canvas.width = image.width
        canvas.height = image.height
        
        context.drawImage(image, 0, 0, image.width, image.height)
        
        // return new CanvasTexture(canvas) or something...
    })

In this code, canvas element will create for each CanvasTexture.

I think it is not good way, so I move canvas const to outside

    const canvas = document.createElement("canvas")

    new ImageLoader().load(src, (image) => {
        const context = canvas.getContext("2d")

        context.clearRect(0, 0, canvas.width, canvas.height)
        
        canvas.width = image.width
        canvas.height = image.height
        
        context.drawImage(image, 0, 0, image.width, image.height)
        
        // return new CanvasTexture(canvas) or something...
    })

But if I create multiple texture in this code, previous texture’s image(from same canvas) is pasted by after image.

Is there a good way to optimize the code using minimum element & resource?

Should I create every canvas element for each CanvasTexture(& image)?

You can wrap the entire thing in a function to prevent the scope leak:

const createTextureFromImage = (imageUrl) => {
  const canvas = document.createElement('canvas');
  const texture = new CanvasTexture(canvas);

  new ImageLoader().load(imageUrl, image => {
    const ctx = canvas.getContext('2d');

    canvas.width = image.width;
    canvas.height = image.height;
    ctx.clearRect(0.0, 0.0, canvas.width, canvas.height);

    ctx.drawImage(image, 0.0, 0.0, image.width, image.height);
  });

  return texture;
};

Another advantage of doing so is that you don’t have to wait for the image to load before returning the CanvasTexture. You’ll be returning an empty CanvasTexture right away, unblocking material creation etc, while the image is loaded asynchronously in the background - and when it’s loaded it’ll just appear on the CanvasTexture automatically.

1 Like

Thanks for the advice!

But it seems like create each canvas element for each image(Finally I will call N-times of createTextureFromImage).

What I expect is something like 1 canvas element for N image resource & N CanvasTexture(or Texture). Maybe RenderTarget will help? I’m going to find another way…

But your advice for return empty CanvasTexture with canvas & load asynchronously is good! I will change the code :slight_smile:

If that would make you feel any better about creating new canvas every time - every Texture in the scene has it’s own, separate Image element. For CanvasTexture that <image /> element is simply replaced by a <canvas /> element - and you can’t really avoid creating multiple canvases for multiple textures (unless you’d be creating some kind of texture atlas - which in turn will simply require you to use way bigger canvases.)

Your best (if not the only) bet is to limit the amount of CanvasTextures - and reuse whenever possible. But unless you’re planning to have 1000s of textures, that limit isn’t really something you need to worry about.

1 Like

Oh thanks for the detail information!

I’m not sure what I understood, should I understand your solution like this?

In three.js Texture-CanvasTexture is almost same class except update immediately.
As in three.js code, I can see parameter “canvas” will replace to “image”

class CanvasTexture extends Texture {

	constructor( canvas, ... ) {}
}

class Texture extends EventDispatcher {

	constructor( image = Texture.DEFAULT_IMAGE, ... ) {}
}

that means as you said,

So, the conclusion is…

If I want to create single Texture(or CanvasTexture), every single <image />(for Texture) or <canvas />(for CanvasTexture) element will be needed.

What I tried (1 <canvas /> for N CanvasTexture) is like (1 <image /> for N Texture) and… that is nonsense. right? :crazy_face:

Thanks.

It’s called a texture atlas:

Not a nonsense - but it has it’s own limitations (ie. you’re can use just a single texture for many objects, but in return that texture resolution will need to increase, plus you’ll no longer be able to use repeat wrapping when UV unwrapping your models.) Useful in “old-school” and lowpoly projects - ones that use pixelated textures up to ~64x64 in size - since you can pack a lot of these within an atlas.

2 Likes

Hey @mjurczyk I’m trying to use this method, but my canvas texture does not display correctly. It is all messed up when I try to resize the canvas based on the image size. If I set the canvas size before creating the CanvasTexture then it works fine. I would like this to work because I want to load images dynamically and resize the canvas accordingly