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
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.
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
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
,
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):
128 blob_key: The blob_key that is believed to be invalid. May be None if the
131 self
._blob
_key
= blob_key
134 """Returns a string representation of this Error."""
136 return 'InvalidBlobKeyError: %s' % repr(self
._blob
_key
)
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.
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.
166 The Images error if found, otherwise the original error.
169 images_service_pb
.ImagesServiceError
.NOT_IMAGE
:
171 images_service_pb
.ImagesServiceError
.BAD_IMAGE_DATA
:
173 images_service_pb
.ImagesServiceError
.IMAGE_TOO_LARGE
:
175 images_service_pb
.ImagesServiceError
.INVALID_BLOB_KEY
:
177 images_service_pb
.ImagesServiceError
.ACCESS_DENIED
:
179 images_service_pb
.ImagesServiceError
.OBJECT_NOT_FOUND
:
181 images_service_pb
.ImagesServiceError
.UNSPECIFIED_ERROR
:
183 images_service_pb
.ImagesServiceError
.BAD_TRANSFORM_DATA
:
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
)
197 """Image object to manipulate."""
199 def __init__(self
, image_data
=None, blob_key
=None, filename
=None):
202 Only one of image_data, blob_key or filename can be specified.
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'.
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
224 self
._blob
_key
= blobstore
.create_gs_key(filename
)
226 self
._blob
_key
= _extract_blob_key(blob_key
)
227 self
._transforms
= []
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.
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.
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
()
258 elif size
>= 8 and self
._image
_data
.startswith("\x89PNG\x0D\x0A\x1A\x0A"):
259 self
._update
_png
_dimensions
()
261 elif size
>= 2 and self
._image
_data
.startswith("\xff\xD8"):
262 self
._update
_jpeg
_dimensions
()
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
()
268 elif size
>= 2 and self
._image
_data
.startswith("BM"):
269 self
._update
_bmp
_dimensions
()
271 elif size
>= 4 and self
._image
_data
.startswith("\x00\x00\x01\x00"):
272 self
._update
_ico
_dimensions
()
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
()
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
_vp
8x
_dimensions
()
286 raise NotImageError("Unrecognized image format")
288 def _update_gif_dimensions(self
):
289 """Updates the width and height fields of the gif image.
292 BadImageError if the image string is not a valid gif image.
295 size
= len(self
._image
_data
)
297 self
._width
, self
._height
= struct
.unpack("<HH", self
._image
_data
[6:10])
299 raise BadImageError("Corrupt GIF format")
301 def _update_png_dimensions(self
):
302 """Updates the width and height fields of the png image.
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])
313 raise BadImageError("Corrupt PNG format")
315 def _update_jpeg_dimensions(self
):
316 """Updates the width and height fields of the jpeg image.
319 BadImageError if the image string is not a valid jpeg image.
322 size
= len(self
._image
_data
)
325 while offset
< size
and ord(self
._image
_data
[offset
]) != 0xFF:
327 while offset
< size
and ord(self
._image
_data
[offset
]) == 0xFF:
329 if (offset
< size
and ord(self
._image
_data
[offset
]) & 0xF0 == 0xC0 and
330 ord(self
._image
_data
[offset
]) != 0xC4):
332 if offset
+ 4 <= size
:
333 self
._height
, self
._width
= struct
.unpack(
335 self
._image
_data
[offset
:offset
+ 4])
338 raise BadImageError("Corrupt JPEG format")
339 elif offset
+ 3 <= size
:
341 offset
+= struct
.unpack(">H", self
._image
_data
[offset
:offset
+ 2])[0]
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.
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"):
360 ifd_offset
= struct
.unpack(endianness
+ "I", self
._image
_data
[4:8])[0]
361 if ifd_offset
+ 14 <= size
:
362 ifd_size
= struct
.unpack(
364 self
._image
_data
[ifd_offset
:ifd_offset
+ 2])[0]
366 for unused_i
in range(0, ifd_size
):
367 if ifd_offset
+ 12 <= size
:
370 self
._image
_data
[ifd_offset
:ifd_offset
+ 2])[0]
371 if tag
== 0x100 or tag
== 0x101:
372 value_type
= struct
.unpack(
374 self
._image
_data
[ifd_offset
+ 2:ifd_offset
+ 4])[0]
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
382 format
= endianness
+ "B"
383 end_offset
= ifd_offset
+ 9
385 self
._width
= struct
.unpack(
387 self
._image
_data
[ifd_offset
+ 8:end_offset
])[0]
388 if self
._height
is not None:
391 self
._height
= struct
.unpack(
393 self
._image
_data
[ifd_offset
+ 8:end_offset
])[0]
394 if self
._width
is not None:
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.
406 BadImageError if the image string is not a valid bmp image.
412 size
= len(self
._image
_data
)
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])
424 raise BadImageError("Corrupt BMP format")
426 raise BadImageError("Corrupt BMP format")
428 def _update_ico_dimensions(self
):
429 """Updates the width and height fields of the ico image.
432 BadImageError if the image string is not a valid ico image.
434 size
= len(self
._image
_data
)
436 self
._width
, self
._height
= struct
.unpack("<BB", self
._image
_data
[6:8])
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.
465 correct_orientation: a value from ORIENTATION_CORRECTION_TYPE.
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
)
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)
492 raise BadImageError("Corrupt WEBP format")
494 profile
= (bits
>> 1) & 7
495 show_frame
= (bits
>> 4) & 1
498 raise BadImageError("Corrupt WEBP format")
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
)
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.
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
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.
596 degrees: int, must be a multiple of 90.
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.
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.
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.
653 val: float, value of the argument.
654 val_name: str, name of the argument.
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
):
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).
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).
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
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.
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
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.
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
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
742 It will return None, if it is called before a successful
745 return self
._original
_metadata
747 def _set_imagedata(self
, imagedata
):
748 """Fills in an ImageData PB from this Image instance.
751 imagedata: An ImageData PB instance
754 imagedata
.set_content("")
755 imagedata
.set_blob_key(self
._blob
_key
)
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,
763 """Perform transformations on a given image.
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.
778 str, image data after the transformations have been performed on it.
781 BadRequestError when there is something wrong with the request
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
792 Error when something unknown, but bad, happens.
794 rpc
= self
.execute_transforms_async(output_encoding
=output_encoding
,
796 parse_source_metadata
=parse_source_metadata
,
797 transparent_substitution_rgb
=transparent_substitution_rgb
,
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,
805 """Perform transformations on a given image - async version.
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.
823 BadRequestError when there is something wrong with the request
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):
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.
878 rpc: A UserRPC object.
881 See docstring for execute_transforms_async for more details.
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()
894 if response
.image().has_height():
895 self
._height
= rpc
.response
.image().height()
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
,
907 execute_transforms_hook
,
912 """Gets the width of the image."""
913 if self
._width
is None:
914 self
._update
_dimensions
()
919 """Gets the height of the image."""
920 if self
._height
is None:
921 self
._update
_dimensions
()
926 """Gets the format of the image."""
927 if self
._format
is None:
928 self
._update
_dimensions
()
931 def histogram(self
, rpc
=None):
932 """Calculates the histogram of the image.
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.
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.
957 rpc: An optional UserRPC object.
960 rpc: A UserRPC object.
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.
977 rpc: A UserRPC object.
980 See docstring for histogram_async for more details.
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
,
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.
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.
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
):
1035 rpc
.make_call(method
, request
, response
, get_result_hook
, user_data
)
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.
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
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.
1077 TypeError when width or height not either 'int' or 'long' types.
1078 BadRequestError when there is something wrong with the given height or
1080 Error when something went wrong with the call. See Image.ExecuteTransforms
1083 rpc
= resize_async(image_data
,
1086 output_encoding
=output_encoding
,
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
,
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.
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
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.
1137 A UserRPC object, call get_result() to obtain the result of the RPC.
1140 TypeError when width or height not either 'int' or 'long' types.
1141 BadRequestError when there is something wrong with the given height or
1143 Error when something went wrong with the call. See Image.ExecuteTransforms
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
,
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.
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.
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
1181 rpc
= rotate_async(image_data
,
1183 output_encoding
=output_encoding
,
1185 correct_orientation
=correct_orientation
,
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.
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.
1210 A UserRPC object, call get_result to complete the RPC and obtain the crop
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
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
,
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.
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.
1246 Error when something went wrong with the call. See Image.ExecuteTransforms
1249 rpc
= horizontal_flip_async(image_data
,
1250 output_encoding
=output_encoding
,
1252 correct_orientation
=correct_orientation
,
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
,
1261 transparent_substitution_rgb
=None):
1262 """Flip the image horizontally - async version.
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.
1277 A UserRPC object, call get_result to complete the RPC and obtain the crop
1281 Error when something went wrong with the call. See Image.ExecuteTransforms
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
,
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.
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.
1311 Error when something went wrong with the call. See Image.ExecuteTransforms
1314 rpc
= vertical_flip_async(image_data
,
1315 output_encoding
=output_encoding
,
1317 correct_orientation
=correct_orientation
,
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.
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.
1341 A UserRPC object, call get_result to complete the RPC and obtain the crop
1345 Error when something went wrong with the call. See Image.ExecuteTransforms
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
,
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).
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.
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
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).
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.
1424 A UserRPC object, call get_result to complete the RPC and obtain the crop
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
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
,
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.
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.
1462 Error when something went wrong with the call. See Image.ExecuteTransforms
1465 rpc
= im_feeling_lucky_async(image_data
,
1466 output_encoding
=output_encoding
,
1468 correct_orientation
=correct_orientation
,
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.
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.
1497 Error when something went wrong with the call. See Image.ExecuteTransforms
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
,
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.
1514 inputs: a list of tuples (image_data, x_offset, y_offset, opacity, anchor)
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.
1534 str, image data of the composited image.
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
,
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.
1555 inputs: a list of tuples (image_data, x_offset, y_offset, opacity, anchor)
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.
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.")
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
1619 request
= images_service_pb
.ImagesCompositeRequest()
1620 response
= images_service_pb
.ImagesTransformResponse()
1621 for (image
, x
, y
, opacity
, anchor
) in inputs
:
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"
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())
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.
1666 rpc: A UserRPC object.
1669 Images bytes of the composite image.
1672 See docstring for composite_async for more details.
1676 except apiproxy_errors
.ApplicationError
, e
:
1677 raise _ToImagesError(e
)
1678 return rpc
.response
.image().content()
1680 return _make_async_call(rpc
,
1688 def histogram(image_data
, rpc
=None):
1689 """Calculates the histogram of the given image.
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.
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.
1713 image_data: str, source image data.
1714 rpc: An optional UserRPC object.
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
,
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
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.
1770 blob_key: BlobKey, BlobInfo, str, or unicode representation of BlobKey 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.
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
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
,
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
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.
1825 blob_key: BlobKey, BlobInfo, str, or unicode representation of BlobKey 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.
1834 A UserRPC whose result will be a string that is the serving url
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.");
1862 _blob_key
= blobstore
.create_gs_key(filename
)
1863 readable_blob_key
= filename
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
)
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.
1880 rpc: A UserRPC object.
1883 The URL for serving the image.
1886 See docstring for get_serving_url for more details.
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
1902 return _make_async_call(rpc
,
1906 get_serving_url_hook
,
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.
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.
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
)
1927 def delete_serving_url_async(blob_key
, rpc
=None):
1928 """Delete a serving url created using get_serving_url - async version.
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.
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.
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.
1955 rpc: A UserRPC object.
1958 See docstring for delete_serving_url_async for more details.
1962 except apiproxy_errors
.ApplicationError
, e
:
1963 raise _ToImagesError(e
, blob_key
)
1965 return _make_async_call(rpc
,
1969 delete_serving_url_hook
,
1973 def _extract_blob_key(blob
):
1974 """Extract a unicode blob key from a str, BlobKey, or BlobInfo.
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')