App Engine Python SDK version 1.7.7
[gae.git] / python / google / appengine / api / images / __init__.py
blob9320643be897079de5fe4eb3ef1169b68bf91ab9
1 #!/usr/bin/env python
3 # Copyright 2007 Google Inc.
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
21 """Image manipulation API.
23 Classes defined in this module:
24 Image: class used to encapsulate image information and transformations for
25 that image.
27 The current manipulations that are available are resize, rotate,
28 horizontal_flip, vertical_flip, crop and im_feeling_lucky.
30 It should be noted that each transform can only be called once per image
31 per execute_transforms() call.
32 """
40 import struct
42 try:
43 import json
44 except:
45 import simplejson as json
47 from google.appengine.api import apiproxy_stub_map
48 from google.appengine.api import blobstore
49 from google.appengine.api import datastore_types
50 from google.appengine.api.images import images_service_pb
51 from google.appengine.runtime import apiproxy_errors
54 BlobKey = datastore_types.BlobKey
57 JPEG = images_service_pb.OutputSettings.JPEG
58 PNG = images_service_pb.OutputSettings.PNG
59 WEBP = images_service_pb.OutputSettings.WEBP
60 BMP = -1
61 GIF = -2
62 ICO = -3
63 TIFF = -4
65 OUTPUT_ENCODING_TYPES = frozenset([JPEG, PNG, WEBP])
67 UNCHANGED_ORIENTATION = images_service_pb.InputSettings.UNCHANGED_ORIENTATION
68 CORRECT_ORIENTATION = images_service_pb.InputSettings.CORRECT_ORIENTATION
70 ORIENTATION_CORRECTION_TYPE = frozenset([UNCHANGED_ORIENTATION,
71 CORRECT_ORIENTATION])
73 TOP_LEFT = images_service_pb.CompositeImageOptions.TOP_LEFT
74 TOP_CENTER = images_service_pb.CompositeImageOptions.TOP
75 TOP_RIGHT = images_service_pb.CompositeImageOptions.TOP_RIGHT
76 CENTER_LEFT = images_service_pb.CompositeImageOptions.LEFT
77 CENTER_CENTER = images_service_pb.CompositeImageOptions.CENTER
78 CENTER_RIGHT = images_service_pb.CompositeImageOptions.RIGHT
79 BOTTOM_LEFT = images_service_pb.CompositeImageOptions.BOTTOM_LEFT
80 BOTTOM_CENTER = images_service_pb.CompositeImageOptions.BOTTOM
81 BOTTOM_RIGHT = images_service_pb.CompositeImageOptions.BOTTOM_RIGHT
83 ANCHOR_TYPES = frozenset([TOP_LEFT, TOP_CENTER, TOP_RIGHT, CENTER_LEFT,
84 CENTER_CENTER, CENTER_RIGHT, BOTTOM_LEFT,
85 BOTTOM_CENTER, BOTTOM_RIGHT])
89 MAX_TRANSFORMS_PER_REQUEST = 10
94 MAX_COMPOSITES_PER_REQUEST = 16
97 class Error(Exception):
98 """Base error class for this module."""
101 class TransformationError(Error):
102 """Error while attempting to transform the image."""
105 class BadRequestError(Error):
106 """The parameters given had something wrong with them."""
109 class NotImageError(Error):
110 """The image data given is not recognizable as an image."""
113 class BadImageError(Error):
114 """The image data given is corrupt."""
117 class LargeImageError(Error):
118 """The image data given is too large to process."""
121 class InvalidBlobKeyError(Error):
122 """The provided blob key was invalid."""
124 def __init__(self, blob_key=None):
125 """Constructor.
127 Args:
128 blob_key: The blob_key that is believed to be invalid. May be None if the
129 BlobKey is unknown.
131 self._blob_key = blob_key
133 def __str__(self):
134 """Returns a string representation of this Error."""
135 if self._blob_key:
136 return 'InvalidBlobKeyError: %s' % repr(self._blob_key)
137 else:
138 return 'InvalidBlobKeyError'
141 class BlobKeyRequiredError(Error):
142 """A blobkey is required for this operation."""
145 class UnsupportedSizeError(Error):
146 """Specified size is not supported by requested operation."""
149 class AccessDeniedError(Error):
150 """The application does not have permission to access the image."""
153 class ObjectNotFoundError(Error):
154 """The object referred to by a BlobKey does not exist."""
157 def _ToImagesError(error, blob_key=None):
158 """Translate an application error to an Images error, if possible.
160 Args:
161 error: an ApplicationError to translate.
162 blob_key: The blob_key that used in the function that caused the error.
163 May be None if the BlobKey is unknown.
165 Returns:
166 The Images error if found, otherwise the original error.
168 error_map = {
169 images_service_pb.ImagesServiceError.NOT_IMAGE:
170 NotImageError,
171 images_service_pb.ImagesServiceError.BAD_IMAGE_DATA:
172 BadImageError,
173 images_service_pb.ImagesServiceError.IMAGE_TOO_LARGE:
174 LargeImageError,
175 images_service_pb.ImagesServiceError.INVALID_BLOB_KEY:
176 InvalidBlobKeyError,
177 images_service_pb.ImagesServiceError.ACCESS_DENIED:
178 AccessDeniedError,
179 images_service_pb.ImagesServiceError.OBJECT_NOT_FOUND:
180 ObjectNotFoundError,
181 images_service_pb.ImagesServiceError.UNSPECIFIED_ERROR:
182 TransformationError,
183 images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA:
184 BadRequestError,
187 error_code = error.application_error
189 if error_code == images_service_pb.ImagesServiceError.INVALID_BLOB_KEY:
190 return InvalidBlobKeyError(blob_key)
192 desired_exc = error_map.get(error_code, Error)
193 return desired_exc(error.error_detail)
196 class Image(object):
197 """Image object to manipulate."""
199 def __init__(self, image_data=None, blob_key=None, filename=None):
200 """Constructor.
202 Only one of image_data, blob_key or filename can be specified.
204 Args:
205 image_data: str, image data in string form.
206 blob_key: BlobKey, BlobInfo, str, or unicode representation of BlobKey of
207 blob containing the image data.
208 filename: str, the filename of a Google Storage file containing the
209 image data. Must be in the format '/gs/bucket_name/object_name'.
211 Raises:
212 NotImageError if the given data is empty.
215 if not image_data and not blob_key and not filename:
216 raise NotImageError("Empty image data.")
217 if image_data and (blob_key or filename):
218 raise NotImageError("Can only take one of image, blob key or filename.")
219 if blob_key and filename:
220 raise NotImageError("Can only take one of image, blob key or filename.")
222 self._image_data = image_data
223 if filename:
224 self._blob_key = blobstore.create_gs_key(filename)
225 else:
226 self._blob_key = _extract_blob_key(blob_key)
227 self._transforms = []
228 self._width = None
229 self._height = None
230 self._format = None
231 self._correct_orientation = UNCHANGED_ORIENTATION
232 self._original_metadata = None
234 def _check_transform_limits(self):
235 """Ensure some simple limits on the number of transforms allowed.
237 Raises:
238 BadRequestError if MAX_TRANSFORMS_PER_REQUEST transforms have already been
239 requested for this image
241 if len(self._transforms) >= MAX_TRANSFORMS_PER_REQUEST:
242 raise BadRequestError("%d transforms have already been requested on this "
243 "image." % MAX_TRANSFORMS_PER_REQUEST)
245 def _update_dimensions(self):
246 """Updates the width and height fields of the image.
248 Raises:
249 NotImageError if the image data is not an image.
250 BadImageError if the image data is corrupt.
252 if not self._image_data:
253 raise NotImageError("Dimensions unavailable for blob key input")
254 size = len(self._image_data)
255 if size >= 6 and self._image_data.startswith("GIF"):
256 self._update_gif_dimensions()
257 self._format = GIF;
258 elif size >= 8 and self._image_data.startswith("\x89PNG\x0D\x0A\x1A\x0A"):
259 self._update_png_dimensions()
260 self._format = PNG
261 elif size >= 2 and self._image_data.startswith("\xff\xD8"):
262 self._update_jpeg_dimensions()
263 self._format = JPEG
264 elif (size >= 8 and (self._image_data.startswith("II\x2a\x00") or
265 self._image_data.startswith("MM\x00\x2a"))):
266 self._update_tiff_dimensions()
267 self._format = TIFF
268 elif size >= 2 and self._image_data.startswith("BM"):
269 self._update_bmp_dimensions()
270 self._format = BMP
271 elif size >= 4 and self._image_data.startswith("\x00\x00\x01\x00"):
272 self._update_ico_dimensions()
273 self._format = ICO
274 elif (size >= 16 and (self._image_data.startswith("RIFF", 0, 4) and
275 self._image_data.startswith("WEBP", 8, 12) and
276 self._image_data.startswith("VP8 ", 12, 16))):
277 self._update_webp_dimensions()
278 self._format = WEBP
280 elif (size >= 16 and (self._image_data.startswith("RIFF", 0, 4) and
281 self._image_data.startswith("WEBP", 8, 12) and
282 self._image_data.startswith("VP8X", 12, 16))):
283 self._update_webp_vp8x_dimensions()
284 self._format = WEBP
285 else:
286 raise NotImageError("Unrecognized image format")
288 def _update_gif_dimensions(self):
289 """Updates the width and height fields of the gif image.
291 Raises:
292 BadImageError if the image string is not a valid gif image.
295 size = len(self._image_data)
296 if size >= 10:
297 self._width, self._height = struct.unpack("<HH", self._image_data[6:10])
298 else:
299 raise BadImageError("Corrupt GIF format")
301 def _update_png_dimensions(self):
302 """Updates the width and height fields of the png image.
304 Raises:
305 BadImageError if the image string is not a valid png image.
309 size = len(self._image_data)
310 if size >= 24 and self._image_data[12:16] == "IHDR":
311 self._width, self._height = struct.unpack(">II", self._image_data[16:24])
312 else:
313 raise BadImageError("Corrupt PNG format")
315 def _update_jpeg_dimensions(self):
316 """Updates the width and height fields of the jpeg image.
318 Raises:
319 BadImageError if the image string is not a valid jpeg image.
322 size = len(self._image_data)
323 offset = 2
324 while offset < size:
325 while offset < size and ord(self._image_data[offset]) != 0xFF:
326 offset += 1
327 while offset < size and ord(self._image_data[offset]) == 0xFF:
328 offset += 1
329 if (offset < size and ord(self._image_data[offset]) & 0xF0 == 0xC0 and
330 ord(self._image_data[offset]) != 0xC4):
331 offset += 4
332 if offset + 4 <= size:
333 self._height, self._width = struct.unpack(
334 ">HH",
335 self._image_data[offset:offset + 4])
336 break
337 else:
338 raise BadImageError("Corrupt JPEG format")
339 elif offset + 3 <= size:
340 offset += 1
341 offset += struct.unpack(">H", self._image_data[offset:offset + 2])[0]
342 else:
343 raise BadImageError("Corrupt JPEG format")
344 if self._height is None or self._width is None:
345 raise BadImageError("Corrupt JPEG format")
347 def _update_tiff_dimensions(self):
348 """Updates the width and height fields of the tiff image.
350 Raises:
351 BadImageError if the image string is not a valid tiff image.
355 size = len(self._image_data)
356 if self._image_data.startswith("II"):
357 endianness = "<"
358 else:
359 endianness = ">"
360 ifd_offset = struct.unpack(endianness + "I", self._image_data[4:8])[0]
361 if ifd_offset + 14 <= size:
362 ifd_size = struct.unpack(
363 endianness + "H",
364 self._image_data[ifd_offset:ifd_offset + 2])[0]
365 ifd_offset += 2
366 for unused_i in range(0, ifd_size):
367 if ifd_offset + 12 <= size:
368 tag = struct.unpack(
369 endianness + "H",
370 self._image_data[ifd_offset:ifd_offset + 2])[0]
371 if tag == 0x100 or tag == 0x101:
372 value_type = struct.unpack(
373 endianness + "H",
374 self._image_data[ifd_offset + 2:ifd_offset + 4])[0]
375 if value_type == 3:
376 format = endianness + "H"
377 end_offset = ifd_offset + 10
378 elif value_type == 4:
379 format = endianness + "I"
380 end_offset = ifd_offset + 12
381 else:
382 format = endianness + "B"
383 end_offset = ifd_offset + 9
384 if tag == 0x100:
385 self._width = struct.unpack(
386 format,
387 self._image_data[ifd_offset + 8:end_offset])[0]
388 if self._height is not None:
389 break
390 else:
391 self._height = struct.unpack(
392 format,
393 self._image_data[ifd_offset + 8:end_offset])[0]
394 if self._width is not None:
395 break
396 ifd_offset += 12
397 else:
398 raise BadImageError("Corrupt TIFF format")
399 if self._width is None or self._height is None:
400 raise BadImageError("Corrupt TIFF format")
402 def _update_bmp_dimensions(self):
403 """Updates the width and height fields of the bmp image.
405 Raises:
406 BadImageError if the image string is not a valid bmp image.
412 size = len(self._image_data)
413 if size >= 18:
414 header_length = struct.unpack("<I", self._image_data[14:18])[0]
415 if ((header_length == 40 or header_length == 108 or
416 header_length == 124 or header_length == 64) and size >= 26):
418 self._width, self._height = struct.unpack("<II",
419 self._image_data[18:26])
420 elif header_length == 12 and size >= 22:
421 self._width, self._height = struct.unpack("<HH",
422 self._image_data[18:22])
423 else:
424 raise BadImageError("Corrupt BMP format")
425 else:
426 raise BadImageError("Corrupt BMP format")
428 def _update_ico_dimensions(self):
429 """Updates the width and height fields of the ico image.
431 Raises:
432 BadImageError if the image string is not a valid ico image.
434 size = len(self._image_data)
435 if size >= 8:
436 self._width, self._height = struct.unpack("<BB", self._image_data[6:8])
438 if not self._width:
439 self._width = 256
440 if not self._height:
441 self._height = 256
442 else:
443 raise BadImageError("Corrupt ICO format")
445 def set_correct_orientation(self, correct_orientation):
446 """Set flag to correct image orientation based on image metadata.
448 EXIF metadata within the image may contain a parameter indicating its proper
449 orientation. This value can equal 1 through 8, inclusive. "1" means that the
450 image is in its "normal" orientation, i.e., it should be viewed as it is
451 stored. Normally, this "orientation" value has no effect on the behavior of
452 the transformations. However, calling this function with the value
453 CORRECT_ORIENTATION any orientation specified in the EXIF metadata will be
454 corrected during the first transformation.
456 NOTE: If CORRECT_ORIENTATION is specified but the image is already in
457 portrait orientation, i.e., "taller" than it is "wide" no corrections will
458 be made, since it appears that the camera has already corrected it.
460 Regardless whether the correction was requested or not, the orientation
461 value in the transformed image is always cleared to indicate that no
462 additional corrections of the returned image's orientation is necessary.
464 Args:
465 correct_orientation: a value from ORIENTATION_CORRECTION_TYPE.
467 Raises:
468 BadRequestError if correct_orientation value is invalid.
470 if correct_orientation not in ORIENTATION_CORRECTION_TYPE:
471 raise BadRequestError("Orientation correction must be in %s" %
472 ORIENTATION_CORRECTION_TYPE)
473 self._correct_orientation = correct_orientation
476 def _update_webp_dimensions(self):
477 """Updates the width and height fields of the webp image."""
479 size = len(self._image_data)
483 if size < 30:
484 raise BadImageError("Corrupt WEBP format")
486 bits = (ord(self._image_data[20]) | (ord(self._image_data[21])<<8) |
487 (ord(self._image_data[22]) << 16))
489 key_frame = ((bits & 1) == 0)
491 if not key_frame:
492 raise BadImageError("Corrupt WEBP format")
494 profile = (bits >> 1) & 7
495 show_frame = (bits >> 4) & 1
497 if profile > 3:
498 raise BadImageError("Corrupt WEBP format")
500 if show_frame == 0:
501 raise BadImageError("Corrupt WEBP format")
503 self._width, self._height = struct.unpack("<HH", self._image_data[26:30])
505 if self._height is None or self._width is None:
506 raise BadImageError("Corrupt WEBP format")
508 def _update_webp_vp8x_dimensions(self):
509 """Updates the width and height fields of a webp image with vp8x chunk."""
510 size = len(self._image_data)
512 if size < 30:
513 raise BadImageError("Corrupt WEBP format")
515 self._width, self._height = struct.unpack("<II", self._image_data[24:32])
517 if self._height is None or self._width is None:
518 raise BadImageError("Corrupt WEBP format")
520 def resize(self, width=0, height=0, crop_to_fit=False,
521 crop_offset_x=0.5, crop_offset_y=0.5, allow_stretch=False):
522 """Resize the image maintaining the aspect ratio.
524 If both width and height are specified, the more restricting of the two
525 values will be used when resizing the image. The maximum dimension allowed
526 for both width and height is 4000 pixels.
527 If both width and height are specified and crop_to_fit is True, the less
528 restricting of the two values will be used when resizing and the image will
529 be cropped to fit the specified size. In this case the center of cropping
530 can be adjusted by crop_offset_x and crop_offset_y.
532 Args:
533 width: int, width (in pixels) to change the image width to.
534 height: int, height (in pixels) to change the image height to.
535 crop_to_fit: If True and both width and height are specified, the image is
536 cropped after resize to fit the specified dimensions.
537 crop_offset_x: float value between 0.0 and 1.0, 0 is left and 1 is right,
538 default is 0.5, the center of image.
539 crop_offset_y: float value between 0.0 and 1.0, 0 is top and 1 is bottom,
540 default is 0.5, the center of image.
541 allow_stretch: If True and both width and height are specified, the image
542 is stretched to fit the resize dimensions without maintaining the
543 aspect ratio.
545 Raises:
546 TypeError when width or height is not either 'int' or 'long' types.
547 BadRequestError when there is something wrong with the given height or
548 width or if MAX_TRANSFORMS_PER_REQUEST transforms have already been
549 requested on this image.
551 if (not isinstance(width, (int, long)) or
552 not isinstance(height, (int, long))):
553 raise TypeError("Width and height must be integers.")
554 if width < 0 or height < 0:
555 raise BadRequestError("Width and height must be >= 0.")
557 if not width and not height:
558 raise BadRequestError("At least one of width or height must be > 0.")
560 if width > 4000 or height > 4000:
561 raise BadRequestError("Both width and height must be <= 4000.")
563 if not isinstance(crop_to_fit, bool):
564 raise TypeError("crop_to_fit must be boolean.")
566 if crop_to_fit and not (width and height):
567 raise BadRequestError("Both width and height must be > 0 when "
568 "crop_to_fit is specified.")
570 if not isinstance(allow_stretch, bool):
571 raise TypeError("allow_stretch must be boolean.")
573 if allow_stretch and not (width and height):
574 raise BadRequestError("Both width and height must be > 0 when "
575 "allow_stretch is specified.")
577 self._validate_crop_arg(crop_offset_x, "crop_offset_x")
578 self._validate_crop_arg(crop_offset_y, "crop_offset_y")
580 self._check_transform_limits()
582 transform = images_service_pb.Transform()
583 transform.set_width(width)
584 transform.set_height(height)
585 transform.set_crop_to_fit(crop_to_fit)
586 transform.set_crop_offset_x(crop_offset_x)
587 transform.set_crop_offset_y(crop_offset_y)
588 transform.set_allow_stretch(allow_stretch)
590 self._transforms.append(transform)
592 def rotate(self, degrees):
593 """Rotate an image a given number of degrees clockwise.
595 Args:
596 degrees: int, must be a multiple of 90.
598 Raises:
599 TypeError when degrees is not either 'int' or 'long' types.
600 BadRequestError when there is something wrong with the given degrees or
601 if MAX_TRANSFORMS_PER_REQUEST transforms have already been requested.
603 if not isinstance(degrees, (int, long)):
604 raise TypeError("Degrees must be integers.")
606 if degrees % 90 != 0:
607 raise BadRequestError("degrees argument must be multiple of 90.")
610 degrees = degrees % 360
615 self._check_transform_limits()
617 transform = images_service_pb.Transform()
618 transform.set_rotate(degrees)
620 self._transforms.append(transform)
622 def horizontal_flip(self):
623 """Flip the image horizontally.
625 Raises:
626 BadRequestError if MAX_TRANSFORMS_PER_REQUEST transforms have already been
627 requested on the image.
629 self._check_transform_limits()
631 transform = images_service_pb.Transform()
632 transform.set_horizontal_flip(True)
634 self._transforms.append(transform)
636 def vertical_flip(self):
637 """Flip the image vertically.
639 Raises:
640 BadRequestError if MAX_TRANSFORMS_PER_REQUEST transforms have already been
641 requested on the image.
643 self._check_transform_limits()
644 transform = images_service_pb.Transform()
645 transform.set_vertical_flip(True)
647 self._transforms.append(transform)
649 def _validate_crop_arg(self, val, val_name):
650 """Validate the given value of a Crop() method argument.
652 Args:
653 val: float, value of the argument.
654 val_name: str, name of the argument.
656 Raises:
657 TypeError if the args are not of type 'float'.
658 BadRequestError when there is something wrong with the given bounding box.
660 if type(val) != float:
661 raise TypeError("arg '%s' must be of type 'float'." % val_name)
663 if not (0 <= val <= 1.0):
664 raise BadRequestError("arg '%s' must be between 0.0 and 1.0 "
665 "(inclusive)" % val_name)
667 def crop(self, left_x, top_y, right_x, bottom_y):
668 """Crop the image.
670 The four arguments are the scaling numbers to describe the bounding box
671 which will crop the image. The upper left point of the bounding box will
672 be at (left_x*image_width, top_y*image_height) the lower right point will
673 be at (right_x*image_width, bottom_y*image_height).
675 Args:
676 left_x: float value between 0.0 and 1.0 (inclusive).
677 top_y: float value between 0.0 and 1.0 (inclusive).
678 right_x: float value between 0.0 and 1.0 (inclusive).
679 bottom_y: float value between 0.0 and 1.0 (inclusive).
681 Raises:
682 TypeError if the args are not of type 'float'.
683 BadRequestError when there is something wrong with the given bounding box
684 or if MAX_TRANSFORMS_PER_REQUEST transforms have already been requested
685 for this image.
687 self._validate_crop_arg(left_x, "left_x")
688 self._validate_crop_arg(top_y, "top_y")
689 self._validate_crop_arg(right_x, "right_x")
690 self._validate_crop_arg(bottom_y, "bottom_y")
692 if left_x >= right_x:
693 raise BadRequestError("left_x must be less than right_x")
694 if top_y >= bottom_y:
695 raise BadRequestError("top_y must be less than bottom_y")
697 self._check_transform_limits()
699 transform = images_service_pb.Transform()
700 transform.set_crop_left_x(left_x)
701 transform.set_crop_top_y(top_y)
702 transform.set_crop_right_x(right_x)
703 transform.set_crop_bottom_y(bottom_y)
705 self._transforms.append(transform)
707 def im_feeling_lucky(self):
708 """Automatically adjust image contrast and color levels.
710 This is similar to the "I'm Feeling Lucky" button in Picasa.
712 Raises:
713 BadRequestError if MAX_TRANSFORMS_PER_REQUEST transforms have already
714 been requested for this image.
716 self._check_transform_limits()
717 transform = images_service_pb.Transform()
718 transform.set_autolevels(True)
720 self._transforms.append(transform)
722 def get_original_metadata(self):
723 """Metadata of the original image.
725 Returns a dictionary of metadata extracted from the original image during
726 execute_transform.
727 Note, that some of the EXIF fields are processed, e.g., fields with multiple
728 values returned as lists, rational types are returned as floats, GPS
729 coordinates already parsed to signed floats, etc.
730 ImageWidth and ImageLength fields are corrected if they did not correspond
731 to the actual dimensions of the original image.
733 Returns:
734 dict with string keys. If execute_transform was called with parse_metadata
735 being True, this dictionary contains information about various properties
736 of the original image, such as dimensions, color profile, and properties
737 from EXIF.
738 Even if parse_metadata was False or the images did not have any metadata,
739 the dictionary will contain a limited set of metadata, at least
740 'ImageWidth' and 'ImageLength', corresponding to the dimensions of the
741 original image.
742 It will return None, if it is called before a successful
743 execute_transfrom.
745 return self._original_metadata
747 def _set_imagedata(self, imagedata):
748 """Fills in an ImageData PB from this Image instance.
750 Args:
751 imagedata: An ImageData PB instance
753 if self._blob_key:
754 imagedata.set_content("")
755 imagedata.set_blob_key(self._blob_key)
756 else:
757 imagedata.set_content(self._image_data)
759 def execute_transforms(self, output_encoding=PNG, quality=None,
760 parse_source_metadata=False,
761 transparent_substitution_rgb=None,
762 rpc=None):
763 """Perform transformations on a given image.
765 Args:
766 output_encoding: A value from OUTPUT_ENCODING_TYPES.
767 quality: A value between 1 and 100 to specify the quality of the
768 encoding. This value is only used for JPEG & WEBP quality control.
769 parse_source_metadata: when True the metadata (EXIF) of the source image
770 is parsed before any transformations. The results can be retrieved
771 via Image.get_original_metadata.
772 transparent_substition_rgb: When transparent pixels are not support in the
773 destination image format then transparent pixels will be substituted
774 for the specified color, which must be 32 bit rgb format.
775 rpc: A UserRPC object.
777 Returns:
778 str, image data after the transformations have been performed on it.
780 Raises:
781 BadRequestError when there is something wrong with the request
782 specifications.
783 NotImageError when the image data given is not an image.
784 BadImageError when the image data given is corrupt.
785 LargeImageError when the image data given is too large to process.
786 InvalidBlobKeyError when the blob key provided is invalid.
787 TransformtionError when something errors during image manipulation.
788 AccessDeniedError: when the blobkey refers to a Google Storage object, and
789 the application does not have permission to access the object.
790 ObjectNotFoundError:: when the blobkey refers to an object that no longer
791 exists.
792 Error when something unknown, but bad, happens.
794 rpc = self.execute_transforms_async(output_encoding=output_encoding,
795 quality=quality,
796 parse_source_metadata=parse_source_metadata,
797 transparent_substitution_rgb=transparent_substitution_rgb,
798 rpc=rpc)
799 return rpc.get_result()
801 def execute_transforms_async(self, output_encoding=PNG, quality=None,
802 parse_source_metadata=False,
803 transparent_substitution_rgb=None,
804 rpc=None):
805 """Perform transformations on a given image - async version.
807 Args:
808 output_encoding: A value from OUTPUT_ENCODING_TYPES.
809 quality: A value between 1 and 100 to specify the quality of the
810 encoding. This value is only used for JPEG & WEBP quality control.
811 parse_source_metadata: when True the metadata (EXIF) of the source image
812 is parsed before any transformations. The results can be retrieved
813 via Image.get_original_metadata.
814 transparent_substition_rgb: When transparent pixels are not support in the
815 destination image format then transparent pixels will be substituted
816 for the specified color, which must be 32 bit rgb format.
817 rpc: A UserRPC object.
819 Returns:
820 A UserRPC object.
822 Raises:
823 BadRequestError when there is something wrong with the request
824 specifications.
825 NotImageError when the image data given is not an image.
826 BadImageError when the image data given is corrupt.
827 LargeImageError when the image data given is too large to process.
828 InvalidBlobKeyError when the blob key provided is invalid.
829 TransformtionError when something errors during image manipulation.
830 AccessDeniedError: when the blobkey refers to a Google Storage object, and
831 the application does not have permission to access the object.
832 ValueError: when transparent_substitution_rgb is not an integer
833 Error when something unknown, but bad, happens.
835 if output_encoding not in OUTPUT_ENCODING_TYPES:
836 raise BadRequestError("Output encoding type not in recognized set "
837 "%s" % OUTPUT_ENCODING_TYPES)
839 if not self._transforms:
840 raise BadRequestError("Must specify at least one transformation.")
842 if transparent_substitution_rgb:
843 if not isinstance(transparent_substitution_rgb, int):
844 raise ValueError(
845 "transparent_substitution_rgb must be a 32 bit integer")
847 self.CheckValidIntParameter(quality, 1, 100, "quality")
849 request = images_service_pb.ImagesTransformRequest()
850 response = images_service_pb.ImagesTransformResponse()
852 input_settings = request.mutable_input()
853 input_settings.set_correct_exif_orientation(
854 self._correct_orientation)
856 if parse_source_metadata:
857 input_settings.set_parse_metadata(True)
859 self._set_imagedata(request.mutable_image())
861 for transform in self._transforms:
862 request.add_transform().CopyFrom(transform)
864 request.mutable_output().set_mime_type(output_encoding)
866 if ((output_encoding == JPEG or output_encoding == WEBP) and
867 (quality is not None)):
868 request.mutable_output().set_quality(quality)
870 if transparent_substitution_rgb:
871 input_settings.set_transparent_substitution_rgb(
872 transparent_substitution_rgb)
874 def execute_transforms_hook(rpc):
875 """Check success, handles exceptions and returns the converted RPC result.
877 Args:
878 rpc: A UserRPC object.
880 Raises:
881 See docstring for execute_transforms_async for more details.
883 try:
884 rpc.check_success()
885 except apiproxy_errors.ApplicationError, e:
886 raise _ToImagesError(e, self._blob_key)
887 self._image_data = rpc.response.image().content()
888 self._blob_key = None
889 self._transforms = []
890 if response.image().has_width():
891 self._width = rpc.response.image().width()
892 else:
893 self._width = None
894 if response.image().has_height():
895 self._height = rpc.response.image().height()
896 else:
897 self._height = None
898 self._format = None
899 if response.source_metadata():
900 self._original_metadata = json.loads(response.source_metadata())
901 return self._image_data
903 return _make_async_call(rpc,
904 "Transform",
905 request,
906 response,
907 execute_transforms_hook,
908 None)
910 @property
911 def width(self):
912 """Gets the width of the image."""
913 if self._width is None:
914 self._update_dimensions()
915 return self._width
917 @property
918 def height(self):
919 """Gets the height of the image."""
920 if self._height is None:
921 self._update_dimensions()
922 return self._height
924 @property
925 def format(self):
926 """Gets the format of the image."""
927 if self._format is None:
928 self._update_dimensions()
929 return self._format
931 def histogram(self, rpc=None):
932 """Calculates the histogram of the image.
934 Args:
935 rpc: A UserRPC object.
937 Returns: 3 256-element lists containing the number of occurences of each
938 value of each color in the order RGB. As described at
939 http://en.wikipedia.org/wiki/Color_histogram for N = 256. i.e. the first
940 value of the first list contains the number of pixels with a red value of
941 0, the second the number with a red value of 1.
943 Raises:
944 NotImageError when the image data given is not an image.
945 BadImageError when the image data given is corrupt.
946 LargeImageError when the image data given is too large to process.
947 Error when something unknown, but bad, happens.
950 rpc = self.histogram_async(rpc)
951 return rpc.get_result()
953 def histogram_async(self, rpc=None):
954 """Calculates the histogram of the image - async version.
956 Args:
957 rpc: An optional UserRPC object.
959 Returns:
960 rpc: A UserRPC object.
962 Raises:
963 NotImageError when the image data given is not an image.
964 BadImageError when the image data given is corrupt.
965 LargeImageError when the image data given is too large to process.
966 Error when something unknown, but bad, happens.
968 request = images_service_pb.ImagesHistogramRequest()
969 response = images_service_pb.ImagesHistogramResponse()
971 self._set_imagedata(request.mutable_image())
973 def get_histogram_hook(rpc):
974 """Check success, handles exceptions and returns the converted RPC result.
976 Args:
977 rpc: A UserRPC object.
979 Raises:
980 See docstring for histogram_async for more details.
982 try:
983 rpc.check_success()
984 except apiproxy_errors.ApplicationError, e:
985 raise _ToImagesError(e, self._blob_key)
987 histogram = rpc.response.histogram()
988 return [histogram.red_list(),
989 histogram.green_list(),
990 histogram.blue_list()]
992 return _make_async_call(rpc,
993 "Histogram",
994 request,
995 response,
996 get_histogram_hook,
997 None)
999 @staticmethod
1000 def CheckValidIntParameter(parameter, min_value, max_value, name):
1001 """Checks that a parameters is an integer within the specified range."""
1003 if parameter is not None:
1004 if not isinstance(parameter, (int, long)):
1005 raise TypeError("%s must be an integer." % name)
1006 if parameter > max_value or parameter < min_value:
1007 raise BadRequestError("%s must be between %s and %s."
1008 % name, str(min_value), str(max_value))
1017 def create_rpc(deadline=None, callback=None):
1018 """Creates an RPC object for use with the images API.
1020 Args:
1021 deadline: Optional deadline in seconds for the operation; the default
1022 is a system-specific deadline (typically 5 seconds).
1023 callback: Optional callable to invoke on completion.
1025 Returns:
1026 An apiproxy_stub_map.UserRPC object specialized for this service.
1028 return apiproxy_stub_map.UserRPC("images", deadline, callback)
1031 def _make_async_call(rpc, method, request, response,
1032 get_result_hook, user_data):
1033 if rpc is None:
1034 rpc = create_rpc()
1035 rpc.make_call(method, request, response, get_result_hook, user_data)
1036 return rpc
1039 def resize(image_data, width=0, height=0, output_encoding=PNG, quality=None,
1040 correct_orientation=UNCHANGED_ORIENTATION,
1041 crop_to_fit=False, crop_offset_x=0.5, crop_offset_y=0.5,
1042 allow_stretch=False, rpc=None, transparent_substitution_rgb=None):
1043 """Resize a given image file maintaining the aspect ratio.
1045 If both width and height are specified, the more restricting of the two
1046 values will be used when resizing the image. The maximum dimension allowed
1047 for both width and height is 4000 pixels.
1048 If both width and height are specified and crop_to_fit is True, the less
1049 restricting of the two values will be used when resizing and the image will be
1050 cropped to fit the specified size. In this case the center of cropping can be
1051 adjusted by crop_offset_x and crop_offset_y.
1053 Args:
1054 image_data: str, source image data.
1055 width: int, width (in pixels) to change the image width to.
1056 height: int, height (in pixels) to change the image height to.
1057 output_encoding: a value from OUTPUT_ENCODING_TYPES.
1058 quality: A value between 1 and 100 to specify the quality of the
1059 encoding. This value is only used for JPEG quality control.
1060 correct_orientation: one of ORIENTATION_CORRECTION_TYPE, to indicate if
1061 orientation correction should be performed during the transformation.
1062 crop_to_fit: If True and both width and height are specified, the image is
1063 cropped after resize to fit the specified dimensions.
1064 crop_offset_x: float value between 0.0 and 1.0, 0 is left and 1 is right,
1065 default is 0.5, the center of image.
1066 crop_offset_y: float value between 0.0 and 1.0, 0 is top and 1 is bottom,
1067 default is 0.5, the center of image.
1068 allow_stretch: If True and both width and height are specified, the image
1069 is stretched to fit the resize dimensions without maintaining the
1070 aspect ratio.
1071 rpc: Optional UserRPC object.
1072 transparent_substition_rgb: When transparent pixels are not support in the
1073 destination image format then transparent pixels will be substituted
1074 for the specified color, which must be 32 bit rgb format.
1076 Raises:
1077 TypeError when width or height not either 'int' or 'long' types.
1078 BadRequestError when there is something wrong with the given height or
1079 width.
1080 Error when something went wrong with the call. See Image.ExecuteTransforms
1081 for more details.
1083 rpc = resize_async(image_data,
1084 width=width,
1085 height=height,
1086 output_encoding=output_encoding,
1087 quality=quality,
1088 correct_orientation=correct_orientation,
1089 crop_to_fit=crop_to_fit,
1090 crop_offset_x=crop_offset_x,
1091 crop_offset_y=crop_offset_y,
1092 allow_stretch=allow_stretch,
1093 rpc=rpc,
1094 transparent_substitution_rgb=transparent_substitution_rgb)
1095 return rpc.get_result()
1098 def resize_async(image_data, width=0, height=0, output_encoding=PNG,
1099 quality=None, correct_orientation=UNCHANGED_ORIENTATION,
1100 crop_to_fit=False, crop_offset_x=0.5, crop_offset_y=0.5,
1101 allow_stretch=False, rpc=None,
1102 transparent_substitution_rgb=None):
1103 """Resize a given image file maintaining the aspect ratio - async version.
1105 If both width and height are specified, the more restricting of the two
1106 values will be used when resizing the image. The maximum dimension allowed
1107 for both width and height is 4000 pixels.
1108 If both width and height are specified and crop_to_fit is True, the less
1109 restricting of the two values will be used when resizing and the image will be
1110 cropped to fit the specified size. In this case the center of cropping can be
1111 adjusted by crop_offset_x and crop_offset_y.
1113 Args:
1114 image_data: str, source image data.
1115 width: int, width (in pixels) to change the image width to.
1116 height: int, height (in pixels) to change the image height to.
1117 output_encoding: a value from OUTPUT_ENCODING_TYPES.
1118 quality: A value between 1 and 100 to specify the quality of the
1119 encoding. This value is only used for JPEG quality control.
1120 correct_orientation: one of ORIENTATION_CORRECTION_TYPE, to indicate if
1121 orientation correction should be performed during the transformation.
1122 crop_to_fit: If True and both width and height are specified, the image is
1123 cropped after resize to fit the specified dimensions.
1124 crop_offset_x: float value between 0.0 and 1.0, 0 is left and 1 is right,
1125 default is 0.5, the center of image.
1126 crop_offset_y: float value between 0.0 and 1.0, 0 is top and 1 is bottom,
1127 default is 0.5, the center of image.
1128 allow_stretch: If True and both width and height are specified, the image
1129 is stretched to fit the resize dimensions without maintaining the
1130 aspect ratio.
1131 rpc: A UserRPC object.
1132 transparent_substition_rgb: When transparent pixels are not support in the
1133 destination image format then transparent pixels will be substituted
1134 for the specified color, which must be 32 bit rgb format.
1136 Returns:
1137 A UserRPC object, call get_result() to obtain the result of the RPC.
1139 Raises:
1140 TypeError when width or height not either 'int' or 'long' types.
1141 BadRequestError when there is something wrong with the given height or
1142 width.
1143 Error when something went wrong with the call. See Image.ExecuteTransforms
1144 for more details.
1146 image = Image(image_data)
1147 image.resize(width, height, crop_to_fit=crop_to_fit,
1148 crop_offset_x=crop_offset_x, crop_offset_y=crop_offset_y,
1149 allow_stretch=allow_stretch)
1150 image.set_correct_orientation(correct_orientation)
1151 return image.execute_transforms_async(output_encoding=output_encoding,
1152 quality=quality,
1153 rpc=rpc,
1154 transparent_substitution_rgb=transparent_substitution_rgb)
1157 def rotate(image_data, degrees, output_encoding=PNG, quality=None,
1158 correct_orientation=UNCHANGED_ORIENTATION, rpc=None,
1159 transparent_substitution_rgb=None):
1160 """Rotate a given image a given number of degrees clockwise.
1162 Args:
1163 image_data: str, source image data.
1164 degrees: value from ROTATE_DEGREE_VALUES.
1165 output_encoding: a value from OUTPUT_ENCODING_TYPES.
1166 quality: A value between 1 and 100 to specify the quality of the
1167 encoding. This value is only used for JPEG quality control.
1168 correct_orientation: one of ORIENTATION_CORRECTION_TYPE, to indicate if
1169 orientation correction should be performed during the transformation.
1170 rpc: An optional UserRPC object.
1171 transparent_substition_rgb: When transparent pixels are not support in the
1172 destination image format then transparent pixels will be substituted
1173 for the specified color, which must be 32 bit rgb format.
1175 Raises:
1176 TypeError when degrees is not either 'int' or 'long' types.
1177 BadRequestError when there is something wrong with the given degrees.
1178 Error when something went wrong with the call. See Image.ExecuteTransforms
1179 for more details.
1181 rpc = rotate_async(image_data,
1182 degrees,
1183 output_encoding=output_encoding,
1184 quality=quality,
1185 correct_orientation=correct_orientation,
1186 rpc=rpc,
1187 transparent_substitution_rgb=transparent_substitution_rgb)
1188 return rpc.get_result()
1191 def rotate_async(image_data, degrees, output_encoding=PNG, quality=None,
1192 correct_orientation=UNCHANGED_ORIENTATION, rpc=None,
1193 transparent_substitution_rgb=None):
1194 """Rotate a given image a given number of degrees clockwise - async version.
1196 Args:
1197 image_data: str, source image data.
1198 degrees: value from ROTATE_DEGREE_VALUES.
1199 output_encoding: a value from OUTPUT_ENCODING_TYPES.
1200 quality: A value between 1 and 100 to specify the quality of the
1201 encoding. This value is only used for JPEG quality control.
1202 correct_orientation: one of ORIENTATION_CORRECTION_TYPE, to indicate if
1203 orientation correction should be performed during the transformation.
1204 rpc: An optional UserRPC object.
1205 transparent_substition_rgb: When transparent pixels are not support in the
1206 destination image format then transparent pixels will be substituted
1207 for the specified color, which must be 32 bit rgb format.
1209 Returns:
1210 A UserRPC object, call get_result to complete the RPC and obtain the crop
1211 result.
1213 Raises:
1214 TypeError when degrees is not either 'int' or 'long' types.
1215 BadRequestError when there is something wrong with the given degrees.
1216 Error when something went wrong with the call. See Image.ExecuteTransforms
1217 for more details.
1219 image = Image(image_data)
1220 image.rotate(degrees)
1221 image.set_correct_orientation(correct_orientation)
1222 return image.execute_transforms_async(output_encoding=output_encoding,
1223 quality=quality,
1224 rpc=rpc,
1225 transparent_substitution_rgb=transparent_substitution_rgb)
1228 def horizontal_flip(image_data, output_encoding=PNG, quality=None,
1229 correct_orientation=UNCHANGED_ORIENTATION, rpc=None,
1230 transparent_substitution_rgb=None):
1231 """Flip the image horizontally.
1233 Args:
1234 image_data: str, source image data.
1235 output_encoding: a value from OUTPUT_ENCODING_TYPES.
1236 quality: A value between 1 and 100 to specify the quality of the
1237 encoding. This value is only used for JPEG quality control.
1238 correct_orientation: one of ORIENTATION_CORRECTION_TYPE, to indicate if
1239 orientation correction should be performed during the transformation.
1240 rpc: An Optional UserRPC object
1241 transparent_substition_rgb: When transparent pixels are not support in the
1242 destination image format then transparent pixels will be substituted
1243 for the specified color, which must be 32 bit rgb format.
1245 Raises:
1246 Error when something went wrong with the call. See Image.ExecuteTransforms
1247 for more details.
1249 rpc = horizontal_flip_async(image_data,
1250 output_encoding=output_encoding,
1251 quality=quality,
1252 correct_orientation=correct_orientation,
1253 rpc=rpc,
1254 transparent_substitution_rgb=transparent_substitution_rgb)
1255 return rpc.get_result()
1258 def horizontal_flip_async(image_data, output_encoding=PNG, quality=None,
1259 correct_orientation=UNCHANGED_ORIENTATION,
1260 rpc=None,
1261 transparent_substitution_rgb=None):
1262 """Flip the image horizontally - async version.
1264 Args:
1265 image_data: str, source image data.
1266 output_encoding: a value from OUTPUT_ENCODING_TYPES.
1267 quality: A value between 1 and 100 to specify the quality of the
1268 encoding. This value is only used for JPEG quality control.
1269 correct_orientation: one of ORIENTATION_CORRECTION_TYPE, to indicate if
1270 orientation correction should be performed during the transformation.
1271 rpc: An Optional UserRPC object
1272 transparent_substition_rgb: When transparent pixels are not support in the
1273 destination image format then transparent pixels will be substituted
1274 for the specified color, which must be 32 bit rgb format.
1276 Returns:
1277 A UserRPC object, call get_result to complete the RPC and obtain the crop
1278 result.
1280 Raises:
1281 Error when something went wrong with the call. See Image.ExecuteTransforms
1282 for more details.
1284 image = Image(image_data)
1285 image.horizontal_flip()
1286 image.set_correct_orientation(correct_orientation)
1287 return image.execute_transforms_async(output_encoding=output_encoding,
1288 quality=quality,
1289 rpc=rpc,
1290 transparent_substitution_rgb=transparent_substitution_rgb)
1293 def vertical_flip(image_data, output_encoding=PNG, quality=None,
1294 correct_orientation=UNCHANGED_ORIENTATION, rpc=None,
1295 transparent_substitution_rgb=None):
1296 """Flip the image vertically.
1298 Args:
1299 image_data: str, source image data.
1300 output_encoding: a value from OUTPUT_ENCODING_TYPES.
1301 quality: A value between 1 and 100 to specify the quality of the
1302 encoding. This value is only used for JPEG quality control.
1303 correct_orientation: one of ORIENTATION_CORRECTION_TYPE, to indicate if
1304 orientation correction should be performed during the transformation.
1305 rpc: An Optional UserRPC object
1306 transparent_substition_rgb: When transparent pixels are not support in the
1307 destination image format then transparent pixels will be substituted
1308 for the specified color, which must be 32 bit rgb format.
1310 Raises:
1311 Error when something went wrong with the call. See Image.ExecuteTransforms
1312 for more details.
1314 rpc = vertical_flip_async(image_data,
1315 output_encoding=output_encoding,
1316 quality=quality,
1317 correct_orientation=correct_orientation,
1318 rpc=rpc,
1319 transparent_substitution_rgb=transparent_substitution_rgb)
1320 return rpc.get_result()
1323 def vertical_flip_async(image_data, output_encoding=PNG, quality=None,
1324 correct_orientation=UNCHANGED_ORIENTATION, rpc=None,
1325 transparent_substitution_rgb=None):
1326 """Flip the image vertically - async version.
1328 Args:
1329 image_data: str, source image data.
1330 output_encoding: a value from OUTPUT_ENCODING_TYPES.
1331 quality: A value between 1 and 100 to specify the quality of the
1332 encoding. This value is only used for JPEG quality control.
1333 correct_orientation: one of ORIENTATION_CORRECTION_TYPE, to indicate if
1334 orientation correction should be performed during the transformation.
1335 rpc: An Optional UserRPC object
1336 transparent_substition_rgb: When transparent pixels are not support in the
1337 destination image format then transparent pixels will be substituted
1338 for the specified color, which must be 32 bit rgb format.
1340 Returns:
1341 A UserRPC object, call get_result to complete the RPC and obtain the crop
1342 result.
1344 Raises:
1345 Error when something went wrong with the call. See Image.ExecuteTransforms
1346 for more details.
1348 image = Image(image_data)
1349 image.vertical_flip()
1350 image.set_correct_orientation(correct_orientation)
1351 return image.execute_transforms_async(output_encoding=output_encoding,
1352 quality=quality,
1353 rpc=rpc,
1354 transparent_substitution_rgb=transparent_substitution_rgb)
1357 def crop(image_data, left_x, top_y, right_x, bottom_y, output_encoding=PNG,
1358 quality=None, correct_orientation=UNCHANGED_ORIENTATION, rpc=None,
1359 transparent_substitution_rgb=None):
1360 """Crop the given image.
1362 The four arguments are the scaling numbers to describe the bounding box
1363 which will crop the image. The upper left point of the bounding box will
1364 be at (left_x*image_width, top_y*image_height) the lower right point will
1365 be at (right_x*image_width, bottom_y*image_height).
1367 Args:
1368 image_data: str, source image data.
1369 left_x: float value between 0.0 and 1.0 (inclusive).
1370 top_y: float value between 0.0 and 1.0 (inclusive).
1371 right_x: float value between 0.0 and 1.0 (inclusive).
1372 bottom_y: float value between 0.0 and 1.0 (inclusive).
1373 output_encoding: a value from OUTPUT_ENCODING_TYPES.
1374 quality: A value between 1 and 100 to specify the quality of the
1375 encoding. This value is only used for JPEG quality control.
1376 correct_orientation: one of ORIENTATION_CORRECTION_TYPE, to indicate if
1377 orientation correction should be performed during the transformation.
1378 rpc: A User RPC Object
1379 transparent_substition_rgb: When transparent pixels are not support in the
1380 destination image format then transparent pixels will be substituted
1381 for the specified color, which must be 32 bit rgb format.
1383 Raises:
1384 TypeError if the args are not of type 'float'.
1385 BadRequestError when there is something wrong with the given bounding box.
1386 Error when something went wrong with the call. See Image.ExecuteTransforms
1387 for more details.
1389 rpc = crop_async(image_data, left_x, top_y, right_x, bottom_y,
1390 output_encoding=output_encoding, quality=quality,
1391 correct_orientation=correct_orientation, rpc=rpc,
1392 transparent_substitution_rgb=transparent_substitution_rgb)
1393 return rpc.get_result()
1396 def crop_async(image_data, left_x, top_y, right_x, bottom_y,
1397 output_encoding=PNG, quality=None,
1398 correct_orientation=UNCHANGED_ORIENTATION, rpc=None,
1399 transparent_substitution_rgb=None):
1400 """Crop the given image - async version.
1402 The four arguments are the scaling numbers to describe the bounding box
1403 which will crop the image. The upper left point of the bounding box will
1404 be at (left_x*image_width, top_y*image_height) the lower right point will
1405 be at (right_x*image_width, bottom_y*image_height).
1407 Args:
1408 image_data: str, source image data.
1409 left_x: float value between 0.0 and 1.0 (inclusive).
1410 top_y: float value between 0.0 and 1.0 (inclusive).
1411 right_x: float value between 0.0 and 1.0 (inclusive).
1412 bottom_y: float value between 0.0 and 1.0 (inclusive).
1413 output_encoding: a value from OUTPUT_ENCODING_TYPES.
1414 quality: A value between 1 and 100 to specify the quality of the
1415 encoding. This value is only used for JPEG quality control.
1416 correct_orientation: one of ORIENTATION_CORRECTION_TYPE, to indicate if
1417 orientation correction should be performed during the transformation.
1418 rpc: An optional UserRPC object.
1419 transparent_substition_rgb: When transparent pixels are not support in the
1420 destination image format then transparent pixels will be substituted
1421 for the specified color, which must be 32 bit rgb format.
1423 Returns:
1424 A UserRPC object, call get_result to complete the RPC and obtain the crop
1425 result.
1427 Raises:
1428 TypeError if the args are not of type 'float'.
1429 BadRequestError when there is something wrong with the given bounding box.
1430 Error when something went wrong with the call. See Image.ExecuteTransforms
1431 for more details.
1433 image = Image(image_data)
1434 image.crop(left_x, top_y, right_x, bottom_y)
1435 image.set_correct_orientation(correct_orientation)
1436 return image.execute_transforms_async(output_encoding=output_encoding,
1437 quality=quality,
1438 rpc=rpc,
1439 transparent_substitution_rgb=transparent_substitution_rgb)
1442 def im_feeling_lucky(image_data, output_encoding=PNG, quality=None,
1443 correct_orientation=UNCHANGED_ORIENTATION, rpc=None,
1444 transparent_substitution_rgb=None):
1445 """Automatically adjust image levels.
1447 This is similar to the "I'm Feeling Lucky" button in Picasa.
1449 Args:
1450 image_data: str, source image data.
1451 output_encoding: a value from OUTPUT_ENCODING_TYPES.
1452 quality: A value between 1 and 100 to specify the quality of the
1453 encoding. This value is only used for JPEG quality control.
1454 correct_orientation: one of ORIENTATION_CORRECTION_TYPE, to indicate if
1455 orientation correction should be performed during the transformation.
1456 rpc: An optional UserRPC object.
1457 transparent_substition_rgb: When transparent pixels are not support in the
1458 destination image format then transparent pixels will be substituted
1459 for the specified color, which must be 32 bit rgb format.
1461 Raises:
1462 Error when something went wrong with the call. See Image.ExecuteTransforms
1463 for more details.
1465 rpc = im_feeling_lucky_async(image_data,
1466 output_encoding=output_encoding,
1467 quality=quality,
1468 correct_orientation=correct_orientation,
1469 rpc=rpc,
1470 transparent_substitution_rgb=transparent_substitution_rgb)
1471 return rpc.get_result()
1474 def im_feeling_lucky_async(image_data, output_encoding=PNG, quality=None,
1475 correct_orientation=UNCHANGED_ORIENTATION, rpc=None,
1476 transparent_substitution_rgb=None):
1477 """Automatically adjust image levels - async version.
1479 This is similar to the "I'm Feeling Lucky" button in Picasa.
1481 Args:
1482 image_data: str, source image data.
1483 output_encoding: a value from OUTPUT_ENCODING_TYPES.
1484 quality: A value between 1 and 100 to specify the quality of the
1485 encoding. This value is only used for JPEG quality control.
1486 correct_orientation: one of ORIENTATION_CORRECTION_TYPE, to indicate if
1487 orientation correction should be performed during the transformation.
1488 rpc: An optional UserRPC object.
1489 transparent_substition_rgb: When transparent pixels are not support in the
1490 destination image format then transparent pixels will be substituted
1491 for the specified color, which must be 32 bit rgb format.
1493 Returns:
1494 A UserRPC object.
1496 Raises:
1497 Error when something went wrong with the call. See Image.ExecuteTransforms
1498 for more details.
1500 image = Image(image_data)
1501 image.im_feeling_lucky()
1502 image.set_correct_orientation(correct_orientation)
1503 return image.execute_transforms_async(output_encoding=output_encoding,
1504 quality=quality,
1505 rpc=rpc,
1506 transparent_substitution_rgb=transparent_substitution_rgb)
1509 def composite(inputs, width, height, color=0, output_encoding=PNG,
1510 quality=None, rpc=None):
1511 """Composite one or more images onto a canvas - async version.
1513 Args:
1514 inputs: a list of tuples (image_data, x_offset, y_offset, opacity, anchor)
1515 where
1516 image_data: str, source image data.
1517 x_offset: x offset in pixels from the anchor position
1518 y_offset: y offset in piyels from the anchor position
1519 opacity: opacity of the image specified as a float in range [0.0, 1.0]
1520 anchor: anchoring point from ANCHOR_POINTS. The anchor point of the image
1521 is aligned with the same anchor point of the canvas. e.g. TOP_RIGHT would
1522 place the top right corner of the image at the top right corner of the
1523 canvas then apply the x and y offsets.
1524 width: canvas width in pixels.
1525 height: canvas height in pixels.
1526 color: canvas background color encoded as a 32 bit unsigned int where each
1527 color channel is represented by one byte in order ARGB.
1528 output_encoding: a value from OUTPUT_ENCODING_TYPES.
1529 quality: A value between 1 and 100 to specify the quality of the
1530 encoding. This value is only used for JPEG quality control.
1531 rpc: Optional UserRPC object.
1533 Returns:
1534 str, image data of the composited image.
1536 Raises:
1537 TypeError If width, height, color, x_offset or y_offset are not of type
1538 int or long or if opacity is not a float
1539 BadRequestError If more than MAX_TRANSFORMS_PER_REQUEST compositions have
1540 been requested, if the canvas width or height is greater than 4000 or less
1541 than or equal to 0, if the color is invalid or if for any composition
1542 option, the opacity is outside the range [0,1] or the anchor is invalid.
1544 rpc = composite_async(inputs, width, height, color=color,
1545 output_encoding=output_encoding, quality=quality,
1546 rpc=rpc)
1547 return rpc.get_result()
1550 def composite_async(inputs, width, height, color=0, output_encoding=PNG,
1551 quality=None, rpc=None):
1552 """Composite one or more images onto a canvas - async version.
1554 Args:
1555 inputs: a list of tuples (image_data, x_offset, y_offset, opacity, anchor)
1556 where
1557 image_data: str, source image data.
1558 x_offset: x offset in pixels from the anchor position
1559 y_offset: y offset in piyels from the anchor position
1560 opacity: opacity of the image specified as a float in range [0.0, 1.0]
1561 anchor: anchoring point from ANCHOR_POINTS. The anchor point of the image
1562 is aligned with the same anchor point of the canvas. e.g. TOP_RIGHT would
1563 place the top right corner of the image at the top right corner of the
1564 canvas then apply the x and y offsets.
1565 width: canvas width in pixels.
1566 height: canvas height in pixels.
1567 color: canvas background color encoded as a 32 bit unsigned int where each
1568 color channel is represented by one byte in order ARGB.
1569 output_encoding: a value from OUTPUT_ENCODING_TYPES.
1570 quality: A value between 1 and 100 to specify the quality of the
1571 encoding. This value is only used for JPEG quality control.
1572 rpc: Optional UserRPC object.
1574 Returns:
1575 A UserRPC object.
1577 Raises:
1578 TypeError If width, height, color, x_offset or y_offset are not of type
1579 int or long or if opacity is not a float
1580 BadRequestError If more than MAX_TRANSFORMS_PER_REQUEST compositions have
1581 been requested, if the canvas width or height is greater than 4000 or less
1582 than or equal to 0, if the color is invalid or if for any composition
1583 option, the opacity is outside the range [0,1] or the anchor is invalid.
1585 if (not isinstance(width, (int, long)) or
1586 not isinstance(height, (int, long)) or
1587 not isinstance(color, (int, long))):
1588 raise TypeError("Width, height and color must be integers.")
1589 if output_encoding not in OUTPUT_ENCODING_TYPES:
1590 raise BadRequestError("Output encoding type '%s' not in recognized set "
1591 "%s" % (output_encoding, OUTPUT_ENCODING_TYPES))
1593 if quality is not None:
1594 if not isinstance(quality, (int, long)):
1595 raise TypeError("Quality must be an integer.")
1596 if quality > 100 or quality < 1:
1597 raise BadRequestError("Quality must be between 1 and 100.")
1599 if not inputs:
1600 raise BadRequestError("Must provide at least one input")
1601 if len(inputs) > MAX_COMPOSITES_PER_REQUEST:
1602 raise BadRequestError("A maximum of %d composition operations can be"
1603 "performed in a single request" %
1604 MAX_COMPOSITES_PER_REQUEST)
1606 if width <= 0 or height <= 0:
1607 raise BadRequestError("Width and height must be > 0.")
1608 if width > 4000 or height > 4000:
1609 raise BadRequestError("Width and height must be <= 4000.")
1611 if color > 0xffffffff or color < 0:
1612 raise BadRequestError("Invalid color")
1614 if color >= 0x80000000:
1615 color -= 0x100000000
1617 image_map = {}
1619 request = images_service_pb.ImagesCompositeRequest()
1620 response = images_service_pb.ImagesTransformResponse()
1621 for (image, x, y, opacity, anchor) in inputs:
1622 if not image:
1623 raise BadRequestError("Each input must include an image")
1624 if (not isinstance(x, (int, long)) or
1625 not isinstance(y, (int, long)) or
1626 not isinstance(opacity, (float))):
1627 raise TypeError("x_offset, y_offset must be integers and opacity must"
1628 "be a float")
1629 if x > 4000 or x < -4000:
1630 raise BadRequestError("xOffsets must be in range [-4000, 4000]")
1631 if y > 4000 or y < -4000:
1632 raise BadRequestError("yOffsets must be in range [-4000, 4000]")
1633 if opacity < 0 or opacity > 1:
1634 raise BadRequestError("Opacity must be in the range 0.0 to 1.0")
1635 if anchor not in ANCHOR_TYPES:
1636 raise BadRequestError("Anchor type '%s' not in recognized set %s" %
1637 (anchor, ANCHOR_TYPES))
1638 if image not in image_map:
1639 image_map[image] = request.image_size()
1641 if isinstance(image, Image):
1642 image._set_imagedata(request.add_image())
1643 else:
1644 request.add_image().set_content(image)
1646 option = request.add_options()
1647 option.set_x_offset(x)
1648 option.set_y_offset(y)
1649 option.set_opacity(opacity)
1650 option.set_anchor(anchor)
1651 option.set_source_index(image_map[image])
1653 request.mutable_canvas().mutable_output().set_mime_type(output_encoding)
1654 request.mutable_canvas().set_width(width)
1655 request.mutable_canvas().set_height(height)
1656 request.mutable_canvas().set_color(color)
1658 if ((output_encoding == JPEG or output_encoding == WEBP) and
1659 (quality is not None)):
1660 request.mutable_canvas().mutable_output().set_quality(quality)
1662 def composite_hook(rpc):
1663 """Check success, handles exceptions and returns the converted RPC result.
1665 Args:
1666 rpc: A UserRPC object.
1668 Returns:
1669 Images bytes of the composite image.
1671 Raises:
1672 See docstring for composite_async for more details.
1674 try:
1675 rpc.check_success()
1676 except apiproxy_errors.ApplicationError, e:
1677 raise _ToImagesError(e)
1678 return rpc.response.image().content()
1680 return _make_async_call(rpc,
1681 "Composite",
1682 request,
1683 response,
1684 composite_hook,
1685 None)
1688 def histogram(image_data, rpc=None):
1689 """Calculates the histogram of the given image.
1691 Args:
1692 image_data: str, source image data.
1693 rpc: An optional UserRPC object.
1695 Returns: 3 256-element lists containing the number of occurences of each
1696 value of each color in the order RGB.
1699 Raises:
1700 NotImageError when the image data given is not an image.
1701 BadImageError when the image data given is corrupt.
1702 LargeImageError when the image data given is too large to process.
1703 Error when something unknown, but bad, happens.
1705 rpc = histogram_async(image_data, rpc=rpc)
1706 return rpc.get_result()
1709 def histogram_async(image_data, rpc=None):
1710 """Calculates the histogram of the given image - async version.
1712 Args:
1713 image_data: str, source image data.
1714 rpc: An optional UserRPC object.
1716 Returns:
1717 An UserRPC object.
1719 Raises:
1720 NotImageError when the image data given is not an image.
1721 BadImageError when the image data given is corrupt.
1722 LargeImageError when the image data given is too large to process.
1723 Error when something unknown, but bad, happens.
1725 image = Image(image_data)
1726 return image.histogram_async(rpc)
1729 IMG_SERVING_SIZES_LIMIT = 1600
1732 IMG_SERVING_SIZES = [
1733 32, 48, 64, 72, 80, 90, 94, 104, 110, 120, 128, 144,
1734 150, 160, 200, 220, 288, 320, 400, 512, 576, 640, 720,
1735 800, 912, 1024, 1152, 1280, 1440, 1600]
1738 IMG_SERVING_CROP_SIZES = [32, 48, 64, 72, 80, 104, 136, 144, 150, 160]
1741 def get_serving_url(blob_key,
1742 size=None,
1743 crop=False,
1744 secure_url=None,
1745 filename=None,
1746 rpc=None):
1747 """Obtain a url that will serve the underlying image.
1749 This URL is served by a high-performance dynamic image serving infrastructure.
1750 This URL format also allows dynamic resizing and crop with certain
1751 restrictions. To get dynamic resizing and cropping, specify size and crop
1752 arguments, or simply append options to the end of the default url obtained via
1753 this call. Here is an example:
1755 get_serving_url -> "http://lh3.ggpht.com/SomeCharactersGoesHere"
1757 To get a 32 pixel sized version (aspect-ratio preserved) simply append
1758 "=s32" to the url:
1760 "http://lh3.ggpht.com/SomeCharactersGoesHere=s32"
1762 To get a 32 pixel cropped version simply append "=s32-c":
1764 "http://lh3.ggpht.com/SomeCharactersGoesHere=s32-c"
1766 Available sizes are any integer in the range [0, 1600] and is available as
1767 IMG_SERVING_SIZES_LIMIT.
1769 Args:
1770 blob_key: BlobKey, BlobInfo, str, or unicode representation of BlobKey of
1771 blob to get URL of.
1772 size: int, size of resulting images
1773 crop: bool, True requests a cropped image, False a resized one.
1774 secure_url: bool, True requests a https url, False requests a http url.
1775 filename: The filename of a Google Storage object to get the URL of.
1776 rpc: Optional UserRPC object.
1778 Returns:
1779 str, a url
1781 Raises:
1782 BlobKeyRequiredError: when no blobkey was specified in the ctor.
1783 UnsupportedSizeError: when size parameters uses unsupported sizes.
1784 BadRequestError: when crop/size are present in wrong combination, or a
1785 blob_key and a filename have been specified.
1786 TypeError: when secure_url is not a boolean type.
1787 AccessDeniedError: when the blobkey refers to a Google Storage object, and
1788 the application does not have permission to access the object.
1789 ObjectNotFoundError:: when the blobkey refers to an object that no longer
1790 exists.
1792 rpc = get_serving_url_async(blob_key, size, crop, secure_url, filename, rpc)
1793 return rpc.get_result()
1796 def get_serving_url_async(blob_key,
1797 size=None,
1798 crop=False,
1799 secure_url=None,
1800 filename=None,
1801 rpc=None):
1802 """Obtain a url that will serve the underlying image - async version.
1804 This URL is served by a high-performance dynamic image serving infrastructure.
1805 This URL format also allows dynamic resizing and crop with certain
1806 restrictions. To get dynamic resizing and cropping, specify size and crop
1807 arguments, or simply append options to the end of the default url obtained via
1808 this call. Here is an example:
1810 get_serving_url -> "http://lh3.ggpht.com/SomeCharactersGoesHere"
1812 To get a 32 pixel sized version (aspect-ratio preserved) simply append
1813 "=s32" to the url:
1815 "http://lh3.ggpht.com/SomeCharactersGoesHere=s32"
1817 To get a 32 pixel cropped version simply append "=s32-c":
1819 "http://lh3.ggpht.com/SomeCharactersGoesHere=s32-c"
1821 Available sizes are any integer in the range [0, 1600] and is available as
1822 IMG_SERVING_SIZES_LIMIT.
1824 Args:
1825 blob_key: BlobKey, BlobInfo, str, or unicode representation of BlobKey of
1826 blob to get URL of.
1827 size: int, size of resulting images
1828 crop: bool, True requests a cropped image, False a resized one.
1829 secure_url: bool, True requests a https url, False requests a http url.
1830 filename: The filename of a Google Storage object to get the URL of.
1831 rpc: Optional UserRPC object.
1833 Returns:
1834 A UserRPC whose result will be a string that is the serving url
1836 Raises:
1837 BlobKeyRequiredError: when no blobkey was specified in the ctor.
1838 UnsupportedSizeError: when size parameters uses unsupported sizes.
1839 BadRequestError: when crop/size are present in wrong combination, or a
1840 blob_key and a filename have been specified.
1841 TypeError: when secure_url is not a boolean type.
1842 AccessDeniedError: when the blobkey refers to a Google Storage object, and
1843 the application does not have permission to access the object.
1845 if not blob_key and not filename:
1846 raise BlobKeyRequiredError(
1847 "A Blobkey or a filename is required for this operation.")
1849 if crop and not size:
1850 raise BadRequestError("Size should be set for crop operation")
1852 if size is not None and (size > IMG_SERVING_SIZES_LIMIT or size < 0):
1853 raise UnsupportedSizeError("Unsupported size")
1855 if secure_url and not isinstance(secure_url, bool):
1856 raise TypeError("secure_url must be boolean.")
1858 if filename and blob_key:
1859 raise BadRequestError("Cannot specify a blob_key and a filename.");
1861 if filename:
1862 _blob_key = blobstore.create_gs_key(filename)
1863 readable_blob_key = filename
1864 else:
1865 _blob_key = _extract_blob_key(blob_key)
1866 readable_blob_key = blob_key
1868 request = images_service_pb.ImagesGetUrlBaseRequest()
1869 response = images_service_pb.ImagesGetUrlBaseResponse()
1871 request.set_blob_key(_blob_key)
1873 if secure_url:
1874 request.set_create_secure_url(secure_url)
1876 def get_serving_url_hook(rpc):
1877 """Check success, handle exceptions, and return converted RPC result.
1879 Args:
1880 rpc: A UserRPC object.
1882 Returns:
1883 The URL for serving the image.
1885 Raises:
1886 See docstring for get_serving_url for more details.
1888 try:
1889 rpc.check_success()
1890 except apiproxy_errors.ApplicationError, e:
1891 raise _ToImagesError(e, readable_blob_key)
1893 url = rpc.response.url()
1895 if size is not None:
1896 url += "=s%s" % size
1897 if crop:
1898 url += "-c"
1900 return url
1902 return _make_async_call(rpc,
1903 "GetUrlBase",
1904 request,
1905 response,
1906 get_serving_url_hook,
1907 None)
1910 def delete_serving_url(blob_key, rpc=None):
1911 """Delete a serving url that was created for a blob_key using get_serving_url.
1913 Args:
1914 blob_key: BlobKey, BlobInfo, str, or unicode representation of BlobKey of
1915 blob that has an existing URL to delete.
1916 rpc: Optional UserRPC object.
1918 Raises:
1919 BlobKeyRequiredError: when no blobkey was specified.
1920 InvalidBlobKeyError: the blob_key supplied was invalid.
1921 Error: There was a generic error deleting the serving url.
1923 rpc = delete_serving_url_async(blob_key, rpc)
1924 rpc.get_result()
1927 def delete_serving_url_async(blob_key, rpc=None):
1928 """Delete a serving url created using get_serving_url - async version.
1930 Args:
1931 blob_key: BlobKey, BlobInfo, str, or unicode representation of BlobKey of
1932 blob that has an existing URL to delete.
1933 rpc: Optional UserRPC object.
1935 Returns:
1936 A UserRPC object.
1938 Raises:
1939 BlobKeyRequiredError: when no blobkey was specified.
1940 InvalidBlobKeyError: the blob_key supplied was invalid.
1941 Error: There was a generic error deleting the serving url.
1943 if not blob_key:
1944 raise BlobKeyRequiredError("A Blobkey is required for this operation.")
1946 request = images_service_pb.ImagesDeleteUrlBaseRequest()
1947 response = images_service_pb.ImagesDeleteUrlBaseResponse()
1949 request.set_blob_key(_extract_blob_key(blob_key))
1951 def delete_serving_url_hook(rpc):
1952 """Checks success, handles exceptions and returns the converted RPC result.
1954 Args:
1955 rpc: A UserRPC object.
1957 Raises:
1958 See docstring for delete_serving_url_async for more details.
1960 try:
1961 rpc.check_success()
1962 except apiproxy_errors.ApplicationError, e:
1963 raise _ToImagesError(e, blob_key)
1965 return _make_async_call(rpc,
1966 "DeleteUrlBase",
1967 request,
1968 response,
1969 delete_serving_url_hook,
1970 None)
1973 def _extract_blob_key(blob):
1974 """Extract a unicode blob key from a str, BlobKey, or BlobInfo.
1976 Args:
1977 blob: The str, unicode, BlobKey, or BlobInfo that contains the blob key.
1979 if isinstance(blob, str):
1980 return blob.decode('utf-8')
1981 elif isinstance(blob, BlobKey):
1982 return str(blob).decode('utf-8')
1983 elif blob.__class__.__name__ == 'BlobInfo':
1987 return str(blob.key()).decode('utf-8')
1990 return blob