Switch to full style
Ask all general Gimp related questions here
Post a reply

use of a color curves preset in a python filter

Thu Jan 25, 2024 9:12 am

GIMP Version: 2.10.34
Operating System: Windows
GIMP Experience: Intermediate Level



- given that exist predefined color curves presets and that those can be used interactively and each preset usually contains 256 values per color
- given that exists a procedure called gimp-drawable-curves-explicit available for those who creates filters

my question is:

- does exist somewhere an example on how to:
- 1.load the 256 values (text) per color stored in one preset
- 2.convert those into the requested series of 256 float values required by the plug-in statement
- 3.create a python routine to be inserted into a python plug-in?

Thanks.

Re: use of a color curves preset in a python filter

Thu Jan 25, 2024 5:16 pm

None I know of but not that difficult. Things to take care of:

* Spotting the right setting
* Settings lists all 5 channels (value/red/green/blue/alpha) and you possibly want to recognize those that are identity to avoid playing them.

Re: use of a color curves preset in a python filter

Thu Jan 25, 2024 10:26 pm

Thanks Ofnuts, for your reply.
Yes, you are right, it's not difficult, but simply ...annoying, it would have been much simpler if there was a defined (python) function which having in input the name of the preset and its location, could give in output the gimp procedure. And in practice, there is: just embedded in the online selection of a stored preset.

Re: use of a color curves preset in a python filter

Fri Jan 26, 2024 5:19 am

Code:
import os,sys,traceback,re,codecs

from itertools import islice

class Channel:
    def __init__(self,name,samples):
        self.name=str(name)  # Never unicode
        self.samples=samples
   
class Setting:
    def __init__(self,name,time,channels):
        self.name=name
        self.time=time
        self.channels=channels

def extract(pattern,line):
    matched=re.match(pattern,line.strip())
    return matched.group(1) if matched else None

def readChannel(settingsFile,channelName):
    nameLine,_,typeLine,pointsLine,pointsTypeLine,samplesCountLine,samplesLine=islice(settingsFile,7)
    actualName=extract(r'\(channel (\w+)\)',nameLine)
    if actualName!=channelName:
        raise Exception("Channel name mismatch: expected: %s, actual: %s" % (channelName,actualName))
    if pointsLine.strip()=='(points 4 0 0 1 1)': # identity for channel
        return Channel(channelName,None)
    samplesCount=int(extract(r'\(n-samples (\d+)\)',samplesCountLine))
    samplesString=extract(r'\(samples \d+ ([0-9. ]+)\)',samplesLine) # skip count value
    samples=[float(x) for x in samplesString.split()]
    if len(samples)!=samplesCount:
        raise Exception("Samples count mismatch: expected: %d, actual: %d" % (samplesCount,len(samples)))
    return Channel(channelName,samples)
   
def readSetting(settingsFile):
    try:
        nameLine,timeLine,linearLine=islice(settingsFile,3)
    except ValueError:
        return None
    settingName=extract(r'\(GimpCurvesConfig "(.+)"',nameLine)
    settingTime=int(extract(r'\(time (\d+)\)',timeLine))
    channels=[]
    for channelName in ["value","red","green","blue","alpha"]:
        channel=readChannel(settingsFile,channelName)
        if channel.samples:
            channels.append(channel) # Don't add identity channels
    return Setting(settingName,settingTime,channels)

def settingsFrom(settingsFile):
    # Skip top two lines
    _,_=islice(settingsFile,2)
    while True:
        setting=readSetting(settingsFile)
        if not setting:
            break
        yield setting

def load(settingsFilePath):
    settings=[]
    with codecs.open(settingsFilePath, 'r', encoding='utf-8') as settingsFile:
        for setting in settingsFrom(settingsFile):
            if setting.time==0: # Only named settings
                settings.append(setting)
    return settings

if __name__=="__main__":
    settings=load("./GimpCurvesConfig.settings")
   
    for s in settings:
        print "### %s" % s.name
        for c in s.channels:
            print "   %s: %d" % (c.name,len(c.samples))
            for sample in c.samples[:5]:
                print "        %7.5f" % sample


As written, only keeps settings explicitly saved with a name, and only saves channels that have been changed so you can iterate these and apply gimp-drawable-curves-explicit() on each.

