How to create texture from image in EditorScript?

Godot Version

v4.3.dev5.official [c9c17d6ca]

Question

Hello, I would like to make an EditorScript that creates a texture from my scene and that sets this texture as a member of a custom node, but I struggle to save and load the texture.

Here is a minimal example of how I proceed :

My scene consists of a single node with this script :

@tool
class_name MainScene extends Node2D

@export var texture:Texture

func create_texture() -> void :
	var img:Image = Image.create(32,32,false, Image.FORMAT_RGBA8)
	img.fill(Color.BLACK)
	ResourceSaver.save(ImageTexture.create_from_image(img), "res://img.png")
	texture = ResourceLoader.load("res://img.png")

I also have an EditorScript to run create_texture() :

@tool
extends EditorScript

func _run() -> void:
	var main_node:MainScene = get_scene()
	main_node.create_texture()

When I run the EditorScript, I get an error in the console (No loader found for resource: res://img.png (expected type: )) and a img.png file appears in the filesystem but it seems errored because it has a red cross on it and and I get an error when I click on it (editor/editor_node.cpp:1239 - Condition "!res.is_valid()" is true. Returning: ERR_CANT_OPEN). Also, when I look into my node inspector, I can see that it has no texture.

If I get the Godot file system to update (either by calling EditorInterface.get_resource_filesystem().scan(), or by making Godot lose and regain focus), the img.png file does not have the red cross anymore and I can click on it.

If I re-run my script then, I get no error and the texture appears in the inspector.

All of this makes me guess that resource saving is not completely achieved after ResourceSaver.save() call, so immediate loading of the texture cannot be done. When I re-run the script, I can load a texture from img.png because the file already exists, but the texture that will be loaded into my node will be the previous version of the file.

So, here’s my question : what is the proper way to create a texture and attach it to a node from EditorScript ?

Ok, here’s how I implemented create_texture() to fix my issue :

func create_texture() -> void :
	# Create image
	var img:Image = Image.create(32,32,false, Image.FORMAT_RGBA8)
	img.fill(Color.BLACK)

	# Create a PortableCompressedTexture2D from image, and save it in resources
	var portable_texture:PortableCompressedTexture2D = PortableCompressedTexture2D.new()
	texture.create_from_image(image, PortableCompressedTexture2D.COMPRESSION_MODE_LOSSLESS) 
	ResourceSaver.save(texture, "res://img.res")

	# ... later in code, or in another function, you can load texture from file path :
	texture = load("res://img.res")

PortableCompressedTexture2D should be saved with .res or .tres extensions.

What you’re doing is creating a new file which is used as resource.

The problem here is the format you use to save it (png): Engine tries to load the resource equivalent from the generated file, but since it never have been imported, it doesn’t have an equivalent, so the load step fails.

Your solution kinda works, because you’re creating a resource and saving the resource directly (res/tres) as file, so it doesn’t go through importation step.

Create the texture and use it directly doing texture = ImageTexture.create_from_image(img) and later you can save it overriding the resource cache, you don’t need to save it to disk to use it immediately after.

Ideally, you should tell FileSystem that a new file appeared so it can scan it an use the proper importer for the file if you want to use it later from disk and don’t want to do the unfocus/focus editor trick.

1 Like

Thanks for your answer,

I tried to follow your advice but I’m still having trouble getting it to work.

I would like to have 2 methods :

  • The first one should create the image and the texture and save the texture to the file system
  • The second one should load the texture from the file system and assign it to an export var member

I would like to call these 2 methods in a row in the editor.

So I created a test project with only one scene, which has only a Node2D that has this script :

@tool
class_name MainScene extends Node2D

@export var texture:Texture

func main() -> void :
	create_texture()
	use_texture()


func create_texture() -> void :
	# 1. Create image
	var img:Image = Image.create(256,256,false, Image.FORMAT_RGBA8)
	img.fill(Color.BLUE)
	
	# 2. Create and save texture
	var img_texture:ImageTexture = ImageTexture.create_from_image(img)
	img_texture.take_over_path("res://img.png")
	ResourceSaver.save(img_texture)
	
	# 3. Scan file system
	EditorInterface.get_resource_filesystem().scan()


func use_texture() -> void :
	texture = ResourceLoader.load("res://img.png")

I also have an EditorScript that will call main().

By calling main(), I would expect :

  • To see an img.png file appears in my file system
  • To see a blue texture appears on my texture slot on my node inspector
  • To have no error in the console

Instead :

  • I can see an img.png file in my file system, but is has the CompressedTexture2D icon instead of the img.png preview icon (I don’t know if this indicates a problem)
  • The texture slot of my node inspector remains empty
  • I have an error in my console : “No loader found for resource: res://img.png (expected type: )”

What did I do wrong ?