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 TOP_LEFT = images_service_pb.CompositeImageOptions.TOP_LEFT |
|
46 TOP_CENTER = images_service_pb.CompositeImageOptions.TOP |
|
47 TOP_RIGHT = images_service_pb.CompositeImageOptions.TOP_RIGHT |
|
48 CENTER_LEFT = images_service_pb.CompositeImageOptions.LEFT |
|
49 CENTER_CENTER = images_service_pb.CompositeImageOptions.CENTER |
|
50 CENTER_RIGHT = images_service_pb.CompositeImageOptions.RIGHT |
|
51 BOTTOM_LEFT = images_service_pb.CompositeImageOptions.BOTTOM_LEFT |
|
52 BOTTOM_CENTER = images_service_pb.CompositeImageOptions.BOTTOM |
|
53 BOTTOM_RIGHT = images_service_pb.CompositeImageOptions.BOTTOM_RIGHT |
|
54 |
|
55 ANCHOR_TYPES = frozenset([TOP_LEFT, TOP_CENTER, TOP_RIGHT, CENTER_LEFT, |
|
56 CENTER_CENTER, CENTER_RIGHT, BOTTOM_LEFT, |
|
57 BOTTOM_CENTER, BOTTOM_RIGHT]) |
|
58 |
45 MAX_TRANSFORMS_PER_REQUEST = 10 |
59 MAX_TRANSFORMS_PER_REQUEST = 10 |
|
60 |
|
61 MAX_COMPOSITES_PER_REQUEST = 16 |
46 |
62 |
47 |
63 |
48 class Error(Exception): |
64 class Error(Exception): |
49 """Base error class for this module.""" |
65 """Base error class for this module.""" |
50 |
66 |
509 """Gets the height of the image.""" |
525 """Gets the height of the image.""" |
510 if self._height is None: |
526 if self._height is None: |
511 self._update_dimensions() |
527 self._update_dimensions() |
512 return self._height |
528 return self._height |
513 |
529 |
|
530 def histogram(self): |
|
531 """Calculates the histogram of the image. |
|
532 |
|
533 Returns: 3 256-element lists containing the number of occurences of each |
|
534 value of each color in the order RGB. As described at |
|
535 http://en.wikipedia.org/wiki/Color_histogram for N = 256. i.e. the first |
|
536 value of the first list contains the number of pixels with a red value of |
|
537 0, the second the number with a red value of 1. |
|
538 |
|
539 Raises: |
|
540 NotImageError when the image data given is not an image. |
|
541 BadImageError when the image data given is corrupt. |
|
542 LargeImageError when the image data given is too large to process. |
|
543 Error when something unknown, but bad, happens. |
|
544 """ |
|
545 request = images_service_pb.ImagesHistogramRequest() |
|
546 response = images_service_pb.ImagesHistogramResponse() |
|
547 |
|
548 request.mutable_image().set_content(self._image_data) |
|
549 try: |
|
550 apiproxy_stub_map.MakeSyncCall("images", |
|
551 "Histogram", |
|
552 request, |
|
553 response) |
|
554 except apiproxy_errors.ApplicationError, e: |
|
555 if (e.application_error == |
|
556 images_service_pb.ImagesServiceError.NOT_IMAGE): |
|
557 raise NotImageError() |
|
558 elif (e.application_error == |
|
559 images_service_pb.ImagesServiceError.BAD_IMAGE_DATA): |
|
560 raise BadImageError() |
|
561 elif (e.application_error == |
|
562 images_service_pb.ImagesServiceError.IMAGE_TOO_LARGE): |
|
563 raise LargeImageError() |
|
564 else: |
|
565 raise Error() |
|
566 histogram = response.histogram() |
|
567 return [histogram.red_list(), |
|
568 histogram.green_list(), |
|
569 histogram.blue_list()] |
|
570 |
514 |
571 |
515 def resize(image_data, width=0, height=0, output_encoding=PNG): |
572 def resize(image_data, width=0, height=0, output_encoding=PNG): |
516 """Resize a given image file maintaining the aspect ratio. |
573 """Resize a given image file maintaining the aspect ratio. |
517 |
574 |
518 If both width and height are specified, the more restricting of the two |
575 If both width and height are specified, the more restricting of the two |
629 for more details. |
686 for more details. |
630 """ |
687 """ |
631 image = Image(image_data) |
688 image = Image(image_data) |
632 image.im_feeling_lucky() |
689 image.im_feeling_lucky() |
633 return image.execute_transforms(output_encoding=output_encoding) |
690 return image.execute_transforms(output_encoding=output_encoding) |
|
691 |
|
692 def composite(inputs, width, height, color=0, output_encoding=PNG): |
|
693 """Composite one or more images onto a canvas. |
|
694 |
|
695 Args: |
|
696 inputs: a list of tuples (image_data, x_offset, y_offset, opacity, anchor) |
|
697 where |
|
698 image_data: str, source image data. |
|
699 x_offset: x offset in pixels from the anchor position |
|
700 y_offset: y offset in piyels from the anchor position |
|
701 opacity: opacity of the image specified as a float in range [0.0, 1.0] |
|
702 anchor: anchoring point from ANCHOR_POINTS. The anchor point of the image |
|
703 is aligned with the same anchor point of the canvas. e.g. TOP_RIGHT would |
|
704 place the top right corner of the image at the top right corner of the |
|
705 canvas then apply the x and y offsets. |
|
706 width: canvas width in pixels. |
|
707 height: canvas height in pixels. |
|
708 color: canvas background color encoded as a 32 bit unsigned int where each |
|
709 color channel is represented by one byte in order ARGB. |
|
710 output_encoding: a value from OUTPUT_ENCODING_TYPES. |
|
711 |
|
712 Returns: |
|
713 str, image data of the composited image. |
|
714 |
|
715 Raises: |
|
716 TypeError If width, height, color, x_offset or y_offset are not of type |
|
717 int or long or if opacity is not a float |
|
718 BadRequestError If more than MAX_TRANSFORMS_PER_REQUEST compositions have |
|
719 been requested, if the canvas width or height is greater than 4000 or less |
|
720 than or equal to 0, if the color is invalid or if for any composition |
|
721 option, the opacity is outside the range [0,1] or the anchor is invalid. |
|
722 """ |
|
723 if (not isinstance(width, (int, long)) or |
|
724 not isinstance(height, (int, long)) or |
|
725 not isinstance(color, (int, long))): |
|
726 raise TypeError("Width, height and color must be integers.") |
|
727 if output_encoding not in OUTPUT_ENCODING_TYPES: |
|
728 raise BadRequestError("Output encoding type '%s' not in recognized set " |
|
729 "%s" % (output_encoding, OUTPUT_ENCODING_TYPES)) |
|
730 |
|
731 if not inputs: |
|
732 raise BadRequestError("Must provide at least one input") |
|
733 if len(inputs) > MAX_COMPOSITES_PER_REQUEST: |
|
734 raise BadRequestError("A maximum of %d composition operations can be" |
|
735 "performed in a single request" % |
|
736 MAX_COMPOSITES_PER_REQUEST) |
|
737 |
|
738 if width <= 0 or height <= 0: |
|
739 raise BadRequestError("Width and height must be > 0.") |
|
740 if width > 4000 or height > 4000: |
|
741 raise BadRequestError("Width and height must be <= 4000.") |
|
742 |
|
743 if color > 0xffffffff or color < 0: |
|
744 raise BadRequestError("Invalid color") |
|
745 if color >= 0x80000000: |
|
746 color -= 0x100000000 |
|
747 |
|
748 image_map = {} |
|
749 |
|
750 request = images_service_pb.ImagesCompositeRequest() |
|
751 response = images_service_pb.ImagesTransformResponse() |
|
752 for (image, x, y, opacity, anchor) in inputs: |
|
753 if not image: |
|
754 raise BadRequestError("Each input must include an image") |
|
755 if (not isinstance(x, (int, long)) or |
|
756 not isinstance(y, (int, long)) or |
|
757 not isinstance(opacity, (float))): |
|
758 raise TypeError("x_offset, y_offset must be integers and opacity must" |
|
759 "be a float") |
|
760 if x > 4000 or x < -4000: |
|
761 raise BadRequestError("xOffsets must be in range [-4000, 4000]") |
|
762 if y > 4000 or y < -4000: |
|
763 raise BadRequestError("yOffsets must be in range [-4000, 4000]") |
|
764 if opacity < 0 or opacity > 1: |
|
765 raise BadRequestError("Opacity must be in the range 0.0 to 1.0") |
|
766 if anchor not in ANCHOR_TYPES: |
|
767 raise BadRequestError("Anchor type '%s' not in recognized set %s" % |
|
768 (anchor, ANCHOR_TYPES)) |
|
769 if image not in image_map: |
|
770 image_map[image] = request.image_size() |
|
771 request.add_image().set_content(image) |
|
772 |
|
773 option = request.add_options() |
|
774 option.set_x_offset(x) |
|
775 option.set_y_offset(y) |
|
776 option.set_opacity(opacity) |
|
777 option.set_anchor(anchor) |
|
778 option.set_source_index(image_map[image]) |
|
779 |
|
780 request.mutable_canvas().mutable_output().set_mime_type(output_encoding) |
|
781 request.mutable_canvas().set_width(width) |
|
782 request.mutable_canvas().set_height(height) |
|
783 request.mutable_canvas().set_color(color) |
|
784 |
|
785 try: |
|
786 apiproxy_stub_map.MakeSyncCall("images", |
|
787 "Composite", |
|
788 request, |
|
789 response) |
|
790 except apiproxy_errors.ApplicationError, e: |
|
791 if (e.application_error == |
|
792 images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA): |
|
793 raise BadRequestError() |
|
794 elif (e.application_error == |
|
795 images_service_pb.ImagesServiceError.NOT_IMAGE): |
|
796 raise NotImageError() |
|
797 elif (e.application_error == |
|
798 images_service_pb.ImagesServiceError.BAD_IMAGE_DATA): |
|
799 raise BadImageError() |
|
800 elif (e.application_error == |
|
801 images_service_pb.ImagesServiceError.IMAGE_TOO_LARGE): |
|
802 raise LargeImageError() |
|
803 elif (e.application_error == |
|
804 images_service_pb.ImagesServiceError.UNSPECIFIED_ERROR): |
|
805 raise TransformationError() |
|
806 else: |
|
807 raise Error() |
|
808 |
|
809 return response.image().content() |
|
810 |
|
811 |
|
812 def histogram(image_data): |
|
813 """Calculates the histogram of the given image. |
|
814 |
|
815 Args: |
|
816 image_data: str, source image data. |
|
817 Returns: 3 256-element lists containing the number of occurences of each |
|
818 value of each color in the order RGB. |
|
819 |
|
820 Raises: |
|
821 NotImageError when the image data given is not an image. |
|
822 BadImageError when the image data given is corrupt. |
|
823 LargeImageError when the image data given is too large to process. |
|
824 Error when something unknown, but bad, happens. |
|
825 """ |
|
826 image = Image(image_data) |
|
827 return image.histogram() |