3

I use Python-Fu inside GIMP 2.8.14 on OS X to automate my asset pipeline production for a game.

But I noticed that the method pdb.gimp_image_scale is slower when I execute it from my script, compared to the build-in feature "Image > Scale Image... ".

Scaling down a white image from 8000x8000 to 2000x2000 takes 6.8 seconds by script compared to 1.7 seconds by GUI. That's not that critical, but scaling down my assets with a lot of layers takes 3 minutes and 47 seconds by script, compared to 40 seconds by GUI.

My Activity Monitor showed my that the CPU usage when I executed my script goes only up to about 30%, where the built-in GUI scaling uses up to 100%, which means on OS X a single CPU core is going as fast it can.

Does anybody have an idea, how I can change that behavior?

The odd thing: This seams only to be cause with gimp_image_scale. Other operations like gimp_image_select_contiguous_color, gimp_selection_grow, gimp_selection_feather and gimp_edit_bucket_fill_full drives the CPU usage up to 100%.

On Windows it's the same, but not that bad actually: 1min 28s via script and 33 seconds via build-in GUI.

from gimpfu import *

def scale_image(scale):
    image = gimp.image_list()[0]
    w = image.width
    h = image.height

    pdb.gimp_progress_init("Scaling Image...",None)
    pdb.gimp_context_set_interpolation(INTERPOLATION_LANCZOS)
    pdb.gimp_image_scale(image, w/scale, h/scale)
pass

register(
         "jeanluc_scale_image",
         "Scale Image",
         "Scale Image",
         "JeanLuc",
         "JeanLuc",
         "2015",
         "Scale Image...",
         "*",
         [
          (PF_INT, "scale", "Scale of Image", 1)
          ],
         [],
         scale_image,
         menu="<Image>/JeanLuc"
)

main()

UPDATE1: I found out that Activity Monitor has "CPU History"-feature, where I saw that my assumption was wrong: the 100% is not on 1 core it's distributed 25% over 4 Cores.

So why does it in both cases only run at 25% percent? and why is gimp_image_scale not multithreaded?

Multithreaded Scaling via GUI (on the left) vs Single Threaded via Script (on the right)

UPDATE 2: When I run my script from the "Filters>Python-Fu>Console", it is actually multithreaded and fast.

UPDATE 3: When I run my script without an input value (e.g. scale) and hardcode the value, it runs also multithreaded and fast. It seams that when the scaling is triggered from the dialog it is single threaded.

JeanLuc
  • 4,783
  • 1
  • 33
  • 47

2 Answers2

3

As a hint, that won't affect this issue, but should help others with performance issues on scripts that perform several (hundreds) pixel level operations, like brush stroking, or creating selections and fillings: the UNDO system drags everything down, even when properly grouping the UNDO steps.

So, the hint for an intensive script, is to duplicate (pdb.gimp_image_duplicate) the image, call pdb.gimp_image_undo_disable on the copy, perform the operations there, and on finish, pdb.gimp_edit_copy and pdb.gimp_edit_paste to transfer the relevant drawables to the original image.

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • Thanks for that hint: Since my Version Control System can do an undo/revert, I don't need it in GIMP. So I just deactivated it. The performance of my script was increased from 2min 13s to 1min 51s. – JeanLuc Jan 22 '15 at 12:18
1

I found a hacky workaround to execute gimp_image_scale from a new thread. And now instead of 3 minutes and 37 seconds it just takes 24 seconds, so actually faster then build-in GUI solution, that takes 40 seconds.

If somebody knows or finds a proper solution, I will accept that as an answer.

#!/usr/bin/env python

from threading import Thread
import time
from gimpfu import *

def scale_image(scale):
    pdb.gimp_progress_init("Scaling Image...",None)
    time.sleep(0.5)
    thread = Thread(target = thread_scale_image, args = (scale, ))
    thread.start()
    thread.join()
pass

def thread_scale_image(scale):
    image = gimp.image_list()[0]
    w = image.width
    h = image.height
    pdb.gimp_context_set_interpolation(INTERPOLATION_LANCZOS)
    pdb.gimp_image_scale(image, w/scale, h/scale)
pass

register(
         "jeanluc_scale_image",
         "Scale Image",
         "Scale Image",
         "JeanLuc",
         "JeanLuc",
         "2015",
         "Scale Image...",
         "RGB*",
         [
          (PF_INT, "scale", "Scale of Image", 4)
          ],
         [],
         scale_image,
         menu="<Image>/JeanLuc"
)

main()
JeanLuc
  • 4,783
  • 1
  • 33
  • 47
  • I think this is a more than proper solution for this case. If you had the time, you could file the information contained in this question in GIMP bugzilla - that maybe could help us address this in the foreseable future. https://bugzilla.gnome.org/buglist.cgi?product=GIMP – jsbueno Jan 22 '15 at 10:41
  • If you are feeling courageous, maybe you could try using Python GEGL bindings - I should come back to hack on them shortly, but they provide transparent Pythonic access to the majority of GEGL features. GEGL, the "Generic Graphics Library" being the code actually used as image manipulation engine by current GIMP versions. The downside is having to work with an API completly diffferent than that made available by GIMP. The upside is not being surprised by performance problems due to U.I. issues. – jsbueno Jan 22 '15 at 10:45
  • thanks, I will at least submit a bug report. I might take a look at Python GEGL in the future, when I need more control over GIMP or I encounter another performance hit. – JeanLuc Jan 22 '15 at 12:16
  • Yse, sure -I forgot to mention, but if one is not manipulating the image interactively, it always makes sense to disable the undo. – jsbueno Jan 22 '15 at 13:03
  • 1
    Major buzzkill: [GNOME Bugzilla](https://bugzilla.gnome.org/createaccount.cgi) will show my email to the public. Sorry, but no thanks, I prefer even my tertiary email to be private. – JeanLuc Jan 22 '15 at 15:56