COLOR PALETTE FROM IMAGE (OLD)

This is an old version for a python learning experiment. You can see the new version/tool here.

This was an experiment trying to create a color palette from an image, it was made just for fun and as an exercise trying to solve that problem using Python. I used two Blocky Nodes combined to mix base and complementary/saturated color. The Blocky node creates a pixelated effect, I store those pixel values in an array and then create a new image (ramp) with the colors sorted by luminance (I used this equation to calculate the luminance: lumaValue = redSample*0.2126 + greenSample*0.7152 + blueSample*0.0722). The result was interesting, I didn’t group the operations so the whole process is visible, however it is not updated automatically as the python code stores the colors when run. The GitHub code is here as I couldn’t embed it anymore in Squarespace XD.

There’s a variable called blockySize in the code that controls the number of colors (it’s not the number of colors, but the size of the blocky, a smaller size will create more colors and a larger number will create less colors), now is set to the 15% of the image width, you can change this value to have more or less colors but WARNING, it can be heavy to process. It is not a perfect approach as pixelation (Blocky) blends the colors, however I’m happy with the results as it can be a fast approach to get a color palette.

The images used to illustrate how the tool is working were taken from Google. The first one is an awesome illustration by Wei Wang, the second one is a concept art by Juan Diego Leon, and the last image is from Edvige Faini.

color1.jpg
color2.jpg
color3.jpg

#GenerateColorPaletteFromImage
#AUTHOR: Juan Francisco Cevallos C.
#DESCRIPTION: This is just an experiment to try to create a color palette from an image.
#This script takes the selected node and using a Blocky Node creates a color palette.
#v001 - September 2019
#setVariables
import math
indexArray = []
colorArray = []
lumaArray = []
a = nuke.selectedNode()
width = a.width()
height = a.height()
print width
print height
#BlockySize = Percentage of the Width
blockySize = int(float(width)*0.15)
#cellNumber
reformatX = math.ceil (float(width) / float(blockySize))
reformatY = math.ceil (float(height) / float(blockySize))
print reformatX
print reformatY
#finalBlur (if used)
blurSize = round (width*0.15)
#nodesCreation
shuRed = nuke.createNode("Shuffle")
shuRed.knob("red").setValue("red1")
shuRed.knob("green").setValue(0)
shuRed.knob("blue").setValue(0)
shuGreen = nuke.createNode("Shuffle")
shuGreen.knob("red").setValue(0)
shuGreen.knob("green").setValue("green1")
shuGreen.knob("blue").setValue(0)
shuGreen.setInput(0,a)
shuBlue = nuke.createNode("Shuffle")
shuBlue.knob("red").setValue(0)
shuBlue.knob("green").setValue(0)
shuBlue.knob("blue").setValue("blue1")
shuBlue.setInput(0,a)
mChannels = nuke.createNode("Merge2")
mChannels.knob("operation").setValue("average")
mChannels.setInput(0,shuRed)
mChannels.setInput(1,shuGreen)
mChannels.setInput(3,shuBlue)
bFrom = nuke.createNode('Blur')
bFrom.knob("size").setValue(20)
bFrom.setInput(0,a)
mFrom = nuke.createNode("Merge2")
mFrom.knob("operation").setValue("from")
mFrom.setInput(1,bFrom)
mFrom.setInput(0,mChannels)
gFrom = nuke.createNode("Grade")
gFrom.knob("gamma").setValue(5)
gOriginal = nuke.createNode("Grade")
gOriginal.knob("multiply").setValue(2)
gOriginal.knob("gamma").setValue(0.5)
gOriginal.setInput(0,a)
satOriginal = nuke.createNode("Saturation")
satOriginal.knob("saturation").setValue(9)
satOriginal.knob("mode").setValue("Average")
keyFrom = nuke.createNode("Keyer")
keyFrom.knob("range").setValue((0,0.0001,1,1))
keyFrom.setInput(0,gFrom)
mOriginal = nuke.createNode("Merge2")
mOriginal.setInput(0,gFrom)
mOriginal.setInput(1,satOriginal)
mOriginal.setInput(2,keyFrom)
#createBlockyComplementary
bComp = nuke.createNode('Blocky')
bComp.knob("size").setValue(blockySize)
#createBlocky
b = nuke.createNode('Blocky')
b.knob("size").setValue(blockySize)
b.setInput(0,a)
#mergeBothBlockys
mBlocky = nuke.createNode("Merge2")
mBlocky.knob("operation").setValue("plus")
mBlocky.setInput(0,b)
mBlocky.setInput(1,bComp)
#small CC to contrast
gra = nuke.createNode('Grade')
gra.knob("multiply").setValue(1.35)
gra.knob("gamma").setValue(0.75)
crop = nuke.createNode('Crop')
#divide in cells for each color
ref = nuke.createNode('Reformat')
ref.knob("type").setValue(1)
ref.knob("box_fixed").setValue(1)
ref.knob("box_width").setValue(reformatX)
ref.knob("box_height").setValue(reformatY)
ref.knob("resize").setValue("fit")
ref.knob("filter").setValue(0)
#sample all cells to store colors
for y in range (int(reformatY)):
    for x in range (int(reformatX)):
        colorOriginal = []
        indexOriginal = []
        redSample = ref.sample('red',x,y)
        greenSample = ref.sample('green',x,y)
        blueSample = ref.sample('blue',x,y)
        indexOriginal = [x,y]
        colorOriginal = [redSample,greenSample,blueSample]
        print colorOriginal
        indexArray.append(indexOriginal)
        colorArray.append(colorOriginal)
        lumaValue = redSample*0.2126 + greenSample*0.7152 + blueSample*0.0722
        lumaArray.append(lumaValue)
