thirdparty/google_appengine/google/appengine/api/images/__init__.py
changeset 2273 e4cb9c53db3e
parent 1278 a7766286a7be
child 3031 7678f72140e6
--- a/thirdparty/google_appengine/google/appengine/api/images/__init__.py	Tue Apr 21 16:28:13 2009 +0000
+++ b/thirdparty/google_appengine/google/appengine/api/images/__init__.py	Fri Apr 24 14:16:00 2009 +0000
@@ -42,8 +42,24 @@
 
 OUTPUT_ENCODING_TYPES = frozenset([JPEG, PNG])
 
+TOP_LEFT = images_service_pb.CompositeImageOptions.TOP_LEFT
+TOP_CENTER = images_service_pb.CompositeImageOptions.TOP
+TOP_RIGHT = images_service_pb.CompositeImageOptions.TOP_RIGHT
+CENTER_LEFT = images_service_pb.CompositeImageOptions.LEFT
+CENTER_CENTER = images_service_pb.CompositeImageOptions.CENTER
+CENTER_RIGHT = images_service_pb.CompositeImageOptions.RIGHT
+BOTTOM_LEFT = images_service_pb.CompositeImageOptions.BOTTOM_LEFT
+BOTTOM_CENTER = images_service_pb.CompositeImageOptions.BOTTOM
+BOTTOM_RIGHT = images_service_pb.CompositeImageOptions.BOTTOM_RIGHT
+
+ANCHOR_TYPES = frozenset([TOP_LEFT, TOP_CENTER, TOP_RIGHT, CENTER_LEFT,
+                          CENTER_CENTER, CENTER_RIGHT, BOTTOM_LEFT,
+                          BOTTOM_CENTER, BOTTOM_RIGHT])
+
 MAX_TRANSFORMS_PER_REQUEST = 10
 
+MAX_COMPOSITES_PER_REQUEST = 16
+
 
 class Error(Exception):
   """Base error class for this module."""
@@ -296,7 +312,7 @@
       raise BadRequestError("At least one of width or height must be > 0.")
 
     if width > 4000 or height > 4000:
-      raise BadRequestError("Both width and height must be < 4000.")
+      raise BadRequestError("Both width and height must be <= 4000.")
 
     self._check_transform_limits()
 
@@ -424,7 +440,7 @@
 
     Raises:
       BadRequestError if MAX_TRANSFORMS_PER_REQUEST transforms have already
-      been requested for this image.
+        been requested for this image.
     """
     self._check_transform_limits()
     transform = images_service_pb.Transform()
@@ -511,6 +527,47 @@
       self._update_dimensions()
     return self._height
 
+  def histogram(self):
+    """Calculates the histogram of the image.
+
+    Returns: 3 256-element lists containing the number of occurences of each
+    value of each color in the order RGB. As described at
+    http://en.wikipedia.org/wiki/Color_histogram for N = 256. i.e. the first
+    value of the first list contains the number of pixels with a red value of
+    0, the second the number with a red value of 1.
+
+    Raises:
+      NotImageError when the image data given is not an image.
+      BadImageError when the image data given is corrupt.
+      LargeImageError when the image data given is too large to process.
+      Error when something unknown, but bad, happens.
+    """
+    request = images_service_pb.ImagesHistogramRequest()
+    response = images_service_pb.ImagesHistogramResponse()
+
+    request.mutable_image().set_content(self._image_data)
+    try:
+      apiproxy_stub_map.MakeSyncCall("images",
+                                     "Histogram",
+                                     request,
+                                     response)
+    except apiproxy_errors.ApplicationError, e:
+      if (e.application_error ==
+          images_service_pb.ImagesServiceError.NOT_IMAGE):
+        raise NotImageError()
+      elif (e.application_error ==
+            images_service_pb.ImagesServiceError.BAD_IMAGE_DATA):
+        raise BadImageError()
+      elif (e.application_error ==
+            images_service_pb.ImagesServiceError.IMAGE_TOO_LARGE):
+        raise LargeImageError()
+      else:
+        raise Error()
+    histogram = response.histogram()
+    return [histogram.red_list(),
+            histogram.green_list(),
+            histogram.blue_list()]
+
 
 def resize(image_data, width=0, height=0, output_encoding=PNG):
   """Resize a given image file maintaining the aspect ratio.
@@ -631,3 +688,140 @@
   image = Image(image_data)
   image.im_feeling_lucky()
   return image.execute_transforms(output_encoding=output_encoding)
