Load images from URL

I’m trying to load textures from the web at runtime to replace the current texture on a model’s material diffuse node. So far I’ve tried…

Texture.prototype.initialize = function() {
    app = pc.Application.getApplication();
    var url = "../test.jpg";
    app.assets.loadFromUrl(url, "texture", function (err, asset) {
        var texture = asset._resources[0];
        var entity = app.root.findByName("Modal");      

        entity.model.model.meshInstances[1].material.diffuseMap = texture;
        entity.model.model.meshInstances[1].material.update();
    });
};

But no luck. Any ideas?

Take a look at this tutorial project:

https://playcanvas.com/project/445011/overview/loading-image-from-the-web

It loads a Gravatar image via an Image object and sets that on a PlayCanvas texture. The image URL is set on a script attribute.

The script is:

var LoadImage = pc.createScript('loadImage');

LoadImage.attributes.add('url', { type: 'string' });

// initialize code called once per entity
LoadImage.prototype.initialize = function() {
    var self = this;

    var image = new Image();
    image.crossOrigin = "anonymous";
    image.onload = function () {
        var texture = new pc.Texture(self.app.graphicsDevice);
        texture.setSource(image);

        var material = self.entity.model.material;
        material.diffuseMap = texture;
        material.update();
    };
    image.src = this.url;
};

Notice how the Image is marked as being CORS-enabled (which is mandatory for passing images to WebGL) with the line:

    image.crossOrigin = "anonymous";

Note that the server from where the image is served must be CORS-enabled.

Just as a side note, I notice this was also submitted to the Answers site. Best to chose one or the other rather than both. :slight_smile: Personally, I’d recommend sticking to the forum. The Answers site will probably be retired at some point.

3 Likes

I would do it slightly differently to Will using the Asset API.

initialize: function () {
    // allow cross origin texture requests
    app.loader.getHandler("texture").crossOrigin = "anonymous";
},

loadRemoteImage: function (url) {
    var asset = new pc.Asset("myTexture", "texture", {
        url: url
    });
    app.assets.add(asset);
    asset.on("load", function (asset) {
        // do stuff
    });
    app.assets.load(asset);
}
4 Likes

Is this possible on asset models as well? (the example is with a Box)

Cannot (for instance) make this work:

var LoadImage = pc.createScript('loadImage');

LoadImage.attributes.add('url', { type: 'string' });

// initialize code called once per entity
LoadImage.prototype.initialize = function() {
    var self = this;

    var image = new Image();
    image.crossOrigin = "anonymous";
    image.onload = function () {
      /*  var texture = new pc.Texture(self.app.graphicsDevice);
        texture.setSource(image);

        var material = self.entity.model.material;
        material.diffuseMap = texture;
        material.update();*/
        
        
                var texture = asset._resources[0];
        var entity = app.root.findByName("BSO");      

        entity.model.model.meshInstances[1].material.diffuseMap = texture;
        entity.model.model.meshInstances[1].material.update();
        
    };
    image.src = this.url;
};

What errors do you get? Do you have a project you can share or create a small reproducible one that has this issue?

hello, In Will’s offer, where should the URL be located?

