Save & load a texture with alpha component

The following code loads a texture and applies it to a simple mesh. It works perfectly for images that do not contain an alpha channel.

toJSON() {
    let output = super.toJSON();
    output["geometry"] = this.geometry;
    output['imageURL'] = this.mesh.toJSON().images[0]["url"];
    return output;
}

fromJSON(data) {
    super.fromJSON(data);
    this.geometry = data["geometry"];
    this.image_path = data["imageURL"];
    this.refreshImage();
}

refreshImage() {
    const this_obj = this;
    const image_texture = new THREE.TextureLoader().load(
        //image to load
        this.image_path,
        //onLoad callback to create the material only once the texture is loaded and its dimensions are available,
        //this will ensure aspect ratio is based on the actual texture loaded.
        (texture) => {
            this_obj.changeGeometry(texture.image.width / texture.image.height)
        },
        //not required
        undefined,
        //onError callback
        (err) => {
            alert("An error occurred while attempting to load image");
        }
    );
    this.mesh.material.map.dispose();
    this.mesh.material.dispose();
    this.mesh.material = new THREE.MeshPhongMaterial({map: image_texture, side: THREE.DoubleSide,
        transparent: true})
    this.mesh.material.color.set(this.color);

    this.mesh.material.needsUpdate = true;
}

Unfortunately, it does not work properly for images with alpha channel, because transparent areas in the source image are rendered in black opaque color.

Does anyone know why this happens and how best to achieve the desired result?

did you do :

    this.renderer = new THREE.WebGLRenderer({alpha:true, antialias : true});

?

Yes, I did.

It did not appear to make any difference whether the transparency is shown or not.

Perhaps I should elaborate that the issue is not with creating and then using textures with transparency.

The issue is with serializing the material and then deserializing it again. When I deserialize it, the transparency is rendered in black.

Ideally I want to only serialize the texture information of the material, keeping the RGBA information, and then deserialize it back from JSON…

maybe the .color is only rgb, not rgba?

i looked at the threejs source, and there’s transparency features in MeshPhysicalMaterial, possibly others too…

The source is RGBA, because I can create meshes (planes, cubes and cylinders) with it and the transparency is fine.

Is there any way that one can use ObjectLoader.parseTextures for this ? Frankly, from the source I cannot figure out how to use parseTextures.

parseMaterials use MaterialLoader internally which I already know does not preserve the texture data.

you’d need to examine the source by adding a debugger statement to three.module.js;
https://github.com/nicerapp/nicerapp_v2/blob/2777b7be948e62852b89d30956cebf5962095fbd/nicerapp/3rd-party/3D/libs/three.js/build/three.module.js#L41262

then you’d inspect data.image in the debugger, and figure out how to use that data.

sorry, but that’s as far as my current knowledge about the inner workings of threejs goes at the moment.

maybe someone else can be of more assistance…

I got an answer to my question when I realized that the issue is coming from the Mesh.toJSON call.
The method is a recursive one that is a real rabbit-hole. But at the bottom of the rabbit-hole you find that texture images are converted to base64 by drawing the image onto an temporary internal canvas. This happens in the ImageUtils.js module inside the getDataURL() function

The issue is that texture images larger than 2048 in width or height are converted into compressed “jpeg” format rather than “png” format that retains the alpha component.

This explains everything.

You can load any image, apply it to a material using TextureLoader, but as soon as you call toJSON to serialize your mesh, the alpha component is lost if the underlying image is larger than 2048 wide or long.

The solution in my case is to write my own function that draws to a canvas and converts the image to base64, but supports larger image sizes. Ofcourse one would have to warn the user that it may take some time to perform the conversion.

1 Like

Thank you @Rene_Veerman for the inspiration to dig deeper.

1 Like

Here is the texture to url converter that I came up with…stealing heavily from ImageUtils.js and removing error handling code.

function ImageURLfromTexture( image_texture, retain_alpha = true ) {
    const image = image_texture.image;
    if (image !== undefined) {
            if (/^data:/i.test(image.src)) {
                return image.src;
            }
            let _canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
            _canvas.width = image.width;
            _canvas.height = image.height;

            const context = _canvas.getContext('2d');
            if (image instanceof ImageData) {
                context.putImageData(image, 0, 0);
            } else {
                context.drawImage(image, 0, 0, image.width, image.height);
            }
            if ((_canvas.width > 2048 || _canvas.height > 2048) && (!retain_alpha)) {
                return _canvas.toDataURL('image/jpeg', 0.6);
            } else {
                return _canvas.toDataURL('image/png');
            }
    } else {
        return null;
    }
}
1 Like