+
+def composite(inputs, width, height, color=0, output_encoding=PNG):
+  """Composite one or more images onto a canvas.
+
+  Args:
+    inputs: a list of tuples (image_data, x_offset, y_offset, opacity, anchor)
+    where
+      image_data: str, source image data.
+      x_offset: x offset in pixels from the anchor position
+      y_offset: y offset in piyels from the anchor position
+      opacity: opacity of the image specified as a float in range [0.0, 1.0]
+      anchor: anchoring point from ANCHOR_POINTS. The anchor point of the image
+      is aligned with the same anchor point of the canvas. e.g. TOP_RIGHT would
+      place the top right corner of the image at the top right corner of the
+      canvas then apply the x and y offsets.
+    width: canvas width in pixels.
+    height: canvas height in pixels.
+    color: canvas background color encoded as a 32 bit unsigned int where each
+    color channel is represented by one byte in order ARGB.
+    output_encoding: a value from OUTPUT_ENCODING_TYPES.
+
+  Returns:
+      str, image data of the composited image.
+
+  Raises:
+    TypeError If width, height, color, x_offset or y_offset are not of type
+    int or long or if opacity is not a float
+    BadRequestError If more than MAX_TRANSFORMS_PER_REQUEST compositions have
+    been requested, if the canvas width or height is greater than 4000 or less
+    than or equal to 0, if the color is invalid or if for any composition
+    option, the opacity is outside the range [0,1] or the anchor is invalid.
+  """
+  if (not isinstance(width, (int, long)) or
+      not isinstance(height, (int, long)) or
+      not isinstance(color, (int, long))):
+    raise TypeError("Width, height and color must be integers.")
+  if output_encoding not in OUTPUT_ENCODING_TYPES:
+    raise BadRequestError("Output encoding type '%s' not in recognized set "
+                          "%s" % (output_encoding, OUTPUT_ENCODING_TYPES))
+
+  if not inputs:
+    raise BadRequestError("Must provide at least one input")
+  if len(inputs) > MAX_COMPOSITES_PER_REQUEST:
+    raise BadRequestError("A maximum of %d composition operations can be"
+                          "performed in a single request" %
+                          MAX_COMPOSITES_PER_REQUEST)
+
+  if width <= 0 or height <= 0:
+    raise BadRequestError("Width and height must be > 0.")
+  if width > 4000 or height > 4000:
+    raise BadRequestError("Width and height must be <= 4000.")
+
+  if color > 0xffffffff or color < 0:
+    raise BadRequestError("Invalid color")
+  if color >= 0x80000000:
+    color -= 0x100000000
+
+  image_map = {}
+
+  request = images_service_pb.ImagesCompositeRequest()
+  response = images_service_pb.ImagesTransformResponse()
+  for (image, x, y, opacity, anchor) in inputs:
+    if not image:
+      raise BadRequestError("Each input must include an image")
+    if (not isinstance(x, (int, long)) or
+        not isinstance(y, (int, long)) or
+        not isinstance(opacity, (float))):
+      raise TypeError("x_offset, y_offset must be integers and opacity must"
+                      "be a float")
+    if x > 4000 or x < -4000:
+      raise BadRequestError("xOffsets must be in range [-4000, 4000]")
+    if y > 4000 or y < -4000:
+      raise BadRequestError("yOffsets must be in range [-4000, 4000]")
+    if opacity < 0 or opacity > 1:
+      raise BadRequestError("Opacity must be in the range 0.0 to 1.0")
+    if anchor not in ANCHOR_TYPES:
+      raise BadRequestError("Anchor type '%s' not in recognized set %s" %
+                            (anchor, ANCHOR_TYPES))
+    if image not in image_map:
+      image_map[image] = request.image_size()
+      request.add_image().set_content(image)
+
+    option = request.add_options()
+    option.set_x_offset(x)
+    option.set_y_offset(y)
+    option.set_opacity(opacity)
+    option.set_anchor(anchor)
+    option.set_source_index(image_map[image])
+
+  request.mutable_canvas().mutable_output().set_mime_type(output_encoding)
+  request.mutable_canvas().set_width(width)
+  request.mutable_canvas().set_height(height)
+  request.mutable_canvas().set_color(color)
+
+  try:
+    apiproxy_stub_map.MakeSyncCall("images",
+                                   "Composite",
+                                   request,
+                                   response)
+  except apiproxy_errors.ApplicationError, e:
+    if (e.application_error ==
+        images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA):
+      raise BadRequestError()
+    elif (e.application_error ==
+          images_service_pb.ImagesServiceError.NOT_IMAGE):
+      raise NotImageError()
+    elif (e.application_error ==
+          images_service_pb.ImagesServiceError.BAD_IMAGE_DATA):
+      raise BadImageError()
+    elif (e.application_error ==
+          images_service_pb.ImagesServiceError.IMAGE_TOO_LARGE):
+      raise LargeImageError()
+    elif (e.application_error ==
+          images_service_pb.ImagesServiceError.UNSPECIFIED_ERROR):
+      raise TransformationError()
+    else:
+      raise Error()
+
+  return response.image().content()
+
+
+def histogram(image_data):
+  """Calculates the histogram of the given image.
+
+  Args:
+    image_data: str, source image data.
+  Returns: 3 256-element lists containing the number of occurences of each
+  value of each color in the order RGB.
+
+  Raises:
+    NotImageError when the image data given is not an image.
+    BadImageError when the image data given is corrupt.
+    LargeImageError when the image data given is too large to process.
+    Error when something unknown, but bad, happens.
+  """
+  image = Image(image_data)
+  return image.histogram()