The bit with "if __name__=="__main__":" is of course to test that the thing works.

Re: use of a color curves preset in a python filter

Fri Jan 26, 2024 6:50 am

Wow, thanks a lot!
I will try to understand it as much as possible for my limited capabilities.

Re: use of a color curves preset in a python filter

Fri Jan 26, 2024 7:26 am

apply_curve by code Tin Tran:
viewtopic.php?f=9&t=14058&start=0

apply_color_curves_preset.py by code Tin Tran:
viewtopic.php?f=9&t=14064&start

My way of using it:
https://www.gimpscripts.net/2021/10/mrq ... s-rpl.html

Re: use of a color curves preset in a python filter

Fri Jan 26, 2024 7:47 am

Thanks Marek, I'll download also yours.

Re: use of a color curves preset in a python filter

Fri Jan 26, 2024 7:59 am

Here's my finding I got stuck trying to debug because I can't seem to export presets. Not sure why. (ofnuts code seems way cleaner so I try to use it but...)
My export1 file always has a single time 0. No matter what I do I can't get my saved named color curved presets to export to file with multiple named. it's always single and without my named saved color curve settings. Wonder if my gimp is broken with that (I am on GIMP 2.10.34)
Code:
#!/usr/bin/env python
from gimpfu import *
import os,sys,traceback,re,codecs

from itertools import islice

class Channel:
    def __init__(self,name,samples):
        self.name=str(name)  # Never unicode
        self.samples=samples
   
class Setting:
    def __init__(self,name,time,channels):
        self.name=name
        self.time=time
        self.channels=channels

def extract(pattern,line):
    matched=re.match(pattern,line.strip())
    return matched.group(1) if matched else None

def readChannel(settingsFile,channelName):
    nameLine,_,typeLine,pointsLine,pointsTypeLine,samplesCountLine,samplesLine=islice(settingsFile,7)
    actualName=extract(r'\(channel (\w+)\)',nameLine)
    if actualName!=channelName:
        raise Exception("Channel name mismatch: expected: %s, actual: %s" % (channelName,actualName))
    if pointsLine.strip()=='(points 4 0 0 1 1)': # identity for channel
        return Channel(channelName,None)
    samplesCount=int(extract(r'\(n-samples (\d+)\)',samplesCountLine))
    samplesString=extract(r'\(samples \d+ ([0-9. ]+)\)',samplesLine) # skip count value
    samples=[float(x) for x in samplesString.split()]
    if len(samples)!=samplesCount:
        raise Exception("Samples count mismatch: expected: %d, actual: %d" % (samplesCount,len(samples)))
    return Channel(channelName,samples)
   
def readSetting(settingsFile):
    try:
        pdb.gimp_message("readSetting try")
        nameLine,timeLine,linearLine=islice(settingsFile,3)
    except ValueError:
        pdb.gimp_message("readSetting Valueerror")
        return None
    pdb.gimp_message("readSetting settingNameTime")   
    pdb.gimp_message(nameLine)   
    pdb.gimp_message(timeLine)   
    settingName=extract(r'\(GimpCurvesConfig "(.+)"',nameLine)
    settingTime=int(extract(r'\(time (\d+)\)',timeLine))
    pdb.gimp_message("readSetting after settingNameTime")
    channels=[]
    for channelName in ["value","red","green","blue","alpha"]:
        pdb.gimp_message("channelName in list")
        channel=readChannel(settingsFile,channelName)
        if channel.samples:
            channels.append(channel) # Don't add identity channels
    return Setting(settingName,settingTime,channels)

def settingsFrom(settingsFile):
    # Skip top two lines
    pdb.gimp_message("inside settingsFrom")
    _,_=islice(settingsFile,2)
    while True:
        pdb.gimp_message("settingsFrom while Loop")
        setting=readSetting(settingsFile)
        pdb.gimp_message("settings after readSetting")
        if not setting:
            break
        yield setting

