﻿#!/usr/bin/env python
#
# ==============================================================================
#                   RangeSelection.py V4 - (c) Bert Hinz 2009
# ==============================================================================
#
# RANGE SELECTION FOR THE GIMP
#
# ==============================================================================
#
# This program is free software; you can redistribute it and/or modify it.
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# ==============================================================================
from gimpfu import *
from string import *

def RangeSelection(img, drw, area, width_of_range, border_l, border_r, channel, feathering, mask, mask_mode, ahc, hc):
    pdb.gimp_image_undo_group_start(img)
    ch={'R':0,'G':1,'B':2, 'S':1, 'V':2, "L": 0, "C":0, "M":1, "Y":2}
    if "RGB".find(channel)>-1:
        cmod="RGB"
        itype="RGB_IMAGE"
    elif "HSV".find(channel)>-1:
        cmod="HSV"
        itype="HSV_IMAGE"
    elif "CMY".find(channel)>-1:
        cmod="CMY"
        itype="CMY_IMAGE"
    elif "LAB".find(channel)>-1:
        cmod="LAB"
        itype="LAB_IMAGE"
    nr_base=pdb.gimp_image_get_layer_position(img,drw)

    # deletes former selections
    pdb.gimp_selection_none(img)

    # remembers initial background color
    bgc = gimp.get_background()
##    pdb.gimp_message(channel+"\n"+cmod +"\n"+itype)

    # creates new layer for displaying selection
    DEC_img=pdb.plug_in_decompose(img, drw, cmod, 1)[0]
    width = drw.width
    height = drw.height
    layer_check = gimp.Layer(img, "check", width, height, RGB_IMAGE, 100, NORMAL_MODE)
    img.add_layer(layer_check, 0)
    pdb.gimp_edit_copy(DEC_img.layers[ch[channel]])
    pdb.gimp_edit_paste(layer_check,0)
    pdb.gimp_floating_sel_anchor(img.layers[0])
    layer_check.mode = NORMAL_MODE
    layer_check.opacity=50.0
    drw=img.layers[0]
    pdb.gimp_image_delete(DEC_img)

 # calculates borders of range ('min' and 'max')
    if area=="highlights":
        min=255-255.0/100*width_of_range
        max=255
    elif area=="shadows":
        min=0
        max=255.0/100*width_of_range
    elif area=="midtones":
        min=127.5-(255.0/100*width_of_range/2.0)
        max=127.5+(255.0/100*width_of_range/2.0)
    else:
        min=border_l
        max=border_r
        if max<min:
            min=border_r
            max=border_l
    min=round(min)
    max=round(max)

    # creates checking layer name that contains chosen settings
    LName=str(int(min))+";"+str(int(max))+" "+channel
    if area<>"custom":
        if area=="highlights":
            LName=LName+" (" + str(width_of_range)+"% HL)"
        if area=="midtones":
            LName=LName+" (" + str(width_of_range)+"% MT)"
        if area=="shadows":
            LName=LName+" (" + str(width_of_range)+"% SH)"
    if feathering>0:
            LName=LName + " F"+str(int(feathering))

    # creates copy of drawable if conversion to layer mask is activated
    if mask==True:
        drw=img.layers[nr_base+1]
        layer_copy = drw.copy(True)
        layer_copy.mode = NORMAL_MODE
        layer_copy.name = LName
        layer_copy.opacity=100.0
        img.add_layer(layer_copy,-1)

    # creates selection
    layer_number=0
    if mask==True:
        layer_number=layer_number+1
    if min>0 and max==255:
        pdb.plug_in_exchange (img,img.layers[layer_number],255,255,255,255,255,255,255-min,255-min,255-min)
        pdb.gimp_by_color_select(img.layers[layer_number],(255,255,255), 0, 0, True, True, feathering, False)
    if max<255 and min==0:
        pdb.gimp_invert(img.layers[layer_number])
        pdb.plug_in_exchange (img,img.layers[layer_number],255,255,255,255,255,255,max,max,max)
        pdb.gimp_by_color_select(img.layers[layer_number],(255,255,255), 0, 0, True, True, feathering, False)
        pdb.gimp_invert(img.layers[layer_number])

    if max<255 and min>0:
        pdb.gimp_selection_none(img)
        THR=int(max-min)/2
        AM=int(min+THR)
        CS=(AM,AM,AM)
        pdb.gimp_by_color_select(img.layers[layer_number],(AM,AM,AM), THR, 0, True, True, feathering, False)

    pdb.gimp_layer_add_alpha(img.layers[layer_number])
    if max<255 and min==0:
        pdb.gimp_invert(img.layers[layer_number])

    # erases unselected parts of the image
    pdb.gimp_selection_invert(img)
    pdb.plug_in_threshold_alpha(img,img.layers[layer_number],255)
    pdb.gimp_selection_invert(img)
    
    # highlights selection according chosen highlighting color
    gimp.set_background(hc)
    pdb.gimp_edit_fill(img.layers[layer_number],1)
    gimp.set_background(bgc)
    drw=layer_check

    # conversion to layer mask
    if mask==True:
        layer_masked = drw.copy(True)
        layer_masked.mode = NORMAL_MODE
        layer_masked.name = "masked"
        layer_masked.opacity=100.0
        img.add_layer(layer_masked,-1)
        gimp.set_background(0,0,0)
        pdb.gimp_edit_fill(img.layers[0],1)
        pdb.gimp_edit_copy(layer_masked)
        mask = pdb.gimp_layer_create_mask(img.layers[2], ADD_WHITE_MASK)
        pdb.gimp_layer_add_mask(img.layers[1],mask)
        pdb.gimp_edit_paste(mask,0)
        drw=mask
        pdb.gimp_floating_sel_anchor(img.layers[0])
        if mask_mode=="white on black":
            pdb.gimp_invert(mask)
        pdb.gimp_image_remove_layer(img,img.layers[0])
        pdb.gimp_image_lower_layer(img,img.layers[0])
        
    img.layers[0].name =LName
    pdb.gimp_image_set_active_layer(img,img.layers[0])
    if ahc==False:
        pdb.gimp_image_remove_layer(img,img.layers[0])

    pdb.gimp_image_undo_group_end(img)

