ImageTransparencyCalculator - Check sprite on click

I wrote two projects, one of which uses native extensions. The first generates a special file with data of image transparency. The second uses it to determine if the sprite is clicked or not.

Now you can determine whether the game object clicks, even if it is of complex shape without physics!

How to use

  1. Open ImageTransparencyCalculator project
  2. Put images in a folder Images and write their size in image_data.lua%D0%B8%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5
  3. Run the project and click anywhere. It will close automatically, and files will appear in the folder generated_data
    %D0%B8%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5
  4. Copy generated files to folder generated data in project Check Click
  5. Position the game object with the sprite in the game. Plug in module check_click.lua
  6. Specify game object identifiers to load their data
  7. You can check clicked game object or not using check_click.check(game_object_id, x, y)
  8. Specify a folder generated_data in custom resources

Perfect accuracy is often not needed, you can customize it and reduce the file size here:
(ImageTransparencyCalculator project)

Link: https://github.com/JAlHund/ImageTransparencyCalculator
License: MIT
Written for Defold 1.2.169

The texture of London from my game. We all perfectly understand that not all of London is like that, and this is just the name of the province

18 Likes

To determine the click in your game you need:

  1. Generate files in ImageTransparencyCalculator project and add in your project
  2. Add and require check_click.lua
  3. Init data using
check_click.init({
    game_object_id1,
    game_object_id2,
    ...
})
  1. Check click
    if check_click.check(game_object_id, x, y) then
        print("Clicked")
    else
        print("No :(")
    end

Project Check click is example of how to work and track clicks
Project ImageTransparencyCalculator generates files for this

How it works in the example:
%D0%9D%D0%BE%D0%B2%D1%8B%D0%B9%20%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82

6 Likes

Bundling error fixed (I forgot about custom resources)
Added zlib, now the generated files weigh even less:
Compression: 2, image from example
Size: 23,6 kb -> 799b

2 Likes

Really cool project! Thank you for sharing!

2 Likes

I made a few more optimizations, the changes are available on the github.
Please update those using large images.
Large image data takes a while to initialize. There are no lags with click detection, only with initializations.

Comparison made by @russelkgd

Loading time.
Before: 0.65s
After: 0.28

The size of the generated data
Decreased by 30-35%

If you generate large images and generating data takes time, you can use this code and do not generate data again
It must be put into the init function once (check_click.lua), the data in the new format will appear in the new_generated_data folder.

code
local str_begin = "/generated_data/"
for k, v in pairs(init_list) do
		local loaded_data = sys_load_resource(str_begin..v..".data")
		if loaded_data then
			loaded_data = zlib_inflate(loaded_data)

                        -- Should be executed once and then removed:
			local file = io.open("new_generated_data/"..v..".data", "wb")
			local new_data = string.gsub(loaded_data, "255", "1")
			file:write(zlib.deflate(new_data))
			file:close()
6 Likes

Optimized again. I have a lot of images in the game that I have to track when I click.
For those who do not use large images or many images, you may not need to update.

I rewrote the click tracking module into a native extension.
The main reason for the slow initialization was that the transparency data was stored in the Lua array. And adding items to it was slow. I am assuming that when elements are added, the array is re-created.
Therefore, the data is now stored and checked in a C++ module.
When initializing with data, memory of a certain size is allocated and filled with the necessary data.

Comparison of module initialization time (maps from my game):
Europe:
0.659 sec -> 0.064 sec
America (The map is too big):
5.2 sec -> 0.364 sec

I could not think that data is added to the lua table so slowly compared to my implementation in native code.

I hope that I don’t need to change anything here anymore.
The data format has also changed, you will need to regenerate the data again or use this code by analogy with what was above

code
		local file = io.open("new_generated_data/"..v..".data", "wb")
		local new_data = string.gsub(loaded_data, " ", "")
		file:write(zlib.deflate(new_data))
		file:close()

The data format has changed since at first it was thought that you can adjust the transparency threshold for the click detection. Now that’s either transparent or not. Data is now located as compactly as possible

3 Likes

A bit surprising. How did the code look that added to the table?

In lua, an element was added to the array with each new received character. In C ++ a chunk of memory is pre-allocated and filled

lua
                   for val in string_gmatch(loaded_data, ".") do
			if is_compression then
				data_compression[v] = string_byte(val) - 48
				is_compression = false
			else
				i = i + 1
				data[v][i] = val == "0" and 0 or 255 -- It was the most demanding performance. I tried removing the conditions, it has no effect. Even data[v][i] = 1 is demanding
			end
		end
c++
ImageData imageData;
imageData.id = new char[strlen(id) + 1];
strncpy(imageData.id, id, strlen(id) + 1);
imageData.size = len;
imageData.data = new uint8_t[len - 1];

int compression = str[0] - '0';

for(uint32_t i = 0;i < len - 1;++i)
    imageData.data[i] = str[i + 1] - '0';

I think it’s string_gmatch that is slow. But you’d have to profile the code to know for sure.

But I guess it doesn’t really matter since you’ve moved that logic to C which is a perfectly valid solution to get more speed!

I did some performance checks with the old module. It looks like the same amount of time is spent on string_gmatch and adding elements.
The simplest measurement:
0.61s with adding
0.27s without

I tried in several ways, it looks like 50/50

In any case, I have no idea how it can be faster in lua. Native extensions are awesome

4 Likes

simply table generator on python:

from PIL import Image

file_name="file"

for number in range(5):
    im = Image.open(file_name+str(number)+".png")
    width, height = im.size
    print(file_name+str(number)+"=vmath.vector3("+str(width)+","+str(height)+",0),")
5 Likes

a more advanced script: renames .png, copies to the images folder and overwrites image_data.lua. After that, all that remains is to launch the project.

from PIL import Image
import os
import shutil

directory = os.path.abspath(os.curdir)
files = os.listdir(directory)
it_path=r"PATH_TO ImageTransparencyCalculator - Check sprite on click/ImageTransparencyCalculator/"
img_path=it_path + r"images/"
name = "new name for png"
png_files=[]
code=["local t = {"]

#clear images folder
for filename in os.listdir(img_path):
    file_path = os.path.join(img_path, filename)
    try:
        if os.path.isfile(file_path) or os.path.islink(file_path):
            os.unlink(file_path)
        elif os.path.isdir(file_path):
            shutil.rmtree(file_path)
    except Exception as e:
        print('Failed to delete %s. Reason: %s' % (file_path, e))


for i in range(len(files)):
    if files[i].startswith("optional") and files[i].endswith(".png"):
        png_files.append(files[i])
        os.rename(directory+"/"+files[i], directory+"/"+name+str(i)+".png")
        shutil.copy(directory + r"/" + name+str(i) + ".png", img_path)

for i in range(len(png_files)):
    im = Image.open(name+str(i)+".png")
    width, height = im.size
    code.append(name+str(i)+"=vmath.vector3("+str(width)+","+str(height)+",0),")


code.append("}")
code.append("return t")

it_file = os.path.join(it_path+r"main/", 'image_data.lua')

with open(it_file, "w") as file:
    file.writelines("%s\n" % line for line in code)
4 Likes