#!/usr/bin/env python3
#
# Walk through the color spectrum in an orderly fashion and display the colors in Inkscape,
# Language: Simple Inkscape Scripting (SIS, apparently)
# Details: https://inkscape.org/~pakin/%E2%98%85simple-inkscape-scripting
# Github: https://github.com/spakin/SimpInkScr
# Developed against Python 3.8.10
# rgbx version 1.0
#
# Copyright 2022 Joseph Smith
#
# Permission to use, copy, modify, and/or distribute this software for any purpose with or without
# fee is hereby granted, provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
# SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
# OF THIS SOFTWARE.
# Changes:
# - exceedpageborders -> spillover
# - objectstroke -> objectstrokecolor
# - dropped support for buggy page dimensions handling (pagewidth, pageheight)
# - simplified and improved skip/shuffle/printvalues handling (separate functions)
# TODO: maybe: fieldalign (left/center/right), for multiple runs
# Bugs/shortcomings
# - turnaround only works with plainrgb color set, which suggests it doesn't survive a recharge
# - no repetitive output with colorfile
# - FIXED: fieldkip plus shufflecolors will shuffle skipped fields as well
# - FIXED: fieldskip plus invertcolors: TypeError: unsupported operand type(s) for -: 'int' and 'str'
# - FIXED: controlling the script from a config file is cumbersome (added GUI)
# --------------------------------------------------------------------------------------------------
# Q and A secion.
#
# Q: why not group functions neatly into objects?
# A: because doing so would bring lots of overhead with little or no real gain.
#
# Q: why so many global variables?
# A: because having them snake through the code in the form of function parameters would result in
# a maintenance nightmare (tried and failed).
#
# Having said that, the initialization section must rank high among the ugliest code blocks ever
# written, but no amount of de-globalization or objectification would fix that.
# --------------------------------------------------------------------------------------------------
import os, sys
import os.path
# Config file written by RGBX-GUI
CONFIGFILE = 'rgbx-config.txt'
# ==================================================================================================
# Function definitions
# CONFIG
# Parse configuration file; sets globals directly
def loadconfig(cffile):
# For maintainers to maintain: expected types CAN be speciified; without, everything returns as str.
# Lists of (quoted) config var names.
expectint = ['backgroundcolor', 'colorstep', 'fieldnum', 'fieldheight', 'fieldsep', 'fieldskip', 'skipoffset', 'turnaround', 'huerotation', 'objectsize', 'minobjectsize', 'objectstrokewidth', 'rotatecolor']
expectfloat = ['fieldopacity', 'objectopacity']
# List of config params read from the file.
cflist = []
# Read in file contents.
with open(cffile) as fp:
lines = fp.readlines()
for line in lines:
if '#' in line:
parts = line.split('#')
line = parts[0].rstrip()
else:
line = line.rstrip()
if '=' in line:
entry = line.split('=')
cfname = entry[0].strip()
cfval = entry[1].strip()
cfval = cfval.replace("'", "")
cfval = cfval.replace('"', "")
configentry = [cfname, cfval]
cflist.append(configentry)
fp.close()
# Set global variables.
for i in range(len(cflist)):
name = cflist[i][0]
value = cflist[i][1]
if (value.startswith("0x")):
iv = int(value[2:], 16)
globals()[name] = int(iv)
elif name in expectint:
iv = int('%s' % value, 0)
globals()[name] = iv
elif name in expectfloat:
fv = float('%s' % value)
globals()[name] = float(fv)
else:
globals()[name] = value
return()
# ===============================================================================
# HIGH-LEVEL FUNCTIONS
# - all of these are called from the main code
# - most of these prepare the final print list
# Create a list of fields to be printed or skipped.
# This is in fact the MAIN LOOP - the highest-level function in this script.
def createprintlist():
global RGBCODE, CHANGEOP, fieldcount
for i in range(fieldnum):
# Update the print list, keeping field skips into account
PRINTLIST.append(RGBCODE)
# Change values of color bytes for next rum
RGBCODE = updatergb(RGBCODE)
# Switch roles of color bytes after every N runs
if ((i + 1) % turnaround == 0):
# R-B-G to R-G-B output: reverse shift
# Works only with an M/R index swap at initialization; see initlmr()
if (swapgb == 'yes'): shiftlmr('right')
else: shiftlmr('left')
if (colorset == 'web'):
# No need to recharge bytes
# The shift has taken care of the correct index switch (M becomes former L/M)
if (CHANGEOP == 'inc'): CHANGEOP = 'dec'
elif (CHANGEOP == 'dec'): CHANGEOP = 'inc'
elif (colorset != 'plainrgb'):
# Recharge bytes (set values high)
RGBCODE = rechargergb(RGBCODE)
# Reshuffle print list.
def handleshuffle():
if (shufflecolors == 'yes'):
from random import random, shuffle
shuffle(PRINTLIST)
# Invert colors.
def handleinvert():
if (invertcolors == 'yes'):
for i in range(len(PRINTLIST)):
PRINTLIST[i] = 0xffffff - PRINTLIST[i]
# For handleskips(): decide whether to skip a field.
# Assumes (should only be calles with) a fieldskip value > 0.
def testskip(fnum):
skip = 0
if (fnum < skipoffset):
skip = 1
# Complex condition, admittedly rooted in trial, error and heuristics.
elif ((fnum - skipoffset) % (fieldskip + 1) != 0):
skip = 1
if (invertskip == 'yes'):
skip = 1 - skip
return(skip)
# Handle field skip.
def handleskips():
global PRINTLIST, skipcount
# No need to traverse the entire print list once more if fieldskip equals 0.
if (fieldskip > 0):
for i in range(fieldnum):
if testskip(i):
PRINTLIST[i] = 'skip'
# Print color values.
def handleprintvalues():
if (printvalues == 'yes'):
for i in range(len(PRINTLIST)):
if (PRINTLIST[i] != 'skip'):
print('%d: %06x' % (i+1, PRINTLIST[i]))
# Read 0x-less hex rgb codes from a file to be used as print list.
def readcolorfile(rgbf):
rgbl = []
with open(rgbf, 'r') as fp:
line = fp.readline()
while line:
if '#' in line:
parts = line.split('#')
line = parts[0]
line = line.strip()
if line:
rgbl.append(str2hex(line))
line = fp.readline()
fp.close()
return(rgbl)
# ===============================================================================
# COLOR SET PROCESSING
# Change RGB byte values for next rum (called from main loop)
def updatergb(rgbval):
if (colorset == 'darkrgb'):
# RGB colors: decrement R bytes only
rgbval = changecolorbytes(rgbval, rgblist[lindex], 'ignore', 'ignore', -1, 0, 0, colorstep)
elif (colorset == 'plainrgb'):
# Plainrgb colors: increment L bytes, decrement R bytes; the W bytes remain 00 throughout
rgbval = changecolorbytes(rgbval, rgblist[lindex], 'ignore', rgblist[rindex], -1, 0, 1, colorstep)
elif (colorset == 'hybrid'):
# Hybrid colors: increment L bytes, decrement W and R bytes
rgbval = changecolorbytes(rgbval, rgblist[lindex], rgblist[mindex], rgblist[rindex], -1, -1, 1, colorstep)
elif (colorset == 'lightrgb'):
# Bright colors: decrement W bytes only
rgbval = changecolorbytes(rgbval, 'ignore', rgblist[mindex], 'ignore', 0, -1, 0, colorstep)
elif (colorset == 'darkmyc'):
# MYC colors: decrement W and R bytes
rgbval = changecolorbytes(rgbval, rgblist[lindex], 'ignore', rgblist[mindex], -1, 0, -1, colorstep)
elif (colorset == 'web'):
# 'Web safe' colors: decrement M __OR__ increment R bytes, depending on direction
if (CHANGEOP == 'inc'):
rgbval = changecolorbytes(rgbval, 'ignore', rgblist[mindex], 'ignore', 0, 1, 0, colorstep)
elif (CHANGEOP == 'dec'):
rgbval = changecolorbytes(rgbval, 'ignore', rgblist[mindex], 'ignore', 0, -1, 0, colorstep)
return(rgbval)
# Recharge bytes (set them to 0xff or ceiling value)
def rechargergb(rgbval):
if (colorset == 'hybrid'):
rgbval = chargecolorbytes(rgbval, rgblist[mindex])
elif (colorset == 'darkmyc'):
rgbval = chargecolorbytes(rgbval, rgblist[mindex])
rgbval = chargecolorbytes(rgbval, rgblist[lindex])
elif (colorset == 'lightrgb'):
rgbval = chargecolorbytes(rgbval, rgblist[lindex])
elif (colorset == 'darkrgb'):
### Could handle turnaround here
rgbval = chargecolorbytes(rgbval, rgblist[lindex])
return(rgbval)
# -------------------------------------------------------------------------------
# LOW LEVEL RGB MANAGEMENT
# Reset color values (vulnerable to overflow)
def chargecolorbytes(clr, clname):
curvalue = getbyteval(clr, clname)
if (clname == 'r'):
ceiling = CEILINGR
#threshold = THRESHOLDR
elif (clname == 'g'):
ceiling = CEILINGG
#threshold = THRESHOLDG
elif (clname == 'b'):
ceiling = CEILINGB
#threshold = THRESHOLDB
else:
return(-1)
newvalue = ceiling - curvalue
newclr = setbyteval(clr, newvalue, clname)
return(newclr)
# Change the rgb value
# The order of names doesn't really matter, as long as signs correspond
# If a name is tagged 'ignore', the value of the sign argument is irrelevant
def changecolorbytes(clr, lname, wname, rname, lsign, wsign, rsign, inc):
newrgbv = clr
cnames = (lname, wname, rname) # values: 'r', 'g', 'b', 'ignore'
csigns = (lsign, wsign, rsign) # values: -1, 1
for i in range(len(cnames)):
if (cnames[i] != 'ignore'):
newrgbv = setbyteval(newrgbv, csigns[i] * inc, cnames[i])
return(newrgbv)
# Change the value of a byte for specified color (vulnerable to overflow)
def setbyteval(clr, clbytes, clname):
if (clname == 'r'):
newclr = clr + clbytes * 0x10000
elif (clname == 'g'):
newclr = clr + clbytes * 0x100
elif (clname == 'b'):
newclr = clr + clbytes
else:
return(-1)
return(newclr)
# Retrieve the value of a byte for specified color
def getbyteval(clr, clname):
if (clname == 'r'):
# The modulo operation flushes out any overflow
clbytes = int(clr / 0x10000) % 0x100
elif (clname == 'g'):
clbytes = int(clr / 0x100) % 0x100
elif (clname == 'b'):
clbytes = int(clr) % 0x100
else:
return(-1)
return(clbytes)
# -------------------------------------------------------------------------------
# INDEX/SWITCH HANDLING FOR MAIN LOOP
# Set initial indices of RGB bytes in rgblist: 0, 1, 2; with red startcolor: R, G, B
# This is the only occasion where LMR meets RGB concretely, and only through the L index
# R and M values (positions in RGB list) are relative to that of L
# The heart of rotational complexity beats here; see the README about LMR vs LRM
def initlmr(rgboffs):
global rindex, mindex, lindex
lindex = rgboffs # L: offset in {r, g, b} list
if (swapgb == 'yes'): # This ALSO requires a shift R i.o. a shift L
mindex = (lindex + 2) % 3 # M = L + 2 }
rindex = (mindex + 2) % 3 # R = L + 1 } => LRM (swapped scheme RBG)
else:
rindex = (lindex + 2) % 3 # R = L + 2 }
mindex = (rindex + 2) % 3 # M = L + 1 } => LMR (plainrgb scheme RGB)
# Handle R/L index shifts
# Shift values: 'left', 'right'; anything other means no shift is performed
def shiftlmr(shift):
global rindex, mindex, lindex
if (shift == 'left'): # used with LMR (plainrgb scheme RGB)
# new X = old X - 1: left shift
rindex = (rindex + 2) % 3
mindex = (mindex + 2) % 3
lindex = (lindex + 2) % 3
if (shift == 'right'): # NEEDED with LRM (swapped scheme RBG)
# new X = old X + 1: right shift
rindex = (rindex + 4) % 3
mindex = (mindex + 4) % 3
lindex = (lindex + 4) % 3
# -------------------------------------------------------------------------------
# FIELD PRINTING
# Print items from the rgb list taking field skips into account
# This one should be easy to replace with various other routines
# E.g. print a bunch of circles (or Super Marios) using the colors from the list
def printfields(plist):
global ymin
for i in range(len(plist)):
if (plist[i] != 'skip'):
if (type(plist[i]) == int):
value = plist[i]
else:
value = int(plist[i], 0)
fld = fieldprep(xmin, ymin, xmin + fieldwidth, ymin + fieldheight, value)
l1.add(fld)
# The '- 1' should largely do away with annoying 1px-wide stipes
ymin = ymin + fieldheight + fieldsep - stripefix
# Examine field printing conditions; handle color inversion
def fieldprep(x0, y0, x1, y1, fcolor):
# Wrap color if its value exceeds the 000000-ffffff range. Best effort / quick fix.
# Could be handled per byte instead, with equally little meaning
if (wrapcolors == 'yes'):
if (fcolor < 0):
fcolor = -fcolor
while (fcolor > 0xffffff):
fcolor = int(fcolor / 0x10)
# Print first, adjust indices later
fld = writefield(x0, y0, x1, y1, fcolor)
return(fld)
# Function: display a color field
def writefield(x0, y0, x1, y1, clr):
fieldcolor = hex2str(clr)
f = rect((x0, y0), (x1, y1), stroke=0, stroke_width=0, opacity='%f' % fieldopacity, fill = '#%s' % fieldcolor)
return(f)
def writebackground(x0, y0, x1, y1, clr):
fieldcolor = hex2str(clr)
f = rect((x0, y0), (x1, y1), stroke=0, stroke_width=0, fill = '#%s' % fieldcolor)
return(f)
# -------------------------------------------------------------------------------
# LOW LEVEL
# Turn a numeric color value into a 6-digit (padded) hex string
def hex2str(value, chars = 6):
return('{0:0{1}x}'.format(value, chars))
# Turn a hex color string into a numeric value
# Unused, but has won its spurs for debugging
def str2hex(string):
rv = int('0x%s' % string, 0)
return(rv)
def restrict(v, minval, maxval):
if v < minval: return minval
if v > maxval: return maxval
return(v)
# ===============================================================================
# BONUS CODE: print objects i.o printfields
# TODO: provide a better hook mechanism for insertable code.
# Return a random object position
def getobjectpos(pwidth, pheight, size):
if (spillover == 'yes'):
(x, y) = uniform(0, pwidth), uniform(0, pheight)
else:
(x, y) = uniform(size, pwidth - size), uniform(size, pheight - size)
return(x, y)
# Print randomly positioned squares using colors generated by this script
def printsquares(plist):
for i in range(len(plist)):
if (plist[i] != 'skip'):
(x, y) = getobjectpos(pagewidth, pageheight, objectsize)
if (minobjectsize != 0):
xsize = uniform(int(minobjectsize), int(objectsize))
size = (xsize, xsize)
else:
size = (float(objectsize), float(objectsize))
sqr = rect((0, 0), size, transform='translate(%g, %g)' % (x, y), stroke='%s' % objectstrokecolor, stroke_width='%d' % objectstrokewidth, opacity='%f' % float(objectopacity), fill='#%06x' % plist[i])
l1.add(sqr)
# Print randomly positioned rectangles using colors generated by this script
# TODO: with an objectsize only things get rather vague (maybe add rectangle ratio to the config)
def printrectangles(plist):
for i in range(len(plist)):
if (plist[i] != 'skip'):
(x, y) = getobjectpos(pagewidth, pageheight, objectsize)
if (minobjectsize != 0):
xsize = uniform(int(minobjectsize), int(objectsize))
ysize = uniform(int(minobjectsize), int(objectsize))
size = (xsize, ysize)
else:
# This is a bit random
xsize = uniform(0, int(objectsize) * 2)
ysize = uniform(0, int(objectsize) * 2)
size = (xsize, ysize)
rct = rect((0, 0), size, transform='translate(%g, %g)' % (x, y), stroke='%s' % objectstrokecolor, stroke_width='%d' % objectstrokewidth, opacity='%f' % float(objectopacity), fill='#%06x' % plist[i])
l1.add(rct)
# Print randomly positioned circles using colors generated by this script
def printcircles(plist):
for i in range(len(plist)):
if (plist[i] != 'skip'):
(x, y) = getobjectpos(pagewidth, pageheight, objectsize)
if (minobjectsize != 0):
size = uniform(int(minobjectsize), int(objectsize))
else:
size = float(objectsize)
# Ovals: ckl = circle((x, y), 50, transform='translate(%g, %g) scale(0.75, 1) rotate(45)' % (x, y), fill='#%06x' % PRINTLIST[i])
ckl = circle((0, 0), size, transform='translate(%g, %g)' % (x, y), stroke='%s' % objectstrokecolor, stroke_width='%d' % objectstrokewidth, opacity='%f' % float(objectopacity), fill='#%06x' % plist[i])
l1.add(ckl)
# Print randomly positioned triangles in randomized perspective using colors generated by this script
def printtriangles(plist):
for i in range(len(plist)):
if (plist[i] != 'skip'):
(x, y) = getobjectpos(pagewidth, pageheight, objectsize)
if (minobjectsize != 0):
size = uniform(int(minobjectsize), int(objectsize))
else:
size = float(objectsize)
trg = regular_polygon(3, (x, y), size, 2, 0.0, 0.7, stroke='%s' % objectstrokecolor, stroke_width='%d' % objectstrokewidth, opacity='%f' % float(objectopacity), fill='#%06x' % plist[i])
l1.add(trg)
# ===============================================================================
# COLOR SET 'ROTATE'
# For color set 'rotate': rotate the hue by a specified amount of degrees.
# Code credit: Mark Ransom on Stack Overflow; adapted for specific use
# Original code: https://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color
from math import sqrt,cos,sin,radians
rgbmatrix = [[1,0,0],[0,1,0],[0,0,1]]
def clamp(v):
if v < 0x00: return 0x00
if v > 0xff: return 0xff
return int(v + 0.5)
def rotatehue(degrees):
cosA = cos(radians(degrees))
sinA = sin(radians(degrees))
rgbmatrix[0][0] = cosA + (1.0 - cosA) / 3.0
rgbmatrix[0][1] = 1./3. * (1.0 - cosA) - sqrt(1./3.) * sinA
rgbmatrix[0][2] = 1./3. * (1.0 - cosA) + sqrt(1./3.) * sinA
rgbmatrix[1][0] = 1./3. * (1.0 - cosA) + sqrt(1./3.) * sinA
rgbmatrix[1][1] = cosA + 1./3.*(1.0 - cosA)
rgbmatrix[1][2] = 1./3. * (1.0 - cosA) - sqrt(1./3.) * sinA
rgbmatrix[2][0] = 1./3. * (1.0 - cosA) - sqrt(1./3.) * sinA
rgbmatrix[2][1] = 1./3. * (1.0 - cosA) + sqrt(1./3.) * sinA
rgbmatrix[2][2] = cosA + 1./3. * (1.0 - cosA)
def getrotatedvalue(rgbval):
r = getbyteval(rgbval, 'r')
g = getbyteval(rgbval, 'g')
b = getbyteval(rgbval, 'b')
rx = r * rgbmatrix[0][0] + g * rgbmatrix[0][1] + b * rgbmatrix[0][2]
gx = r * rgbmatrix[1][0] + g * rgbmatrix[1][1] + b * rgbmatrix[1][2]
bx = r * rgbmatrix[2][0] + g * rgbmatrix[2][1] + b * rgbmatrix[2][2]
rx = clamp(rx)
gx = clamp(gx)
bx = clamp(bx)
rv = 0x10000 * rx + 0x100 * gx + bx
return(rv)
def getnexthue(rgbv, hrot):
rotatehue(hrot)
newrgb = getrotatedvalue(rgbv)
if (printvalues == 'yes'): print('%06x' % RGBCODE)
return(newrgb)
# Populate the print list for color set 'rotate'.
def createrotateprintlist():
global RGBCODE, PRINTLIST
testhr = int(huerotation % 360)
if (testhr == 0):
PRINTLIST = []
else:
# Initial RGB code (color to start with) was set during initialization.
# If rotatecolor is set, use this color instead.
if (rotatecolor != -1):
RGBCODE = rotatecolor
PRINTLIST = [RGBCODE]
if (autofieldnum == 'yes'):
rotatesteps = int(360 / huerotation)
else:
rotatesteps = fieldnum
for step in range(rotatesteps - 1):
RGBCODE = getnexthue(RGBCODE, huerotation)
PRINTLIST.append(RGBCODE)
return()
#====================================================================================================
# MAIN CODE: INITIALIZATION
# Load the contents of the configuration file.
loadconfig(CONFIGFILE)
# Configuration defaults.
if not 'fieldnum' in globals(): fieldnum = 0
if not 'fieldheight' in globals(): fieldheight = 0
if not 'colorstep' in globals(): colorstep = 0x33
if not 'fixstriping' in globals(): fixstriping = 'no'
if not 'fieldsep' in globals(): fieldsep = 0
if not 'fieldskip' in globals(): fieldskip = 0
if not 'skipoffset' in globals(): skipoffset = 0
if not 'invertskip' in globals(): invertskip = 'no'
if not 'pagemargin' in globals(): pagemargin = 0
if not 'showbackground' in globals(): showbackground = 'no'
if not 'backgroundcolor' in globals(): backgroundcolor = 0xffffff
if not 'colorset' in globals(): colorset = 'plainrgb'
if not 'startcolor' in globals(): startcolor = 'red'
if not 'invertcolors' in globals(): invertcolors = 'no'
if not 'fieldopacity' in globals(): fieldopacity = 1
if not 'huerotation' in globals(): huerotation = 30
if not 'rotatecolor' in globals(): rotatecolor = -1
if not 'rbgoutput' in globals(): rbgoutput = 'no'
if not 'turnaround' in globals(): turnaround = int(0xff / colorstep)
if not 'colorfile' in globals(): colorfile = ''
if not 'shufflecolors' in globals(): shufflecolors = 'no'
if not 'printvalues' in globals(): printvalues = 'no'
#----------------------------------------------------------------------------------------------------
# For BONUS CODE: print objects i.o. fields
if not 'showmesquares' in globals(): showmesquares = 'no'
if not 'showmerectangles' in globals(): showmerectangles = 'no'
if not 'showmecircles' in globals(): showmecircles = 'no'
if not 'showmetriangles' in globals(): showmetriangles = 'no'
if not 'objectsize' in globals(): objectsize = 10
if not 'minobjectsize' in globals(): minobjectsize = 0
if not 'objectstrokecolor' in globals(): objectstrokecolor = 'white'
if not 'objectstrokewidth' in globals(): objectstrokewidth = 0
if not 'objectopacity' in globals(): objectopacity = 1
if not 'spillover' in globals(): spillover = 'no'
#----------------------------------------------------------------------------------------------------
# Shelved settings (dysfunctional)
###rgbthreshold = 0x333333 # DEFAULT is 0x000000; value at which to declare underflow
###rgbceiling = 0xcccccc # DEFAULT is 0xffffff; value at which to declare SWITCHTIME
###wrapcolors = 'yes' # DEFAULT is 'no'; affects the entire RGB triplet
if not 'rgbthreshold' in globals(): rgbthreshold = 0x0
if not 'rgbceiling' in globals(): rgbceiling = 0xffffff
if not 'wrapcolors' in globals(): wrapcolors = 'no'
# What follows are further initializations based on the configuration settings. While these could be
# neatly packed in descriptive functions, since we're dealing with globals that would only complicate
# matters.
#----------------------------------------------------------------------------------------------------
# (Re)initialze configuration variables depending on others. Set some control variables.
# Threshold and ceiling for byte values, normally 0x00 and 0xff respectively.
# rgbthreshold and rgbceiling are currently not in the configuration settings.
THRESHOLDR = int(rgbthreshold / 0x10000) % 0x100
THRESHOLDG = int(rgbthreshold / 0x100) % 0x100
THRESHOLDB = rgbthreshold % 0x100
CEILINGR = int(rgbceiling / 0x10000) % 0x100
CEILINGG = int(rgbceiling / 0x100) % 0x100
CEILINGB = rgbceiling % 0x100
# These allow fieldnum and fieldheight to be reused.
autofieldnum = 'no'
autofieldheight = 'no'
if (fieldnum == 0): autofieldnum = 'yes'
if (fieldheight == 0): autofieldheight = 'yes'
# Stripe fixing: shift field start up one pixel.
stripefix = 0
if (fixstriping == 'yes'): stripefix = 1
# invertskip has no meaning without fieldskip; may result in no output at all.
if (fieldskip == 0): invertskip = 0
# A turnaround value higher than the natural one makes no sense.
if (turnaround > int(0xff / colorstep)): turnaround = int(0xff / colorstep)
# Get current page dimensions from SIS
pagewidth = float(svg_root.get('width').rstrip('px'))
pageheight = float(svg_root.get('height').rstrip('px'))
# Field width equalss pagewidth, unless a margin is set.
pagemargin = float(pagemargin)
fieldwidth = pagewidth - 2 * pagemargin
# Xmin and Ymin can be easily determined; Xmax/Ymax depend on fieldheight, set later.
if (pagemargin == 0): (xmin, ymin) = (0, 0)
else: (xmin, ymin) = (pagemargin, pagemargin)
# Hex color code of current color to be printed; referred to throughout this script.
RGBCODE = None
# List of hex values for colors to be displayed.
PRINTLIST = []
# For 'web' color set
CHANGEOP = 'inc'
# Make all color sets print R-G-B by default (only 'web' does that by nature).
# This is much easier and safer than tinkering with the near-inscrutable innards of the script.
swapgb = 'no'
if (rbgoutput == 'yes'):
if (colorset == 'web'):
swapgb = 'yes'
elif (colorset != 'web'):
swapgb = 'yes'
# Set uo the list of RGB color naes to rotate through.
# Sets the byte to be pointed to by the L initial index high.
rgblist = ('r', 'g', 'b')
RGBCODE = 0x000000
if (startcolor == 'red'):
RGBCODE = chargecolorbytes(RGBCODE, 'r')
rgboffset = 0
elif (startcolor == 'green'):
RGBCODE = chargecolorbytes(RGBCODE, 'g')
rgboffset = 1
elif (startcolor == 'blue'):
RGBCODE = chargecolorbytes(RGBCODE, 'b')
rgboffset = 2
# This hooks the initial L index onto a color name in the list. The other indices (M/R) are set
# relative to the L index. Each index will point to a different RGB color name, which is used for
# retrieving the corresponding part of the RGB value, processed (changed) over and over again.
initlmr(rgboffset)
# Initialize (set high) the M bytes (not with 'plainrgb' or 'darkrgb').
if (colorset == 'hybrid' or colorset == 'lightrgb' or colorset == 'darkmyc'):
RGBCODE = chargecolorbytes(RGBCODE, rgblist[mindex])
# VAGUE but functional: set the R byte moderately high so that the colors repeat.
# Has no menaing for 'darkrgb' (because it wouldn't render primary colors).
if (colorset == 'hybrid' or colorset == 'lightrgb' or colorset == 'darkmyc'):
# Vulnerable to underflow: depends on turnaround and colorstep values.
highrbytes = 0xff - turnaround * colorstep
# Failsafe
if (highrbytes < 0): highrbytes = 0
RGBCODE = RGBCODE + highrbytes
COLORFILELIST = []
# Automatic field number calculation.
if (autofieldnum == 'yes'):
if (colorfile != ''):
COLORFILELIST = readcolorfile(colorfile)
fieldnum = len(COLORFILELIST)
elif (colorset == 'rotate'):
fieldnum = int(360 / huerotation)
elif (colorset == 'web'):
# Color set 'web' requires twice as many fields as the others to complete a round.
fieldnum = int(CEILINGR / int(colorstep)) * 6
else:
fieldnum = int(CEILINGR / int(colorstep)) * 3
# Automatic field alignment.
if (autofieldheight == 'yes'):
emptypagespace = 2 * pagemargin + (fieldnum - 1) * (fieldsep - stripefix)
fieldheight = (pageheight - emptypagespace) / fieldnum
#====================================================================================================
# MAIN CODE: ACTION
# POPULATE PRINT THE LIST
# Create the list of fields to be printed.
if COLORFILELIST:
PRINTLIST = COLORFILELIST
elif (colorset == 'rotate'):
createrotateprintlist()
else:
createprintlist()
# Hande color shuffle and invert.
handleshuffle()
handleinvert()
# Decide which fields to skip. The RGB code for these fields is simply replaced with the keyword 'skip'.
handleskips()
# Print color values on request.
handleprintvalues()
# PRINT STUFF
# Add a background layer on request.
if (showbackground == 'yes'):
l0 = layer('Background')
bg = writebackground(0, 0, pagewidth, pageheight, backgroundcolor)
l0.add(bg)
# Add a layer for field output
l1 = layer('Fields')
# For BONUS CODE: print onbjects i.o. fields.
# Number of objects is determined by fieldnum. If less shown than expected, disable fieldskip.
# Cause stuff to appear on the screen.
if (showmesquares == 'yes'):
printsquares(PRINTLIST)
if (showmerectangles == 'yes'):
printrectangles(PRINTLIST)
if (showmecircles == 'yes'):
printcircles(PRINTLIST)
if (showmetriangles == 'yes'):
printtriangles(PRINTLIST)
if (showmesquares == 'no' and showmerectangles == 'no' and showmecircles == 'no' and showmetriangles == 'no'):
# Normal output: fields.
printfields(PRINTLIST)