Image APIを利用する

http://code.google.com/appengine/docs/images/usingimages.html

はじめに

Image APIを使うとリサイズや回転、色やコントラストの調節など標準的な変換を行うことができる。これらの変換は通常はユーザーがアップロードした画像や写真に対して行われることだろう。このドキュメントでは画像を動的にアップロードしたり変換したり保存したりする手順について説明する。Getting Started Guideにある例題Guestbookを利用して、挨拶と共にアバターをアップロードできるように改造してみよう。

画像プロパティ作成

初めにやるべきことは、ゲストブックサンプルのモデルを修正して、アップロードされる画像をBlobで保存できるようにすることだ。

class Greeting(db.Model):
  author = db.UserProperty()
  content = db.StringProperty(multiline=True)
  avatar = db.BlobProperty()
  date = db.DateTimeProperty(auto_now_add=True)

ユーザー画像をアップロード

次にやるのは、フォームを修正してユーザーがファイルを選択してアップロードするためのフィールドを追加することだ。また、フォームタグにenctype属性を追加して、マルチパートフォームデータであることを忘れずに指定しておくこと。

self.response.out.write("""
          <form action="/sign" enctype="multipart/form-data" method="post">
            <div><label>Message:</label></div>
            <div><textarea name="content" rows="3" cols="60"></textarea></div>
            <div><label>Avatar:</label></div>
            <div><input type="file" name="img"/><</div>
            <div><input type="submit" value="Sign Guestbook"></div>
          </form>
        </body>
      </html>""")

これでフィールドを二つ持つ簡単なフォームができた。

ここまで来たらGuestbookハンドラを変更して、フォームから画像データを受け取り、データストアにBlobとして保存できる。

class Guestbook(webapp.RequestHandler):
  def post(self):
    greeting = Greeting()
    if users.get_current_user():
      greeting.author = users.get_current_user()
    greeting.content = self.request.get("content")
    avatar = self.request.get("img")
    greeting.avatar = db.Blob(avatar)
    greeting.put()
    self.redirect('/')

画像の変換

Images APIを使えばいくつかの画像変換が利用できる。

リサイズ

縦横比を保ったまま画像サイズを変更できる。
http://code.google.com/appengine/docs/images/transform_before.gif
http://code.google.com/appengine/docs/images/transform_resize_after.jpg

回転

90度回転できる。

http://code.google.com/appengine/docs/images/transform_before.gif
http://code.google.com/appengine/docs/images/transform_rotate_after.jpg

水平鏡像変換

水平方向の鏡像変換できる。

http://code.google.com/appengine/docs/images/transform_before.gif
http://code.google.com/appengine/docs/images/transform_fliph_after.jpg

垂直鏡像変換

垂直方向にも鏡像変換できる。

http://code.google.com/appengine/docs/images/transform_before.gif
http://code.google.com/appengine/docs/images/transform_flipv_after.jpg

切抜き

任意の範囲を切り取れる。

http://code.google.com/appengine/docs/images/transform_before.gif
http://code.google.com/appengine/docs/images/transform_crop_after.png

I'm Feeling Lucky

この「I'm Feeling Lucky」変換は暗さや明るさを強調し、色を調節してコントラストを最適なレベルにしてくれる。

http://code.google.com/appengine/docs/images/transform_before.gif
http://code.google.com/appengine/docs/images/transform_lucky_after.png

Guestbookアプリケーションでは32x32のアバターを作りたい。まず初めにgoogle.appengine.api.imagesモジュールをインポートし、それからresize関数を呼んで画像データを渡してやる必要がある。

from google.appengine.api import images

class Guestbook(webapp.RequestHandler):
  def post(self):
    greeting = Greeting()
    if users.get_current_user():
      greeting.author = users.get_current_user()
    greeting.content = self.request.get("content")
    avatar = images.resize(self.request.get("img"), 32, 32)
    greeting.avatar = db.Blob(avatar)
    greeting.put()
    self.redirect('/')

動的画像表示(servingってどう訳せばいいんだろう・・・)

最後に/imgパスにアクセスがあったときに動的にそれらの画像を表示するImageハンドラを作成しなければいけない。またそれらの動的な画像を呼び出すようにHTMLも変更する。

class Image (webapp.RequestHandler):
  def get(self):
    greeting = db.get(self.request.get("img_id"))
    if greeting.avatar:
      self.response.headers['Content-Type'] = "image/png"
      self.response.out.write(greeting.avatar)
    else:
      self.error(404)

Imageハンドラではimg_idをリクエストから取得する。つまりGuestbookのHTMLを修正して、Imageハンドラにgreetingのkeyを渡す必要がある。

self.response.out.write("<div><img src='img?img_id=%s'></img>" %
                              greeting.key())
self.response.out.write(' %s</div>' %
                              cgi.escape(greeting.content))

これでGuestbookアプリケーションの修正は完了だ:

import cgi
import datetime
import wsgiref.handlers
import logging

from google.appengine.ext import db
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.api import images

logging.getLogger().setLevel(logging.DEBUG)


class Greeting(db.Model):
  author = db.UserProperty()
  content = db.StringProperty(multiline=True)
  avatar = db.BlobProperty()
  date = db.DateTimeProperty(auto_now_add=True)

