Using the color picker in Inkscape extensions
Because Inkscape doesn’t yet have styles, changing colors is a mess. I often end up wanting to change colors and having to do it on an object-by-object basis; usually, this also means ungrouping some objects so as to set the colors inside that group.
Seems like a perfect job for an Inkscape extension. Setting up a simple extension that allowed me to type in the color I wanted changed and the color I wanted it changed to was easy. The “simplestyle” module makes it simple enough to get the current style, change the “fill” and/or “stroke” color value, and then put the new style back on the object. It’s all XML, and the style is an attribute on the object element. The colors are hex strings, such as “#000000” for black and “#00FF00” for green.
Recursing through a group’s children is as easy as looping through each child in node.iterchildren().
But it’s a bit lame to have to type in color values when Inkscape has a color picker built in. While I probably know the RGB code for the color I’m changing from, I probably want to play around with the color I’m changing to in the preview. After all, Inkscape gives custom extensions a preview mode automatically.
Turns out there is a color picker parameter type for Inkscape extensions, but my guess is that it’s experimental; the description of what it does is two characters long: “??”.
Here’s the .inx file to set up the dialog box with two color pickers:
[toggle code]
- <?xml version="1.0" encoding="UTF-8"?>
-
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
- <_name>Switch Colors</_name>
- <id>com.hoboes.filter.switchcolors</id>
- <dependency type="executable" location="extensions">inkex.py</dependency>
- <dependency type="executable" location="extensions">simpletransform.py</dependency>
- <dependency type="executable" location="extensions">switchColor.py</dependency>
- <param name="from" type="color" _gui-text="Color to switch from?">#000000</param>
- <param name="to" type="color" _gui-text="Color to switch to?">#000000</param>
-
<effect>
- <object-type>all</object-type>
-
<effects-menu>
- <submenu _name="Mine" />
- </effects-menu>
- </effect>
-
<script>
- <command reldir="extensions" interpreter="python">switchColor.py</command>
- </script>
- </inkscape-extension>
It appears to work fine; the only problem is that the color picker currently returns a signed long (though it does so as a string). This means that large numbers are negative, and they become difficult to convert to the hex string format that Inkscape uses for object styles. It’s easier to first convert them to unsigned longs. Python tries to hide signedness from us, but we can force it to switch a signed long to an unsigned one by ‘and’-ing it with all Fs.
[toggle code]
-
def unsignedLong(self, signedLongString):
- longColor = long(signedLongString)
-
if longColor < 0:
- longColor = longColor & 0xFFFFFFFF
- return longColor
The long appears to be calculated by adding up the alpha value, the blue value times 256, the green value times 65,536 (256 squared), and the red value by 16,777,216 (256 cubed). That is, it’s meant to be used as a hex string; each pair of hexadecimal digits is part of the RGBA value for the color. Convert it to a hex string using the hex() function, remove the special characters from the ends (as well as the alpha value), and add a pound sign (#) in front of it. That’s the RGB format the style attribute needs.
Here’s the full code:
[toggle code]
- #!/usr/bin/python
- import copy
- import inkex, simplestyle
- #http://wiki.inkscape.org/wiki/index.php/INX_Parameters
- #love that description: color ??; I'm assuming it's still experimental (which is why it returns signed longs)
-
class SwitchColor(inkex.Effect):
-
def __init__(self):
- inkex.Effect.__init__(self)
- self.OptionParser.add_option('-f', '--from', action='store', type='string', dest='colorFrom', default=0, help='Color to switch from?')
- self.OptionParser.add_option('-t', '--to', action='store', type='string', dest='colorTo', default=0, help='Color to switch to?')
-
def unsignedLong(self, signedLongString):
- longColor = long(signedLongString)
-
if longColor < 0:
- longColor = longColor & 0xFFFFFFFF
- return longColor
- #A*256^0 + B*256^1 + G*256^2 + R*256^3
-
def getColorString(self, longColor):
- longColor = self.unsignedLong(longColor)
- hexColor = hex(longColor)[2:-3]
- hexColor = hexColor.rjust(6, '0')
- return '#' + hexColor.upper()
-
def switchColor(self, styles, colorType):
- changed = False
- color = styles.get(colorType)
-
if color:
- color = color.upper()
-
if color == self.options.colorFrom:
- styles[colorType] = self.options.colorTo
- changed = True
- return styles, changed
-
def switchColors(self, node):
- changed = False
- styles = simplestyle.parseStyle(node.get('style'))
- styles, strokeChanged = self.switchColor(styles, 'stroke')
- styles, fillChanged = self.switchColor(styles, 'fill')
-
if strokeChanged or fillChanged:
- format = simplestyle.formatStyle(styles)
- #inkex.debug(format)
- node.set('style', format)
-
for child in node.iterchildren():
- self.switchColors(child)
-
def effect(self):
- self.options.colorFrom = self.getColorString(self.options.colorFrom)
- self.options.colorTo = self.getColorString(self.options.colorTo)
-
if self.selected:
-
for id, node in self.selected.iteritems():
- self.switchColors(node)
-
for id, node in self.selected.iteritems():
-
def __init__(self):
- effect = SwitchColor()
- effect.affect()
This recursively goes through each selected item, and it also checks for children on each selected item; this allows it to fix the colors on grouped items, something that can be tricky when you’re doing it by hand.
I’m expecting that the need to convert to an unsigned long (and probably even the need to convert to hex) will disappear once the color picker officially goes live.
One feature that would be really nice is if the color picker let me pick a color from the screen—so that I don’t have to remember the RGB code I’m changing even for a few seconds. This functionality is invaluable in the Mac OS X color picker.
Update, March 15 2014: I’ve modified the script and .inx file as described in Updated Inkscape extension to make it more portable.
May 16 2016: It looks like the _gui-text attribute and the default color value are now superfluous; as far as I can tell, both are ignored by Inkscape when presenting the color dialog.
- INX Parameters
- “Here you will find the differents parameters you may use in your .inx files (Inkscape Extensions).” They are currently: boolean, int, float, string, description, enum, notebook, optiongroup, color.
- Updated Inkscape extension
- Inkscape extensions on Mac OS X are a little easier in the latest versions.
More Inkscape
- Updated Inkscape extension
- Inkscape extensions on Mac OS X are a little easier in the latest versions.
- Get element by id in Inkscape
- A Google search couldn’t find this before, so I’m going to nudge it along. The way to get an Inkscape node by Id in an Effect is with self.getElementById('Id').
- Write an Inkscape extension: create multiple duplicates
- Once you get past the complete lack of simple real-world examples, it’s pretty easy to make Inkscape extensions. So, here’s a simple real-world example.
File switchColor.py, line 7 def __init__(self):
IndentationError: expected an indented block
Hark Thrice at 10:44 p.m. August 6th, 2014
M+/IZ
Regarding the IndentationError, you may need to hit “toggle code” before copying it; this converts all of the list-based indentation to tabbed text. Or, if you’re using space-based indentation, you may have to convert the tabs to whichever number of spaces you use for indentation.
Jerry Stratton in Round Rock, TX at 9:23 p.m. August 7th, 2014
ZGpVP