CalligraphedOutlineFill

From Inkscape Wiki
Revision as of 19:50, 3 February 2023 by Moini (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

The Inkscape Wiki is no longer used to host information about Extension development.

You can now find related information at GitLab.

This page is kept for historical reasons, e.g. to document specific decisions in Inkscape development.

Info: This idea has been superseded by the PowerStroke Live Path Effect.


Introduction

Graphic Tablet users using the Calligraphy tool can easily draw sketches with variable width outlines. However, filling the interior area of such a calligraphed outline is not so easy: if you try to apply the Fill tool to the path you just created, you are actually changing the style of the outline stroke itself - not that of the area it encloses...

You can manually copy the calligraphed outline and edit the dots it is made of to just remove the inner dots to obtain a path covering the area you are interested in, then apply the Fill tool to that new path... or you can try using the Python extension with source code below.

Method

This extension was built on the observation that the Calligraphy tool creates a path that starts at the point where you started to draw, and is apparently completely made of curves (C commands in the "d" attribute of the created path element) - except for two lines (L commands in the "d" attribute): one marking the end of the stroke you drew (about mid way through the "d" element"), and one at the beginning of the stroke (last command in the "d" element before the final "z" command).

The proposed extension will:

  1. loop through each of the paths you selected before calling the extension
  2. for each such path:
    1. create a copy of the current "path" element
    2. in the "d" attribute of that copied path: truncate away everything happening after the first "L" command encountered inside that attribute
    3. give a default style of "fill navy blue - no line" to that new path
    4. create a "g" element in the document, just before the presently selected path
    5. first put the duplicate path in that group
    6. put a copy of the original path inside the group (so that the original path is displayed in front of the created "fill" area)
    7. delete the original path from the document

Expected Issues

  • In case the observation about "just two L commands being present inside a Calligraphy-tool generated path" is wrong, the extension will not produce the expected result
  • If the extension is applied to anything else but a path just created with the Calligraphy tool, its behaviour will probably not be what you would like
  • Especially: if you edit a path generated with the Calligraphy tool, the L position of the L commands inside the "d" attribute is not necessarily maintained. This will definitely make the results of subsequently using this extension unpredictable.
  • Even if you do take the points above into account, it is most likely that small corrections will need to be applied to the dots corresponding to the very first and last ones in the original path. This is due to the "L" commands sometimes esthetically belonging to the shape you want to fill, sometimes not...
  • Of course, using this extension makes most sense on a path that was meant to be a more or less closed area.

How to best use the extension

  1. draw your sketch with the Calligraphy tool (many separate strokes are OK)
  2. select all the strokes, the inside of which you would like to see filled in
  3. apply the extension
  4. fine-tune the start and end dots of the generated fill areas
  5. change the default fill color to the one you wish
  6. apply transforms to the generated groups of "original stroke + fill area" if you wish to alter their shapes

Source code

Two files to be created in the "extensions" folder of Inkscape

File: fillstroke.inx

<inkscape-extension>
    <_name>FillStroke</_name>
    <id>org.dch.fillstroke</id>
	<dependency type="executable" location="extensions">fillstroke.py</dependency>
	<dependency type="executable" location="extensions">inkex.py</dependency>
    <effect>
		<object-type>path</object-type>
                <effects-menu>
                    <submenu _name="Stroke"/>
                </effects-menu>
    </effect>
    <script>
        <command reldir="extensions" interpreter="python">fillstroke.py</command>
    </script>
</inkscape-extension>

File: fillstroke.py

#!/usr/bin/env python 
'''
Copyright (C) 2007 Didier Chalon, chalondidier@skynet.be

version 2.2
 1.0: first working version - just connecting the average dots
 1.1: also average the tangents
 2.0: keep stroke outline by pruning before first L
 2.1: insert filled area before parent stroke
 2.2: group parent stroke and filled area

From selected stroke:
* assume the stroke "end point" is the first L command in the d attribute
* prune before first L

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
'''
import inkex, simplepath

class FillStroke(inkex.Effect):
    def __init__(self):
        inkex.Effect.__init__(self)

    def effect(self):

        for id in self.options.ids:

            # just keep selected paths
            node = self.selected[id]
            if node.tagName =='path':
                d = node.attributes.getNamedItem('d').value
                #inkex.debug(d)
            else:
                self.options.ids.remove(id)

            #find first L
            dEnd = d.find("L") - 1
            #inkex.debug(d[:dEnd])

            #create group to hold parent stroke and filled area
            newGroup = self.document.createElement('svg:g')
            newGroup.setAttribute('inkscape:groupmode','layer')

            #append group before parent stroke
            node.parentNode.insertBefore(newGroup,node)

            #create filled area as stripped path
            newPath = self.document.createElement('svg:path')
            newPath.setAttribute('d',d[:dEnd])
            newPath.setAttribute('style','display:inline;fill:#00004a;stroke:none;stroke-opacity:1;fill-opacity:1')

            #append stripped path to group
            newGroup.appendChild(newPath)

            #copy parent stroke
            copyStroke = node.cloneNode(1)

            #append copy of parent stroke to group
            newGroup.appendChild(copyStroke)

            #remove original stroke
            node.parentNode.removeChild(node)

e = FillStroke()
e.affect()

Conclusion

I'm routinely using this extension for my own drawing purposes. Suggestions to improve the functionality, the quality of the code etc are quite welcome !