Image Conversion being too slow
Hey, I really love this tool. Its able to accurately convert images to terminal codes. But I have one issue - the conversion process is too slow....
Here is an example:
ORIGINAL - 500x500_random.png

Command: climage.convert("500x500_random.png", is_unicode=True, width=100)
OUTPUT -

And this conversion took about 0.51 seconds.
My original idea was to try to make a real-time video-transmission tool using only the terminal. But the output video cannot be like 2 FPS....
So is there any way to optimize the conversion process without losing a lot of quality?
Thanks for the feedback!
I took a look at this, and it looks like most expensive part occurs for non-truecolor colour schemes.
TL;DR: Use is_truecolor=True, is_256colour=False, if your terminal supports it.
Some cProfile stats, sorted by the total time spent in each line, for the input:
convert('image.png', is_unicode=True, width=100)
2722518 function calls (2539177 primitive calls) in 0.762 seconds
Ordered by: internal time
ncalls tottime percall cumtime percall filename:lineno(function)
181030/4540 0.210 0.000 0.627 0.000 kdtree.py:431(_search_node)
362670 0.120 0.000 0.199 0.000 kdtree.py:382(axis_dist)
120890 0.061 0.000 0.260 0.000 kdtree.py:396(<listcomp>)
120890 0.058 0.000 0.336 0.000 kdtree.py:390(dist)
540962 0.049 0.000 0.049 0.000 climage.py:15(__getitem__)
362670 0.046 0.000 0.046 0.000 {built-in method math.pow}
120890 0.027 0.000 0.363 0.000 kdtree.py:418(<lambda>)
120890 0.019 0.000 0.019 0.000 {built-in method builtins.sum}
181030 0.017 0.000 0.017 0.000 kdtree.py:172(__nonzero__)
201152/198397 0.011 0.000 0.012 0.000 {built-in method builtins.len}
Now, it's much different for this:
convert('image.png', is_unicode=True, width=100, is_truecolor=True, is_256color=False)
Note, yeah, I notice that it's kinda annoying to enable truecolour, using default values gets in the way here.
Log:
235341 function calls (230805 primitive calls) in 0.118 seconds
Ordered by: internal time
ncalls tottime percall cumtime percall filename:lineno(function)
482/80 0.007 0.000 0.019 0.000 sre_parse.py:493(_parse)
93 0.006 0.000 0.006 0.000 {built-in method marshal.loads}
1 0.004 0.004 0.020 0.020 climage.py:123(_toAnsi)
5272 0.004 0.000 0.004 0.000 {method 'format' of 'str' objects}
10000 0.004 0.000 0.009 0.000 Image.py:1423(getpixel)
373/368 0.003 0.000 0.023 0.000 {built-in method builtins.__build_class__}
13452 0.003 0.000 0.003 0.000 sre_parse.py:233(__next)
10003 0.003 0.000 0.004 0.000 Image.py:814(load)
829/76 0.003 0.000 0.007 0.000 sre_compile.py:71(_compile)
3 0.002 0.001 0.002 0.001 {method 'decode' of 'ImagingDecoder' objects}
I benchmarked a bunch of different invocations, too:
duration = timeit.timeit(lambda: convert('image.png', is_unicode=True, width=2, is_truecolor=True, is_256color=False), number=1000)
print(f"Width=2 (unicode, truecolor) took {duration}")
duration = timeit.timeit(lambda: convert('image.png', is_unicode=True, width=100, is_truecolor=True, is_256color=False), number=1000)
print(f"Width=100 (unicode, truecolor) took {duration}")
duration = timeit.timeit(lambda: convert('image.png', is_unicode=True, width=2, is_truecolor=False, is_256color=True), number=1000)
print(f"Width=2 (unicode, 256color) took {duration}")
duration = timeit.timeit(lambda: convert('image.png', is_unicode=True, width=100, is_truecolor=False, is_256color=True), number=1000)
print(f"Width=100 (unicode, 256color) took {duration}")
duration = timeit.timeit(lambda: convert('image.png', is_unicode=False, width=2, is_truecolor=True, is_256color=False), number=1000)
print(f"Width=2 (truecolor) took {duration}")
duration = timeit.timeit(lambda: convert('image.png', is_unicode=False, width=100, is_truecolor=True, is_256color=False), number=1000)
print(f"Width=100 (truecolor) took {duration}")
duration = timeit.timeit(lambda: convert('image.png', is_unicode=False, width=2, is_truecolor=False, is_256color=True), number=1000)
print(f"Width=2 (256color) took {duration}")
duration = timeit.timeit(lambda: convert('image.png', is_unicode=False, width=100, is_truecolor=False, is_256color=True), number=1000)
print(f"Width=100 (256color) took {duration}")
Prints (this is the time each function call takes, in milliseconds):
Width=2 (unicode, truecolor) took 3.259595004012226
Width=100 (unicode, truecolor) took 13.441674665999017
Width=2 (unicode, 256color) took 3.350438164998195
Width=100 (unicode, 256color) took 314.9447129469918
Width=2 (truecolor) took 3.495372111996403
Width=100 (truecolor) took 6.761303802995826
Width=2 (256color) took 3.4730656869942322
Width=100 (256color) took 140.41951910499483
Which is confirming that the part that's mapping colours from the image to terminal colours is a bit slow. I might try and re-implement it in C++, or investigate different data-structures, because Python's just kinda slow at it. :/
Thanks, adding is_truecolor=True, is_256color=False did increase the performance :-)
I see that the colour mapping functions take the most time for computing. I hope re-implementing this in C++ optimizes and improves it.
Haha, don't worry about closing the issue, I'm still looking to fix the performance issues for 256 bit colour, just haven't had the time quite yet :)
Thanks again for pointing it out!
Okay, sorry. That's cool then.
Thanks :+1: