def _transform_image(self, blob_key, options): """Construct and execute a transform request using the images stub. Args: blob_key: A str containing the blob_key of the image to transform. options: A str containing the resize and crop options to apply to the image. Returns: A str containing the tranformed (if necessary) image. """ resize, crop = self._parse_options(options) image_data = images_service_pb.ImageData() image_data.set_blob_key(blob_key) image = _get_images_stub()._OpenImageData(image_data) original_mime_type = image.format width, height = image.size # Crop to square if necessary if crop: crop_xform = None if width > height: # landscape: slice the sides crop_xform = images_service_pb.Transform() delta = (width - height) / (width * 2.0) crop_xform.set_crop_left_x(delta) crop_xform.set_crop_right_x(1.0 - delta) elif width < height: # portrait: slice the top and bottom with bias crop_xform = images_service_pb.Transform() delta = (height - width) / (height * 2.0) top_delta = max(0.0, delta - 0.25) bottom_delta = 1.0 - (2.0 * delta) + top_delta crop_xform.set_crop_top_y(top_delta) crop_xform.set_crop_bottom_y(bottom_delta) if crop_xform: image = _get_images_stub()._Crop(image, crop_xform) # Resize if resize is None: if width > _DEFAULT_SERVING_SIZE or height > _DEFAULT_SERVING_SIZE: resize = _DEFAULT_SERVING_SIZE # resize value of 0 is valid and translates to 'serve at original size'. if resize: # Note that resize transform maintains the image aspect ratio. resize_xform = images_service_pb.Transform() resize_xform.set_width(resize) resize_xform.set_height(resize) image = _get_images_stub()._Resize(image, resize_xform) output_settings = images_service_pb.OutputSettings() # EncodeImage only saves out to JPEG or PNG. All image formats other than # GIF or PNG, will be served as a JPEG. output_mime_type = images_service_pb.OutputSettings.JPEG if original_mime_type in ['PNG', 'GIF']: output_mime_type = images_service_pb.OutputSettings.PNG output_settings.set_mime_type(output_mime_type) return (_get_images_stub()._EncodeImage(image, output_settings), _MIME_TYPE_MAP[output_mime_type])
def _TransformImage(self, blob_key, options): """Construct and execute transform request to the images stub. Args: blob_key: blob_key to the image to transform. options: resize and crop option string to apply to the image. Returns: The tranformed (if necessary) image bytes. """ resize, crop = self._ParseOptions(options) image_data = images_service_pb.ImageData() image_data.set_blob_key(blob_key) image = self._images_stub._OpenImageData(image_data) original_mime_type = image.format width, height = image.size if crop: crop_xform = None if width > height: crop_xform = images_service_pb.Transform() delta = (width - height) / (width * 2.0) crop_xform.set_crop_left_x(delta) crop_xform.set_crop_right_x(1.0 - delta) elif width < height: crop_xform = images_service_pb.Transform() delta = (height - width) / (height * 2.0) top_delta = max(0.0, delta - 0.25) bottom_delta = 1.0 - (2.0 * delta) + top_delta crop_xform.set_crop_top_y(top_delta) crop_xform.set_crop_bottom_y(bottom_delta) if crop_xform: image = self._images_stub._Crop(image, crop_xform) if resize is None: if width > DEFAULT_SERVING_SIZE or height > DEFAULT_SERVING_SIZE: resize = DEFAULT_SERVING_SIZE if resize: resize_xform = images_service_pb.Transform() resize_xform.set_width(resize) resize_xform.set_height(resize) image = self._images_stub._Resize(image, resize_xform) output_settings = images_service_pb.OutputSettings() output_mime_type = images_service_pb.OutputSettings.JPEG if original_mime_type in ['PNG', 'GIF']: output_mime_type = images_service_pb.OutputSettings.PNG output_settings.set_mime_type(output_mime_type) return (self._images_stub._EncodeImage(image, output_settings), self._mime_type_map[output_mime_type])
def rotate(self, degrees): """Rotate an image a given number of degrees clockwise. Args: degrees: int, must be a multiple of 90. Raises: TypeError when degrees is not either 'int' or 'long' types. BadRequestError when there is something wrong with the given degrees or if MAX_TRANSFORMS_PER_REQUEST transforms have already been requested. """ if not isinstance(degrees, (int, long)): raise TypeError("Degrees must be integers.") if degrees % 90 != 0: raise BadRequestError("degrees argument must be multiple of 90.") degrees = degrees % 360 self._check_transform_limits() transform = images_service_pb.Transform() transform.set_rotate(degrees) self._transforms.append(transform)
def resize(self, width=0, height=0, crop_to_fit=False, crop_offset_x=0.5, crop_offset_y=0.5): """Resize the image maintaining the aspect ratio. If both width and height are specified, the more restricting of the two values will be used when resizing the image. The maximum dimension allowed for both width and height is 4000 pixels. If both width and height are specified and crop_to_fit is True, the less restricting of the two values will be used when resizing and the image will be cropped to fit the specified size. In this case the center of cropping can be adjusted by crop_offset_x and crop_offset_y. Args: width: int, width (in pixels) to change the image width to. height: int, height (in pixels) to change the image height to. crop_to_fit: If True and both width and height are specified, the image is cropped after resize to fit the specified dimensions. crop_offset_x: float value between 0.0 and 1.0, 0 is left and 1 is right, default is 0.5, the center of image. crop_offset_y: float value between 0.0 and 1.0, 0 is top and 1 is bottom, default is 0.5, the center of image. Raises: TypeError when width or height is not either 'int' or 'long' types. BadRequestError when there is something wrong with the given height or width or if MAX_TRANSFORMS_PER_REQUEST transforms have already been requested on this image. """ if (not isinstance(width, (int, long)) or not isinstance(height, (int, long))): raise TypeError("Width and height must be integers.") if width < 0 or height < 0: raise BadRequestError("Width and height must be >= 0.") if not width and not height: 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.") if not isinstance(crop_to_fit, bool): raise TypeError("crop_to_fit must be boolean.") if crop_to_fit and not (width and height): raise BadRequestError("Both width and height must be > 0 when " "crop_to_fit is specified") self._validate_crop_arg(crop_offset_x, "crop_offset_x") self._validate_crop_arg(crop_offset_y, "crop_offset_y") self._check_transform_limits() transform = images_service_pb.Transform() transform.set_width(width) transform.set_height(height) transform.set_crop_to_fit(crop_to_fit) transform.set_crop_offset_x(crop_offset_x) transform.set_crop_offset_y(crop_offset_y) self._transforms.append(transform)
def expect_resize(self, resize): """Setup a mox expectation to images_stub._Resize.""" resize_xform = images_service_pb.Transform() resize_xform.set_width(resize) resize_xform.set_height(resize) self._images_stub._Resize(mox.IsA(MockImage), resize_xform).AndReturn(self._image)
def expect_crop(self, left_x=None, right_x=None, top_y=None, bottom_y=None): """Setup a mox expectation to images_stub._Crop.""" crop_xform = images_service_pb.Transform() if left_x is not None: if not isinstance(left_x, float): raise self.failureException('Crop argument must be a float.') crop_xform.set_crop_left_x(left_x) if right_x is not None: if not isinstance(right_x, float): raise self.failureException('Crop argument must be a float.') crop_xform.set_crop_right_x(right_x) if top_y is not None: if not isinstance(top_y, float): raise self.failureException('Crop argument must be a float.') crop_xform.set_crop_top_y(top_y) if bottom_y is not None: if not isinstance(bottom_y, float): raise self.failureException('Crop argument must be a float.') crop_xform.set_crop_bottom_y(bottom_y) self._images_stub._Crop(mox.IsA(MockImage), crop_xform).AndReturn(self._image)
def vertical_flip(self): """Flip the image vertically. Raises: BadRequestError if MAX_TRANSFORMS_PER_REQUEST transforms have already been requested on the image. """ self._check_transform_limits() transform = images_service_pb.Transform() transform.set_vertical_flip(True) self._transforms.append(transform)
def im_feeling_lucky(self): """Automatically adjust image contrast and color levels. This is similar to the "I'm Feeling Lucky" button in Picasa. Raises: BadRequestError if MAX_TRANSFORMS_PER_REQUEST transforms have already been requested for this image. """ self._check_transform_limits() transform = images_service_pb.Transform() transform.set_autolevels(True) self._transforms.append(transform)
def crop(self, left_x, top_y, right_x, bottom_y): """Crop the image. The four arguments are the scaling numbers to describe the bounding box which will crop the image. The upper left point of the bounding box will be at (left_x*image_width, top_y*image_height) the lower right point will be at (right_x*image_width, bottom_y*image_height). Args: left_x: float value between 0.0 and 1.0 (inclusive). top_y: float value between 0.0 and 1.0 (inclusive). right_x: float value between 0.0 and 1.0 (inclusive). bottom_y: float value between 0.0 and 1.0 (inclusive). Raises: TypeError if the args are not of type 'float'. BadRequestError when there is something wrong with the given bounding box or if MAX_TRANSFORMS_PER_REQUEST transforms have already been requested for this image. """ self._validate_crop_arg(left_x, "left_x") self._validate_crop_arg(top_y, "top_y") self._validate_crop_arg(right_x, "right_x") self._validate_crop_arg(bottom_y, "bottom_y") if left_x >= right_x: raise BadRequestError("left_x must be less than right_x") if top_y >= bottom_y: raise BadRequestError("top_y must be less than bottom_y") self._check_transform_limits() transform = images_service_pb.Transform() transform.set_crop_left_x(left_x) transform.set_crop_top_y(top_y) transform.set_crop_right_x(right_x) transform.set_crop_bottom_y(bottom_y) self._transforms.append(transform)
def resize(self, width=0, height=0): """Resize the image maintaining the aspect ratio. If both width and height are specified, the more restricting of the two values will be used when resizing the photo. The maximum dimension allowed for both width and height is 4000 pixels. Args: width: int, width (in pixels) to change the image width to. height: int, height (in pixels) to change the image height to. Raises: TypeError when width or height is not either 'int' or 'long' types. BadRequestError when there is something wrong with the given height or width or if MAX_TRANSFORMS_PER_REQUEST transforms have already been requested on this image. """ if (not isinstance(width, (int, long)) or not isinstance(height, (int, long))): raise TypeError("Width and height must be integers.") if width < 0 or height < 0: raise BadRequestError("Width and height must be >= 0.") if not width and not height: 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.") self._check_transform_limits() transform = images_service_pb.Transform() transform.set_width(width) transform.set_height(height) self._transforms.append(transform)