# [Image-SIG] HSL adjustment, and others

Ray Pasco pascor at hotpop.com
Thu Apr 7 16:31:58 CEST 2005

```I've been playing around with just this. This program converts to/from
several different colorspaces
and changes the alternate colorspace's "ramp" slope and offset. The
I've been trying to merge PIL and wxPython and since have come up with a
better way to display the results,
but I think you want only the processing algorithms, anyway. I don't
really can't swear to the accuracy
of the colorspace conversions other than they convert from  RGB and then
back again perfectly.  I was able
to correct several apparent bugs in the algorithms. I don't know where
to find a "definitive" source for the algorithms,
much less an explanation for how they work.

I'd appreciate any comments/suggestions you may have.  I intend to
rewrite the app's GUI and
would also appreciate finding a more useful mapping function than a
linear-ramp to linear-ramp function.

Ray Pasco
-------------- next part --------------

import wx

#----------------------------

def Pil2Bitmap (imPil):
wxImage = Pil2WxImage (imPil)
bitmap = WxImage2Bitmap (wxImage)
return bitmap
#end def

def Pil2WxImage (imPil):
wxImage = wx.EmptyImage (imPil.size[0], imPil.size[1])
wxImage.SetData (imPil.convert('RGB').tostring())
return wxImage
#end def

def WxImage2Bitmap (image):
bitmap = image.ConvertToBitmap()
return bitmap
#end def

#--------

def Bitmap2Pil (bitmap):
WxImage = Bitmap2WxImage (bitmap)
imPil = WxImage2Pil (WxImage)
return imPil
#end def

def Bitmap2WxImage (bitmap):
wximage = wx.ImageFromBitmap (bitmap)
return wximage
#end def

def WxImage2Pil (wxImage):
imPil = Image.new ('RGB', (wxImage.GetWidth(), wxImage.GetHeight()))
imPil.fromstring (wxImage.GetData())
return imPil
#end def

#----------------------------

-------------- next part --------------
##  Colorspace conversions library

import sys

def Rgb2Cmyk (rgbtuple):

def Rgb2Cmy (rgbtuple):

# rgb tuple values are in [0 ... 255]
r, g, b = rgbtuple[0], rgbtuple[1], rgbtuple[2],

c = 1 - (r / 255.0)
m = 1 - (g / 255.0)
y = 1 - (b / 255.0)

return c, m, y

#end def Rgb2Cmy

def Cmy2Cmyk (cmytuple):

# cmy tuple values are in [0.0 ... 1.0]
c, m, y = cmytuple[0], cmytuple[1], cmytuple[2]

if (c == 1.0) and (m == 1.0) and (y == 1.0):
K = 1.0         # special dispensation for absolute black to avoid division-by-zero

C = 0.0
M = 0.0
Y = 0.0

else:
K = min (1.0, c, m, y)

C = (c - K) / (1.0 - K)
M = (m - K) / (1.0 - K)
Y = (y - K) / (1.0 - K)
#end if

return C, M, Y, K

#end def Cmy2Cmyk

#------------------------

# rgb tuple values are in [0...255]
cmytuple   = Rgb2Cmy  (rgbtuple)
C, M, Y, K = Cmy2Cmyk (cmytuple)

return C, M, Y, K

#end def Rgb2Cmyk

def Cmyk2Rgb (cmyktuple):

def Cmyk2Cmy (cmyktuple):

# CMYK tuple values are in [0.0 ... 1.0]
C, M, Y, K = cmyktuple[0], cmyktuple[1], cmyktuple[2], cmyktuple[3]

c = ((C * (1.0 - K)) + K)
m = ((M * (1.0 - K)) + K)
y = ((Y * (1.0 - K)) + K)

return c, m, y

#end def Cmyk2Cmy

def Cmy2Rgb (cmytuple):

# cmy tuple values are in [0.0 .. 1.0]
c, m, y = cmytuple[0], cmytuple[1], cmytuple[2]

r = int (((1.0 - c) * 255.0) + 0.5)
g = int (((1.0 - m) * 255.0) + 0.5)
b = int (((1.0 - y) * 255.0) + 0.5)

return r, g, b

#end def Cmy2Rgb

#------------------------

C, M, Y, K = cmyktuple

# Make sure that CMYK values are be in [0.0 ... 1.0]
C = min (max (0.0, C), 1.0)
M = min (max (0.0, M), 1.0)
Y = min (max (0.0, Y), 1.0)
K = min (max (0.0, K), 1.0)

cmytuple = Cmyk2Cmy (cmyktuple)
r, g, b  = Cmy2Rgb  (cmytuple)

return r, g, b

#end def Cmyk2Rgb

def Rgb2Hsl (rgbtuple):

# H, S, L will be in [0.0 ... 1.0]

r, g, b = rgbtuple
rScaled = r/255.0
gScaled = g/255.0
bScaled = b/255.0

rgbMin = min (rScaled, gScaled, bScaled)    # Min RGB value
rgbMax = max (rScaled, gScaled, bScaled)    # Max RGB value
deltaRgb = rgbMax - rgbMin                  # Delta RGB value

L = (rgbMax + rgbMin) / 2.0

if (deltaRgb == 0.0):   # This is a gray, no chroma.
H = 0.0
S = 0.0              # Done !

else:                   # Chromatic data...
if (L < 0.5):
S = deltaRgb / (rgbMax + rgbMin)
else:
S = deltaRgb / (2.0 - rgbMax - rgbMin)
#end if

deltaR = (((rgbMax - rScaled)/6.0) + (deltaRgb/2.0)) / deltaRgb
deltaG = (((rgbMax - gScaled)/6.0) + (deltaRgb/2.0)) / deltaRgb
deltaB = (((rgbMax - bScaled)/6.0) + (deltaRgb/2.0)) / deltaRgb

if   (rScaled == rgbMax):
H = deltaB - deltaG

elif (gScaled == rgbMax):
H = (1.0/3.0) + deltaR - deltaB

elif (bScaled == rgbMax):
H = (2.0/3.0) + deltaG - deltaR

#end if

H = H % 1.0

#end if

return (H, S, L)

#end def Rgb2Hsl

def Hsl2Rgb (hsltuple):

def HslHue2Rgb (v1, v2, vH):
if (vH < 0.0):    vH += 1.0
if (vH > 1.0):    vH -= 1.0
if ((6.0 * vH) < 1.0):    return (v1 + (v2 - v1) * 6.0 * vH)
if ((2.0 * vH) < 1.0):    return (v2)
if ((3.0 * vH) < 2.0):    return (v1 + (v2 - v1) * ((2.0/3.0) - vH) * 6.0)

return v1

#end def HslHue2Rgb

#------------------------

# H, S, L in [0.0 .. 1.0]
H, S, L = hsltuple

if (S == 0.0):          # RGB grayscale

r = L * 255.0        # R, G, B in [0 .. 255]
g = L * 255.0
b = L * 255.0

else:

if (L < 0.5):
var_2 = L * (1.0 + S)
else:
var_2 = (L + S) - (S * L)
#end if

var_1 = (2.0 * L) - var_2

r = 255 * HslHue2Rgb (var_1, var_2, H + (1.0/3.0))
g = 255 * HslHue2Rgb (var_1, var_2, H            )
b = 255 * HslHue2Rgb (var_1, var_2, H - (1.0/3.0))

#end if

r = int (r + 0.5)
g = int (g + 0.5)
b = int (b + 0.5)

return (r, g, b)

#end def Hsl2Rgb

def Rgb2Hsv (rgbtuple):

# H, S, V will be in [0.0 ... 1.0]

r, g, b = rgbtuple

r = r / 255.0
g = g / 255.0
b = b / 255.0

minRgb = min (r, g, b)
maxRgb = max (r, g, b)
deltaRgb = maxRgb - minRgb

v = maxRgb

if (deltaRgb == 0):     # This is a gray, no chroma...
h = 0.0
s = 0.0

else:                   # Chromatic data
s = deltaRgb / maxRgb

del_R = (((maxRgb - r) / 6.0) + (deltaRgb / 2.0)) / deltaRgb
del_G = (((maxRgb - g) / 6.0) + (deltaRgb / 2.0)) / deltaRgb
del_B = (((maxRgb - b) / 6.0) + (deltaRgb / 2.0)) / deltaRgb

if   (r == maxRgb):
h = del_B - del_G

elif (g == maxRgb):
h = (1.0 / 3.0) + del_R - del_B

elif (b == maxRgb):
h = (2.0 / 3.0) + del_G - del_R
#end if

h = h % 1.0
#end if

return (h, s, v)

#end def Rgb2Hsv

def Hsv2Rgb (hsvtuple):

h, s, v = hsvtuple

if (s == 0.0):
r = v * 255.0
g = v * 255.0
b = v * 255.0

else:
h = h * 6.0
I = int (h)          # floor function
F = h - I
P = v * (1.0 - s)
Q = v * (1.0 - s * F)
T = v * (1.0 - s * (1.0 - F))

if   (I == 4):
r = T
g = P
b = v

elif (I == 5):
r = v
g = P
b = Q

elif (I == 0):
r = v
g = T
b = P

elif (I == 1):
r = Q
g = v
b = P

elif (I == 2):
r = P
g = v
b = T

elif (I == 3):
r = P
g = Q
b = v
#end if

r = int ((r * 255.0) + 0.5)
g = int ((g * 255.0) + 0.5)
b = int ((b * 255.0) + 0.5)
#end if

return (r, g, b)

#end def Hsv2Rgb

#--------------------------------------

# Stand-alone test for colorspaces.py

if __name__ == '__main__':

import Image

if len(sys.argv) < 4:
print
print 'Colorspaces:   Values for R, G and B must be given, each in the range [0..255]'
print
sys.exit (1)
#end if

r = eval (sys.argv [1])
g = eval (sys.argv [2])
b = eval (sys.argv [3])
rgbtuple = (r, g, b)

#---------  HSL  --------------------------------------

hsltuple = Rgb2Hsl (rgbtuple)

h, s, l = hsltuple
print
print 'Colorspaces:    RGB (%3d, %3d, %3d)  ==>  HSL (%1.3f, %1.3f, %1.3f)' % (r, g, b, h, s, l)

print
rPrime, gPrime, bPrime = Hsl2Rgb (hsltuple)

print 'Colorspaces:    HSL (%1.3f, %1.3f, %1.3f)  ==>  RGB (%3d, %3d, %3d)' % (h, s, l, rPrime, gPrime, bPrime)
print

#---------  CMYK  -------------------------------------

cmyktuple = Rgb2Cmyk (rgbtuple)

c, y, m, k = cmyktuple
print
print 'Colorspaces:    RGB (%3d, %3d, %3d)  ==>  CMYK (%1.3f, %1.3f, %1.3f, %1.3f)' % (r, g, b, c, y, m, k)

print
rPrime, gPrime, bPrime = Cmyk2Rgb (cmyktuple)

print 'Colorspaces:    CMYK (%1.3f, %1.3f, %1.3f, %1.3f)  ==>  RGB (%3d, %3d, %3d)' % (c, m, y, k, rPrime, gPrime, bPrime)
print

#---------  HSV  --------------------------------------

hsvtuple = Rgb2Hsv (rgbtuple)

h, s, v = hsvtuple
print
print 'Colorspaces:    RGB (%3d, %3d, %3d)  ==>  HSV (%1.3f, %1.3f, %1.3f)' % (r, g, b, h, s, v)

print
rPrime, gPrime, bPrime = Hsv2Rgb (hsvtuple)

print 'Colorspaces:    HSL (%1.3f, %1.3f, %1.3f)  ==>  RGB (%3d, %3d, %3d)' % (h, s, v, rPrime, gPrime, bPrime)
print

#end if

-------------- next part --------------

import wx
import Image
import os, sys

from colorspaces import *           # pil <--> hsl, hsv, cmyk
from imageconverts import *         # pilIm --> bitmap

#----------------------------

import inspect, os
def pressenter (prompt='\npress return...'):   raw_input (prompt)
def rangelen(listobj):  return xrange(len(listobj))
def showStack():
for stack in inspect.stack():    print stack[2:4]
#end def
exit = os._exit
def delFileExist (filename):
if os.path.exists (filename):    os.remove (filename)
#end def

#----------------------------

global filpathname;    filepathname = None      # create the global instance
global thumbnailXsize;    thumbnailXsize = 0    # instantiate
global thumbnailYsize;    thumbnailYsize = 0

global colorspaces;    colorspaces = ['HSL-', 'HSV-', 'CMYK']
global colorspaceStr;    colorspaceStr = colorspaces [0]
global tctrlVar1;    tctrlVar1 = None   # instantiate
global tctrlVar2;    tctrlVar2 = None
global tctrlVar3;    tctrlVar3 = None
global tctrlVar4;    tctrlVar4 = None

# These default values cause no modifications to be made to the transformed image's vector space values.
#   Thus, when the inverse transform is applied, the resulting image will be identical
#   to the original image.
global x1Var1;    x1Var1 = 0.0      # initial Y=0.0 intercept (the X axis intercept)
global x2Var1;    x2Var1 = 1.0      # initial Y=1.0 intercept
global x1Var2;    x1Var2 = 0.0
global x2Var2;    x2Var2 = 1.0
global x1Var3;    x1Var3 = 0.0
global x2Var3;    x2Var3 = 1.0
global x1Var4;    x1Var4 = 0.0
global x2Var4;    x2Var4 = 1.0

class AppFrame (wx.Frame):

def __init__ (self, parent, id, title, position, size):

global filepathname
self.parentFrame = self
self.mainframePsn = position
self.mainframeSize = size

wx.Frame.__init__ (self, parent, id, title, position, size)

self.CreateStatusBar()      # A StatusBar in the bottom of the window

idOpen = wx.NewId()
filemenu.Append (idOpen, "&Open"," Open a Text File")

idExit = wx.NewId()

#----------

AppPanel (self, -1, position, size)

self.Show (True)            # show self frame

#end def __init__

def OnOpen (self, event):       # Open a file

global filepathname

# 'wx.CHANGE_DIR' is necessary to return a full pathname in Win98
dlg = wx.FileDialog (self, "Choose a file",
style=wx.OPEN|wx.FILE_MUST_EXIST|wx.CHANGE_DIR, wildcard='*.*')

if dlg.ShowModal() == wx.ID_OK:
filename = dlg.GetFilename()
dirname = dlg.GetDirectory()
filepathname = os.path.join (dirname, filename)
#end if

if (type (filepathname) == type ('abc')) or (type (filepathname) == type (u'abc')):
try:
self.referenceFrame.Destroy()
except:
pass
#end if

self.ReferenceFrame()
#end if

dlg.Destroy()
#end def OnOpen

def ReferenceFrame (self):

global filepathname

referenceframeXpsn = self.mainframePsn[0] + self.mainframeSize[0]
referenceframepsn = (referenceframeXpsn, self.mainframePsn[1])
referenceframesize = (300, 300)         # is also the transformed image frame's size
self.referenceFrame = wx.Frame (self.parentFrame, -1, "Original Image ", referenceframepsn, referenceframesize)

# Create the reference frame's panel
referencePanel = wx.Panel (self.referenceFrame, -1)
referencePanel.SetBackgroundColour ('#C9E3E5')

# Create the reference bitmap
staticBitmap = wx.StaticBitmap (referencePanel, -1, wx.EmptyBitmap(5, 5))

# Get and resize the original image to fit in the reference panel and store it to a bitmap
bitmap = GetBitmapFromFile (filepathname, referenceframesize)
staticBitmap.SetBitmap (bitmap)

boxsizer = wx.BoxSizer (wx.VERTICAL)
expand = False

referencePanel.SetAutoLayout (True)
referencePanel.SetSizer (boxsizer)

# Show the reference frame
self.referenceFrame.Show (True)

#end def ReferenceFrame

def OnAbout (self, event):      # Create a message dialog box
dlg = wx.MessageDialog (self, 'A colorspace manipulation\nprogram in wxPython',

dlg.ShowModal()   # Shows it
dlg.Destroy()     # finally destroy it when finished.
#end def

def OnExit (self, event):
self.Close (True)           # Close the application.
#end def

#end class AppFrame

class AppPanel (wx.Panel):

def __init__ (self, parentframe, id, mainframePsn, mainframeSize):

global colorspaces
global colorspaceStr
global filepathname
global x1Var1, x2Var1

self.parentFrame = parentframe
self.mainframePsn = mainframePsn
self.mainframeSize = mainframeSize

wx.Panel.__init__ (self, parentframe, id)
self.SetBackgroundColour ('#C9E3E5')

boxsizerV = wx.BoxSizer (wx.VERTICAL)   # all panel widgets go in here
expand = False
hgt = 40

#--------------------

rbId = wx.NewId()
numRbRows = 1
# Display the colorspace strings without any trailing dash characters;
#   Create a new string list for this purpose.
modcolorspaces = []
for cspaceStr in colorspaces:
if cspaceStr[-1] == '-':
modcolorspaces.append (cspaceStr[:-1])      # up to the dash char
else:
modcolorspaces.append (cspaceStr[:])        # the wholeoriginal string
#end if
#end for
modcolorspaces, numRbRows, wx.RA_SPECIFY_ROWS)
radbox.SetToolTip (wx.ToolTip ('Select a colorspace model:'))
# Apparently the first radiobutton is always selected by default,
#   so, adjust the program's setting to reflect this.
colorspaceStr = colorspaces [0]

boxsizerX1X2var1 = self.VarControlsVar1 (self, colorspaceStr[0].upper())
boxsizerX1X2var2 = self.VarControlsVar2 (self, colorspaceStr[1].upper())
boxsizerX1X2var3 = self.VarControlsVar3 (self, colorspaceStr[2].upper())
boxsizerX1X2var4 = self.VarControlsVar4 (self, colorspaceStr[3].upper())

#--------------------

bordersize = 0

xformbtn = wx.Button (self, -1, "Show Transformed Image")
wx.EVT_BUTTON (self, xformbtn.GetId(), self.xformBtnHandler)
boxsizerV.Add (xformbtn, expand, wx.CENTER|wx.ALL, 10)    # a 10-pixel border all around

self.SetAutoLayout (True)
self.SetSizer (boxsizerV)
self.Show (True)

#end def __init__

global tctrlVar1
global tctrlVar2
global tctrlVar3
global tctrlVar4

colorspaceIndex = event.GetInt()
colorspaceStr = colorspaces [colorspaceIndex]

# Write the colorspace string chars into their text controls.

tctrlVar1.Clear()
tctrlVar1.SetInsertionPoint (0)
tctrlVar1.AppendText (colorspaceStr[0].upper())

tctrlVar2.Clear()
tctrlVar2.SetInsertionPoint (0)
tctrlVar2.AppendText (colorspaceStr[1].upper())

tctrlVar3.Clear()
tctrlVar3.SetInsertionPoint (0)
tctrlVar3.AppendText (colorspaceStr[2].upper())

tctrlVar4.Clear()
tctrlVar4.SetInsertionPoint (0)
tctrlVar4.AppendText (colorspaceStr[3].upper())

#end def

def VarControlsVar1 (self, parentPanel, varLabelChar):

global tctrlVar1

def OnSpinX1 (event):
global x1Var1

x1Var1 = (event.GetPosition() - 200) / 200.0     # raw spinbutton value
self.textX1Var1.SetValue ('%1.3f' % (x1Var1))
#end def

def OnSpinX2 (event):
global x2Var1

x2Var1 = (event.GetPosition() - 200) / 200.0     # raw spinbutton value
self.textX2Var1.SetValue ('%1.3f' % (x2Var1))
#end def

def ResetX1BtnHandler (event):
global x1Var1

self.spinX1.SetValue (200)                        # 0.000
x1Var1 = 0.00
self.textX1Var1.SetValue ('%1.3f' % (x1Var1))
#end def ResetX1BtnHandler

def ResetX2BtnHandler (event):
global x2Var1

self.spinX2.SetValue (400)                        # 0.000
x2Var1 = 1.00
self.textX2Var1.SetValue ('%1.3f' % (x2Var1))
#end def ResetX1BtnHandler

#--------------------

expand = False
hgt = 40

boxsizerX1X2 = wx.BoxSizer (wx.HORIZONTAL)

boxsizerX1V = wx.BoxSizer (wx.VERTICAL)

labelX1 = wx.StaticText (self, -1, "X1:  Y=0.0 Intercept:", wx.DefaultPosition)

boxsizerX1 = wx.BoxSizer (wx.HORIZONTAL)
self.textX1Var1 = wx.TextCtrl (self, -1, "0.000", wx.DefaultPosition, wx.Size(60, -1),
self.spinX1 = wx.SpinButton (self, 20, wx.Point(0, 0), wx.Size(hgt, hgt), wx.SP_HORIZONTAL)
self.spinX1.SetRange (0, 600)                   # -1.000 .. 2.000
self.spinX1.SetValue (200)                      # x1 = 0.000
wx.EVT_SPIN (self.spinX1, 20, OnSpinX1)

resetX1btn = wx.Button (self, -1, "Reset to 0.000")
wx.EVT_BUTTON (self, resetX1btn.GetId(), ResetX1BtnHandler)

boxsizerX2V = wx.BoxSizer (wx.VERTICAL)

labelX2 = wx.StaticText (self, -1, "X2:  Y=1.0 Intercept:", wx.DefaultPosition)

boxsizerX2 = wx.BoxSizer (wx.HORIZONTAL)
self.textX2Var1 = wx.TextCtrl (self, -1, "1.000", wx.DefaultPosition, wx.Size(60, -1),
self.spinX2 = wx.SpinButton (self, 20, wx.DefaultPosition, wx.Size(hgt, hgt), wx.SP_HORIZONTAL)
self.spinX2.SetRange (0, 600)                   # -1.000 .. 2.000
self.spinX2.SetValue (400)                      # x2 = 1.000
wx.EVT_SPIN (self.spinX2, 20, OnSpinX2)

resetX2btn = wx.Button (self, -1, "Reset to 1.000")
wx.EVT_BUTTON (self, resetX2btn.GetId(), ResetX2BtnHandler)

tctrlVar1 = wx.TextCtrl (self, -1, varLabelChar.upper(), wx.DefaultPosition, wx.Size(20, -1),

return boxsizerX1X2

#end def VarControlsVar1

def VarControlsVar2 (self, parentPanel, varLabelChar):

global tctrlVar2

def OnSpinX1 (event):
global x1Var2

x1Var2 = (event.GetPosition() - 200) / 200.0     # raw spinbutton value
self.textX1Var2.SetValue ('%1.3f' % (x1Var2))
#end def

def OnSpinX2 (event):
global x2Var2

x2Var2 = (event.GetPosition() - 200) / 200.0     # raw spinbutton value
self.textX2Var2.SetValue ('%1.3f' % (x2Var2))
#end def

def ResetX1BtnHandler (event):
global x1Var2
self.spinX1.SetValue (200)                        # 0.000
x1Var2 = 0.00
self.textX1Var2.SetValue ('%1.3f' % (x1Var2))
#end def ResetX1BtnHandler

def ResetX2BtnHandler (event):
global x2Var2

self.spinX2.SetValue (400)                        # 0.000
x2Var2 = 1.00
self.textX2Var2.SetValue ('%1.3f' % (x2Var2))
#end def ResetX1BtnHandler

#--------------------

expand = False
hgt = 40

boxsizerX1X2 = wx.BoxSizer (wx.HORIZONTAL)

boxsizerX1V = wx.BoxSizer (wx.VERTICAL)

labelX1 = wx.StaticText (self, -1, "X1:  Y=0.0 Intercept:", wx.DefaultPosition)

boxsizerX1 = wx.BoxSizer (wx.HORIZONTAL)
self.textX1Var2 = wx.TextCtrl (self, -1, "0.000", wx.DefaultPosition, wx.Size(60, -1),
self.spinX1 = wx.SpinButton (self, 20, wx.DefaultPosition, wx.Size(hgt, hgt), wx.SP_HORIZONTAL)
self.spinX1.SetRange (0, 600)                   # -1.000 .. 2.000
self.spinX1.SetValue (200)                        # x1 = 0.000
wx.EVT_SPIN (self.spinX1, 20, OnSpinX1)

resetX1btn = wx.Button (self, -1, "Reset to 0.000")
wx.EVT_BUTTON (self, resetX1btn.GetId(), ResetX1BtnHandler)

boxsizerX2V = wx.BoxSizer (wx.VERTICAL)

labelX2 = wx.StaticText (self, -1, "X2:  Y=1.0 Intercept:", wx.DefaultPosition)

boxsizerX2 = wx.BoxSizer (wx.HORIZONTAL)
self.textX2Var2 = wx.TextCtrl (self, -1, "1.000", wx.DefaultPosition, wx.Size(60, -1),
self.spinX2 = wx.SpinButton (self, 20, wx.DefaultPosition, wx.Size(hgt, hgt), wx.SP_HORIZONTAL)
self.spinX2.SetRange (0, 600)                   # -1.000 .. 2.000
self.spinX2.SetValue (400)                        # x2 = 1.000
wx.EVT_SPIN (self.spinX2, 20, OnSpinX2)

resetX2btn = wx.Button (self, -1, "Reset to 1.000")
wx.EVT_BUTTON (self, resetX2btn.GetId(), ResetX2BtnHandler)

tctrlVar2 = wx.TextCtrl (self, -1, varLabelChar.upper(),

boxsizerX1X2.Add (tctrlVar2, expand, wx.CENTER|wx.ALL, 10)    # a 10-pixel border all around
boxsizerX1X2.Add (boxsizerX1V, expand, wx.CENTER|wx.ALL, 10)    # a 10-pixel border all around
boxsizerX1X2.Add (boxsizerX2V, expand, wx.CENTER|wx.ALL, 10)    # a 10-pixel border all around

return boxsizerX1X2

#end def VarControlsVar2

def VarControlsVar3 (self, parentPanel, varLabelChar):

global tctrlVar3

def OnSpinX1 (event):
global x1Var3

x1Var3 = (event.GetPosition() - 200) / 200.0     # raw spinbutton value
self.textX1Var3.SetValue ('%1.3f' % (x1Var3))
#end def

def OnSpinX2 (event):
global x2Var3

x2Var3 = (event.GetPosition() - 200) / 200.0     # raw spinbutton value
self.textX2Var3.SetValue ('%1.3f' % (x2Var3))
#end def

def ResetX1BtnHandler (event):
global x1Var3

self.spinX1.SetValue (200)                        # 0.000
x1Var3 = 0.00
self.textX1Var3.SetValue ('%1.3f' % (x1Var3))
#end def ResetX1BtnHandler

def ResetX2BtnHandler (event):
global x2Var3

self.spinX2.SetValue (400)                        # 0.000
x2Var3 = 1.00
self.textX2Var3.SetValue ('%1.3f' % (x2Var3))
#end def ResetX1BtnHandler

#--------------------

expand = False
hgt = 40

boxsizerX1X2 = wx.BoxSizer (wx.HORIZONTAL)

boxsizerX1V = wx.BoxSizer (wx.VERTICAL)

labelX1 = wx.StaticText (self, -1, "X1:  Y=0.0 Intercept:", wx.DefaultPosition)

boxsizerX1 = wx.BoxSizer (wx.HORIZONTAL)
self.textX1Var3 = wx.TextCtrl (self, -1, "0.000", wx.DefaultPosition, wx.Size(60, -1),
self.spinX1 = wx.SpinButton (self, 20, wx.DefaultPosition, wx.Size(hgt, hgt), wx.SP_HORIZONTAL)
self.spinX1.SetRange (0, 600)                   # -1.000 .. 2.000
self.spinX1.SetValue (200)                        # x1 = 0.000
wx.EVT_SPIN (self.spinX1, 20, OnSpinX1)

resetX1btn = wx.Button (self, -1, "Reset to 0.000")
wx.EVT_BUTTON (self, resetX1btn.GetId(), ResetX1BtnHandler)

boxsizerX2V = wx.BoxSizer (wx.VERTICAL)

labelX2 = wx.StaticText (self, -1, "X2:  Y=1.0 Intercept:", wx.DefaultPosition)

boxsizerX2 = wx.BoxSizer (wx.HORIZONTAL)
self.textX2Var3 = wx.TextCtrl (self, -1, "1.000", wx.DefaultPosition, wx.Size(60, -1),
self.spinX2 = wx.SpinButton (self, 20, wx.DefaultPosition, wx.Size(hgt, hgt), wx.SP_HORIZONTAL)
self.spinX2.SetRange (0, 600)                   # -1.000 .. 2.000
self.spinX2.SetValue (400)                        # x2 = 1.000
wx.EVT_SPIN (self.spinX2, 20, OnSpinX2)

resetX2btn = wx.Button (self, -1, "Reset to 1.000")
wx.EVT_BUTTON (self, resetX2btn.GetId(), ResetX2BtnHandler)

tctrlVar3 = wx.TextCtrl (self, -1, varLabelChar.upper(),
boxsizerX1X2.Add (tctrlVar3, expand, wx.CENTER|wx.ALL, 10)    # a 10-pixel border all around

return boxsizerX1X2

#end def VarControlsVar3

def VarControlsVar4 (self, parentPanel, varLabelChar):

global tctrlVar4

def OnSpinX1 (event):
global x1Var4

x1Var4 = (event.GetPosition() - 200) / 200.0     # raw spinbutton value
self.textX1Var4.SetValue ('%1.3f' % (x1Var4))
#end def

def OnSpinX2 (event):
global x2Var4

x2Var4 = (event.GetPosition() - 200) / 200.0     # raw spinbutton value
self.textX2Var4.SetValue ('%1.3f' % (x2Var4))
#end def

def ResetX1BtnHandler (event):
global x1Var4

self.spinX1.SetValue (200)                        # 0.000
x1Var4 = 0.00
self.textX1Var4.SetValue ('%1.3f' % (x1Var4))
#end def ResetX1BtnHandler

def ResetX2BtnHandler (event):
global x2Var4

self.spinX2.SetValue (400)                        # 0.000
x2Var4 = 1.00
self.textX2Var4.SetValue ('%1.3f' % (x2Var4))
#end def ResetX1BtnHandler

#--------------------

expand = False
hgt = 40

boxsizerX1X2 = wx.BoxSizer (wx.HORIZONTAL)

boxsizerX1V = wx.BoxSizer (wx.VERTICAL)

labelX1 = wx.StaticText (self, -1, "X1:  Y=0.0 Intercept:", wx.DefaultPosition)

boxsizerX1 = wx.BoxSizer (wx.HORIZONTAL)
self.textX1Var4 = wx.TextCtrl (self, -1, "0.000", wx.DefaultPosition, wx.Size(60, -1),
self.spinX1 = wx.SpinButton (self, 20, wx.DefaultPosition, wx.Size(hgt, hgt), wx.SP_HORIZONTAL)
self.spinX1.SetRange (0, 600)                   # -1.000 .. 2.000
self.spinX1.SetValue (200)                      # x1 = 0.000
wx.EVT_SPIN (self.spinX1, 20, OnSpinX1)

resetX1btn = wx.Button (self, -1, "Reset to 0.000")
wx.EVT_BUTTON (self, resetX1btn.GetId(), ResetX1BtnHandler)

boxsizerX2V = wx.BoxSizer (wx.VERTICAL)

labelX2 = wx.StaticText (self, -1, "X2:  Y=1.0 Intercept:", wx.DefaultPosition)

boxsizerX2 = wx.BoxSizer (wx.HORIZONTAL)
self.textX2Var4 = wx.TextCtrl (self, -1, "1.000", wx.DefaultPosition, wx.Size(60, -1),
self.spinX2 = wx.SpinButton (self, 20, wx.DefaultPosition, wx.Size(hgt, hgt), wx.SP_HORIZONTAL)
self.spinX2.SetRange (0, 600)                   # -1.000 .. 2.000
self.spinX2.SetValue (400)                      # x2 = 1.000
wx.EVT_SPIN (self.spinX2, 20, OnSpinX2)

resetX2btn = wx.Button (self, -1, "Reset to 1.000")
wx.EVT_BUTTON (self, resetX2btn.GetId(), ResetX2BtnHandler)

tctrlVar4 = wx.TextCtrl (self, -1, varLabelChar.upper(),
boxsizerX1X2.Add (tctrlVar4, expand, wx.CENTER|wx.ALL, 10)    # a 10-pixel border all around
boxsizerX1X2.Add (boxsizerX1V, expand, wx.CENTER|wx.ALL, 10)    # a 10-pixel border all around
boxsizerX1X2.Add (boxsizerX2V, expand, wx.CENTER|wx.ALL, 10)    # a 10-pixel border all around

return boxsizerX1X2

#end def VarControlsVar4

def xformBtnHandler (self, event):

global filepathname

if (type (filepathname)==type('abc')) or (type(filepathname)==type(u'abc')):
try:
self.childFrame.Destroy()
except:
pass
#end if

# Transform the original image into a new one.
self.ChildFrame()
#end if

#end def xformBtnHandler

def ChildFrame (self):

global filepathname
global thumbnailXsize
global thumbnailYsize
global x1Var1
global x2Var1

childframeXpsn = self.mainframePsn[0] + 2*self.mainframeSize[0]
childframepsn = (childframeXpsn, self.mainframePsn[1])
childframesize = (300, 300)
childFrame = wx.Frame (self.parentFrame, -1, "Image Reverse-Transformed", childframepsn, childframesize)

# Create the child panel
self.childPanel = wx.Panel (childFrame, -1)
self.childPanel.SetBackgroundColour ('#C9E3E5')

# Create the child's bitmap
staticBitmap = wx.StaticBitmap (self.childPanel, -1, wx.EmptyBitmap(5, 5))

# Get and resize the original image to fit in the child panel and store it to a bitmap
imPil = Image.open (filepathname)
imPil = imPil.resize ( (thumbnailXsize, thumbnailYsize) )

# Transform the thumbnail image.  This is where all the hard stuff is performed.
imRgbPrime = Brighten (imPil)           # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
bitmap = Pil2Bitmap (imRgbPrime)
staticBitmap.SetBitmap (bitmap)

# Add the static bitmap to the panel.
boxsizer = wx.BoxSizer (wx.VERTICAL)
expand = False

self.childPanel.SetAutoLayout (True)
self.childPanel.SetSizer (boxsizer)

# Show the child frame
childFrame.Show (True)

self.childFrame = childFrame

#end def ChildFrame

#end class AppPanel

def GetBitmapFromFile (filepathname, framesize):

global thumbnailXsize
global thumbnailYsize

# If too large, resize the image to fit in the panel space.
imPil = Image.open (filepathname)
imXsize, imYsize = imPil.size

panelXextent = framesize[0] - 10    # border size estimations for MS Windows
panelYextent = framesize[1] - 24

if (imXsize > panelXextent) or (imYsize > panelYextent):

# The "primary" axis is the one that must be scaled down to fit its extent.
#   The "secondary" axis will automatically scale to fit within its extent.
# Find which axis is the primary.
x2yExtentRatio = 1.0 * panelXextent / panelYextent
imageXYratio = 1.0 * imXsize / imYsize

if imageXYratio > x2yExtentRatio:       # The X axis is the primary axis

# Find the image scale factor using the X axis.
scalefactor = (1.0 * panelXextent) / imXsize
else:
scalefactor = (1.0 * panelYextent) / imYsize
#end if

# Resize the original image
newXsize = int (scalefactor * imXsize)
newYsize = int (scalefactor * imYsize)
imPil = imPil.resize ( (newXsize, newYsize) )
#end if

# Convert the Pil image to a bitmap and install it into the displayed static bitmap
bitmap = Pil2Bitmap (imPil)

thumbnailXsize = newXsize   # save globally; child frame will use these
thumbnailYsize = newYsize

return bitmap

#end def GetBitmapFromFile

def Brighten (imRgb):

global colorspaceStr
global x1Var1, x2Var1
global x1Var2, x2Var2
global x1Var3, x2Var3
global x1Var4, x2Var4

# Extract the data from the input image and convert it to the particular colorspace data.
rgbData = list (imRgb.getdata())    # must make a list of tuples out of the special sequence

#print 'Transforming RGB data to a new colorspace ...',
spaceData = []
for rgbtuple in rgbData:

if   colorspaceStr.lower() == 'hsl-':
spaceData.append (Rgb2Hsl (rgbtuple))

elif colorspaceStr.lower() == 'hsv-':
spaceData.append (Rgb2Hsv (rgbtuple))

elif colorspaceStr.lower() == 'cmyk':
spaceData.append (Rgb2Cmyk (rgbtuple))

else:
print
print 'Brighten(1):    Colorspace string [%s] not known.' % (colorspaceStr.upper())
print
os._exit (1)
#end if
#end for
#print 'Finished'

# Calculate the slope and offset (derived from y = mx + b).
slopeVar1 = 1.0e6       # arbitrary maximum value of slope: avoid division-by-zero
if x1Var1 != x2Var1:    slopeVar1 = 1.0/(x2Var1 - x1Var1)
offsetVar1 = -1.0 * slopeVar1 * x1Var1

slopeVar2 = 1.0e6       # arbitrary maximum
if x1Var2 != x2Var2:    slopeVar2 = 1.0/(x2Var2 - x1Var2)
offsetVar2 = -1.0 * slopeVar2 * x1Var2

slopeVar3 = 1.0e6       # arbitrary maximum
if x1Var3 != x2Var3:    slopeVar3 = 1.0/(x2Var3 - x1Var3)
offsetVar3 = -1.0 * slopeVar3 * x1Var3

slopeVar4 = 1.0e6       # arbitrary maximum
if x1Var4 != x2Var4:    slopeVar4 = 1.0/(x2Var4 - x1Var4)
offsetVar4 = -1.0 * slopeVar4 * x1Var4

# Modify the particular colorspace values

if   colorspaceStr.lower() == 'hsl-':
for index in xrange (len(spaceData)):
h, s, l = spaceData [index]

# Apply an arbitrary algorithm to modify the colorspace vectors.
h = (h * slopeVar1) + offsetVar1
s = (s * slopeVar2) + offsetVar2
l = (l * slopeVar3) + offsetVar3

spaceData [index] = (h, s, l)
#end for

elif colorspaceStr.lower() == 'hsv-':
for index in xrange (len(spaceData)):
h, s, v = spaceData [index]

# Apply an arbitrary algorithm to modify the colorspace vectors.
h = (h * slopeVar1) + offsetVar1
s = (s * slopeVar2) + offsetVar2
v = (v * slopeVar3) + offsetVar3

spaceData [index] = (h, s, v)
#end for

elif colorspaceStr.lower() == 'cmyk':
for index in xrange (len(spaceData)):
c, m, y, k = spaceData [index]

# Apply an arbitrary algorithm to modify the colorspace vectors.
c = (c * slopeVar1) + offsetVar1
m = (m * slopeVar2) + offsetVar2
y = (y * slopeVar3) + offsetVar3
k = (k * slopeVar4) + offsetVar4

spaceData [index] = (c, m, y, k)      # replace old values with new in-place
#end for

else:
print
print 'Brighten(2):    Colorspace string [%s] not known.' % (colorspaceStr.upper())
print
os._exit (1)
#end if

# Convert the modified colorspace data back into RGB data
#print 'Returning modified colorspace data to RGB ...',
rgbPrimedata = []
for spacetuple in spaceData:

if   colorspaceStr.lower() == 'cmyk':
rgbPrimetuple = Cmyk2Rgb (spacetuple)

elif colorspaceStr.lower() == 'hsl-':
rgbPrimetuple = Hsl2Rgb (spacetuple)

elif colorspaceStr.lower() == 'hsv-':
rgbPrimetuple = Hsv2Rgb (spacetuple)

else:
print
print 'Brighten(3):   Colorspace string not recognized = [%s]' % (colorspaceStr.upper())
print
os._exit (1)
#end if

rgbPrimedata.append (rgbPrimetuple)
#end for
#print 'Finished'

# Create a new image from the new RGB data
imRgbPrime = Image.new ('RGB', imRgb.size)
imRgbPrime.putdata (rgbPrimedata)

return imRgbPrime

#end def Brighten

#------------------

if __name__ == "__main__":

app = wx.PySimpleApp()
wx.InitAllImageHandlers()

framepsn = (100, 100)
framesize = (300, 575)          # master frame size
AppFrame (None, -1, "Colorspace Transformer", framepsn, framesize)

app.MainLoop()

#end if

```