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.
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.
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.
More From ivanidris
ivanidris Recommends
- Seven Characteristics of Stepper Motors | Solder In The Veins (Solder In The Veins)

Pingback: Video | Enjolt.com | Innovate for Success