three.js: Couldn't create Texture from resized DOM Image

Describe the bug If the DOM image is resized with CSS, like: width: 10%, and you try to create texture with THREE.Texture(DOM_ELEMENT) class, that will result in: GL ERROR :GL_INVALID_VALUE : glTexSubImage2D: bad dimensions. The behaviour appears since v135. It is not present in v134.

To Reproduce

Steps to reproduce the behavior:

  1. Create a DOM image in HTML
  2. Resize it with CSS: img{width: 10%}
  3. Create a texture from image with THREE.Texture(DomElement) class
  4. Use texture on any material
  5. You will get: GL ERROR :GL_INVALID_VALUE : glTexSubImage2D: bad dimensions. in latest Chrome.

Code

  let material = new THREE.MeshBasicMaterial();
  let img = document.querySelector('img')
  let texture = new THREE.Texture(img)
  texture.needsUpdate = true;
  material.map = texture;
  mesh = new THREE.Mesh( geometry, material );
  scene.add( mesh );

Live example

Expected behavior

To see sphere with kitten texture.

Screenshots

This is the expected result of demo. Screenshot 2022-01-07 at 01 51 56

Platform:

  • Device: Desktop
  • OS: MacOs
  • Browser: Chrome
  • Three.js version: r136

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 25 (3 by maintainers)

Commits related to this issue

Most upvoted comments

Yeah, so, instead of

let texture = new THREE.Texture(DOM_IMG)

I used this workaround:

let image = new Image();
image.src = DOM_IMG.src;
let texture = new THREE.Texture(image);

also in one case this:

let CLONED_IMAGE = DOM_IMG.cloneNode(true); // this helped when i set image width in JS
let texture = new THREE.Texture(CLONED_IMAGE);

But of course, would be perfect to fix issue, to not do this. Or is it best practice now to clone everything from DOM before converting it to texture? 🤔

could asking other developers on discord be a way to make up a decision? For me as one of three.js dev-users it is obvious that i expect three.js to disregard any CSS, when i convert DOM element to WebGL Texture. And my hypothesis is its like that for 99% people. Because those are two different mental models/concepts/coordinate-systems, and i dont want them to interfere.

What are the possible drawbacks of using naturalWidth here?

I have exactly this issue when trying to load images from the DOM as textures in r157 (same scenario as @akella’s using THREE.Texture). I am surprised that under the bonnet Three is thrown due to some CSS applied to the image - as @mrdoob suggested I agree that image.naturalWidth and image.naturalHeight should be used instead the current mechanism - it would be amazing to see a fix for this.

The mentioned code path in WebGLTextures is not only relevant for HTMLImageElement. ImageBitmap also uses it which has no naturalWidth and naturalHeight properties.

It might be also necessary to update other logic in WebGLTextures like the resize logic for WebGL 1. Working with two different image dimension properties tends to be more error prone so a change must verify many uses cases.

The current implementation is inconsistent because it doesn’t work around the inconsistent property behaviour in the spec:

The width and height properties of both HTMLCanvasElement and ImageBitmap reflect the logical pixels of their intrinsic ImageData container. Changing the display properties of a canvas via CSS does not affect its intrinsic width and height properties.

The correct equivalents for HTMLImageElement are naturalWidth and naturalHeight because, like width and height for HTMLCanvasElement and ImageBitmap, these properties reflect the logical pixels of the intrinsic imageData and are not affected by changes in display logic.

I guess the question is whether or not Three.js should correct for this inconsistency by internally mapping width to naturalWidth and height to naturalHeight when image instanceof HTMLImageElement, instead of using image.width and image.height regardless of the constructor.

Hmm… image.naturalWidth || image.width? 😅

It sure makes things a bit more complicated…

This approach appears to work regardless of the CSS.

https://jsfiddle.net/wh9g0dqn/

@Mugen87 Any idea what is going on?

i will just add my 2c: the current change from v135 is actually breaking a lot of code. Common use case: Converting DOM Images into WebGL layer to animate them. With this case you have images in HTML with design applied to them, and you convert them to textures, and now it doesnt work if you have any sizing CSS applied to them. Which will always be because web is responsive now. So this makes texture creation from DOM element kind of useless in most cases, does it?

What would be the alternative way? To copy all the IMG into hidden DIV and strip all CSS from them before converting to Texture? Or to collect SRC’s and load images second time with TextureLoader? =(