thirdparty/google_appengine/google/appengine/api/images/__init__.py
changeset 1278 a7766286a7be
parent 828 f5fd65cc3bf3
child 2273 e4cb9c53db3e
equal deleted inserted replaced
1277:5c931bd3dc1e 1278:a7766286a7be
    40 JPEG = images_service_pb.OutputSettings.JPEG
    40 JPEG = images_service_pb.OutputSettings.JPEG
    41 PNG = images_service_pb.OutputSettings.PNG
    41 PNG = images_service_pb.OutputSettings.PNG
    42 
    42 
    43 OUTPUT_ENCODING_TYPES = frozenset([JPEG, PNG])
    43 OUTPUT_ENCODING_TYPES = frozenset([JPEG, PNG])
    44 
    44 
       
    45 MAX_TRANSFORMS_PER_REQUEST = 10
       
    46 
    45 
    47 
    46 class Error(Exception):
    48 class Error(Exception):
    47   """Base error class for this module."""
    49   """Base error class for this module."""
    48 
    50 
    49 
    51 
    82     if not image_data:
    84     if not image_data:
    83       raise NotImageError("Empty image data.")
    85       raise NotImageError("Empty image data.")
    84 
    86 
    85     self._image_data = image_data
    87     self._image_data = image_data
    86     self._transforms = []
    88     self._transforms = []
    87     self._transform_map = {}
       
    88     self._width = None
    89     self._width = None
    89     self._height = None
    90     self._height = None
    90 
    91 
    91   def _check_transform_limits(self, transform):
    92   def _check_transform_limits(self):
    92     """Ensure some simple limits on the number of transforms allowed.
    93     """Ensure some simple limits on the number of transforms allowed.
    93 
    94 
    94     Args:
    95     Raises:
    95       transform: images_service_pb.ImagesServiceTransform, enum of the
    96       BadRequestError if MAX_TRANSFORMS_PER_REQUEST transforms have already been
    96         trasnform called.
    97       requested for this image
    97 
    98     """
    98     Raises:
    99     if len(self._transforms) >= MAX_TRANSFORMS_PER_REQUEST:
    99       BadRequestError if the transform has already been requested for the image.
   100       raise BadRequestError("%d transforms have already been requested on this "
   100     """
   101                             "image." % MAX_TRANSFORMS_PER_REQUEST)
   101     if not images_service_pb.ImagesServiceTransform.Type_Name(transform):
       
   102       raise BadRequestError("'%s' is not a valid transform." % transform)
       
   103 
       
   104     if transform in self._transform_map:
       
   105       transform_name = images_service_pb.ImagesServiceTransform.Type_Name(
       
   106           transform)
       
   107       raise BadRequestError("A '%s' transform has already been "
       
   108                             "requested on this image." % transform_name)
       
   109     self._transform_map[transform] = True
       
   110 
   102 
   111   def _update_dimensions(self):
   103   def _update_dimensions(self):
   112     """Updates the width and height fields of the image.
   104     """Updates the width and height fields of the image.
   113 
   105 
   114     Raises:
   106     Raises:
   170       while offset < size and ord(self._image_data[offset]) == 0xFF:
   162       while offset < size and ord(self._image_data[offset]) == 0xFF:
   171         offset += 1
   163         offset += 1
   172       if (offset < size and ord(self._image_data[offset]) & 0xF0 == 0xC0 and
   164       if (offset < size and ord(self._image_data[offset]) & 0xF0 == 0xC0 and
   173           ord(self._image_data[offset]) != 0xC4):
   165           ord(self._image_data[offset]) != 0xC4):
   174         offset += 4
   166         offset += 4
   175         if offset + 4 < size:
   167         if offset + 4 <= size:
   176           self._height, self._width = struct.unpack(
   168           self._height, self._width = struct.unpack(
   177               ">HH",
   169               ">HH",
   178               self._image_data[offset:offset + 4])
   170               self._image_data[offset:offset + 4])
   179           break
   171           break
   180         else:
   172         else:
   181           raise BadImageError("Corrupt JPEG format")
   173           raise BadImageError("Corrupt JPEG format")
   182       elif offset + 2 <= size:
   174       elif offset + 3 <= size:
   183         offset += 1
   175         offset += 1
   184         offset += struct.unpack(">H", self._image_data[offset:offset + 2])[0]
   176         offset += struct.unpack(">H", self._image_data[offset:offset + 2])[0]
   185       else:
   177       else:
   186         raise BadImageError("Corrupt JPEG format")
   178         raise BadImageError("Corrupt JPEG format")
   187     if self._height is None or self._width is None:
   179     if self._height is None or self._width is None:
   197     if self._image_data.startswith("II"):
   189     if self._image_data.startswith("II"):
   198       endianness = "<"
   190       endianness = "<"
   199     else:
   191     else:
   200       endianness = ">"
   192       endianness = ">"
   201     ifd_offset = struct.unpack(endianness + "I", self._image_data[4:8])[0]
   193     ifd_offset = struct.unpack(endianness + "I", self._image_data[4:8])[0]
   202     if ifd_offset < size + 14:
   194     if ifd_offset + 14 <= size:
   203       ifd_size = struct.unpack(
   195       ifd_size = struct.unpack(
   204           endianness + "H",
   196           endianness + "H",
   205           self._image_data[ifd_offset:ifd_offset + 2])[0]
   197           self._image_data[ifd_offset:ifd_offset + 2])[0]
   206       ifd_offset += 2
   198       ifd_offset += 2
   207       for unused_i in range(0, ifd_size):
   199       for unused_i in range(0, ifd_size):
   289       height: int, height (in pixels) to change the image height to.
   281       height: int, height (in pixels) to change the image height to.
   290 
   282 
   291     Raises:
   283     Raises:
   292       TypeError when width or height is not either 'int' or 'long' types.
   284       TypeError when width or height is not either 'int' or 'long' types.
   293       BadRequestError when there is something wrong with the given height or
   285       BadRequestError when there is something wrong with the given height or
   294         width or if a Resize has already been requested on this image.
   286         width or if MAX_TRANSFORMS_PER_REQUEST transforms have already been
       
   287         requested on this image.
   295     """
   288     """
   296     if (not isinstance(width, (int, long)) or
   289     if (not isinstance(width, (int, long)) or
   297         not isinstance(height, (int, long))):
   290         not isinstance(height, (int, long))):
   298       raise TypeError("Width and height must be integers.")
   291       raise TypeError("Width and height must be integers.")
   299     if width < 0 or height < 0:
   292     if width < 0 or height < 0:
   303       raise BadRequestError("At least one of width or height must be > 0.")
   296       raise BadRequestError("At least one of width or height must be > 0.")
   304 
   297 
   305     if width > 4000 or height > 4000:
   298     if width > 4000 or height > 4000:
   306       raise BadRequestError("Both width and height must be < 4000.")
   299       raise BadRequestError("Both width and height must be < 4000.")
   307 
   300 
   308     self._check_transform_limits(
   301     self._check_transform_limits()
   309         images_service_pb.ImagesServiceTransform.RESIZE)
       
   310 
   302 
   311     transform = images_service_pb.Transform()
   303     transform = images_service_pb.Transform()
   312     transform.set_width(width)
   304     transform.set_width(width)
   313     transform.set_height(height)
   305     transform.set_height(height)
   314 
   306 
   321       degrees: int, must be a multiple of 90.
   313       degrees: int, must be a multiple of 90.
   322 
   314 
   323     Raises:
   315     Raises:
   324       TypeError when degrees is not either 'int' or 'long' types.
   316       TypeError when degrees is not either 'int' or 'long' types.
   325       BadRequestError when there is something wrong with the given degrees or
   317       BadRequestError when there is something wrong with the given degrees or
   326       if a Rotate trasnform has already been requested.
   318       if MAX_TRANSFORMS_PER_REQUEST transforms have already been requested.
   327     """
   319     """
   328     if not isinstance(degrees, (int, long)):
   320     if not isinstance(degrees, (int, long)):
   329       raise TypeError("Degrees must be integers.")
   321       raise TypeError("Degrees must be integers.")
   330 
   322 
   331     if degrees % 90 != 0:
   323     if degrees % 90 != 0:
   332       raise BadRequestError("degrees argument must be multiple of 90.")
   324       raise BadRequestError("degrees argument must be multiple of 90.")
   333 
   325 
   334     degrees = degrees % 360
   326     degrees = degrees % 360
   335 
   327 
   336     self._check_transform_limits(
   328     self._check_transform_limits()
   337         images_service_pb.ImagesServiceTransform.ROTATE)
       
   338 
   329 
   339     transform = images_service_pb.Transform()
   330     transform = images_service_pb.Transform()
   340     transform.set_rotate(degrees)
   331     transform.set_rotate(degrees)
   341 
   332 
   342     self._transforms.append(transform)
   333     self._transforms.append(transform)
   343 
   334 
   344   def horizontal_flip(self):
   335   def horizontal_flip(self):
   345     """Flip the image horizontally.
   336     """Flip the image horizontally.
   346 
   337 
   347     Raises:
   338     Raises:
   348       BadRequestError if a HorizontalFlip has already been requested on the
   339       BadRequestError if MAX_TRANSFORMS_PER_REQUEST transforms have already been
   349       image.
   340       requested on the image.
   350     """
   341     """
   351     self._check_transform_limits(
   342     self._check_transform_limits()
   352         images_service_pb.ImagesServiceTransform.HORIZONTAL_FLIP)
       
   353 
   343 
   354     transform = images_service_pb.Transform()
   344     transform = images_service_pb.Transform()
   355     transform.set_horizontal_flip(True)
   345     transform.set_horizontal_flip(True)
   356 
   346 
   357     self._transforms.append(transform)
   347     self._transforms.append(transform)
   358 
   348 
   359   def vertical_flip(self):
   349   def vertical_flip(self):
   360     """Flip the image vertically.
   350     """Flip the image vertically.
   361 
   351 
   362     Raises:
   352     Raises:
   363       BadRequestError if a HorizontalFlip has already been requested on the
   353       BadRequestError if MAX_TRANSFORMS_PER_REQUEST transforms have already been
   364       image.
   354       requested on the image.
   365     """
   355     """
   366     self._check_transform_limits(
   356     self._check_transform_limits()
   367         images_service_pb.ImagesServiceTransform.VERTICAL_FLIP)
       
   368     transform = images_service_pb.Transform()
   357     transform = images_service_pb.Transform()
   369     transform.set_vertical_flip(True)
   358     transform.set_vertical_flip(True)
   370 
   359 
   371     self._transforms.append(transform)
   360     self._transforms.append(transform)
   372 
   361 
   403       bottom_y: float value between 0.0 and 1.0 (inclusive).
   392       bottom_y: float value between 0.0 and 1.0 (inclusive).
   404 
   393 
   405     Raises:
   394     Raises:
   406       TypeError if the args are not of type 'float'.
   395       TypeError if the args are not of type 'float'.
   407       BadRequestError when there is something wrong with the given bounding box
   396       BadRequestError when there is something wrong with the given bounding box
   408         or if there has already been a crop transform requested for this image.
   397         or if MAX_TRANSFORMS_PER_REQUEST transforms have already been requested
       
   398         for this image.
   409     """
   399     """
   410     self._validate_crop_arg(left_x, "left_x")
   400     self._validate_crop_arg(left_x, "left_x")
   411     self._validate_crop_arg(top_y, "top_y")
   401     self._validate_crop_arg(top_y, "top_y")
   412     self._validate_crop_arg(right_x, "right_x")
   402     self._validate_crop_arg(right_x, "right_x")
   413     self._validate_crop_arg(bottom_y, "bottom_y")
   403     self._validate_crop_arg(bottom_y, "bottom_y")
   415     if left_x >= right_x:
   405     if left_x >= right_x:
   416       raise BadRequestError("left_x must be less than right_x")
   406       raise BadRequestError("left_x must be less than right_x")
   417     if top_y >= bottom_y:
   407     if top_y >= bottom_y:
   418       raise BadRequestError("top_y must be less than bottom_y")
   408       raise BadRequestError("top_y must be less than bottom_y")
   419 
   409 
   420     self._check_transform_limits(images_service_pb.ImagesServiceTransform.CROP)
   410     self._check_transform_limits()
   421 
   411 
   422     transform = images_service_pb.Transform()
   412     transform = images_service_pb.Transform()
   423     transform.set_crop_left_x(left_x)
   413     transform.set_crop_left_x(left_x)
   424     transform.set_crop_top_y(top_y)
   414     transform.set_crop_top_y(top_y)
   425     transform.set_crop_right_x(right_x)
   415     transform.set_crop_right_x(right_x)
   431     """Automatically adjust image contrast and color levels.
   421     """Automatically adjust image contrast and color levels.
   432 
   422 
   433     This is similar to the "I'm Feeling Lucky" button in Picasa.
   423     This is similar to the "I'm Feeling Lucky" button in Picasa.
   434 
   424 
   435     Raises:
   425     Raises:
   436       BadRequestError if this transform has already been requested for this
   426       BadRequestError if MAX_TRANSFORMS_PER_REQUEST transforms have already
   437       image.
   427       been requested for this image.
   438     """
   428     """
   439     self._check_transform_limits(
   429     self._check_transform_limits()
   440         images_service_pb.ImagesServiceTransform.IM_FEELING_LUCKY)
       
   441     transform = images_service_pb.Transform()
   430     transform = images_service_pb.Transform()
   442     transform.set_autolevels(True)
   431     transform.set_autolevels(True)
   443 
   432 
   444     self._transforms.append(transform)
   433     self._transforms.append(transform)
   445 
   434 
   502       else:
   491       else:
   503         raise Error()
   492         raise Error()
   504 
   493 
   505     self._image_data = response.image().content()
   494     self._image_data = response.image().content()
   506     self._transforms = []
   495     self._transforms = []
   507     self._transform_map.clear()
       
   508     self._width = None
   496     self._width = None
   509     self._height = None
   497     self._height = None
   510     return self._image_data
   498     return self._image_data
   511 
   499 
   512   @property
   500   @property
   538     output_encoding: a value from OUTPUT_ENCODING_TYPES.
   526     output_encoding: a value from OUTPUT_ENCODING_TYPES.
   539 
   527 
   540   Raises:
   528   Raises:
   541     TypeError when width or height not either 'int' or 'long' types.
   529     TypeError when width or height not either 'int' or 'long' types.
   542     BadRequestError when there is something wrong with the given height or
   530     BadRequestError when there is something wrong with the given height or
   543       width or if a Resize has already been requested on this image.
   531       width.
   544     Error when something went wrong with the call.  See Image.ExecuteTransforms
   532     Error when something went wrong with the call.  See Image.ExecuteTransforms
   545       for more details.
   533       for more details.
   546   """
   534   """
   547   image = Image(image_data)
   535   image = Image(image_data)
   548   image.resize(width, height)
   536   image.resize(width, height)
   557     degrees: value from ROTATE_DEGREE_VALUES.
   545     degrees: value from ROTATE_DEGREE_VALUES.
   558     output_encoding: a value from OUTPUT_ENCODING_TYPES.
   546     output_encoding: a value from OUTPUT_ENCODING_TYPES.
   559 
   547 
   560   Raises:
   548   Raises:
   561     TypeError when degrees is not either 'int' or 'long' types.
   549     TypeError when degrees is not either 'int' or 'long' types.
   562     BadRequestError when there is something wrong with the given degrees or
   550     BadRequestError when there is something wrong with the given degrees.
   563     if a Rotate trasnform has already been requested.
       
   564     Error when something went wrong with the call.  See Image.ExecuteTransforms
   551     Error when something went wrong with the call.  See Image.ExecuteTransforms
   565       for more details.
   552       for more details.
   566   """
   553   """
   567   image = Image(image_data)
   554   image = Image(image_data)
   568   image.rotate(degrees)
   555   image.rotate(degrees)
   617     bottom_y: float value between 0.0 and 1.0 (inclusive).
   604     bottom_y: float value between 0.0 and 1.0 (inclusive).
   618     output_encoding: a value from OUTPUT_ENCODING_TYPES.
   605     output_encoding: a value from OUTPUT_ENCODING_TYPES.
   619 
   606 
   620   Raises:
   607   Raises:
   621     TypeError if the args are not of type 'float'.
   608     TypeError if the args are not of type 'float'.
   622     BadRequestError when there is something wrong with the given bounding box
   609     BadRequestError when there is something wrong with the given bounding box.
   623       or if there has already been a crop transform requested for this image.
       
   624     Error when something went wrong with the call.  See Image.ExecuteTransforms
   610     Error when something went wrong with the call.  See Image.ExecuteTransforms
   625       for more details.
   611       for more details.
   626   """
   612   """
   627   image = Image(image_data)
   613   image = Image(image_data)
   628   image.crop(left_x, top_y, right_x, bottom_y)
   614   image.crop(left_x, top_y, right_x, bottom_y)