def load(settingsFilePath):
    settings=[]
    pdb.gimp_message("inside load")
    with codecs.open(settingsFilePath, 'r', encoding='utf-8') as settingsFile:
        pdb.gimp_message("before settings From")
        for setting in settingsFrom(settingsFile):
            pdb.gimp_message("for loop inside Settings from")
            if setting.time==0: # Only named settings
                pdb.gimp_message("setting.time==0")
                settings.append(setting)
    return settings

# if __name__=="__main__":
#     settings=load("./GimpCurvesConfig.settings")
#     for s in settings:
#         print "### %s" % s.name
#         for c in s.channels:
#             print "   %s: %d" % (c.name,len(c.samples))
#             for sample in c.samples[:5]:
#                 print "        %7.5f" % sample

def python_cc_test(image,layer):
    pdb.gimp_image_undo_group_start(image)
    # pdb.gimp_context_push()
    #YOUR CODE BEGINS=======================
    pdb.gimp_message("what")
    settings=load("C:\\Users\\tintran\\AppData\\Roaming\\GIMP\\2.10\\curves\\export1")

    # for s in settings:
    #     pdb.gimp_message("### " + str(s.name))
    #     for c in s.channels:
    #         pdb.gimp_message("   "+c.name+":"+" "+str(len(c.samples)))
    #         for sample in c.samples[:5]:
    #             pdb.gimp_message("        "+str(sample))

    #pdb.gimp_message(get4Points(ovectors))
    #YOUR CODE ENDS ========================
    # pdb.gimp_context_pop()
    pdb.gimp_image_undo_group_end(image)
    pdb.gimp_displays_flush()

register(
    "python_fu_cc_test",
    "Color Curves Test",
    "Color Curves Test",
    "TT",
    "TT",
    "2024.1.25",
    "<Image>/Python-Fu/A CC Test...",
    "*",      # Create a new image, don't work on an existing one
    [
    #INPUT BEGINS
    #INPUT ENDS
    ],
    [],
    python_cc_test)

main()

Re: use of a color curves preset in a python filter

Fri Jan 26, 2024 8:06 am

Well, Tin, I don't need to "export" a preset but to "load" an existing one (already stored in the appropriate folder) and apply it to the layer active in the python filter.

Re: use of a color curves preset in a python filter

Fri Jan 26, 2024 8:11 am

Any how my first impression from all your lines of code: I'm feeling really a "caveman" as far as a gimp python filter structure is concerned. My filters are all so "troglodyte"...
Hope at end to be able to add -with all your help- the possibility for the user of my filter to:
- select a preset (from those already stored and accessible) and apply it to modify the colors look of the layer.
Thanks

Re: use of a color curves preset in a python filter

Fri Jan 26, 2024 8:22 am

Yeah but it needs to load from a file. And the file with multiple named color curves is my first step in testing and I can't even do that.
I feel like a caveman too. I just cut and paste many things.

Re: use of a color curves preset in a python filter

Fri Jan 26, 2024 8:28 am

Actually do you have file with named settings I can use...my GIMP isn't exporting them.

Re: use of a color curves preset in a python filter

Fri Jan 26, 2024 8:42 am

This is what I put (copied from some post on GC) on samj's folder:"...Preferences/Filters"

GimpCurvesConfig.7z
(158.5 KiB) Downloaded 27 times

Re: use of a color curves preset in a python filter

Fri Jan 26, 2024 12:09 pm

Hi Diego,
With your sample files
I was able to use ofnuts's code with minor changes here
Code:
#!/usr/bin/env python
from gimpfu import *
import os,sys,traceback,re,codecs

from itertools import islice

class Channel:
    def __init__(self,name,samples):
        self.name=str(name)  # Never unicode
        self.samples=samples
   
class Setting:
    def __init__(self,name,time,channels):
        self.name=name
        self.time=time
        self.channels=channels

def extract(pattern,line):
    matched=re.match(pattern,line.strip())
    return matched.group(1) if matched else None

