Effects - spakin/SimpInkScr GitHub Wiki
The features described below apply effects to Simple Inkscape Scripting shapes.
One can define a gradient pattern in Simple Inkscape Scripting with the following two functions:
Function: linear_gradient(pt1, pt2, repeat, gradient_units, template)
Function: radial_gradient(center, radius, focus, fr, repeat, gradient_units, template)
All arguments are optional. For a linear gradient, pt1 and pt2 specify the starting and ending points, each an (x, y) tuple. For a radial gradient, center specifies the center of the final circle as an (x, y) coordinate, and radius specifies its radius. Less commonly used are focus and fr, which specify the center and radius of the initial circle.
repeat specifies how the pattern should repeat if the starting and ending points/radii don't cover an entire shape. It should be one of the values defined in Inkscape's GUI: none (the default) pads the remaining space with the final color; direct repeats the pattern until the remaining space is filled; and reflected repeats the pattern forward and backward until the remaining space is filled.
gradient_units is one of objectBoundingBox or userSpaceOnUse. objectBoundingBox (the default) indicates that coordinates (0, 0) and (1, 1) correspond to the upper-left and lower-right corners of the target shape. userSpaceOnUse uses the underlying document coordinates, independent of the target shape.
template points to another gradient from which to inherit gradient properties. It is common to define a color pattern in one gradient and apply it to different coordinates from an object-specific second gradient that inherits from the first gradient.
Once a gradient object is created, a color pattern is assigned with the following method:
Method: add_stop(ofs, color, opacity)
ofs is an offset, specified either as a fraction from 0 to 1 or as a (string) percentage from 0% to 100%. color is a color. opacity is an optional opacity from 0 to 1 and defaults to 1 (fully opaque).
Example:
grad = linear_gradient((0, 0), (0, 1))
for i in range(5):
r, g, b = randint(0, 255), randint(0, 255), randint(0, 255)
grad.add_stop(i/4.0, '#%02X%02X%02X' % (r, g, b))
ellipse((200, 150), (200, 150), fill=grad)
The preceding code defines a gradient that transitions among five random colors from top to bottom. It then creates an ellipse filled with that gradient.
Markers decorate the ends and interior points of strokes. Think of arrowheads as an example. A Simple Inkscape Scripting object can be converted to a marker using the following function:
Function: marker(obj, (x, y), orient, marker_units, view_box)
obj is the only required argument and represents the shape object, produced by any of the functions described in Shape construction, to convert to a marker. It should be drawn assuming it will be attached to a horizontal line of unit thickness and pointing from left to right. The marker's reference point—the coordinates at which it connects to the line—are given as an (x, y) tuple. orient controls the orientation of the marker with respect to the line or curve to which it is attached. If orient is auto (the default), the marker rotates with its line or curve. If orient is auto-start-reverse, then it behaves just like auto when the marker is placed at the end or middle of a line or curve but 180° rotated from that when the marker is placed at the beginning of a line or curve. orient="auto-start-reverse" can be used, for example, to define double-ended arrows with a single marker ("🡘" with auto-start-reverse versus "↣" with auto).
The remaining options control the coordinate system used for the marker. If marker_units is strokeWidth (the default), then the marker is drawn in terms of units of the line or curve's stroke width. If a user changes the stroke width in the Inkscape GUI, the marker size changes accordingly. In contrast, if marker_units is userSpaceOnUse, the marker is drawn in terms of absolute units, independent of the line or curve's stroke width. The view box, defined with the view_box parameter, is rarely needed but can be used to control the marker's scaling. It is expressed as a tuple of tuples ((x0, y0), (x1, y1)) that defines a rectangle to scale to unit size. view_box can also be the string auto to instruct Simple Inkscape Scripting to set the view box to the marker shape's bounding box.
marker also accepts key=value style parameters as described in Styles.
Example:
arrowhead = path([Move(0, 0),
Line(4, 2),
Line(0, 4),
Curve(0, 4, 1, 3, 1, 2),
Curve(1, 1, 0, 0, 0, 0),
ZoneClose()],
fill=None, stroke=None)
orange_arrowhead = marker(arrowhead, (1, 2), fill='orange')
line((20, 20), (120, 20), stroke='orange', stroke_width=4, stroke_linecap='round', marker_end=orange_arrowhead)
blue_arrowhead = marker(arrowhead, (1, 2), fill='blue')
line((120, 40), (20, 40), stroke='blue', stroke_width=4, stroke_linecap='round', marker_end=blue_arrowhead)
The preceding example first defines a right-facing arrowhead shape with its upper-left corner at (0, 0), its point at (4, 2), its lower-left corner at (0, 4), and the center of its bowl at (1, 2). It is drawn with the "best practice" of setting its fill and stroke colors to None to enable multiple markers of different colors to reuse the same basic shape. (In SVG, shape styles override marker styles. Setting shape styles to None allows the marker styles to take effect.)
The second stanza constructs a marker from the arrowhead shape, colors it orange, and indicates that a line or curve should connect to the arrowhead at (1, 2), the center of its bowl. It then draws an orange line using that marker as its end marker.
The third stanza constructs a second marker from the same arrowhead shape but colors it blue and draws a right-to-left blue line using that marker as its end marker.
Simple Inkscape Scripting provides a simplified means of applying SVG filters to objects. Even so, their usage requires understanding of a number of concepts. These are described in detail in the W3C working draft, "Filter Effects Module Level 1". In essence, a filter element comprises a number of filter primitives organized in a directed acyclic graph (DAG). The input to a filter primitive defaults to the previously added primitive but can be specified explicitly or can be a special string such as SourceGraphic to refer to the source object.
In Simple Inkscape Scripting, a filter is created with the filter_effect function:
Function: filter_effect(name, pt1, pt2, filter_units, primitive_units, auto_region)
All arguments are optional. name is a human-readable name for the effect that is displayed in Inkscape's filter editor. pt1 and pt2 are tuples indicating the upper-left and lower-right corners of a rectangle to which the filter applies. filter_units alters the coordinate system used by pt1 and pt2 and must be either objectBoundingBox (the default) or userSpaceOnUse. With objectBoundingBox coordinates, (0, 0) represents the upper-left corner of the shape, and (1, 1) represents the lower-right corner of the target shape. primitive_units alters the coordinate system used by filter primitives. It must also be either objectBoundingBox or userSpaceOnUse (the default). auto_region corresponds to Filter Editor… → Filter General Settings → Automatic Region in the Inkscape GUI. If auto_region is unspecified or True, pt1 and pt2 are computed automatically; if it is False, the provided pt1 and pt2 values are honored.
Trailing key=value arguments are treated as style parameters. Use _ instead of - in key. For example, write color_interpolation_filters='sRGB' to represent the SVG style color-interpolation-filters:sRGB. Default styles specified using the style function (see Styles) are ignored.
The function returns a filter object that provides the following method:
Method: add(ftype, key=value, …)
The add method adds a filter primitive to the filter and returns a primitive object. ftype is one of the predefined SVG filter primitives minus the fe prefix: Composite, GaussianBlur, Flood, etc. Primitives that require sub-primitives (e.g., Merge, which requires MergeNodes) are not yet supported.
The remaining arguments specify the parameters as defined in the Filter Effects specification with a few small changes:
-
src1should be used instead ofin, andsrc2should be used instead ofin2. This is for two reasons:inis a Python keyword and can't be used conveniently; andsrc1/src2are smart about accepting either strings (e.g.,SourceGraphic) or objects returned from previous calls toadd. -
Because of the preceding change,
resultis not needed. It is cleaner to pass an object directly tosrc1/src2instead of assigning aresultname and using that name as an input string. -
All occurrences of
_in a key are replaced by-. For example,flood_color='black'produces an attribute offlood-color='black'. -
Unlike raw SVG, the value does not have to be a string. Numbers and sequences of strings/numbers are also acceptable.
Example:
blur = filter_effect('Make Blurry')
blur.add('GaussianBlur', stdDeviation=10, edgeMode='duplicate')
circle((canvas.width/2, canvas.height/2), 100, fill='yellow', stroke='black',
stroke_width=5, filter=blur)
The preceding example defines blur as a filter that applies an SVG Gaussian-blur primitive then applies this to a yellow circle.
Objects returned by the add method themselves provide an add method with the same signature as above. This is needed for primitives such as <feDiffuseLighting> and <feSpecularLighting>, which can contain <fePointLight>, <feDistantLight>, and <feSpotLight> elements and <feComponentTransfer> , which can contain <feFuncR>, <feFuncG>, <feFuncB>, and <feFuncA> elements.
Here's the definition of a more complex filter effect:
Example:
# Reproduce Inkscape's Matte Jelly filter.
jelly = filter_effect(name='Matte Jelly',
pt1=(-0.098, -0.147),
pt2=(1.098, 1.147),
color_interpolation_filters='sRGB')
color = jelly.add('ColorMatrix',
src1='SourceGraphic',
values=[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0.85, 0])
blur = jelly.add('GaussianBlur',
src1='SourceAlpha',
stdDeviation=7)
specular = jelly.add('SpecularLighting',
src1=blur,
specularExponent=25,
specularConstant=0.9,
surfaceScale=5,
lighting_color='white')
specular.add('DistantLight',
elevation=60,
azimuth=225)
composite = jelly.add('Composite',
src1=specular,
src2='SourceGraphic',
k2=1,
k3=1,
operator='arithmetic')
jelly.add('Composite',
src1=composite,
src2=color,
operator='atop')
# Apply Matte Jelly to a rectangle with a hole in it.
path([Move(0, 0), Line(0, 128), Line(192, 128), Line(192, 0), Line(0, 0), ZoneClose(),
Move(32, 32), Line(160, 32), Line(160, 96), Line(32, 96), Line(32, 32), ZoneClose()],
stroke='#004455',
fill='#0088aa',
stroke_width=4,
filter=jelly)
The preceding example mimics Inkscape's Filters → Bevels → Matte Jelly… filter. It starts by creating a filter-effect variable called jelly then adds a number of filter-primitives variables—called color, blur, specular, and composite in the code—to it. Of note is the specular variable, representing an <feSpecularLighting> primitive, which itself contains an <feDistantLight> primitive.
An easy way to replicate Inkscape's built-in filters is to apply the desired filter to an object in the GUI, saving the file to a Simple Inkscape Scripting script (see Inkscape to Python code), and incorporating the result into your own script. This is especially helpful when applying multiple filters to the same effect because it lets Inkscape perform the tedious task of merging multiple filters into a single filter. (In SVG, only one filter can be applied to a given shape object.)
Live path effects (LPEs) are an Inkscape-specific feature (i.e., not part of standard SVG) for altering paths in a variety of interesting ways. The "live" in the name indicates that these effects are dynamic; if the underlying path is modified, the LPE will adapt automatically.
One creates an LPE in Simple Inkscape Scripting using the path_effect function:
Function: path_effect(name, key=value, …)
name is required. key=value pairs are optional.
I am unaware of any documentation of the available LPE names and the keys and values they accept. Hence, I would recommend applying an LPE to a path in the Inkscape GUI (Path → Path Effects), saving the file to a Simple Inkscape Scripting script (see Inkscape to Python code), and incorporating the result into your own script. Only key=value pairs that differ from the default need to be specified. For example, applying the Show handles LPE with the default arguments produces code like
path_effect920 = path_effect('show_handles',
handles=True,
is_visible=True,
lpeversion=1,
nodes=True,
original_d=False,
original_path=True,
scale_nodes_and_handles=10,
show_center_node=False)But because these are all default arguments, the function call can be shortened manually to just
path_effect920 = path_effect('show_handles')(and preferably assigning it to a nicer-named variable). The larger, generated code can serve as a reference for parameter names and data types. For example, it is easy to infer from the larger code that invoking Show handles with nodes drawn but not handles involves adding back the handles argument and setting it to False:
path_effect920 = path_effect('show_handles', handles=False)Once a path effect is created, it can be applied to a path with
Method: apply_path_effect(lpe)
where lpe is a path effect created with path_effect or a list of such path effects to apply in sequence.
Example:
roughen = path_effect('rough_hatches',
do_bend=False,
fat_output=False,
dist_rdm=[0, 1])
e = ellipse((150, 100), (150, 100), stroke='#7f2aff', stroke_width=2)
p = e.to_path()
p.apply_path_effect(roughen)
The preceding code instantiates the Roughen LPE with a few non-default arguments and assigns it to a variable called roughen. The code then creates an ellipse, e, to which to apply the LPE. Because apply_path_effect is defined only on paths, the code first converts e to a path, p, by invoking e's to_path method. (See Converting shapes to paths.) Finally, apply_path_effect is invoked on p to apply the roughen path effect.