~ubuntu-branches/ubuntu/oneiric/phatch/oneiric

« back to all changes in this revision

Viewing changes to docs/phatch_dev/ar01s02.html

  • Committer: Bazaar Package Importer
  • Author(s): Emilio Pozuelo Monfort
  • Date: 2008-02-13 23:48:47 UTC
  • Revision ID: james.westby@ubuntu.com-20080213234847-mp6vc4y88a9rz5qz
Tags: upstream-0.1
Import upstream version 0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 
2
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
3
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>Developping Action Plugins</title><link rel="stylesheet" href="./docbook-xsl.css" type="text/css" /><meta name="generator" content="DocBook XSL Stylesheets V1.71.0" /><link rel="start" href="index.html" title="Phatch Contributors Guide" /><link rel="up" href="index.html" title="Phatch Contributors Guide" /><link rel="prev" href="index.html" title="Phatch Contributors Guide" /><link rel="next" href="ar01s03.html" title="Writing Documentation" /></head><body><div class="navheader"><table width="100%" summary="Navigation header"><tr><td width="20%" align="left"><a accesskey="p" href="index.html">Prev</a> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="ar01s03.html">Next</a></td></tr></table><hr /></div><div class="section" lang="en" xml:lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="id2497503"></a>Developping Action Plugins</h2></div></div></div><div class="section" lang="en" xml:lang="en"><div class="titlepage"><div><div><h3 class="title"><a id="id2497509"></a>Getting Started: Invert</h3></div></div></div><div class="section" lang="en" xml:lang="en"><div class="titlepage"><div><div><h4 class="title"><a id="id2497515"></a>Introduction</h4></div></div></div><p>The best way to start is first to develop an PIL function which handles the manipulation. Afterwards you create input fields in order to pass the required parameters to the PIL function. <span class="emphasis"><em>Invert</em></span> is taken as a first case study as it doesn't require any parameters except for the image itself. Let's look at the source code:</p><div class="example"><a id="id2497533"></a><p class="title"><b>Example 1. Invert Action Source Code</b></p><div class="example-contents"><pre class="screen">from core import models
 
4
from core.translation import _t
 
5
 
 
6
def invert(image):
 
7
    """PIL function"""
 
8
    return ImageChops.invert(image)                         <a id="CO1-1"></a><img src="./images/icons/callouts/1.png" alt="1" border="0" />
 
9
 
 
10
class Action(models.Action):
 
11
    label       = _t('Invert')
 
12
    author      = 'Stani'
 
13
    email       = 'spe.stani.be@gmail.com'
 
14
    version     = '0.1'
 
15
    tags        = ['Colours']
 
16
    __doc__     = _t('Invert the colors of an image')       <a id="CO1-2"></a><img src="./images/icons/callouts/2.png" alt="2" border="0" />
 
17
 
 
18
    def import_modules(self):
 
19
        global ImageChops
 
20
        import ImageChops                                   <a id="CO1-3"></a><img src="./images/icons/callouts/3.png" alt="3" border="0" />
 
21
 
 
22
    def apply(self,photo,setting,cache):
 
23
        return self.apply_to_current_layer_image(photo)     <a id="CO1-4"></a><img src="./images/icons/callouts/4.png" alt="4" border="0" />
 
24
 
 
25
    description = _t("""Invert the colors of the image.""") <a id="CO1-5"></a><img src="./images/icons/callouts/5.png" alt="5" border="0" />
 
26
 
 
27
    #icon        = 'icon data'                              <a id="CO1-6"></a><img src="./images/icons/callouts/6.png" alt="6" border="0" /></pre></div></div><br class="example-break" /><div class="calloutlist"><table border="0" summary="Callout list"><tr><td width="5%" valign="top" align="left"><a href="#CO1-1"><img src="./images/icons/callouts/1.png" alt="1" border="0" /></a> <a href="#CO1-3"><img src="./images/icons/callouts/3.png" alt="3" border="0" /></a> </td><td valign="top" align="left">
 
28
Importing Modules
 
29
</td></tr><tr><td width="5%" valign="top" align="left"><a href="#CO1-2"><img src="./images/icons/callouts/2.png" alt="2" border="0" /></a> </td><td valign="top" align="left">
 
30
Defining The Action
 
31
</td></tr><tr><td width="5%" valign="top" align="left"><a href="#CO1-4"><img src="./images/icons/callouts/4.png" alt="4" border="0" /></a> </td><td valign="top" align="left">
 
32
Applying The Action
 
