~nchohan/appscale/zk3.3.4

« back to all changes in this revision

Viewing changes to AppServer/google/appengine/api/images/images_stub.py

  • Committer: Navraj Chohan
  • Date: 2009-03-28 01:14:04 UTC
  • Revision ID: nchohan@cs.ucsb.edu-20090328011404-42m1w6yt60m6yfg3
Initial import

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
#
 
3
# Copyright 2007 Google Inc.
 
4
#
 
5
# Licensed under the Apache License, Version 2.0 (the "License");
 
6
# you may not use this file except in compliance with the License.
 
7
# You may obtain a copy of the License at
 
8
#
 
9
#     http://www.apache.org/licenses/LICENSE-2.0
 
10
#
 
11
# Unless required by applicable law or agreed to in writing, software
 
12
# distributed under the License is distributed on an "AS IS" BASIS,
 
13
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
14
# See the License for the specific language governing permissions and
 
15
# limitations under the License.
 
16
#
 
17
 
 
18
"""Stub version of the images API."""
 
19
 
 
20
 
 
21
 
 
22
import logging
 
23
import StringIO
 
24
 
 
25
try:
 
26
  import PIL
 
27
  from PIL import _imaging
 
28
  from PIL import Image
 
29
except ImportError:
 
30
  import _imaging
 
31
  import Image
 
32
 
 
33
from google.appengine.api import apiproxy_stub
 
34
from google.appengine.api import images
 
35
from google.appengine.api.images import images_service_pb
 
36
from google.appengine.runtime import apiproxy_errors
 
37
 
 
38
 
 
39
class ImagesServiceStub(apiproxy_stub.APIProxyStub):
 
40
  """Stub version of images API to be used with the dev_appserver."""
 
41
 
 
42
  def __init__(self, service_name='images'):
 
43
    """Preloads PIL to load all modules in the unhardened environment.
 
44
 
 
45
    Args:
 
46
      service_name: Service name expected for all calls.
 
47
    """
 
48
    super(ImagesServiceStub, self).__init__(service_name)
 
49
    Image.init()
 
50
 
 
51
  def _Dynamic_Transform(self, request, response):
 
52
    """Trivial implementation of ImagesService::Transform.
 
53
 
 
54
    Based off documentation of the PIL library at
 
55
    http://www.pythonware.com/library/pil/handbook/index.htm
 
56
 
 
57
    Args:
 
58
      request: ImagesTransformRequest, contains image request info.
 
59
      response: ImagesTransformResponse, contains transformed image.
 
60
    """
 
61
    image = request.image().content()
 
62
    if not image:
 
63
      raise apiproxy_errors.ApplicationError(
 
64
          images_service_pb.ImagesServiceError.NOT_IMAGE)
 
65
 
 
66
    image = StringIO.StringIO(image)
 
67
    try:
 
68
      original_image = Image.open(image)
 
69
    except IOError:
 
70
      raise apiproxy_errors.ApplicationError(
 
71
          images_service_pb.ImagesServiceError.BAD_IMAGE_DATA)
 
72
 
 
73
    img_format = original_image.format
 
74
    if img_format not in ("BMP", "GIF", "ICO", "JPEG", "PNG", "TIFF"):
 
75
      raise apiproxy_errors.ApplicationError(
 
76
          images_service_pb.ImagesServiceError.NOT_IMAGE)
 
77
 
 
78
    new_image = self._ProcessTransforms(original_image,
 
79
                                        request.transform_list())
 
80
 
 
81
    response_value = self._EncodeImage(new_image, request.output())
 
82
    response.mutable_image().set_content(response_value)
 
83
 
 
84
  def _EncodeImage(self, image, output_encoding):
 
85
    """Encode the given image and return it in string form.
 
86
 
 
87
    Args:
 
88
      image: PIL Image object, image to encode.
 
89
      output_encoding: ImagesTransformRequest.OutputSettings object.
 
90
 
 
91
    Returns:
 
92
      str with encoded image information in given encoding format.
 
93
    """
 
