FlexPicasa on Google App Engine

This entry is part of 3 in the series Flex on GAE

3 Image manipulation

Google App Engine supplies an image editing library for executing plain image transformations. The API provides for resizing, cropping, rotating and flipping both horizontally and vertically of various image formats. The Images API uses a subset of PIL, the Python Imaging Library, due to restrictions of low level operations. Listing 9 of photoHandler.py illustrates how I used the Images API.

Listing 9: photoHandler.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
from google.appengine.api import images
 
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.api import urlfetch
from google.appengine.ext.webapp.util import run_wsgi_app
import urllib
import logging
 
class ResizePage(webapp.RequestHandler):
  def get(self):
    url =  urllib.unquote_plus( self.request.get('url') )
    result = urlfetch.fetch(url)
 
    if result.status_code == 200:
        self.response.headers['Content-Type'] = 'image/jpeg'
        img = images.Image(result.content)
        width = int(self.request.get('w'))
        height = int(self.request.get('h'))
        img.resize(width, height)
        out = img.execute_transforms(output_encoding=images.JPEG)
        self.response.out.write(out)
    else:
        print result.status_code
 
class FlipPage(webapp.RequestHandler):
  def get(self):
    url =  urllib.unquote_plus( self.request.get('url') )
    result = urlfetch.fetch(url.strip())
    logging.debug('url ' + url)
    logging.debug('result ' + result.content)    
 
    if result.status_code == 200:
        self.response.headers['Content-Type'] = 'image/png'
        img = images.Image(result.content)
 
        flip = self.request.get('flip')
        logging.debug('flip ' + flip) 
        rot = self.request.get('rot')
        logging.debug('rot ' + rot) 
 
        if rot:
            img.rotate(int(rot) * 90)
 
 
        if flip == 'horizontal':
            img.horizontal_flip()
 
        if flip == 'vertical':
            img.vertical_flip()
 
        if flip == 'horizontalvertical':
            img.horizontal_flip()
            img.vertical_flip()
 
        img.resize(400, 300)
        out = img.execute_transforms()
        self.response.out.write(out)
    else:
        self.response.out.write('Status Code: ' + str(result.status_code))
        logging.debug('status code ' + str(result.status_code))
 
 
application = webapp.WSGIApplication(
                                     [('/resize', ResizePage), ('/flip', FlipPage)],
                                     debug=True)
 
def main():
  run_wsgi_app(application)
 
if __name__ == "__main__":
  main()

The photoHandler.py module consists of two classes – ResizePage and FlipPage, that respectively deal with resizing and flipping images. Both classes are subclasses of webapp.RequestHandler and are mapped to certain URL patterns. ResizePage resizes images with the resize method of images.Image using a specified width and height. FlipPage does more transformations due to the fact that rotating and flipping images is very similar in my eyes. It also resizes images to what seemed to be a reasonable size. This is because , unfortunately at the time, some limitations were imposed on the image manipulation, which caused failure of large image transformations.

3.1 Resizing images

Listing 10 shows the code of ResizeForm.mxml – the Flex client component, that works together with ResizePage.

Figure 2: The resize dialog

Figure 2 The resize dialog

Listing 10: ResizeForm.mxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version="1.0" encoding="utf-8"?>
<iic:ClosableTitleWindow xmlns:mx="http://www.adobe.com/2006/mxml" 
    xmlns:iic="net.ivanidris.flex.components.*" title="Resize Image"> 
   <mx:Script>
        <![CDATA[
         import net.ivanidris.flex.CustomToolTipFactory;
            import flash.net.navigateToURL;
            import flash.net.URLRequest;
 
            private function resize():void {
               navigateToURL(new URLRequest('/resize?url=' + encodeURIComponent(url.text) + '&w=' + w.text 
                  + '&h=' + h.text));
               removeMe();
            }
        ]]>
    </mx:Script>
   <mx:Form x="54" y="44">
      <mx:FormItem label="URL" visible="false" width="1" height="1">
         <mx:Label id="url"/>
      </mx:FormItem>
 
      <mx:FormItem label="Width">
         <mx:TextInput id="w" text="400" maxChars="4" restrict="0-9" toolTip=" " 
            toolTipCreate="CustomToolTipFactory.createPanelToolTip('Width', 
            'Fill in the width in pixels', event);"/>
      </mx:FormItem>
      <mx:FormItem label="Height">
         <mx:TextInput id="h" text="300" maxChars="4" restrict="0-9" toolTip=" "
            toolTipCreate="CustomToolTipFactory.createPanelToolTip('Height', 
            'Fill in the height in pixels', event);"/>
      </mx:FormItem>
      <mx:FormItem>
         <mx:Button label="Resize" click="resize();" buttonMode="true"/>
      </mx:FormItem>
   </mx:Form>