def readChannel(settingsFile,channelName):
    nameLine,_,typeLine,pointsLine,pointsTypeLine,samplesCountLine,samplesLine=islice(settingsFile,7)
    #pdb.gimp_message("nameline" +  nameLine)
    #pdb.gimp_message("typeLine" + typeLine)
    #pdb.gimp_message("pointsLine" + pointsLine)
    #pdb.gimp_message(pointsTypeLine)
    #pdb.gimp_message(samplesCountLine)
    #pdb.gimp_message(samplesLine)
    actualName=extract(r'\(channel (\w+)\)',nameLine)
    #pdb.gimp_message(actualName)
    if actualName!=channelName:
        #pdb.gimp_message("1")
        pdb.gimp_message("Channel name mismatch: expected: "+channelName+", actual: "+actualName)
       
    if pointsLine.strip()=='(points 4 0 0 1 1)': # identity for channel
        #pdb.gimp_message("2")
        return Channel(channelName,None)
    #pdb.gimp_message("3")   
    samplesCount=int(extract(r'\(n-samples (\d+)\)',samplesCountLine))
    samplesString=extract(r'\(samples \d+ ([0-9. ]+)\)',samplesLine) # skip count value
    samples=[float(x) for x in samplesString.split()]
    if len(samples)!=samplesCount:
        raise Exception("Samples count mismatch: expected: %d, actual: %d" % (samplesCount,len(samples)))
    return Channel(channelName,samples)
   
def readSetting(settingsFile):
    try:
        #pdb.gimp_message("readSetting try")
        # nameLine,timeLine,linearLine=islice(settingsFile,3)
        nameLine,timeLine=islice(settingsFile,2)
        #pdb.gimp_message("nameLine" + nameLine)
        #pdb.gimp_message("timeLine" + timeLine)
        ##pdb.gimp_message("linearLine" + linearLine)
    except:
        #pdb.gimp_message("readSetting Valueerror")
        return None
    #pdb.gimp_message("readSetting settingNameTime")   
    #pdb.gimp_message(nameLine)   
    #pdb.gimp_message(timeLine)   
    settingName=extract(r'\(GimpCurvesConfig "(.+)"',nameLine)
    settingTime=int(extract(r'\(time (\d+)\)',timeLine))
    #pdb.gimp_message("readSetting after settingNameTime")
    channels=[]
    for channelName in ["value","red","green","blue","alpha"]:
        #pdb.gimp_message("channelName in list")
        channel=readChannel(settingsFile,channelName)
        if channel.samples:
            channels.append(channel) # Don't add identity channels
        #pdb.gimp_message("done")   
    return Setting(settingName,settingTime,channels)

def settingsFrom(settingsFile):
    # Skip top two lines
    #pdb.gimp_message("inside settingsFrom")
    _,_=islice(settingsFile,2)
    while True:
        try:       
            setting=readSetting(settingsFile)
        except:
            setting = None
        #pdb.gimp_message("settings after readSetting")
        if not setting:
            break
        yield setting

def load(settingsFilePath):
    settings=[]
    #pdb.gimp_message("inside load")
    with codecs.open(settingsFilePath, 'r', encoding='utf-8') as settingsFile:
        #pdb.gimp_message("before settings From")
        for setting in settingsFrom(settingsFile):
            #pdb.gimp_message("for loop inside Settings from")
            if setting.time==0: # Only named settings
                #pdb.gimp_message("setting.time==0")
                settings.append(setting)
    return settings

# if __name__=="__main__":
#     settings=load("./GimpCurvesConfig.settings")
#     for s in settings:
#         print "### %s" % s.name
#         for c in s.channels:
#             print "   %s: %d" % (c.name,len(c.samples))
#             for sample in c.samples[:5]:
#                 print "        %7.5f" % sample

def python_cc_test(image,layer):
    pdb.gimp_image_undo_group_start(image)
    # pdb.gimp_context_push()
    #YOUR CODE BEGINS=======================
   
    #CHANGE BELOW to POINT TO YOUR OWN Color Curves Settings
    settings=load("C:\\Users\\tintran\\AppData\\Roaming\\GIMP\\2.10\\curves\\GimpCurvesConfig.settings")
    pdb.gimp_message(settings[0].name)
    pdb.gimp_message(settings[0].time)
    pdb.gimp_message(settings[0].channels[0].name) #value channel
    pdb.gimp_message(settings[0].channels[1].name) #red channel
    pdb.gimp_message(settings[0].channels[1].samples) #red channel
    redchannel_samples = settings[0].channels[1].samples
    pdb.gimp_drawable_curves_explicit(layer,HISTOGRAM_RED,len(redchannel_samples),redchannel_samples)

    # for s in settings:
    #     #pdb.gimp_message("### " + str(s.name))
    #     for c in s.channels:
    #         #pdb.gimp_message("   "+c.name+":"+" "+str(len(c.samples)))
    #         for sample in c.samples[:5]:
    #             #pdb.gimp_message("        "+str(sample))

    ##pdb.gimp_message(get4Points(ovectors))
    #YOUR CODE ENDS ========================
    # pdb.gimp_context_pop()
    pdb.gimp_image_undo_group_end(image)
    pdb.gimp_displays_flush()