94
    image_string = StringIO.StringIO()
 
95
 
 
96
    image_encoding = "PNG"
 
97
 
 
98
    if (output_encoding.mime_type() == images_service_pb.OutputSettings.JPEG):
 
99
      image_encoding = "JPEG"
 
100
 
 
101
      image = image.convert("RGB")
 
102
 
 
103
    image.save(image_string, image_encoding)
 
104
 
 
105
    return image_string.getvalue()
 
106
 
 
107
  def _ValidateCropArg(self, arg):
 
108
    """Check an argument for the Crop transform.
 
109
 
 
110
    Args:
 
111
      arg: float, argument to Crop transform to check.
 
112
 
 
113
    Raises:
 
114
      apiproxy_errors.ApplicationError on problem with argument.
 
115
    """
 
116
    if not isinstance(arg, float):
 
117
      raise apiproxy_errors.ApplicationError(
 
118
          images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA)
 
119
 
 
120
    if not (0 <= arg <= 1.0):
 
121
      raise apiproxy_errors.ApplicationError(
 
122
          images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA)
 
123
 
 
124
  def _CalculateNewDimensions(self,
 
125
                              current_width,
 
126
                              current_height,
 
127
                              req_width,
 
128
                              req_height):
 
129
    """Get new resize dimensions keeping the current aspect ratio.
 
130
 
 
131
    This uses the more restricting of the two requested values to determine
 
132
    the new ratio.
 
133
 
 
134
    Args:
 
135
      current_width: int, current width of the image.
 
136
      current_height: int, current height of the image.
 
137
      req_width: int, requested new width of the image.
 
138
      req_height: int, requested new height of the image.
 
139
 
 
140
    Returns:
 
141
      tuple (width, height) which are both ints of the new ratio.
 
142
    """
 
143
 
 
144
    width_ratio = float(req_width) / current_width
 
145
    height_ratio = float(req_height) / current_height
 
146
 
 
147
    if req_width == 0 or (width_ratio > height_ratio and req_height != 0):
 
148
      return int(height_ratio * current_width), req_height
 
149
    else:
 
150
      return req_width, int(width_ratio * current_height)
 
151
 
 
152
  def _Resize(self, image, transform):
 
153
    """Use PIL to resize the given image with the given transform.
 
154
 
 
155
    Args:
 
156
      image: PIL.Image.Image object to resize.
 
157
      transform: images_service_pb.Transform to use when resizing.
 
158
 
 
159
    Returns:
 
160
      PIL.Image.Image with transforms performed on it.
 
161
 
 
162
    Raises:
 
163
      BadRequestError if the resize data given is bad.
 
164
    """
 
165
    width = 0
 
166
    height = 0
 
167
 
 
168
    if transform.has_width():
 
169
      width = transform.width()
 
170
      if width < 0 or 4000 < width:
 
171
        raise apiproxy_errors.ApplicationError(
 
172
            images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA)
 
173
 
 
174
    if transform.has_height():
 
175
      height = transform.height()
 
176
      if height < 0 or 4000 < height:
 
177
        raise apiproxy_errors.ApplicationError(
 
178
            images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA)
 
179
 
 
180
    current_width, current_height = image.size
 
181
    new_width, new_height = self._CalculateNewDimensions(current_width,
 
182
                                                         current_height,
 
183
                                                         width,
 
184
                                                         height)
 
185
 
 
186
    return image.resize((new_width, new_height), Image.ANTIALIAS)
 
187
 
 
188
  def _Rotate(self, image, transform):
 
189
    """Use PIL to rotate the given image with the given transform.
 
190
 
 
191
    Args:
 
192
      image: PIL.Image.Image object to rotate.
 
193
      transform: images_service_pb.Transform to use when rotating.
 
194
 
 
195
    Returns:
 
196
      PIL.Image.Image with transforms performed on it.
 
197
 
 
198
    Raises:
 
199
      BadRequestError if the rotate data given is bad.
 
200
    """
 
