[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 
program only does linear adjustments.
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)

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

        # Setting up the menu.
        filemenu= wx.Menu()

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

        filemenu.AppendSeparator()

        idAbout = wx.NewId()
        filemenu.Append (idAbout, "&About"," Information about this program")
        wx.EVT_MENU (self, idAbout, self.OnAbout)

        filemenu.AppendSeparator()

        idExit = wx.NewId()
        filemenu.Append (idExit,"E&xit"," Terminate the program")
        wx.EVT_MENU (self, idExit, self.OnExit)

        # Creating the menubar.
        menuBar = wx.MenuBar()
        menuBar.Append (filemenu,"&File") # Adding the "filemenu" to the MenuBar
        self.SetMenuBar (menuBar)  # Adding the MenuBar to the Frame content.

        #----------

        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
        boxsizer.Add (staticBitmap, expand)

        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', 
                                ' About Colorspace Transform', wx.OK)

        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
        padding = 0

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

        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
        radbox = wx.RadioBox (self, rbId, "Color Space", wx.DefaultPosition, wx.DefaultSize,
                              modcolorspaces, numRbRows, wx.RA_SPECIFY_ROWS)
        radbox.SetBackgroundColour (self.GetBackgroundColour())
        radbox.SetBackgroundColour (wx.WHITE)
        radbox.SetToolTip (wx.ToolTip ('Select a colorspace model:'))
        radbox.SetLabel ('Colorspace Model Selection')
        wx.EVT_RADIOBOX (self, rbId, self.EvtRadioBox)
        # 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())

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

        boxsizerV.Add (radbox, expand, wx.CENTER|wx.ALL, 10)
        bordersize = 0
        boxsizerV.Add (boxsizerX1X2var1, expand, wx.CENTER|wx.ALL, bordersize)
        boxsizerV.Add (boxsizerX1X2var2, expand, wx.CENTER|wx.ALL, bordersize)
        boxsizerV.Add (boxsizerX1X2var3, expand, wx.CENTER|wx.ALL, bordersize)
        boxsizerV.Add (boxsizerX1X2var4, expand, wx.CENTER|wx.ALL, bordersize)

        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__

    def EvtRadioBox (self, event):

        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
        padding = 0

        boxsizerX1X2 = wx.BoxSizer (wx.HORIZONTAL)

        boxsizerX1V = wx.BoxSizer (wx.VERTICAL)

        labelX1 = wx.StaticText (self, -1, "X1:  Y=0.0 Intercept:", wx.DefaultPosition)
        boxsizerX1V.Add (labelX1, expand, wx.CENTER|wx.ALL, padding)

        boxsizerX1 = wx.BoxSizer (wx.HORIZONTAL)
        self.textX1Var1 = wx.TextCtrl (self, -1, "0.000", wx.DefaultPosition, wx.Size(60, -1),
                                   style=wx.TE_READONLY)
        boxsizerX1.Add (self.textX1Var1, expand, wx.CENTER|wx.ALL, padding)
        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)
        boxsizerX1.Add (self.spinX1, expand, 0, padding)

        boxsizerX1V.Add (boxsizerX1, expand, 0, padding)

        resetX1btn = wx.Button (self, -1, "Reset to 0.000")
        boxsizerX1V.Add (resetX1btn, expand, 0, padding)
        wx.EVT_BUTTON (self, resetX1btn.GetId(), ResetX1BtnHandler)

        boxsizerX2V = wx.BoxSizer (wx.VERTICAL)

        labelX2 = wx.StaticText (self, -1, "X2:  Y=1.0 Intercept:", wx.DefaultPosition)
        boxsizerX2V.Add (labelX2, expand, wx.CENTER|wx.ALL, padding)

        boxsizerX2 = wx.BoxSizer (wx.HORIZONTAL)
        self.textX2Var1 = wx.TextCtrl (self, -1, "1.000", wx.DefaultPosition, wx.Size(60, -1),
                                   style=wx.TE_READONLY)
        boxsizerX2.Add (self.textX2Var1, expand, wx.CENTER|wx.ALL, padding)
        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)
        boxsizerX2.Add (self.spinX2, expand, wx.CENTER|wx.ALL, padding)

        boxsizerX2V.Add (boxsizerX2, expand, 0, padding)

        resetX2btn = wx.Button (self, -1, "Reset to 1.000")
        boxsizerX2V.Add (resetX2btn, expand, 0, padding)
        wx.EVT_BUTTON (self, resetX2btn.GetId(), ResetX2BtnHandler)

        tctrlVar1 = wx.TextCtrl (self, -1, varLabelChar.upper(), wx.DefaultPosition, wx.Size(20, -1), 
                                 style=wx.TE_READONLY)
        boxsizerX1X2.Add (tctrlVar1, expand, wx.CENTER|wx.ALL, 10)
        boxsizerX1X2.Add (boxsizerX1V, expand, wx.CENTER|wx.ALL, 10)
        boxsizerX1X2.Add (boxsizerX2V, expand, wx.CENTER|wx.ALL, 10)

        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
        padding = 0

        boxsizerX1X2 = wx.BoxSizer (wx.HORIZONTAL)

        boxsizerX1V = wx.BoxSizer (wx.VERTICAL)

        labelX1 = wx.StaticText (self, -1, "X1:  Y=0.0 Intercept:", wx.DefaultPosition)
        boxsizerX1V.Add (labelX1, expand, wx.CENTER|wx.ALL, padding)

        boxsizerX1 = wx.BoxSizer (wx.HORIZONTAL)
        self.textX1Var2 = wx.TextCtrl (self, -1, "0.000", wx.DefaultPosition, wx.Size(60, -1),
                                   style=wx.TE_READONLY)
        boxsizerX1.Add (self.textX1Var2, expand, wx.CENTER|wx.ALL, padding)
        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)
        boxsizerX1.Add (self.spinX1, expand, 0, padding)

        boxsizerX1V.Add (boxsizerX1, expand, 0, padding)

        resetX1btn = wx.Button (self, -1, "Reset to 0.000")
        boxsizerX1V.Add (resetX1btn, expand, 0, padding)
        wx.EVT_BUTTON (self, resetX1btn.GetId(), ResetX1BtnHandler)

        boxsizerX2V = wx.BoxSizer (wx.VERTICAL)

        labelX2 = wx.StaticText (self, -1, "X2:  Y=1.0 Intercept:", wx.DefaultPosition)
        boxsizerX2V.Add (labelX2, expand, wx.CENTER|wx.ALL, padding)

        boxsizerX2 = wx.BoxSizer (wx.HORIZONTAL)
        self.textX2Var2 = wx.TextCtrl (self, -1, "1.000", wx.DefaultPosition, wx.Size(60, -1),
                                   style=wx.TE_READONLY)
        boxsizerX2.Add (self.textX2Var2, expand, wx.CENTER|wx.ALL, padding)
        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)
        boxsizerX2.Add (self.spinX2, expand, wx.CENTER|wx.ALL, padding)

        boxsizerX2V.Add (boxsizerX2, expand, 0, padding)

        resetX2btn = wx.Button (self, -1, "Reset to 1.000")
        boxsizerX2V.Add (resetX2btn, expand, 0, padding)
        wx.EVT_BUTTON (self, resetX2btn.GetId(), ResetX2BtnHandler)

        tctrlVar2 = wx.TextCtrl (self, -1, varLabelChar.upper(), 
                                 wx.DefaultPosition, wx.Size(20, -1), style=wx.TE_READONLY)

        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
        padding = 0

        boxsizerX1X2 = wx.BoxSizer (wx.HORIZONTAL)

        boxsizerX1V = wx.BoxSizer (wx.VERTICAL)

        labelX1 = wx.StaticText (self, -1, "X1:  Y=0.0 Intercept:", wx.DefaultPosition)
        boxsizerX1V.Add (labelX1, expand, wx.CENTER|wx.ALL, padding)

        boxsizerX1 = wx.BoxSizer (wx.HORIZONTAL)
        self.textX1Var3 = wx.TextCtrl (self, -1, "0.000", wx.DefaultPosition, wx.Size(60, -1), 
                                   style=wx.TE_READONLY)
        boxsizerX1.Add (self.textX1Var3, expand, wx.CENTER|wx.ALL, padding)
        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)
        boxsizerX1.Add (self.spinX1, expand, 0, padding)

        boxsizerX1V.Add (boxsizerX1, expand, 0, padding)

        resetX1btn = wx.Button (self, -1, "Reset to 0.000")
        boxsizerX1V.Add (resetX1btn, expand, 0, padding)
        wx.EVT_BUTTON (self, resetX1btn.GetId(), ResetX1BtnHandler)

        boxsizerX2V = wx.BoxSizer (wx.VERTICAL)

        labelX2 = wx.StaticText (self, -1, "X2:  Y=1.0 Intercept:", wx.DefaultPosition)
        boxsizerX2V.Add (labelX2, expand, wx.CENTER|wx.ALL, padding)

        boxsizerX2 = wx.BoxSizer (wx.HORIZONTAL)
        self.textX2Var3 = wx.TextCtrl (self, -1, "1.000", wx.DefaultPosition, wx.Size(60, -1), 
                                   style=wx.TE_READONLY)
        boxsizerX2.Add (self.textX2Var3, expand, wx.CENTER|wx.ALL, padding)
        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)
        boxsizerX2.Add (self.spinX2, expand, wx.CENTER|wx.ALL, padding)

        boxsizerX2V.Add (boxsizerX2, expand, 0, padding)

        resetX2btn = wx.Button (self, -1, "Reset to 1.000")
        boxsizerX2V.Add (resetX2btn, expand, 0, padding)
        wx.EVT_BUTTON (self, resetX2btn.GetId(), ResetX2BtnHandler)

        tctrlVar3 = wx.TextCtrl (self, -1, varLabelChar.upper(), 
                                      wx.DefaultPosition, wx.Size(20, -1), style=wx.TE_READONLY)
        boxsizerX1X2.Add (tctrlVar3, expand, wx.CENTER|wx.ALL, 10)    # a 10-pixel border all around
        boxsizerX1X2.Add (boxsizerX1V, expand, wx.CENTER|wx.ALL, 10)
        boxsizerX1X2.Add (boxsizerX2V, expand, wx.CENTER|wx.ALL, 10)

        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
        padding = 0

        boxsizerX1X2 = wx.BoxSizer (wx.HORIZONTAL)

        boxsizerX1V = wx.BoxSizer (wx.VERTICAL)

        labelX1 = wx.StaticText (self, -1, "X1:  Y=0.0 Intercept:", wx.DefaultPosition)
        boxsizerX1V.Add (labelX1, expand, wx.CENTER|wx.ALL, padding)

        boxsizerX1 = wx.BoxSizer (wx.HORIZONTAL)
        self.textX1Var4 = wx.TextCtrl (self, -1, "0.000", wx.DefaultPosition, wx.Size(60, -1), 
                                   style=wx.TE_READONLY)
        boxsizerX1.Add (self.textX1Var4, expand, wx.CENTER|wx.ALL, padding)
        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)
        boxsizerX1.Add (self.spinX1, expand, 0, padding)

        boxsizerX1V.Add (boxsizerX1, expand, 0, padding)

        resetX1btn = wx.Button (self, -1, "Reset to 0.000")
        boxsizerX1V.Add (resetX1btn, expand, 0, padding)
        wx.EVT_BUTTON (self, resetX1btn.GetId(), ResetX1BtnHandler)

        boxsizerX2V = wx.BoxSizer (wx.VERTICAL)

        labelX2 = wx.StaticText (self, -1, "X2:  Y=1.0 Intercept:", wx.DefaultPosition)
        boxsizerX2V.Add (labelX2, expand, wx.CENTER|wx.ALL, padding)

        boxsizerX2 = wx.BoxSizer (wx.HORIZONTAL)
        self.textX2Var4 = wx.TextCtrl (self, -1, "1.000", wx.DefaultPosition, wx.Size(60, -1), 
                                   style=wx.TE_READONLY)
        boxsizerX2.Add (self.textX2Var4, expand, wx.CENTER|wx.ALL, padding)
        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)
        boxsizerX2.Add (self.spinX2, expand, wx.CENTER|wx.ALL, padding)

        boxsizerX2V.Add (boxsizerX2, expand, 0, padding)

        resetX2btn = wx.Button (self, -1, "Reset to 1.000")
        boxsizerX2V.Add (resetX2btn, expand, 0, padding)
        wx.EVT_BUTTON (self, resetX2btn.GetId(), ResetX2BtnHandler)

        tctrlVar4 = wx.TextCtrl (self, -1, varLabelChar.upper(), 
                                 wx.Point(0, 0), wx.Size(20, -1), style=wx.TE_READONLY)
        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
        boxsizer.Add (staticBitmap, expand)

        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



More information about the Image-SIG mailing list