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
8
return ImageChops.invert(image) <a id="CO1-1"></a><img src="./images/icons/callouts/1.png" alt="1" border="0" />
10
class Action(models.Action):
13
email = 'spe.stani.be@gmail.com'
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" />
18
def import_modules(self):
20
import ImageChops <a id="CO1-3"></a><img src="./images/icons/callouts/3.png" alt="3" border="0" />
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" />
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" />
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">
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">
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">
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">
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">
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>Layer>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
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>
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>
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]])
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]
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)
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)
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>
98
<span class="emphasis"><em>#blur shadow mask</em></span>
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>
104
<span class="strong"><strong>while</strong></span> n < iterations:
105
shadow = shadow.<span class="strong"><strong>filter</strong></span>(ImageFilter.BLUR)
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>:
115
cache[id] = back, back_size
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))
130
<span class="strong"><strong>return</strong></span> back
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>
135
label = <span class="strong"><strong>_t</strong></span>('Shadow')
137
email = 'spe.stani.be@gmail.com'
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')
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>()
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)
153
<span class="strong"><strong>super</strong></span>(Action,self).<span class="strong"><strong>__init__</strong></span>(fields)
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>
163
'x_offset' : self.<span class="strong"><strong>get_field_size</strong></span>('Horizontal Offset',
165
'y_offset' : self.<span class="strong"><strong>get_field_size</strong></span>('Vertical Offset',
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',
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),
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)
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>