33
</td></tr><tr><td width="5%" valign="top" align="left"><a href="#CO1-5"><img src="./images/icons/callouts/5.png" alt="5" border="0" /></a> </td><td valign="top" align="left">
 
34
Describing The Action
 
35
</td></tr><tr><td width="5%" valign="top" align="left"><a href="#CO1-6"><img src="./images/icons/callouts/6.png" alt="6" border="0" /></a> </td><td valign="top" align="left">
 
36
Adding An Icon
 
37
</td></tr></table></div></div><div class="section" lang="en" xml:lang="en"><div class="titlepage"><div><div><h4 class="title"><a id="id2498050"></a>Importing Modules</h4></div></div></div><p>For every action you need to add the first two lines which import the basic functionality for writing action plugins. The module models provide the architecture for the plugin: the <code class="literal">class Action</code> and the input fields. (As invert doesn't require any input, there are no fields here.) Every other module you need to import with the method <code class="literal">import_modules</code>, in which you declare them as global. For example to invert an image with PIL you need the <code class="literal">ImageChops</code> module.</p><div class="note" style="margin-left: 0; margin-right: 10%;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="./images/icons/note.png" /></td><th align="left"></th></tr><tr><td align="left" valign="top"><p>Why do modules have to be imported in a seperate method? The reason is that Phatch at startup imports all actions to extract the information it needs about the actions. If the imports would be declared normally, the startup would be delayed by maybe unneeded modules.</p></td></tr></table></div></div><div class="section" lang="en" xml:lang="en"><div class="titlepage"><div><div><h4 class="title"><a id="id2498094"></a>Defining The Action</h4></div></div></div><p>You need to create a new <code class="literal">class Action</code> which is derived from <code class="literal">models.Action</code>. You need to define the action with the <code class="literal">label</code>, <code class="literal">author</code>, <code class="literal">email</code>, <code class="literal">version</code>, <code class="literal">tags</code> and <code class="literal">__doc__</code> properties. <code class="literal">label</code> and <code class="literal">__doc__</code> will appear translated in the <span class="emphasis"><em>Add Action</em></span> dialog box. That is why you need to mark them with the <code class="literal">_t</code> function. At the moment <code class="literal">tags</code> and <code class="literal">description</code>, which can contain a longer description than the <code class="literal">__doc__</code> one-liner, are not exposed yet.</p></div><div class="section" lang="en" xml:lang="en"><div class="titlepage"><div><div><h4 class="title"><a id="id2498307"></a>Applying The Action</h4></div></div></div><p>Internally Phatch works with <span class="emphasis"><em>photos</em></span>. <span class="emphasis"><em>Photos</em></span> consist of multiple <span class="emphasis"><em>layers</em></span>. Every <span class="emphasis"><em>layer</em></span> contains an <span class="emphasis"><em>image</em></span>, but also has its own offset postion. Phatch doesn't expose this functionality yet, but will later support full layered photos, just like in Gimp. The hierarchy to remember is: Photo&gt;Layer&gt;Image. Luckily you don't have to worry about this in the beginning as Phatch provides you an easy method to apply a PIL function the current layer image: <code class="literal">apply_to_current_layer_image</code>.</p></div><div class="section" lang="en" xml:lang="en"><div class="titlepage"><div><div><h4 class="title"><a id="id2498349"></a>Describing The Action</h4></div></div></div><p>In the description property you can describe the action more elaborately. Use triple quotes for multi-line text.</p></div><div class="section" lang="en" xml:lang="en"><div class="titlepage"><div><div><h4 class="title"><a id="id2498362"></a>Adding An Icon</h4></div></div></div><p>As in the example no specific icon was added, Phatch will use a default one. In the folder <code class="literal">phatch/pyWx/lib</code> there is an utility <code class="literal">img2py.py</code>. With the following command you can convert any image (eg.png) to a python file:</p><pre class="literallayout">$ python img2py.py fileName icon.py</pre><p>This will generate an <code class="literal">icon.py</code> file, in which you will find the following code:</p><pre class="screen"><span class="strong"><strong>def</strong></span> <span class="strong"><strong>getData</strong></span>():
 
38
    <span class="strong"><strong>return</strong></span> zlib.<span class="strong"><strong>decompress</strong></span>('icon data')</pre><p>You can add now the <span class="emphasis"><em>icon data</em></span> to your action source file to define the icon:</p><pre class="screen"><span class="strong"><strong>class</strong></span> <span class="strong"><strong>Action</strong></span>(models.Action):
 
39
    description = """Invert the colors of the image."""
 
40
    icon        = 'icon data'</pre></div></div><div class="section" lang="en" xml:lang="en"><div class="titlepage"><div><div><h3 class="title"><a id="id2498463"></a>Advanced: Drop Shadow</h3></div></div></div><div class="section" lang="en" xml:lang="en"><div class="titlepage"><div><div><h4 class="title"><a id="id2498468"></a>Introduction</h4></div></div></div><p>The following code was taken from the Python Cookbook:
 
41
<a href="http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/474116" target="_top">http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/474116</a>
 
42
It demonstrates how easy it is to integrate existing PIL code into Phatch.</p></div><div class="section" lang="en" xml:lang="en"><div class="titlepage"><div><div><h4 class="title"><a id="id2498490"></a>Source Code</h4></div></div></div><div class="example"><a id="id2498495"></a><p class="title"><b>Example 2. Shadow Action Source Code</b></p><div class="example-contents"><pre class="screen"><span class="strong"><strong>from</strong></span> core <span class="strong"><strong>import</strong></span> models
 
43
<span class="strong"><strong>from</strong></span> core.translation <span class="strong"><strong>import</strong></span> _t
 
44
 
 
45
<span class="strong"><strong>def</strong></span> <span class="strong"><strong>dropShadow</strong></span>(image, x_offset=5, y_offset=5, back_colour=(255,255,255,0),
 
46
                shadow_colour=0x444444, border=8, iterations=3,
 
47
                force_back=False, cache={}):
 
48
<span class="emphasis"><em>    """</em></span>
 
49
<span class="emphasis"><em>    Add a gaussian blur drop shadow to an image.</em></span>
 
50
 
 
51
<span class="emphasis"><em>    image           - The image to overlay on top of the shadow.</em></span>
 
52
<span class="emphasis"><em>    offset          - Offset of the shadow from the image as an (x,y) tuple.</em></span>
 
53
<span class="emphasis"><em>                        Can be positive or negative.</em></span>
 
54
<span class="emphasis"><em>    back_colour     - Background colour behind the image.</em></span>
 
55
<span class="emphasis"><em>    shadow_colour   - Shadow colour (darkness).</em></span>
 
56
<span class="emphasis"><em>    border          - Width of the border around the image.  This must be wide</em></span>
 
57
<span class="emphasis"><em>                        enough to account for the blurring of the shadow.</em></span>
 
58
<span class="emphasis"><em>    iterations      - Number of times to apply the filter.  More iterations</em></span>
 
59
<span class="emphasis"><em>                        produce a more blurred shadow, but increase processing</em></span>
 
60
<span class="emphasis"><em>                        time.</em></span>
 
61
<span class="emphasis"><em>    """</em></span>
 
62
    <span class="emphasis"><em>#get info</em></span>
 
63
    size        = image.size
 
64
    mode        = image.mode
 
65
 
 
66
    back        = None
 
67
 
 
68
    <span class="emphasis"><em>#assert image is RGBA</em></span>
 
69
    <span class="strong"><strong>if</strong></span> mode != 'RGBA':
 
70
        <span class="emphasis"><em>#create cache id</em></span>
 
71
        id  = ''.<span class="strong"><strong>join</strong></span>([<span class="strong"><strong>str</strong></span>(x) <span class="strong"><strong>for</strong></span> x <span class="strong"><strong>in</strong></span> ['shadow_',size,x_offset,y_offset,
 
72
                    border, iterations,back_colour,shadow_colour]])
 
73
 
 
74
        <span class="emphasis"><em>#look up in cache</em></span>
 
75
        <span class="strong"><strong>if</strong></span> cache.<span class="strong"><strong>has_key</strong></span>(id):
 
76
            <span class="emphasis"><em>#retrieve from cache</em></span>
 
77
            back, back_size = cache[id]
 
78
 
 
79
    <span class="strong"><strong>if</strong></span> back <span class="strong"><strong>is</strong></span> None:
 
80
        <span class="emphasis"><em>#size of backdrop</em></span>
 
81
        back_size       = (size[0] + <span class="strong"><strong>abs</strong></span>(x_offset) + 2*border,
 
82
                        size[1] + <span class="strong"><strong>abs</strong></span>(y_offset) + 2*border)
 
83
 
 
84
        <span class="emphasis"><em>#create shadow mask</em></span>
 
85
        <span class="strong"><strong>if</strong></span> mode == 'RGBA':
 
86
            image_mask  = image.<span class="strong"><strong>split</strong></span>()[-1]
 
87
            shadow      = Image.<span class="strong"><strong>new</strong></span>('L',back_size,0)
 
88
        <span class="strong"><strong>else</strong></span>:
 
89
            image_mask  = Image.<span class="strong"><strong>new</strong></span>(mode,size,shadow_colour)
 
90
            shadow      = Image.<span class="strong"><strong>new</strong></span>(mode,back_size,back_colour)
 
91
 
 
92
        shadow_left     = border + <span class="strong"><strong>max</strong></span>(x_offset, 0)
 
93
        shadow_top      = border + <span class="strong"><strong>max</strong></span>(y_offset, 0)
 
94
        shadow.<span class="strong"><strong>paste</strong></span>(image_mask, (shadow_left, shadow_top,
 
95
                                shadow_left + size[0], shadow_top + size[1]))
 
96
        <span class="strong"><strong>del</strong></span> image_mask <span class="emphasis"><em>#free up memory</em></span>
 
97
 
 
98
        <span class="emphasis"><em>#blur shadow mask</em></span>
 
99
 
 
100
        <span class="emphasis"><em>#Apply the filter to blur the edges of the shadow.  Since a small</em></span>
 
101
        <span class="emphasis"><em>#kernel is used, the filter must be applied repeatedly to get a decent</em></span>
 
102
        <span class="emphasis"><em>#blur.</em></span>
 
103
        n   = 0
 
104
        <span class="strong"><strong>while</strong></span> n &lt; iterations:
 
105
            shadow = shadow.<span class="strong"><strong>filter</strong></span>(ImageFilter.BLUR)
 
106
            n += 1
 
107
 
 
108
        <span class="emphasis"><em>#create back</em></span>
 
109
        <span class="strong"><strong>if</strong></span> mode == 'RGBA':
 
110
            back  = Image.<span class="strong"><strong>new</strong></span>('RGBA',back_size,shadow_colour)
 
111
            back.<span class="strong"><strong>putalpha</strong></span>(shadow)
 
112
            <span class="strong"><strong>del</strong></span> shadow <span class="emphasis"><em>#free up memory</em></span>
 
113
        <span class="strong"><strong>else</strong></span>:
 
114
            back        = shadow
 
115
            cache[id]   = back, back_size
 
116
 
 
117
    <span class="emphasis"><em>#Paste the input image onto the shadow backdrop</em></span>
 
118
    image_left  = border - <span class="strong"><strong>min</strong></span>(x_offset, 0)
 
119
    image_top   = border - <span class="strong"><strong>min</strong></span>(y_offset, 0)
 
120
    <span class="strong"><strong>if</strong></span> mode == 'RGBA':
 
121
        back.<span class="strong"><strong>paste</strong></span>(image, (image_left, image_top),image)
 
122
        <span class="strong"><strong>if</strong></span> force_back:
 
123
            mask    = back.<span class="strong"><strong>split</strong></span>()[-1]
 
124
            back.<span class="strong"><strong>paste</strong></span>(Image.<span class="strong"><strong>new</strong></span>('RGB',back.size,back_colour),(0,0),
 
125
                ImageChops.<span class="strong"><strong>invert</strong></span>(mask))
 
126
            back.<span class="strong"><strong>putalpha</strong></span>(mask)
 
127
    <span class="strong"><strong>else</strong></span>:
 
128
        back.<span class="strong"><strong>paste</strong></span>(image, (image_left, image_top))
 
129
 
 
130
    <span class="strong"><strong>return</strong></span> back
 
131
 
 
132
<span class="strong"><strong>class</strong></span> <span class="strong"><strong>Action</strong></span>(models.Action):
 
133
<span class="emphasis"><em>    """Drops shadow"""</em></span>
 
134
 
 
135
    label       = <span class="strong"><strong>_t</strong></span>('Shadow')
 
136
    author      = 'Stani'
 
137
    email       = 'spe.stani.be@gmail.com'
 
138
    version     = '0.1'
 
139
    tags        = [<span class="strong"><strong>_t</strong></span>('filter')]
 
140
    __doc__     = <span class="strong"><strong>_t</strong></span>('Drops a blurred shadow under a photo')
 
141
 
 
142
    <span class="strong"><strong>def</strong></span> <span class="strong"><strong>__init__</strong></span>(self):
 
143
        fields = models.<span class="strong"><strong>Fields</strong></span>()
 
144
 
 
145
        fields[<span class="strong"><strong>_t</strong></span>('Horizontal Offset')]        = models.<span class="strong"><strong>PixelField</strong></span>('5')
 
146
        fields[<span class="strong"><strong>_t</strong></span>('Vertical Offset')]          = models.<span class="strong"><strong>PixelField</strong></span>('5')
 
147
        fields[<span class="strong"><strong>_t</strong></span>('Border')]                   = models.<span class="strong"><strong>PixelField</strong></span>('8')
 
148
        fields[<span class="strong"><strong>_t</strong></span>('Shadow Blur')]              = models.<span class="strong"><strong>SliderField</strong></span>(3,1,20)
 
149
        fields[<span class="strong"><strong>_t</strong></span>('Background Colour')]        = models.<span class="strong"><strong>ColourField</strong></span>('#FFFFFF')
 
150
        fields[<span class="strong"><strong>_t</strong></span>('Shadow Colour')]            = models.<span class="strong"><strong>ColourField</strong></span>('#444444')
 
151
        fields[<span class="strong"><strong>_t</strong></span>('Force Background Colour')]  = models.<span class="strong"><strong>BooleanField</strong></span>(True)
 
152
 
 
153
        <span class="strong"><strong>super</strong></span>(Action,self).<span class="strong"><strong>__init__</strong></span>(fields)
 
154
 
 
155
    <span class="strong"><strong>def</strong></span> <span class="strong"><strong>apply</strong></span>(self,photo,setting,cache):
 
156
        <span class="emphasis"><em>#get info</em></span>
 
157
        info        = photo.<span class="strong"><strong>get_info</strong></span>()
 
158
        <span class="emphasis"><em>#size</em></span>
 
159
        x0, y0      = info['new_size']
 
160
        dpi         = info['new_dpi']
 
161
        <span class="emphasis"><em>#dpi</em></span>
 
162
        parameters  = {
 
163
            'x_offset'      : self.<span class="strong"><strong>get_field_size</strong></span>('Horizontal Offset',
 
164
                                info,x0,dpi),
 
165
            'y_offset'      : self.<span class="strong"><strong>get_field_size</strong></span>('Vertical Offset',
 
166
                                info,x0,dpi),
 
167
            'border'        : self.<span class="strong"><strong>get_field_size</strong></span>('Border', info,x0,dpi),
 
168
            'iterations'    : self.<span class="strong"><strong>get_field_value</strong></span>('Shadow Blur', info,),
 
169
            'force_back'    : self.<span class="strong"><strong>get_field_value</strong></span>('Force Background Colour',
 
170
                                info),
 
171
            'back_colour'   : self.<span class="strong"><strong>get_field_value</strong></span>('Background Colour', info),
 
172
            'shadow_colour' : self.<span class="strong"><strong>get_field_value</strong></span>('Shadow Colour', info),
 
173
            'cache'         : cache,
 
174
        }
 
175
 
 
176
        <span class="emphasis"><em>#manipulate layer</em></span>
 
177
        <span class="strong"><strong>return</strong></span> self.<span class="strong"><strong>apply_to_current_layer_image</strong></span>(photo,dropShadow,**parameters)
 
178
 
 
179
    <span class="strong"><strong>def</strong></span> <span class="strong"><strong>import_modules</strong></span>(self):
 
180
        <span class="emphasis"><em>#lazily import</em></span>
 
181
        <span class="strong"><strong>global</strong></span> Image, ImageChops, ImageFilter
 
182
        <span class="strong"><strong>import</strong></span> Image, ImageChops, ImageFilter</pre></div></div><br class="example-break" /></div></div><div class="section" lang="en" xml:lang="en"><div class="titlepage"><div><div><h3 class="title"><a id="id2545838"></a>Further Study</h3></div></div></div><p>Probably looking at the source code of the actions, will teach you the most. You find all the actions in the folder <code class="literal">phatch/actions</code></p><div class="note" style="margin-left: 0; margin-right: 10%;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="./images/icons/note.png" /></td><th align="left"></th></tr><tr><td align="left" valign="top"><p>If you installed Phatch on Ubuntu, probably the actions are in the folder: <code class="literal">/usr/lib/python2.5/site-packages/phatch/actions</code></p></td></tr></table></div></div></div><div class="navfooter"><hr /><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="index.html">Prev</a> </td><td width="20%" align="center"> </td><td width="40%" align="right"> <a accesskey="n" href="ar01s03.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top"> </td><td width="20%" align="center"><a accesskey="h" href="index.html">Home</a></td><td width="40%" align="right" valign="top"> </td></tr></table></div></body></html>