register(
    "python_fu_cc_test",
    "Color Curves Test",
    "Color Curves Test",
    "TT",
    "TT",
    "2024.1.25",
    "<Image>/Python-Fu/A CC Test...",
    "*",      # Create a new image, don't work on an existing one
    [
    #INPUT BEGINS
    #INPUT ENDS
    ],
    [],
    python_cc_test)

main()

Re: use of a color curves preset in a python filter

Fri Jan 26, 2024 12:31 pm

Code:
    #CHANGE BELOW to POINT TO YOUR OWN Color Curves Settings
    settings=load("C:\\Users\\tintran\\AppData\\Roaming\\GIMP\\2.10\\curves\\GimpCurvesConfig.settings")

No need to hardcode file paths. Normally this should be:
Code:
settings=load(os.path.join(gimp.directory,'filters','GimpCurvesConfig.settings'))

Re: use of a color curves preset in a python filter

Fri Jan 26, 2024 3:26 pm

thanks for that ofnuts :D

Re: use of a color curves preset in a python filter

Fri Jan 26, 2024 9:14 pm

Sorry Tin and Ofnuts.
- if I copy what you posted (included ofnuts' suggested change) I get a list of messages, but nothing else
- whatever I add in the code, for instance create a new layer, it is executed but then no more filter messages but an error
error-msg.PNG
error-msg.PNG (31.22 KiB) Viewed 594 times


As long as my little brain does not see at least one curve applied to an existing image I can't try to use the proposed flow.
You too Tin says: does not work on existing image, which is what I need: apply a curve to the input layer.
Thanks a lot for trying.

Re: use of a color curves preset in a python filter

Fri Jan 26, 2024 9:51 pm

can you share you code?
it looks like it's not reading the file.
Update:
I think instead of
Code:
settings=load("C:\\Users\\tintran\\AppData\\Roaming\\GIMP\\2.10\\curves\\GimpCurvesConfig.settings")

with ofnuts piece it's should be.
Code:
settings=load(os.path.join(gimp.directory,'curves','GimpCurvesConfig.settings'))

Re: use of a color curves preset in a python filter

Fri Jan 26, 2024 11:31 pm

I'm on an old version of 2.10 and my GimpCurvesConfig.settings has lines:
(linear no)
which Ofnuts script is also expecting.
However Tim has taken it out as the, presumably later version, file on post #13 doesn't have them.

Also I'm not getting this even though I deliberately saved a preset that does nothing:

Code:
    if pointsLine.strip()=='(points 4 0 0 1 1)': # identity for channel
            return Channel(channelName,None)

If I print pointsLine I get loads of these:
(n-points 17)

Whereas the above check looks like it's better aimed at pointsTypeLine. This is an example print of one of pointsTypeLine:

(points 34 0.000000 0.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 1.000000 1.000000)

Was there another format change to get rid of the -1s in between the 0 0 and 1 1?

Re: use of a color curves preset in a python filter

Sat Jan 27, 2024 12:13 am

trandoductin wrote:can you share you code?
it looks like it's not reading the file.
Update:
I think instead of
Code:
settings=load("C:\\Users\\tintran\\AppData\\Roaming\\GIMP\\2.10\\curves\\GimpCurvesConfig.settings")

with ofnuts piece it's should be.
Code:
settings=load(os.path.join(gimp.directory,'curves','GimpCurvesConfig.settings'))

this is my filter as tested with my additions
python_cc_test.7z
(2.04 KiB) Downloaded 17 times

note: I left ofnuts indication (filters, not curves), it doesn't find "curves"
Post a reply