Added code to convert colors to #RRGGBB format

This commit is contained in:
JSCHILL1 2009-05-17 14:34:08 -05:00
parent 823c8c45e3
commit a7d2593a58
4 changed files with 250 additions and 18 deletions

View file

@ -9,6 +9,18 @@
<p>Copyright 2009, Jeff Schiller</p>
<section id="0.12">
<header>
<h2><a href="#0.12">Version 0.12</a></h2>
</header>
<ul>
<li>upgraded enthought's path parser to handle scientific notation in path coordinates</li>
<li>convert colors to #RGGBB format</li>
<li>added option to disable color conversion</li>
</ul>
</section>
<section id="0.11">
<header>
<h2><a href="#0.11">Version 0.11</a></h2>

234
scour.py
View file

@ -30,9 +30,6 @@
# * Specify a limit to the precision of all positional elements.
# * Clean up Definitions
# * Collapse duplicate gradient definitions
# * Clean up CSS
# * Convert RGB colours from RGB(r,g,b) to #RRGGBB format
# * Convert RGB colours from #RRGGBB to #RGB if possible
# * Clean up paths
# * Eliminate last segment in a polygon
# * Collapse straight curves.
@ -46,10 +43,10 @@
# Next Up:
# + Up-revved enthought's path parser for integer coords with sci notation:
# https://svn.enthought.com/enthought/changeset/23583?format=diff&new=23583
# + Convert rgb() into #RRGGBB
# + Convert #RRGGBB into #RGB where possible
# - prevent elements from being stripped if they are referenced in a <style> element
# (for instance, filter, marker, pattern) - need a crude CSS parser
# - Convert all colors to #RRGGBB format
# - Reduce #RRGGBB format to #RGB format when possible
# - Remove any unused glyphs from font elements?
# necessary to get true division
@ -125,6 +122,155 @@ svgAttributes = [
'visibility'
]
colors = {
'aliceblue': 'rgb(240, 248, 255)',
'antiquewhite': 'rgb(250, 235, 215)',
'aqua': 'rgb( 0, 255, 255)',
'aquamarine': 'rgb(127, 255, 212)',
'azure': 'rgb(240, 255, 255)',
'beige': 'rgb(245, 245, 220)',
'bisque': 'rgb(255, 228, 196)',
'black': 'rgb( 0, 0, 0)',
'blanchedalmond': 'rgb(255, 235, 205)',
'blue': 'rgb( 0, 0, 255)',
'blueviolet': 'rgb(138, 43, 226)',
'brown': 'rgb(165, 42, 42)',
'burlywood': 'rgb(222, 184, 135)',
'cadetblue': 'rgb( 95, 158, 160)',
'chartreuse': 'rgb(127, 255, 0)',
'chocolate': 'rgb(210, 105, 30)',
'coral': 'rgb(255, 127, 80)',
'cornflowerblue': 'rgb(100, 149, 237)',
'cornsilk': 'rgb(255, 248, 220)',
'crimson': 'rgb(220, 20, 60)',
'cyan': 'rgb( 0, 255, 255)',
'darkblue': 'rgb( 0, 0, 139)',
'darkcyan': 'rgb( 0, 139, 139)',
'darkgoldenrod': 'rgb(184, 134, 11)',
'darkgray': 'rgb(169, 169, 169)',
'darkgreen': 'rgb( 0, 100, 0)',
'darkgrey': 'rgb(169, 169, 169)',
'darkkhaki': 'rgb(189, 183, 107)',
'darkmagenta': 'rgb(139, 0, 139)',
'darkolivegreen': 'rgb( 85, 107, 47)',
'darkorange': 'rgb(255, 140, 0)',
'darkorchid': 'rgb(153, 50, 204)',
'darkred': 'rgb(139, 0, 0)',
'darksalmon': 'rgb(233, 150, 122)',
'darkseagreen': 'rgb(143, 188, 143)',
'darkslateblue': 'rgb( 72, 61, 139)',
'darkslategray': 'rgb( 47, 79, 79)',
'darkslategrey': 'rgb( 47, 79, 79)',
'darkturquoise': 'rgb( 0, 206, 209)',
'darkviolet': 'rgb(148, 0, 211)',
'deeppink': 'rgb(255, 20, 147)',
'deepskyblue': 'rgb( 0, 191, 255)',
'dimgray': 'rgb(105, 105, 105)',
'dimgrey': 'rgb(105, 105, 105)',
'dodgerblue': 'rgb( 30, 144, 255)',
'firebrick': 'rgb(178, 34, 34)',
'floralwhite': 'rgb(255, 250, 240)',
'forestgreen': 'rgb( 34, 139, 34)',
'fuchsia': 'rgb(255, 0, 255)',
'gainsboro': 'rgb(220, 220, 220)',
'ghostwhite': 'rgb(248, 248, 255)',
'gold': 'rgb(255, 215, 0)',
'goldenrod': 'rgb(218, 165, 32)',
'gray': 'rgb(128, 128, 128)',
'grey': 'rgb(128, 128, 128)',
'green': 'rgb( 0, 128, 0)',
'greenyellow': 'rgb(173, 255, 47)',
'honeydew': 'rgb(240, 255, 240)',
'hotpink': 'rgb(255, 105, 180)',
'indianred': 'rgb(205, 92, 92)',
'indigo': 'rgb( 75, 0, 130)',
'ivory': 'rgb(255, 255, 240)',
'khaki': 'rgb(240, 230, 140)',
'lavender': 'rgb(230, 230, 250)',
'lavenderblush': 'rgb(255, 240, 245)',
'lawngreen': 'rgb(124, 252, 0)',
'lemonchiffon': 'rgb(255, 250, 205)',
'lightblue': 'rgb(173, 216, 230)',
'lightcoral': 'rgb(240, 128, 128)',
'lightcyan': 'rgb(224, 255, 255)',
'lightgoldenrodyellow': 'rgb(250, 250, 210)',
'lightgray': 'rgb(211, 211, 211)',
'lightgreen': 'rgb(144, 238, 144)',
'lightgrey': 'rgb(211, 211, 211)',
'lightpink': 'rgb(255, 182, 193)',
'lightsalmon': 'rgb(255, 160, 122)',
'lightseagreen': 'rgb( 32, 178, 170)',
'lightskyblue': 'rgb(135, 206, 250)',
'lightslategray': 'rgb(119, 136, 153)',
'lightslategrey': 'rgb(119, 136, 153)',
'lightsteelblue': 'rgb(176, 196, 222)',
'lightyellow': 'rgb(255, 255, 224)',
'lime': 'rgb( 0, 255, 0)',
'limegreen': 'rgb( 50, 205, 50)',
'linen': 'rgb(250, 240, 230)',
'magenta': 'rgb(255, 0, 255)',
'maroon': 'rgb(128, 0, 0)',
'mediumaquamarine': 'rgb(102, 205, 170)',
'mediumblue': 'rgb( 0, 0, 205)',
'mediumorchid': 'rgb(186, 85, 211)',
'mediumpurple': 'rgb(147, 112, 219)',
'mediumseagreen': 'rgb( 60, 179, 113)',
'mediumslateblue': 'rgb(123, 104, 238)',
'mediumspringgreen': 'rgb( 0, 250, 154)',
'mediumturquoise': 'rgb( 72, 209, 204)',
'mediumvioletred': 'rgb(199, 21, 133)',
'midnightblue': 'rgb( 25, 25, 112)',
'mintcream': 'rgb(245, 255, 250)',
'mistyrose': 'rgb(255, 228, 225)',
'moccasin': 'rgb(255, 228, 181)',
'navajowhite': 'rgb(255, 222, 173)',
'navy': 'rgb( 0, 0, 128)',
'oldlace': 'rgb(253, 245, 230)',
'olive': 'rgb(128, 128, 0)',
'olivedrab': 'rgb(107, 142, 35)',
'orange': 'rgb(255, 165, 0)',
'orangered': 'rgb(255, 69, 0)',
'orchid': 'rgb(218, 112, 214)',
'palegoldenrod': 'rgb(238, 232, 170)',
'palegreen': 'rgb(152, 251, 152)',
'paleturquoise': 'rgb(175, 238, 238)',
'palevioletred': 'rgb(219, 112, 147)',
'papayawhip': 'rgb(255, 239, 213)',
'peachpuff': 'rgb(255, 218, 185)',
'peru': 'rgb(205, 133, 63)',
'pink': 'rgb(255, 192, 203)',
'plum': 'rgb(221, 160, 221)',
'powderblue': 'rgb(176, 224, 230)',
'purple': 'rgb(128, 0, 128)',
'red': 'rgb(255, 0, 0)',
'rosybrown': 'rgb(188, 143, 143)',
'royalblue': 'rgb( 65, 105, 225)',
'saddlebrown': 'rgb(139, 69, 19)',
'salmon': 'rgb(250, 128, 114)',
'sandybrown': 'rgb(244, 164, 96)',
'seagreen': 'rgb( 46, 139, 87)',
'seashell': 'rgb(255, 245, 238)',
'sienna': 'rgb(160, 82, 45)',
'silver': 'rgb(192, 192, 192)',
'skyblue': 'rgb(135, 206, 235)',
'slateblue': 'rgb(106, 90, 205)',
'slategray': 'rgb(112, 128, 144)',
'slategrey': 'rgb(112, 128, 144)',
'snow': 'rgb(255, 250, 250)',
'springgreen': 'rgb( 0, 255, 127)',
'steelblue': 'rgb( 70, 130, 180)',
'tan': 'rgb(210, 180, 140)',
'teal': 'rgb( 0, 128, 128)',
'thistle': 'rgb(216, 191, 216)',
'tomato': 'rgb(255, 99, 71)',
'turquoise': 'rgb( 64, 224, 208)',
'violet': 'rgb(238, 130, 238)',
'wheat': 'rgb(245, 222, 179)',
'white': 'rgb(255, 255, 255)',
'whitesmoke': 'rgb(245, 245, 245)',
'yellow': 'rgb(255, 255, 0)',
'yellowgreen': 'rgb(154, 205, 50)',
}
coord = re.compile("\\-?\\d+\\.?\\d*")
scinumber = re.compile("[\\-\\+]?(\\d*\\.?)?\\d+[eE][\\-\\+]?\\d+")
@ -281,6 +427,7 @@ numAttrsRemoved = 0
numRastersEmbedded = 0
numPathSegmentsReduced = 0
numBytesSavedInPathData = 0
numBytesSavedInColors = 0
# removes all unreferenced elements except for <svg>, <font>, <metadata>, <title>, and <desc>
# also vacuums the defs of any non-referenced renderable elements
@ -644,11 +791,77 @@ def repairStyle(node, options):
else:
node.removeAttribute('style')
# recurse for our child elements
for child in node.childNodes :
num += repairStyle(child,options)
return num
# convert blue to rgb(r,g,b)
# convert rgb(r%,g%,b%) to rgb(r,g,b)
# convert rgb(r,g,b) to #RRGGBB
# finally convert #RRGGBB to #RGB if possible
rgb = re.compile("\\s*rgb\\(\\s*(\\d+)\\s*\\,\\s*(\\d+)\\s*\\,\\s*(\\d+)\\s*\\)\\s*")
rgbp = re.compile("\\s*rgb\\(\\s*(\\d*\\.?\\d+)\\%\\s*\\,\\s*(\\d*\\.?\\d+)\\%\\s*\\,\\s*(\\d*\\.?\\d+)\\%\\s*\\)\\s*")
def convertColor(value):
s = value
if s in colors.keys():
s = colors[s]
rgbpMatch = rgbp.match(s)
if rgbpMatch != None :
r = int( string.atof( rgbpMatch.group(1) ) * 255.0 / 100.0 )
g = int( string.atof( rgbpMatch.group(2) ) * 255.0 / 100.0 )
b = int( string.atof( rgbpMatch.group(3) ) * 255.0 / 100.0 )
s = 'rgb(%d,%d,%d)' % (r,g,b)
rgbMatch = rgb.match(s)
if rgbMatch != None :
r = hex( int( rgbMatch.group(1) ) )[2:].upper()
g = hex( int( rgbMatch.group(2) ) )[2:].upper()
b = hex( int( rgbMatch.group(3) ) )[2:].upper()
if len(r) == 1: r='0'+r
if len(g) == 1: g='0'+g
if len(b) == 1: b='0'+b
s = '#'+r+g+b
if s[0] == '#' and s[1]==s[2] and s[3]==s[4] and s[5]==s[6]:
s = s.upper()
s = '#'+s[1]+s[3]+s[5]
return s
def convertColors(element) :
numBytes = 0
if element.nodeType != 1: return 0
# set up list of color attributes for each element type
attrsToConvert = []
if element.nodeName in ['rect', 'circle', 'ellipse', 'polygon', \
'line', 'polyline', 'path', 'g', 'a']:
attrsToConvert = ['fill', 'stroke']
elif element.nodeName in ['stop']:
attrsToConvert = ['stop-color']
elif element.nodeName in ['solidColor']:
attrsToConvert = ['solid-color']
# now convert all the color formats
for attr in attrsToConvert:
val = element.getAttribute(attr)
oldBytes = len(val)
if val != '':
element.setAttribute(attr, convertColor(val))
numBytes += (oldBytes - len(element.getAttribute(attr)))
# now recurse for our child elements
for child in element.childNodes :
numBytes += convertColors(child)
return numBytes
def cleanPath(element) :
global numBytesSavedInPathData
global numPathSegmentsReduced
@ -981,6 +1194,7 @@ def scourString(in_string, options=[]):
global numAttrsRemoved
global numStylePropsFixed
global numElemsRemoved
global numBytesSavedInColors
doc = xml.dom.minidom.parseString(in_string)
# for whatever reason this does not always remove all inkscape/sodipodi attributes/elements
@ -1005,8 +1219,9 @@ def scourString(in_string, options=[]):
# repair style (remove unnecessary style properties and change them into XML attributes)
numStylePropsFixed = repairStyle(doc.documentElement, options)
# TODO: change color properties to #RRGGBB format
# TODO: change color properties to #RGB where possible
# convert colors to #RRGGBB format
if not '--disable-simplify-colors' in options:
numBytesSavedInColors = convertColors(doc.documentElement)
# remove empty defs, metadata, g
# NOTE: these elements will be removed even if they have (invalid) text nodes
@ -1098,6 +1313,7 @@ def printSyntaxAndQuit():
print 'If the output file is not specified, stdout is used.'
print 'If an option is not available below that means it occurs automatically'
print 'when scour is invoked. Available OPTIONS:\n'
print ' --disable-simplify-colors : Scour will not convert all colors to #RRGGBB format'
print ' --disable-style-to-xml : Scour will not convert style properties into XML attributes'
print ' --disable-group-collapsing : Scour will not collapse <g> elements'
print ' --enable-id-stripping : Scour will remove all un-referenced ID attributes'
@ -1116,6 +1332,7 @@ def parseCLA():
output = sys.stdout
options = []
validOptions = [
'--disable-simplify-colors',
'--disable-style-to-xml',
'--disable-group-collapsing',
'--enable-id-stripping',
@ -1191,7 +1408,10 @@ if __name__ == '__main__':
print ' Number of raster images embedded inline:', numRastersEmbedded
print ' Number of path segments reduced/removed:', numPathSegmentsReduced
print ' Number of bytes saved in path data:', numBytesSavedInPathData
print ' Number of bytes saved in colors:', numBytesSavedInColors
oldsize = os.path.getsize(inputfilename)
newsize = os.path.getsize(outputfilename)
sizediff = (newsize / oldsize);
print ' Original file size:', oldsize, 'bytes; new file size:', newsize, 'bytes (' + str(sizediff)[:5] + 'x)'

View file

@ -388,7 +388,7 @@ class RemoveFillOpacityWhenFillNone(unittest.TestCase):
class ConvertFillPropertyToAttr(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/fill-none.svg')
doc = scour.scourXmlFile('unittests/fill-none.svg', '--disable-simplify-colors')
self.assertEquals(doc.getElementsByTagNameNS(SVGNS, 'path')[1].getAttribute('fill'), 'black',
'fill property not converted to XML attribute' )
@ -510,32 +510,32 @@ class HandleSciNoInPathData(unittest.TestCase):
class TranslateRGBIntoHex(unittest.TestCase):
def runTest(self):
elem = scour.scourXmlFile('unittests/color-formats.svg').getElementsByTagNameNS(SVGNS, 'rect')[0]
self.assertEquals( elem.getAttribute('fill'), '#010203',
self.assertEquals( elem.getAttribute('fill'), '#0F1011',
'Not converting rgb into hex')
class TranslateRGBPctIntoHex(unittest.TestCase):
def runTest(self):
elem = scour.scourXmlFile('unittests/color-formats.svg').getElementsByTagNameNS(SVGNS, 'stop')[0]
self.assertEquals( elem.getAttribute('stop-color'), '#FF0000',
self.assertEquals( elem.getAttribute('stop-color'), '#7F0000',
'Not converting rgb pct into hex')
class TranslateColorNamesIntoHex(unittest.TestCase):
def runTest(self):
elem = scour.scourXmlFile('unittests/color-formats.svg').getElementsByTagNameNS(SVGNS, 'rect')[0]
self.assertEquals( elem.getAttribute('stroke'), '#FF0000',
self.assertEquals( elem.getAttribute('stroke'), '#A9A9A9',
'Not converting standard color names into hex')
class TranslateExtendedColorNamesIntoHex(unittest.TestCase):
def runTest(self):
elem = scour.scourXmlFile('unittests/color-formats.svg').getElementsByTagNameNS(SVGNS, 'solidColor')[0]
self.assertEquals( elem.getAttribute('solid-color'), '#00007F',
self.assertEquals( elem.getAttribute('solid-color'), '#800000',
'Not converting extended color names into hex')
class TranslateLongHexColorIntoShortHex(unittest.TestCase):
def runTest(self):
elem = scour.scourXmlFile('unittests/color-formats.svg').getElementsByTagNameNS(SVGNS, 'ellipse')[0]
self.assertEquals( elem.getAttribute('fill'), '#FFF',
'Not converting extended color names into hex')
'Not converting long hex color into short hex')
if __name__ == '__main__':

View file

@ -1,11 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<linearGradient id="g1" x1="0" y1="0" x2="1" y2="0">
<stop offset="0.5" stop-color="rgb(100%, 0%, 0%)" />
<stop offset="0.5" stop-color="rgb(50.0%, 0%, .0%)" />
</linearGradient>
<solidColor id="c1" solid-color="navy"/>
<solidColor id="c1" solid-color="maroon"/>
</defs>
<rect id="rect" width="100" height="100" fill="rgb(1,2,3)" stroke="red" />
<rect id="rect" width="100" height="100" fill="rgb(15,16,17)" stroke="darkgrey" />
<circle id="circle" cx="100" cy="100" r="30" fill="url(#g1)" stroke="url(#c1)" />
<ellipse id="ellipse" cx="100" cy="100" rx="30" ry="30" style="fill:#ffffff" fill="black" />
</svg>

Before

Width:  |  Height:  |  Size: 508 B

After

Width:  |  Height:  |  Size: 520 B

Before After
Before After