class MainPage(webapp.RequestHandler):
  def get(self):
    self.response.out.write('<html><body>')
    query_str = "SELECT * FROM Greeting ORDER BY date DESC LIMIT 10"
    greetings = db.GqlQuery (query_str)
        
    for greeting in greetings:
      if greeting.author:
        self.response.out.write('<b>%s</b> wrote:' % greeting.author.nickname())
      else:
        self.response.out.write('An anonymous person wrote:')
      self.response.out.write("<div><img src='img?img_id=%s'></img>" %
                              greeting.key())
      self.response.out.write(' %s</div>' %
                              cgi.escape(greeting.content))

    self.response.out.write("""
          <form action="/sign" enctype="multipart/form-data" method="post">
            <div><label>Message:</label></div>
            <div><textarea name="content" rows="3" cols="60"></textarea></div>
            <div><label>Avatar:</label></div>
            <div><input type="file" name="img"/></div>
            <div><input type="submit" value="Sign Guestbook"></div>
          </form>
        </body>
      </html>""")

class Image (webapp.RequestHandler):
  def get(self):
    greeting = db.get(self.request.get("img_id"))
    if greeting.avatar:
      self.response.headers['Content-Type'] = "image/png"
      self.response.out.write(greeting.avatar)
    else:
      self.response.out.write("No image")

class Guestbook(webapp.RequestHandler):
  def post(self):
    greeting = Greeting()
    if users.get_current_user():
      greeting.author = users.get_current_user()
    greeting.content = self.request.get("content")
    avatar = images.resize(self.request.get("img"), 32, 32)
    greeting.avatar = db.Blob(avatar)
    greeting.put()
    self.redirect('/')


application = webapp.WSGIApplication([
  ('/', MainPage),
  ('/img', Image),
  ('/sign', Guestbook)
], debug=True)


def main():
  wsgiref.handlers.CGIHandler().run(application)


if __name__ == '__main__':
  main()

Google App Engineで動的にイメージを提供する

ついでに。

http://code.google.com/intl/ja/appengine/articles/images.html

はじめに

百聞は一見にしかず。視聴者へのアピールという点で画像が大きな役割を演じるというのは今日のウェブアプリケーションにおいても真実だ。Google App Engineでもデータストアを通じてすばやく簡単に画像を保存したり提供したりできる。PythonインターフェースにはデータモデリングAPIとGQL、our very own query languageが含まれる。この短い記事では、例としてMoview Review sample applicationのコードを使って、データストアAPIを画像を保存・提供するために使用する方法についての一通りを説明したい。

データストアに画像を保存

まずはじめに、データストアのオブジェクトとしてアプリケーションにそれぞれのムービーを保存したい。これはムービーをモデリングするクラスを定義することで実現できる。以下のMovieクラスではMovieオブジェクトが持つすべての属性が定義されている。定義される属性のひとつとして「picture」があるが、これがムービーのjpegイメージのバイナリデータを表している。画像を適切に扱うために、それらはBlobとして保存しなければならず、そのためにはpictureをBlobPropertyとして宣言しなければいけない。

Class Movie(db.Model)
  title = db.StringProperty()
  picture = db.BlobProperty(default=None)
  ...

これでモデルが定義されたので、このモデルを利用してムービーオブジェクトを保存することができる。リモートのソースからムービーデータを取ってきてアプリケーションに食わせることも可能だ。それにはGoogle App EngineURL Fetch APIを使ってHTTPまたはHTTPS URLからデータを取得する。urlfetch.Fetch()メソッドを使えばリモートにあるイメージのバイナリコンテンツをフェッチできる。単に引数として画像のURLを渡してやるだけだ。

それからフェッチされた結果のcontent属性を引数として渡してBlobオブジェクトを作り、Movieオブジェクトのpicture属性に設定する。最後にput()メソッドを呼び出して変更をデータストアに保存する。

画像の取得と表示

これでオブジェクトがMovieデータストアに保存されたので、データストアから動的に取り出してブラウザに画像として表示しなおすリクエストハンドラを作成しよう。

初めにGETリクエストを処理するGetImageクラスを作成する。リクエストハンドラは最初に'title'の値をURLパラメータから抽出する。そしてgetMovie()メソッドを呼んでタイトルが一致するMovieオブジェクトをデータストアから読み込む。

Movieオブジェクトの参照が手に入れば、その"picture"属性にもアクセスできる。ブラウザで画像を適切にレンダリングするためにHTTP Content-Typeヘッダーは'image/jpg'に設定しておく。

class GetImage(webapp.RequestHandler):
  def get(self):
    title = self.request.get('title')
    movie = getMovie(title)
    if (movie and movie.picture):
      self.response.headers['Content-Type'] = 'image/jpg'
      self.response.out.write(movie.picture)
    else:
      self.redirect('/static/noimage.jpg')

getMoive()メソッドはタイトルの一致するムービーオブジェクトを取得する簡単なGQLからなる。

def getMovie(title):
  result = db.GqlQuery("SELECT * FROM Movie WHERE title = :1 LIMIT 1", 
                       title).fetch(1)
  if (len(result) > 0):
    return result[0]
  else:
    return None

アプリケーションでムービーの画像に簡単にアクセスできるようにGetImageクラスを'/image'というURLパスにマッチさせる。今回はGETリクエストを処理するのにwebappモジュールを利用することにした。

apps_binding = []
...
apps_binding.append(('/image', GetImage))
application = webapp.WSGIApplication(apps_binding, debug=True)
wsgiref.handlers.CGIHandler().run(application)

これでビジターがhttp://mydomain.com/image?title=matrixというURLにアクセスすると"matrix"ムービーの画像がブラウザに返される。