# 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)