# Flexible code for colorful raster images with randomized colors
# Language: Simple Inkscape Scripting (SIS, apparently)
# Details: https://inkscape.org/~pakin/%E2%98%85simple-inkscape-scripting
# Github: https://github.com/spakin/SimpInkScr
# rasterscript version 0.7
#
# Creates a multicolor background grid consisting of squares or circles (cells)
# Multicolor circles (eyes) are placed on top of it
# Colors used for cell and eyes are to a degree random:
# - default behavior is to use entirely random colors
# - the set of available colors can be limited by supplying a palette file
# The whole thing can be topped by a 'raster': a unicolor layer with round holes in it
# The result is supposed to look cool! Or at least it can be useful for further processing.
# Only circles and squares are created (no ovals or rectangles)
# Next step: import user-defines shapes
# Since this is already getting somewhat serious code, let's plant a license on it:
#
# 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.
configfile = 'rasterscript.cf'
# FUNCTIONS
# function for creating a shape; default is square
def makeshape(kind, xpos, ypos, size, sfill, swidth, objfill):
if kind == 'square':
offs = (CELLSIZE - size) / 2
obj = rect((xpos + offs, ypos + offs), (xpos + size + offs, ypos + size + offs), stroke=sfill, stroke_width=swidth, fill=objfill)
else:
offs = CELLSIZE / 2
radius = size / 2
obj = circle((xpos + offs, ypos + offs), radius, stroke=sfill, stroke_width=swidth, fill=objfill)
return obj
# Different ways for fetching a color, depending on config
# With a matrix, cell colors are accessed by index (flat list)
def getcolor(mindx):
if matrix == 'yes':
code = matrixlist[mindx]
index = colorlist.index(code)
clr = '#%s' % colorlist[index+1]
elif palette == 'yes':
rgbindex = randrange(rgbmax)
clr = '#%s' % rgblist[rgbindex]
else:
clr = '#%02x%02x%02x' % (randrange(256), randrange(256), randrange(256))
return clr
# function for getting a 'random' object position; used for eyes
def getwobble():
# For 'random' positioning of eye
xwob = 0
ywob = 0
if wobbly == 'yes':
xwob = uniform(-2, 2)
ywob = uniform(-2.5, 2.5)
return (xwob, ywob)
# Create both a matrix and a definition list
def colormatrix():
with open(matrixfile) as fp:
rowcount = 0
colcount = 0
line = fp.readline().lstrip()
while line:
if '#' in line:
parts = line.split('#')
line = parts[0].rstrip()
else:
line = line.rstrip()
if line == '':
pass
elif '=' in line:
entry = line.split('=')
for i in range(len(entry)):
colorlist.append(entry[i].strip())
else:
entry = line.split()
colcount = len(entry)
for i in range(colcount):
code = entry[i].split(':')
for j in range(len(code)):
matrixlist.append(code[j])
rowcount += 1
line = fp.readline()
return(rowcount, colcount)
# CONFIG
# Parse configuration file; sets globals directly
def loadconfig(cffile):
# For maintainers to maintain: expected types CAN be speciified; without, everything returns as str
expectint = ['pagewidth', 'pageheight', 'cellsize', 'cellstrokewidth', 'eyesize', 'eyestrokewidth', 'holesize']
expectfloat = []
cflist = []
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 line == '':
pass
elif '=' 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()
for i in range(len(cflist)):
name = cflist[i][0]
value = cflist[i][1]
# Hex strings atarting with 0x are always converted
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()
loadconfig(configfile)
#print(globals())
# This ugly construct allows user to comment out definitions for convenience
if not 'cellshape' in globals(): cellshape = 'square'
if not 'cellstrokewidth' in globals(): cellstrokewidth = '0'
if not 'cellstrokecolor' in globals(): cellstrokecolor = '000000'
if not 'eyeshape' in globals(): eyeshape = 'circle'
if not 'eyestrokewidth' in globals(): eyestrokewidth = '0'
if not 'eyestrokecolor' in globals(): eyestrokecolor = '000000'
if not 'wobbly' in globals(): wobbly = 'no'
if not 'raster' in globals(): raster = 'no'
if not 'holeshape' in globals(): holeshape = 'circle'
if not 'rastercolor' in globals(): rastercolor = 'ffffff'
if not 'palette' in globals(): palette = 'no'
if not 'matrix' in globals(): matrix = 'no'
# INIT
# ALL-CAPS vars are fundemental
CELLSIZE = cellsize
CELLROWS = 0
CELLCOLS = 0
# Set page dimensions if not set already. Doc: "SVG works in nominal "pixel" units."
if ('pageheight' in globals()) and ('pagewidth' in globals()):
svg_root.set('width', '%spx' % pagewidth)
svg_root.set('height', '%spx' % pageheight)
width, height = svg_root.width, svg_root.height
svg_root.set('viewBox', '0 0 %.0f %.0f' % (width, height))
else:
pagewidth = float(svg_root.get('width').rstrip('px'))
pageheight = float(svg_root.get('height').rstrip('px'))
# MAIN CODE
if matrix == 'yes':
import os
colorlist = []
matrixlist = []
(CELLROWS, CELLCOLS) = colormatrix()
# Unused: user should take care of setting sensible page dimensions
#colwidth = CELLCOLS * CELLSIZE
#rowheight = CELLROWS * CELLSIZE
else:
# Keep the output within the page boundaries
CELLROWS = int(pagewidth // cellsize)
CELLCOLS = int(pageheight // cellsize)
# Hanlde matrix or palette; matrix has priority
if (matrix != 'yes') and (palette == 'yes'):
with open(palettefile, 'r') as pf:
rgblist = pf.read().splitlines()
rgbmax = len(rgblist) - 1
pf.close()
# Three layers: 1. cells (background), 2. eyes, 3. raster with holes
l1 = layer('Background')
l2 = layer('Eyes')
if (raster == 'yes') and (rastercolor != 'transparent'): l3 = layer('Raster')
# Every single element is created and positioned individually, then added to its layer
for i in range(CELLROWS):
for j in range(CELLCOLS):
# For getcolor() with matrix
mindex = (i + j) * 2
# Cell position
(y, x) = (i * CELLSIZE, j * CELLSIZE)
# background layer
color = getcolor(mindex)
scolor = '#%s' % cellstrokecolor
if (raster == 'yes') and (rastercolor == 'transparent'):
# Why clip afterwards if you can make cellshape fit holeshape to begin with
bgcell = makeshape(holeshape, x, y, holesize, scolor, cellstrokewidth, color)
else:
bgcell = makeshape(cellshape, x, y, CELLSIZE, scolor, cellstrokewidth, color)
l1.add(bgcell)
# eyes layer
color = getcolor(mindex+1)
scolor = '#%s' % eyestrokecolor
(xwobble, ywobble) = getwobble()
eye = makeshape(eyeshape, x + xwobble, y + ywobble, eyesize, scolor, eyestrokewidth, color)
l2.add(eye)
# raster layer: a new cell covering the background cell, with a hole punched into it
if (raster == 'yes') and (rastercolor != 'transparent'):
# raster cell
color = '#%s' % rastercolor
rstcell = makeshape('square', x, y, CELLSIZE, 'unused', 0, color).to_path()
# Hole shape
color = '#ffffff'
rsthole = makeshape(holeshape, x, y, holesize, 'unused', 0, color).to_path()
# Punch hole. Follows even-odd fill rule and draw direction (?)
if holeshape == 'circle':
rstcell.append(rsthole)
else:
rstcell.append(rsthole.reverse())
l3.add(rstcell)