How to make a new image with small file size

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Gary_CHAN

I am now making a game which divides a texture into several equal parts ( like 4 parts) and change the pixel’s color values and save it in the user:// folder for later use (for example loading it on a new game using load() and set it as the texture_normal of a TextureRect). I am using the following code:

var old_texture = load(res://test_image.png)
var old_image = old_texture.get_data()
var new_image = old_image.get_rect(Rect2(starting_point, image_area ))
new_image.convert(Image.FORMAT_RGBA8)
new_image.lock()
for x_coordinate in range(tab_start_x):
	for y_coordinate in range(tab_height):
		new_image.set_pixel(x_coordinate, y_coordinate, Color(0,0,0,0))
new_image.unlock()
var new_texture = ImageTexture.new()
new_texture.create_from_image(new_image)
ResourceSaver.save("user://Save/1.tres, new_texture)

It did the job but the final file size(.tres) is huge. For testing, I used a png file with 256px*256px which is 162KB. The .stex file is 152KB. But the output four .tres files are 2.30MB in total. I try converting the new_image into different format but it seems to have no impact on file size.
I then compress the new_image using

new_image.compress(0,0,0.2)

it did the job too by shrinking the file size but the console shows an error
“ERROR: compress: Condition ’ !_image_compress_bc_func ’ is true. returned: ERR_UNAVAILABLE
At: core/image.cpp:1809”
It only appears if I export the project. If I run it in editor, it will shows no error and compress the file as expected.

I would like to ask two questions:
1, How can I making a smaller image ( at least has similar file size with the original image)?
2, What does the error mean? I didn’t find ppl talking about this error but I don’t understand the source code in github.

:bust_in_silhouette: Reply From: Zylann

There are several problems with your method.

Ways to load an image

var old_texture = load(res://test_image.png)
var old_image = old_texture.get_data()

A “texture” is how we call an image on the graphics card.
This code loads the imported image from disk, uploads it to your graphics card, and gives you a texture object. Then it downloads it back into memory (which is much slower than upload), while the texture remains unused.
Unless you really want the output of an operation occurring on the graphics card (like a screenshot or shader output), you don’t need to do it that way.

There are two alternatives:

  1. Use Image to load a file.
var old_image = Image.new()
old_image.load("path/to/the/image.png")

Drawback: if used in an exported game, it won’t work on files under res://, but it should be preferred on images provided by the user.

  1. Import your file as an Image

Double-click on your image in the Godot file explorer, and look at the Import dock. You can make it so it is imported as an Image, which means your current code will then return an Image straight away, without the need to use get_data().

However, if you ALSO want to use that image as a texture so you can draw it on screen, importing as ImageTexture might be better, so that Godot will be able to keep the image both on graphics card and in memory. Unfortunately this option is not available, so could be created manually I guess.

If you don’t mind about performance, get_data() is fine, just be aware of the implications I described :wink:

Ways to save it

Problem in your code:

var new_texture = ImageTexture.new()
new_texture.create_from_image(new_image)
ResourceSaver.save("user://Save/1.tres, new_texture)

This code creates a new texture and uploads the image to the graphics card, and then attempts to save it under Godot TEXT format. Again, the graphics card had no reason to be used, and the format you choose is really bad for image data.

If you want the output to be a PNG file, don’t use ResourceSaver. There isn’t one specifically for images, so you have to use a more specific API:

new_image.save_png("user://Save/1.png")

This will produce a regular PNG which is more suitable and lighter.

Thank you so much. It’s been busy recently so sorry for the late reply.
I have been trying your method and it works really great.
For the saving part, It gives reasonable png file size.
For the loading part, it also works as you described.
As you said, Image.load() cannot use res:// when export but I need to display it too.
So I use following code to display it once:

var image = load("res://test_image.png)
var image_texture = ImageTexture.new()
image_texture.create_from_image(image)
self.texture_normal = image_texture

Is it similar to what you meant by creating ImageTexture manually?

BTW, thank you so much again. Now It loads really really fast.

Gary_CHAN | 2019-08-12 15:03

Yes if you import your PNG as image you’d create the ImageTexture that way to display it.

Zylann | 2019-08-12 18:59