also, in Dave’s offer
“loadRemoteImage: function (url) {
var asset = new pc.Asset(“myTexture”, “texture”, {
url: url
});”

which of the url is the arg and which is external?
also i did not see where the loadRemoteImage is called (and if so, where is the ‘url’ assignment)

Dave was showing the code on how to use the asset API. He didn’t show an example of the use of loadRemoteImage.

He has put the URL as an attribute that you can set in the editor.

Here’s an example for reference: https://playcanvas.com/project/603386/overview/load-external-image

hi yaustar

thanks alot!

  1. I tried as he said but either field did not appear. (Created a new script for it to example)
var Foo = pc.createScript('foo');


Foo.attributes.add("fooID", {type: "string"});

// initialize code called once per entity
Foo.prototype.initialize = function() {
    
};
...

Also there are 2 things i did not understand:

  1. ok so now Will has an attribute called ‘url’ which is assigned externally - but i don’t see where he’s using it in the code.
  2. graphicsDevice (self.app.graphicsDevice) - what’s this? and why didn’t he use loadfromURL?
var ChangeMaterial = pc.createScript('changeMaterial');

var URL_prefix ='https://s3-us-west-2.amazonaws.com/ticomsoft-image-repo/';
var ID = '1';
var URL_sufix = '.png';

var name;
var targetMaterial;


ChangeMaterial.prototype.initialize = function() {
    var url = URL_prefix+ID+URL_sufix;
    console.log(url);    
    name ="laptopFBX";
    targetMaterial = 1;
    /*
     //phil165's offer -  kinda works, no cors
    app = pc.Application.getApplication();
    app.assets.loadFromUrl(url, "texture", function (err, asset) {
        var texture = asset._resources[0];
        var entity = app.root.findByName(name);      

        entity.model.model.meshInstances[targetMaterial].material.diffuseMap = texture;
        entity.model.model.meshInstances[targetMaterial].material.update();
    });
    */
                           
    
     //will's offer - error 404;
    var self = this;

    var image = new Image();
    image.crossOrigin = "anonymous";
    image.onload = function () {
        var texture = new pc.Texture(self.app.graphicsDevice);
        texture.setSource(image);

        var material = self.entity.model.material;
        material.diffuseMap = texture;
        material.update();
    };
    image.src = this.url;
};

hi.
thanks, the script is great

in its spirit i managed to create a CORS based code that works

var ChangeMaterial2 = pc.createScript('changeMaterial2');

var URL_prefix ='https://s3-us-west-2.amazonaws.com/ticomsoft-image-repo/';
var ID = '1';
var URL_sufix = '.png';

ChangeMaterial2.attributes.add("targetMaterial", {targetMaterial: 'int'});

ChangeMaterial2.prototype.initialize = function() {
    var self = this;
    
    var imageUrl = URL_prefix+ID+URL_sufix;
    console.log(imageUrl);    
    targetMaterial = 9;
    
    
    // allow cross origin texture requests
    this.app.loader.getHandler("texture").crossOrigin = "anonymous";
        
    var asset = new pc.Asset("myTexture", "texture", {
        url: imageUrl
    });
    
    this.app.assets.add(asset);
    asset.on("load", function (asset) {
        var material = self.entity.model.meshInstances[targetMaterial].material;
        material.diffuseMap = asset.resource;
        material.update();
    });
    
    this.app.assets.load(asset);
    
};

but a strange thing is that the attribute/variable ‘targetMaterial’ doesn’t appear externally, but the ‘url’ does even though it appears in a function scope;

why doesn’t targetMaterial appearing?
and why did ‘url’ appear?

Have you parsed the script in the editor since changing it?

What do you mean parse in the editor?

Look under ‘Getting Attributes into Editor’: https://developer.playcanvas.com/en/user-manual/scripting/script-attributes/

TL;DR: For the editor to show the attributes of the scripts, it needs to ‘parse’ them to know what attributes have been added and this is a manual step.

thanks - solved

i parsed them now

ok, so now the url attribute dissappeared.
i figured out that the ‘ChangeMaterial’ was wrong

ChangeMaterial.attributes.add("targetMaterial", {targetMaterial: 'int'});

should have been

ChangeMaterial2.attributes.add("targetMaterial", {type: 'string'});

and is now showing after parsing.

thanks for your help.

the only question left is that i cic not see where is the url used in Will’s code

var self = this;

    var image = new Image();
    image.crossOrigin = "anonymous";
    image.onload = function () {
        var texture = new pc.Texture(self.app.graphicsDevice);
        texture.setSource(image);

        var material = self.entity.model.material;
        material.diffuseMap = texture;
        material.update();
    };
    image.src = this.url;
};

and what does the new pc.Texture(self.app.graphicsDevice); means? why didn’t he use loadFromUrl ?

new strange issue i’m having:

var ChangeMaterial2 = pc.createScript('changeMaterial2');

var imageUrl ='https://s3-us-west-2.amazonaws.com/ticomsoft-image-repo/1.png';


ChangeMaterial2.attributes.add("targetMaterial", {type: 'number'});

ChangeMaterial2.prototype.initialize = function() {
    var self = this;
    
    console.log(imageUrl);    
    targetMaterial = 9;
    
    
    // allow cross origin texture requests
    this.app.loader.getHandler("texture").crossOrigin = "anonymous";
        
    var asset = new pc.Asset("myTexture", "texture", {
        url: imageUrl
    });
    
    this.app.assets.add(asset);
    asset.on("load", function (asset) {
        var material = self.entity.model.meshInstances[targetMaterial].material;
        material.diffuseMap = asset.resource;
        material.update();
    });
    
    this.app.assets.load(asset);
    
};

if i’m running this it all wors fine
but if i remove the line targetMaterial = 9; and assign 9 in the editor i get an error that material cannot be found.
printing targetMaterial yields undefined.
why? why didn’t it read the number from the scene?

He has it as a script attribute:
image

He was creating a new PlayCanvas texture object that requires the graphics device object to be passed in. (See API reference: Texture | PlayCanvas API Reference). His way didn’t make use of the PlayCanvas asset system or registry.

I would say that Dave’s method would be the preferred method IMO.

Given the age of the post, perhaps the function didn’t exist then? Or maybe it doesn’t handle CORS?

It needs to be this.targetMaterial as it’s part of the object via the attribute system and not a local variable.

ie: var material = self.entity.model.meshInstances[this.targetMaterial].material;

gatcha, everything works now

Is there a way to skip the ‘this’ (or ‘self’) writing every time?
or only by predefining variables beforehand?

It… depends. You can cache the value or reference to a local variable. Eg

var model = this.entity.model;

But generally, there are times where it can’t be avoided.

It all depends on the scope that you are working and is worth reading up about.