201
    degrees = transform.rotate()
 
202
    if degrees < 0 or degrees % 90 != 0:
 
203
      raise apiproxy_errors.ApplicationError(
 
204
          images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA)
 
205
    degrees %= 360
 
206
 
 
207
    degrees = 360 - degrees
 
208
    return image.rotate(degrees)
 
209
 
 
210
  def _Crop(self, image, transform):
 
211
    """Use PIL to crop the given image with the given transform.
 
212
 
 
213
    Args:
 
214
      image: PIL.Image.Image object to crop.
 
215
      transform: images_service_pb.Transform to use when cropping.
 
216
 
 
217
    Returns:
 
218
      PIL.Image.Image with transforms performed on it.
 
219
 
 
220
    Raises:
 
221
      BadRequestError if the crop data given is bad.
 
222
    """
 
223
    left_x = 0.0
 
224
    top_y = 0.0
 
225
    right_x = 1.0
 
226
    bottom_y = 1.0
 
227
 
 
228
    if transform.has_crop_left_x():
 
229
      left_x = transform.crop_left_x()
 
230
      self._ValidateCropArg(left_x)
 
231
 
 
232
    if transform.has_crop_top_y():
 
233
      top_y = transform.crop_top_y()
 
234
      self._ValidateCropArg(top_y)
 
235
 
 
236
    if transform.has_crop_right_x():
 
237
      right_x = transform.crop_right_x()
 
238
      self._ValidateCropArg(right_x)
 
239
 
 
240
    if transform.has_crop_bottom_y():
 
241
      bottom_y = transform.crop_bottom_y()
 
242
      self._ValidateCropArg(bottom_y)
 
243
 
 
244
    width, height = image.size
 
245
 
 
246
    box = (int(transform.crop_left_x() * width),
 
247
           int(transform.crop_top_y() * height),
 
248
           int(transform.crop_right_x() * width),
 
249
           int(transform.crop_bottom_y() * height))
 
250
 
 
251
    return image.crop(box)
 
252
 
 
253
  def _ProcessTransforms(self, image, transforms):
 
254
    """Execute PIL operations based on transform values.
 
255
 
 
256
    Args:
 
257
      image: PIL.Image.Image instance, image to manipulate.
 
258
      trasnforms: list of ImagesTransformRequest.Transform objects.
 
259
 
 
260
    Returns:
 
261
      PIL.Image.Image with transforms performed on it.
 
262
 
 
263
    Raises:
 
264
      BadRequestError if we are passed more than one of the same type of
 
265
      transform.
 
266
    """
 
267
    new_image = image
 
268
    if len(transforms) > images.MAX_TRANSFORMS_PER_REQUEST:
 
269
      raise apiproxy_errors.ApplicationError(
 
270
          images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA)
 
271
    for transform in transforms:
 
272
      if transform.has_width() or transform.has_height():
 
273
        new_image = self._Resize(new_image, transform)
 
274
 
 
275
      elif transform.has_rotate():
 
276
        new_image = self._Rotate(new_image, transform)
 
277
 
 
278
      elif transform.has_horizontal_flip():
 
279
        new_image = new_image.transpose(Image.FLIP_LEFT_RIGHT)
 
280
 
 
281
      elif transform.has_vertical_flip():
 
282
        new_image = new_image.transpose(Image.FLIP_TOP_BOTTOM)
 
283
 
 
284
      elif (transform.has_crop_left_x() or
 
285
          transform.has_crop_top_y() or
 
286
          transform.has_crop_right_x() or
 
287
          transform.has_crop_bottom_y()):
 
288
        new_image = self._Crop(new_image, transform)
 
289
 
 
290
      elif transform.has_autolevels():
 
291
        logging.info("I'm Feeling Lucky autolevels will be visible once this "
 
292
                     "application is deployed.")
 
293
      else:
 
294
        logging.warn("Found no transformations found to perform.")
 
295
 
 
296
    return new_image