</iic:ClosableTitleWindow>

The Form has two TextInput fields, for the specification of the width and height to resize to. Both TextInput tags have restrict and maxChars attributes, which restrict the input to digits with maximum of 4 characters.

3.2 Flipping images

FlipForm.mxml in Listing 11 is a similar Flex class, but for the other image transformations.

Listing 11: FlipForm.mxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<?xml version="1.0" encoding="utf-8"?>
<iic:ClosableTitleWindow xmlns:mx="http://www.adobe.com/2006/mxml" 
    xmlns:iic="net.ivanidris.flex.components.*" title="Flip Image">
   <mx:Script>
        <![CDATA[
            import flash.net.navigateToURL;
            import flash.net.URLRequest;
            import net.ivanidris.flex.CustomToolTipFactory;
 
            private function sendRequest():void {
               var rot:String = "";
 
               if(rotate.selected) {
                  rot = rotateValue.selectedItem.toString();
               }
 
               var query:String = '/flip?url=' + encodeURIComponent(url.text)
                  + '&flip=';
 
               if(horizontal.selected) {
                  query += 'horizontal';
               }
 
               if(vertical.selected) {
                  query += 'vertical';
               }
 
               if(rot.length > 0) {
                  query += '&rot=' + rot;
               }
 
               navigateToURL(new URLRequest( query ) );
               removeMe();
            }
 
            private function showRotateItems():void {
               if(rotate.selected) {
                  rotateValue.visible = true;
                  rotateLabel.visible = true;
               }
               else {
                  rotateValue.visible = false;
                  rotateLabel.visible = false;
               }
            }
        ]]>
    </mx:Script>
    <mx:Form>
      <mx:FormItem visible="false">
         <mx:Label id="url"/>
      </mx:FormItem>
      <mx:FormItem direction="horizontal">
         <mx:CheckBox id="rotate" label="Rotate" click="showRotateItems();" toolTip=" " 
         toolTipCreate="CustomToolTipFactory.createPanelToolTip('Rotate', 
         'Check if you want to rotate the image.', event);"/>
         <mx:ComboBox id="rotateValue" visible="false" textAlign="center" toolTip=" " 
            toolTipCreate="CustomToolTipFactory.createPanelToolTip('Angle', 
            'Rotation angle which will be multiplied by 90.', event);">
            <mx:String>1</mx:String>
            <mx:String>2</mx:String>
            <mx:String>3</mx:String>
         </mx:ComboBox>
         <mx:Label id="rotateLabel" text=" x 90 degrees" visible="false"/>
      </mx:FormItem>
      <mx:FormItem label="Flip" direction="horizontal">
           <mx:CheckBox id="horizontal" label="horizontal" toolTip=" " 
         toolTipCreate="CustomToolTipFactory.createPanelToolTip('Horizontal Flip', 
         'Flip the image around its horizontal axis.', event);"/>
           <mx:CheckBox id="vertical" label="vertical" toolTip=" " 
         toolTipCreate="CustomToolTipFactory.createPanelToolTip('Vertical Flip', 
         'Flip the image around its vertical axis.', event);"/>
      </mx:FormItem>
      <mx:FormItem>
         <mx:Button label="Flip" click="sendRequest();" buttonMode="true"/>
      </mx:FormItem>
    </mx:Form>
</iic:ClosableTitleWindow>

FlipForm sends a request to FlipPage. Rotation in the Images API is limited to 90, 180 and 270 degrees, so I used a ComboBox for the rotation degree values.

Figure 3: The FlipForm dialog.

Figure 3 FlipForm dialog Figure 3 FlipForm dialog

The ComboBox is only shown if the checkbox for rotation is checked as you can see in Figure 3. Horizontal and vertical flipping can either be on or off, thus a CheckBox is appropriate here.

Series Navigation
0saves
If you enjoyed this post, please consider leaving a comment or subscribing to the RSS feed to have future articles delivered to your feed reader.
Share
This entry was posted in programming and tagged , . Bookmark the permalink.

One Response to FlexPicasa on Google App Engine

  1. Pingback: Video | Enjolt.com | Innovate for Success

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">