Switch to side-by-side view

--- a/Allura/allura/model/filesystem.py
+++ b/Allura/allura/model/filesystem.py
@@ -1,7 +1,10 @@
+from mimetypes import guess_type
 from datetime import datetime
 
+import Image
 import pymongo
 from gridfs import GridFS
+import tg
 
 from .session import project_doc_session
 
@@ -13,6 +16,12 @@
 
 from .session import project_orm_session
 
+SUPPORTED_BY_PIL=set([
+        'image/jpg',
+        'image/png',
+        'image/jpeg',
+        'image/gif'])
+
 class File(MappedClass):
     class __mongometa__:
         session = project_orm_session
@@ -23,6 +32,8 @@
     _id=FieldProperty(schema.ObjectId)
     # We don't actually store the filename here, just a randomish hash
     filename=FieldProperty(str, if_missing=lambda:str(pymongo.bson.ObjectId()))
+    # There is much weirdness in gridfs; the fp.content_type property is stored
+    # in the contentType field
     contentType=FieldProperty(str)
     length=FieldProperty(int)
     chunkSize=FieldProperty(int)
@@ -75,11 +86,74 @@
         fn = str(pymongo.bson.ObjectId())
         fp = cls._fs().open(fn, 'w', collection=cls._grid_coll_name())
         fp.content_type = content_type
-        fp.metadata = dict(meta_kwargs)
+        fp.metadata =dict(meta_kwargs)
         return fp
+
+    @classmethod
+    def save_image(cls, filename, fp,
+                   content_type=None,
+                   thumbnail_size=None,
+                   thumbnail_meta=None,
+                   square=False,
+                   save_original=False,
+                   original_meta=None):
+        if content_type is None:
+            content_type = guess_type(filename)
+            if content_type[0]: content_type = content_type[0]
+            else: content_type = 'application/octet-stream'
+        if not content_type.lower() in SUPPORTED_BY_PIL:
+            return None, None
+        thumbnail_meta = thumbnail_meta or {}
+        image = Image.open(fp)
+        format = image.format
+        if save_original:
+            original_meta = original_meta or {}
+            with cls.create(content_type=content_type,
+                            filename=filename,
+                            **original_meta) as fp_w:
+                filename = fp_w.name
+                if 'transparency' in image.info:
+                    image.save(fp_w, format, transparency=image.info['transparency'])
+                else:
+                    image.save(fp_w, format)
+            original = cls.query.get(filename=fp_w.name)
+        else:
+            original=None
+        if square:
+            height = image.size[0]
+            width = image.size[1]
+            sz = max(width, height)
+            if 'transparency' in image.info:
+                new_image = Image.new('RGBA', (sz,sz))
+            else:
+                new_image = Image.new('RGB', (sz,sz), 'white')
+            if height < width:
+                # image is wider than tall, so center horizontally
+                new_image.paste(image, ((width-height)/2, 0))
+            elif height > width:
+                # image is taller than wide, so center vertically
+                new_image.paste(image, (0, (height-width)/2))
+            image = new_image
+        if thumbnail_size:
+            image.thumbnail(thumbnail_size, Image.ANTIALIAS)
+        with cls.create(content_type=content_type,
+                        filename=filename,
+                        **thumbnail_meta) as fp_w:
+            if 'transparency' in image.info:
+                image.save(fp_w, format, transparency=image.info['transparency'])
+            else:
+                image.save(fp_w, format)
+        thumbnail=cls.query.get(filename=fp_w.name)
+        return original, thumbnail
+        
+    def is_image(self):
+        if not self.contentType:
+            return False
+        return self.contentType.lower() in SUPPORTED_BY_PIL
 
     def open(self, mode='r'):
         return self._fs().open(self.filename, mode, collection=self._grid_coll_name())
 
     def delete(self):
         self.remove(self.filename)
+