thirdparty/google_appengine/google/appengine/api/images/__init__.py
changeset 828 f5fd65cc3bf3
parent 109 620f9b141567
child 1278 a7766286a7be
--- a/thirdparty/google_appengine/google/appengine/api/images/__init__.py	Tue Jan 20 01:12:43 2009 +0000
+++ b/thirdparty/google_appengine/google/appengine/api/images/__init__.py	Tue Jan 20 13:19:45 2009 +0000
@@ -30,6 +30,8 @@
 
 
 
+import struct
+
 from google.appengine.api import apiproxy_stub_map
 from google.appengine.api.images import images_service_pb
 from google.appengine.runtime import apiproxy_errors
@@ -83,6 +85,8 @@
     self._image_data = image_data
     self._transforms = []
     self._transform_map = {}
+    self._width = None
+    self._height = None
 
   def _check_transform_limits(self, transform):
     """Ensure some simple limits on the number of transforms allowed.
@@ -104,6 +108,175 @@
                             "requested on this image." % transform_name)
     self._transform_map[transform] = True
 
+  def _update_dimensions(self):
+    """Updates the width and height fields of the image.
+
+    Raises:
+      NotImageError if the image data is not an image.
+      BadImageError if the image data is corrupt.
+    """
+    size = len(self._image_data)
+    if size >= 6 and self._image_data.startswith("GIF"):
+      self._update_gif_dimensions()
+    elif size >= 8 and self._image_data.startswith("\x89PNG\x0D\x0A\x1A\x0A"):
+      self._update_png_dimensions()
+    elif size >= 2 and self._image_data.startswith("\xff\xD8"):
+      self._update_jpeg_dimensions()
+    elif (size >= 8 and (self._image_data.startswith("II\x2a\x00") or
+                         self._image_data.startswith("MM\x00\x2a"))):
+      self._update_tiff_dimensions()
+    elif size >= 2 and self._image_data.startswith("BM"):
+      self._update_bmp_dimensions()
+    elif size >= 4 and self._image_data.startswith("\x00\x00\x01\x00"):
+      self._update_ico_dimensions()
+    else:
+      raise NotImageError("Unrecognized image format")
+
+  def _update_gif_dimensions(self):
+    """Updates the width and height fields of the gif image.
+
+    Raises:
+      BadImageError if the image string is not a valid gif image.
+    """
+    size = len(self._image_data)
+    if size >= 10:
+      self._width, self._height = struct.unpack("<HH", self._image_data[6:10])
+    else:
+      raise BadImageError("Corrupt GIF format")
+
+  def _update_png_dimensions(self):
+    """Updates the width and height fields of the png image.
+
+    Raises:
+      BadImageError if the image string is not a valid png image.
+    """
+    size = len(self._image_data)
+    if size >= 24 and self._image_data[12:16] == "IHDR":
+      self._width, self._height = struct.unpack(">II", self._image_data[16:24])
+    else:
+      raise BadImageError("Corrupt PNG format")
+
+  def _update_jpeg_dimensions(self):
+    """Updates the width and height fields of the jpeg image.
+
+    Raises:
+      BadImageError if the image string is not a valid jpeg image.
+    """
+    size = len(self._image_data)
+    offset = 2
+    while offset < size:
+      while offset < size and ord(self._image_data[offset]) != 0xFF:
+        offset += 1
+      while offset < size and ord(self._image_data[offset]) == 0xFF:
+        offset += 1
+      if (offset < size and ord(self._image_data[offset]) & 0xF0 == 0xC0 and
+          ord(self._image_data[offset]) != 0xC4):
+        offset += 4
+        if offset + 4 < size:
+          self._height, self._width = struct.unpack(
+              ">HH",
+              self._image_data[offset:offset + 4])
+          break
+        else:
+          raise BadImageError("Corrupt JPEG format")
+      elif offset + 2 <= size:
+        offset += 1
+        offset += struct.unpack(">H", self._image_data[offset:offset + 2])[0]
+      else:
+        raise BadImageError("Corrupt JPEG format")
+    if self._height is None or self._width is None:
+      raise BadImageError("Corrupt JPEG format")
+
+  def _update_tiff_dimensions(self):
+    """Updates the width and height fields of the tiff image.
+
+    Raises:
+      BadImageError if the image string is not a valid tiff image.
+    """
+    size = len(self._image_data)
+    if self._image_data.startswith("II"):
+      endianness = "<"
+    else:
+      endianness = ">"
+    ifd_offset = struct.unpack(endianness + "I", self._image_data[4:8])[0]
+    if ifd_offset < size + 14:
+      ifd_size = struct.unpack(
+          endianness + "H",
+          self._image_data[ifd_offset:ifd_offset + 2])[0]
+      ifd_offset += 2
+      for unused_i in range(0, ifd_size):
+        if ifd_offset + 12 <= size:
+          tag = struct.unpack(
+              endianness + "H",
+              self._image_data[ifd_offset:ifd_offset + 2])[0]
+          if tag == 0x100 or tag == 0x101:
+            value_type = struct.unpack(
+                endianness + "H",
+                self._image_data[ifd_offset + 2:ifd_offset + 4])[0]
+            if value_type == 3:
+              format = endianness + "H"
+              end_offset = ifd_offset + 10
+            elif value_type == 4:
+              format = endianness + "I"
+              end_offset = ifd_offset + 12
+            else:
+              format = endianness + "B"
+              end_offset = ifd_offset + 9
+            if tag == 0x100:
+              self._width = struct.unpack(
+                  format,
+                  self._image_data[ifd_offset + 8:end_offset])[0]
+              if self._height is not None:
+                break
+            else:
+              self._height = struct.unpack(
+                  format,
+                  self._image_data[ifd_offset + 8:end_offset])[0]
+              if self._width is not None:
+                break
+          ifd_offset += 12
+        else:
+          raise BadImageError("Corrupt TIFF format")
+    if self._width is None or self._height is None:
+      raise BadImageError("Corrupt TIFF format")
+
+  def _update_bmp_dimensions(self):
+    """Updates the width and height fields of the bmp image.
+
+    Raises:
+      BadImageError if the image string is not a valid bmp image.
+    """
+    size = len(self._image_data)
+    if size >= 18:
+      header_length = struct.unpack("<I", self._image_data[14:18])[0]
+      if ((header_length == 40 or header_length == 108 or
+           header_length == 124 or header_length == 64) and size >= 26):
+        self._width, self._height = struct.unpack("<II",
+                                                  self._image_data[18:26])
+      elif header_length == 12 and size >= 22:
+        self._width, self._height = struct.unpack("<HH",
+                                                  self._image_data[18:22])
+      else:
+        raise BadImageError("Corrupt BMP format")
+    else:
+      raise BadImageError("Corrupt BMP format")
+
+  def _update_ico_dimensions(self):
+    """Updates the width and height fields of the ico image.
+
+    Raises:
+      BadImageError if the image string is not a valid ico image.
+    """
+    size = len(self._image_data)
+    if size >= 8:
+      self._width, self._height = struct.unpack("<BB", self._image_data[6:8])
+      if not self._width:
+        self._width = 256
+      if not self._height:
+        self._height = 256
+    else:
+      raise BadImageError("Corrupt ICO format")
+
   def resize(self, width=0, height=0):
     """Resize the image maintaining the aspect ratio.
 
@@ -332,8 +505,24 @@
     self._image_data = response.image().content()
     self._transforms = []
     self._transform_map.clear()
+    self._width = None
+    self._height = None
     return self._image_data
 
+  @property
+  def width(self):
+    """Gets the width of the image."""
+    if self._width is None:
+      self._update_dimensions()
+    return self._width
+
+  @property
+  def height(self):
+    """Gets the height of the image."""
+    if self._height is None:
+      self._update_dimensions()
+    return self._height
+
 
 def resize(image_data, width=0, height=0, output_encoding=PNG):
   """Resize a given image file maintaining the aspect ratio.