Load images from URL

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.

Ok thx for your focus once again yaustar :slight_smile:

https://playcanvas.com/editor/scene/713040

  • tried som different stuff before sending this
    NB! Have my eyes on the random-model asset and not a generic box as Will example

(have been around this topic for 6 months ago but forgot the CORS-part this time, and I dont have it turned-on at my website for the present).

In relation to this above, I get a lame answer “that I can turn-on that myself” from my website-provider :frowning: … are there by any chance some upload-image services with CORS that I/one can use?

I have found GitHub to work for hosting the odd couple of files. Not really a long term solution though.

Depending on what control panel you have, it can be quite easy to setup CORS for the server https://enable-cors.org/server.html

how would you handle an error (i.e there is no image in that url)?

asset.on("error", function(...))?

Looking at the source code and docs, you are correct: https://github.com/playcanvas/engine/blob/master/src/asset/asset.js#L107

    asset.on("error", function (asset) {
        console.log("Error loading " + asset.getFileUrl());
    });
1 Like

that’s strange
my Cors call looks like this

ChangeMaterial2.prototype.ImgFromURLwithCORS = function()
{
    // allow cross origin texture requests
    _app.loader.getHandler("texture").crossOrigin = "anonymous";

    var asset = new pc.Asset("myTexture", "texture", {
        url: imageUrl
    });
    asset.on("error", function (asset) {
        console.log("Error loading " + asset.getFileUrl());
    });
    
    asset.on("load", function (asset) {
        material.diffuseMap = asset.resource;
        material.update();
    });
    
    _app.assets.add(asset);

    _app.assets.load(asset);
};

but still, even though the 1st thing i do is to catch the on(“error”)
i still get an uncaught exception when i get it
*i call a set of image and increase the number until there is no such image.
What i try to do here is to make sure to handle the exception properly if the call returns error

Corrected code in full:

// initialize code called once per entity
LoadExternalImage.prototype.initialize = function() {
    var self = this;
    
    // allow cross origin texture requests
    this.app.loader.getHandler("texture").crossOrigin = "anonymous";
        
    var asset = new pc.Asset("myTexture", "texture", {
        url: this.imageUrl
    });
    
    this.app.assets.add(asset);
    
    asset.on("error", function (message) {
        console.log(message);
    });
    
    asset.on("load", function (asset) {
        var material = self.entity.model.material;
        material.diffuseMap = asset.resource;
        material.update();
    });
    
    this.app.assets.load(asset);
};

The exception will still trigger if the URL is incorrect but won’t show the message on screen in the app unless you have launched from the editor.

In a published build, this is what you will see if the image URL doesn’t exist:

You don’t need to handle the exception yourself as long as you have the error event callback. In the callback, you can do whatever logic you need to in the event of an image load error.

Got it working with help from supporters at my provider :slight_smile:

1 Like

Hi All,

Resurrecting this old thread. I was trying to load texture from a URL and it works all fine when assigned to a material. But in my case, I need to update the texture on a button and it just doesn’t seem to work. Any ideas why?

Here is a demo project: https://playcanvas.com/project/738398/overview/loadimagefromurlonabutton

If you open the project, the button doesn’t load the texture that I would expect it to, because of the script attached. Besides, if you “Enable” the Box Entity in the scene, you could see that the button starts erroring out too(this is not related to the texture load issue but still worth mentioning). Finally, if you “Disable” the button and only display the box, you could see the updated texture.

I would use assets.loadFromUrl instead:

https://api.playcanvas.com/classes/Engine.AssetRegistry.html#loadFromUrl

Eg

// initialize code called once per entity
LoadUrlTextures.prototype.loadOnButton = function() 
{
    var self = this;
    var asset = this.app.assets.loadFromUrl(this.textureUrl, 'texture', function (error, asset) {
        if (error) {
            console.log(error);
            return;
        }
        
        var element = self.entity.element;
        element.texture = asset.resource;
    });
};

Hello,

I’ve tried several of the suggested solutions but could not get it to work. I’ve noticed that the mentioned example uses Legacy Model instead of Render and StandardMaterial instead of script created BasicMaterial which I use in my project. The currently active code for loading from URL is the code mentioned by @yaustar in his last post.

Anyway, I’ve made a project to debug the issue. It contains 2 boxes and 2 scripts. 1 loads the image via URL and the other sets it via script attribute resource (same image). The only difference when comparing the loaded Texture objects is that the URL-loaded has format 7 and the set image has format 6. Maybe someone could have a look.

https://playcanvas.com/editor/scene/1451840

Thank you very much!

Render models and assets weren’t available during the last exchange in the thread.

Honestly confused by this one. I don’t use BasicMaterial myself but something is a bit odd.

Moving the line where the render material is assigned to the callback after the texture is loaded and assigned to the colorMap makes it work fine: https://playcanvas.com/project/947824/overview/f-debugging-image-loaded-from

Maybe @mvaligursky knows more.

All I did was go from:

var TextureFromUrl = pc.createScript('textureFromUrl');

TextureFromUrl.prototype.initialize = function() {

    // create and set material
    this.material = new pc.BasicMaterial();
    this.material.cull = pc.CULLFACE_NONE;
    //this.material.color.set(0.3, 0.6, 0.1);
    this.material.update();
    this.entity.render.material = this.material;

    var url = "https://dev3.freshfx.at/ffx/debug.jpg"; // uploaded here and CORS flags set properly
    this.loadTexture(url);
};

TextureFromUrl.prototype.loadTexture = function(url) 
{
    console.log("loading image from url", url);

    var asset = this.app.assets.loadFromUrl(url, 'texture', function (error, asset) {
        if (error) {
            console.log(error);
            return;
        }
        console.log("loaded texture", asset.resource);
        this.material.colorMap = asset.resource;
        this.material.update();
    }.bind(this));
};

to

var TextureFromUrl = pc.createScript('textureFromUrl');

TextureFromUrl.prototype.initialize = function() {

    // create and set material
    this.material = new pc.BasicMaterial();
    this.material.cull = pc.CULLFACE_NONE;
    //this.material.color.set(0.3, 0.6, 0.1);
    this.material.update();
   
    var url = "https://dev3.freshfx.at/ffx/debug.jpg"; // uploaded here and CORS flags set properly
    this.loadTexture(url);
};

TextureFromUrl.prototype.loadTexture = function(url) 
{
    console.log("loading image from url", url);

    var asset = this.app.assets.loadFromUrl(url, 'texture', function (error, asset) {
        if (error) {
            console.log(error);
            return;
        }
        console.log("loaded texture", asset.resource);
        this.material.colorMap = asset.resource;
        this.material.update();

        this.entity.render.material = this.material;
    }.bind(this));
};

Thank you very much!
Interesting … I’ve tried setting the material again in the loaded-handler but it seems to work only when it’s set once like you did.

EDIT: I can now confirm that the problem comes from using BasicMaterial. Replacing it with StandardMaterial makes it work as expected.

1 Like