register(
    "python_range_selection",
    "",
    "You can set a percentage width of tonal range for highlights, midtones or shadows." + "\n"+"Alternatively you can define the left and the right border for a custom range."+"\n"+"Furthermore you can decide if the selection should be based on the values of the image or on one of the three color channels." + "\n"+"The created selection can be converted to a layer mask if desired." +"\n"+"After running the script there will be an adittional layer on top that highlights the selection. The name of this layer containes the chosen settings.",
    "Berthold Hinz",
    "public domain",
    "2009",
    "<Image>/Select/Tonal range",
    "RGB*",
        [(PF_RADIO, "area", ("Mode"), "highlights", (("Highlights [HL]", "highlights"),("Midtones [MT]", "midtones"),("Shadows [SH]", "shadows"),("Custom", "custom"))),
        (PF_SLIDER, "width_of_range","Width of range (%)", 33,(1,100,1)),
        (PF_SLIDER, "border_l","Left border (custom mode)", 0,(0,254,1)),
        (PF_SLIDER, "border_r","Right border (custom mode)", 255,(1,255,1)),
        (PF_RADIO, "channel", ("Channel"), "V", (("Value [V]", "V"),("Luminance [L]", "L"),("Red [R]", "R"),("Green [G]", "G"),("Blue [B]", "B"),("Cyan [C]", "C"),("Magenta [M]", "M"),("Yellow [Y]", "Y"),("Saturation [S]", "S"))),
        (PF_SLIDER, "feathering","Feathering [F]", 0,(0,20,1)),
        (PF_TOGGLE, "mask", "Convert to layer mask", True),
        (PF_RADIO, "mask_mode", ("Layer mask"), "white on black", (("White on black", "white on black"),("Black on white", "black on white"))),
        (PF_TOGGLE, "ahc", "Add highlighting layer", True),
        (PF_COLOR, "hc",  "Highlighting color", (29,255,210))
    ],
    [],
    RangeSelection)
main()