#kill all the colors
mult = nuke.createNode('Multiply')
mult.knob("channels").setValue("rgb")
mult.knob("value").setValue(0)
print lumaArray
sortedLuma = sorted ( range (len(lumaArray)), key = lambda k: lumaArray[k])
print sortedLuma
#create a long for each color
refNew = nuke.createNode('Reformat')
refNew.knob("type").setValue(1)
refNew.knob("box_fixed").setValue(1)
refNew.knob("box_width").setValue(reformatX*reformatY)
refNew.knob("box_height").setValue(1)
refNew.knob("filter").setValue(0)
#expression group pixel by pixel
expr_group = nuke.nodes.Group()
expr_group.begin()
input_group = nuke.nodes.Input()
exprArray = []
for i in range (len(lumaArray)):
    print "el item " + str(i) + " de luminancia."
    indice = lumaArray[i]
    print "tiene el valor: " + str(indice)
    indiceDeseado = int(sortedLuma[i])
    colorDeseado = colorArray[indiceDeseado]
    print "le corresponde el indice de color: " + str(indiceDeseado)
    print "y deberia tener este color: " + str(colorDeseado)
    pixel_to_change = [i,0]
    pixel_rgb_value = colorDeseado
    expr = nuke.createNode('Expression')
    expr['expr0'].setValue('x=={0} && y=={1} ? {2}:0'.format(pixel_to_change[0],pixel_to_change[1],round(pixel_rgb_value[0],5) ))
    expr['expr1'].setValue('x=={0} && y=={1} ? {2}:0'.format(pixel_to_change[0],pixel_to_change[1],round(pixel_rgb_value[1],5) ))
    expr['expr2'].setValue('x=={0} && y=={1} ? {2}:0'.format(pixel_to_change[0],pixel_to_change[1],round(pixel_rgb_value[2],5) ))
    expr.knob("selected").setValue(True)
    expr.setInput(0,input_group)
    exprArray.append(expr.knob("name").getValue())
merge = nuke.createNode('Merge2')
for i in exprArray:
    print i
    numero = exprArray.index(i)
    if numero >=2:
        numero = numero + 1
    print numero
    merge.setInput(numero,nuke.toNode(i))
merge.knob("operation").setValue("plus")
expr_output = nuke.nodes.Output()
expr_output.setInput(0,merge)
expr_group.end()
expr_group.setInput(0, refNew)
#Make the image big again based in width and height
refNew.knob("selected").setValue(False)
expr_group.knob("selected").setValue(True)
refo = nuke.createNode('Reformat')
refo.knob("resize").setValue("distort")
refo.knob("filter").setValue(0)
nuke.connectViewer(1,a)
nuke.connectViewer(0,refo)