Gimp-Forum.net

Full Version: Accessing last applied Auto Levels from a script
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Hi all

I'm new to writing GIMP scripts, and relatively new to python, but slowly I'm beginning to make progress with python-fu and simple script development.

I have a process I'm currently performing manually on a regular basis, and I'd like to create a script to automate it. The manual process is as follows:

1. Select an area of the image
2. Perform Auto Levels (Colors -> Levels -> Auto Input Levels)
3. Undo Levels
4. Select all of the image
5. Re-apply the previous levels (Filters -> Repeat Levels)

In scripting the above, I've got as far as step 4 successfully - but for step 5 I'm stuck, as I can't find any procedure in the pdb that lets me re-apply the previous levels. After a bit of searching online, I found that GIMP writes these values to a GimpLevelsConfig.settings file in the AppData\Roaming\GIMP\2.10\Filters directory (on my installation, at least) - and if I look in this file, I can indeed see the levels last applied... but I can't find any procedure that will let me retrieve the values.

So... my questions to the esteemed and knowledgeable python scripters here are:

1. Is there a way to call the Filters -> Repeat Levels function via an existing internal GIMP procedure?
2. If "no" to the above, is there a way to access the levels values last applied without resorting to the "GimpLevelsConfig.settings" file?
3. If "no" to the above, is there a way of reading the file via one or more existing internal GIMP procedures?
4. If "no" to the above, (a) how do I obtain the directory for the file (in case it's configured differently across versions and platforms), and (b) can anyone help me with guidance on reading and parsing the file to a set of variables?

Any assistance for this rank novice would be greatly appreciated. Thanks in advance!

Mike

PS. Sorry, I forgot to mention, I'm running GIMP 2.10 on Windows, Ubuntu and Fedora environments...

  1. For Python, there is an optional run_mode named parameter that supports values'RUN_INTERACTIVE, RUN_NONINTERACTIVE, RUN_WITH_LAST_VALS , but AFAIK this works mostly with the file export dialogs.
  2. None I know of
  3. None I know of
  4. a) You can use the gimp.directory to obtain the Gimp use profiles, and for there its is os.path.join(gimp.directory,'filters','GimpLevelsConfig.settings') to access the file. b) The latest setting is at the top (so you don't need to search for it) and the rest. Will provide some sample code later today.
Hi @Ofnuts and thanks so much for the helpful reply...

I checked the pdb.gimp_drawable_levels procedure and it doesn't seem to support the run_mode parameter, unless I'm missing something:

   pdb.gimp_drawable_levels(drawable, channel, low_input, high_input, clamp_input, gamma, low_output, high_output, clamp_output)

Thanks for the information on how to obtain the correct directory path, and forthcoming sample code. Very much appreciated!
(04-09-2024, 09:40 AM)BigMackCam Wrote: [ -> ]Hi @Ofnuts and thanks so much for the helpful reply...

I checked the pdb.gimp_drawable_levels procedure and it doesn't seem to support the run_mode parameter, unless I'm missing something:

   pdb.gimp_drawable_levels(drawable, channel, low_input, high_input, clamp_input, gamma, low_output, high_output, clamp_output)

Thanks for the information on how to obtain the correct directory path, and forthcoming sample code. Very much appreciated!

Sample code

Code:
#! /bin/env python

import sys,re
from collections import namedtuple

stringValuePattern=r'\(([a-z-]+) ([^)]+)\)'
intValuePattern=r'\(([a-z-]+) (\d+)\)'
floatValuePattern=r'\(([a-z-]+) (\d+(\.\d+)?)\)'
booleanValuePattern=r'\(([a-z-]+) (yes|no)\)'

ChannelSettings=namedtuple('ChannelSettings',['name','loInput','hiInput','gamma','loOutput','hiOutput'])
LevelsSettings=namedtuple('LevelsSettings',['time','linear','clampInput','clampOutput','value','red','green','blue','alpha'])

def expected(pattern,name,line):
    matched=re.search(pattern,line)
    if not matched:
        raise Exception('Pattern not matched for line %s',line)
    if matched.group(1)!=name:
        raise Exception('Unexpected name %s (expected: %s)' % (matched.group(1),name))
    return matched.group(2)

def expectedChannel(channelName,line):
    value=expected(stringValuePattern,'channel',line)
    if value!=channelName:
        raise Exception('Unexpected channel %s (expected: %s)' % (value,channelName))
                     
def expectedInt(name,line):
    value=expected(intValuePattern,name,line)
    return int(value)

def expectedFloat(name,line):
    value=expected(floatValuePattern,name,line)
    return float(value)

def expectedBoolean(name,line):
    value=expected(booleanValuePattern,name,line)
    return value=='yes'

def readChannel(channelName,levelsConf):
    expectedChannel(channelName,next(levelsConf))
    return ChannelSettings(
        channelName,
        expectedFloat('low-input',next(levelsConf)),
        expectedFloat('high-input',next(levelsConf)),
        expectedFloat('gamma',next(levelsConf)),
        expectedFloat('low-output',next(levelsConf)),
        expectedFloat('high-output',next(levelsConf)),
        )

def readLevels(file):
    with open(file) as levelsConf:
        next(levelsConf) # skip top comment
        next(levelsConf) # skip blank line
        next(levelsConf) # settings header

        return LevelsSettings(
            expectedInt('time',next(levelsConf)),
            expectedBoolean('linear',next(levelsConf)),
            expectedBoolean('clamp-input',next(levelsConf)),
            expectedBoolean('clamp-output',next(levelsConf)),
            readChannel('value',levelsConf),
            readChannel('red',levelsConf),
            readChannel('green',levelsConf),
            readChannel('blue',levelsConf),
            readChannel('alpha',levelsConf),
        )   
    
settings=readLevels(sys.argv[1])
print settings       

From a file that beings with:

Code:
# settings

(GimpLevelsConfig "2024-04-09 10:32:38"
    (time 1712651558)
    (linear no)
    (clamp-input no)
    (clamp-output no)
    (channel value)
    (low-input 0)
    (high-input 1)
    (gamma 1)
    (low-output 0.05849056603773585)
    (high-output 1)
    (channel red)
    (low-input 0)
    (high-input 1)
    (gamma 1)
    (low-output 0)
    (high-output 1)
    (channel green)
    (low-input 0)
    (high-input 1)
    (gamma 1)
    (low-output 0)
    (high-output 1)
    (channel blue)
    (low-input 0)
    (high-input 1)
    (gamma 1)
    (low-output 0)
    (high-output 1)
    (channel alpha)
    (low-input 0)
    (high-input 1)
    (gamma 1)
    (low-output 0)
    (high-output 1))
(GimpLevelsConfig "2024-04-09 10:30:18"
    (time 1712651418)
    (linear no)
    (clamp-input no)
    (clamp-output no)
    (channel value)
    (low-input 0)
    (high-input 1)
    (gamma 1)
    (low-output 0)
######## etc....
Yields:
Code:
LevelsSettings(time=1712651558, linear=False, clampInput=False, clampOutput=False,
value=ChannelSettings(name='value', loInput=0.0, hiInput=1.0, gamma=1.0, loOutput=0.05849056603773585, hiOutput=1.0),
red=ChannelSettings(name='red', loInput=0.0, hiInput=1.0, gamma=1.0, loOutput=0.0, hiOutput=1.0),
green=ChannelSettings(name='green', loInput=0.0, hiInput=1.0, gamma=1.0, loOutput=0.0, hiOutput=1.0),
blue=ChannelSettings(name='blue', loInput=0.0, hiInput=1.0, gamma=1.0, loOutput=0.0, hiOutput=1.0),
alpha=ChannelSettings(name='alpha', loInput=0.0, hiInput=1.0, gamma=1.0, loOutput=0.0, hiOutput=1.0))


Moderately tested. Enjoy.
(04-09-2024, 12:55 PM)Ofnuts Wrote: [ -> ]Moderately tested. Enjoy.

Wow... This is great! At my current novice level with Python, I can understand what the code does, but I wouldn't have come up with such an elegant solution at this stage. I can't thank you enough for your help and the time you've spent on this. Hopefully I can pay it forward when my completed and tested scripts are ready for release, as I suspect a few folks may find them as useful as I will...

Many thanks & all the best.

Mike