Anonymous avatar Anonymous committed 02b0af5

Added setup.py and relocated source dir

Comments (0)

Files changed (25)

imagekit/__init__.py

+"""
+
+Django ImageKit
+
+Author: Justin Driscoll <justin.driscoll@gmail.com>
+Version: 1.0
+
+"""
+VERSION = (1, 0, 'pre')
+
+
+
+        

imagekit/config.py

+""" Default ImageKit configuration """
+
+from imagekit.specs import ImageSpec
+from imagekit import processors
+    
+class ResizeThumbnail(processors.Resize):
+    width = 100
+    height = 50
+    crop = True
+    
+class EnhanceSmall(processors.Adjustment):
+    contrast = 1.2
+    sharpness = 1.1
+    
+class SampleReflection(processors.Reflection):
+    size = 0.5
+    background_color = "#000000"
+    
+class DjangoAdminThumbnail(ImageSpec):
+    access_as = 'admin_thumbnail'
+    processors = [ResizeThumbnail, EnhanceSmall, SampleReflection]
+# Required PIL classes may or may not be available from the root namespace
+# depending on the installation method used.
+try:
+    import Image
+    import ImageFile
+    import ImageFilter
+    import ImageEnhance
+    import ImageColor
+except ImportError:
+    try:
+        from PIL import Image
+        from PIL import ImageFile
+        from PIL import ImageFilter
+        from PIL import ImageEnhance
+        from PIL import ImageColor
+    except ImportError:
+        raise ImportError('ImageKit was unable to import the Python Imaging Library. Please confirm it`s installed and available on your current Python path.')

imagekit/management/__init__.py

+

imagekit/management/commands/__init__.py

+

imagekit/management/commands/ikflush.py

+from django.db.models.loading import cache
+from django.core.management.base import BaseCommand, CommandError
+from optparse import make_option
+from imagekit.models import ImageModel
+from imagekit.specs import ImageSpec
+
+
+class Command(BaseCommand):
+    help = ('Clears all ImageKit cached files.')
+    args = '[apps]'
+    requires_model_validation = True
+    can_import_settings = True
+
+    def handle(self, *args, **options):
+        return flush_cache(args, options)
+
+def flush_cache(apps, options):
+    """ Clears the image cache
+    
+    """
+    apps = [a.strip(',') for a in apps]
+    if apps:
+        print 'Flushing cache for %s...' % ', '.join(apps)
+    else:
+        print 'Flushing caches...'
+        
+    for app_label in apps:
+        app = cache.get_app(app_label)
+        models = [m for m in cache.get_models(app) if issubclass(m, ImageModel)]
+
+    for model in models:
+        for obj in model.objects.all():
+            for spec in model._ik.specs:
+                prop = getattr(obj, spec.name(), None)
+                if prop is not None:
+                    prop._delete()
+                if spec.pre_cache:
+                    prop._create()            

imagekit/models.py

+import os
+from datetime import datetime
+from django.conf import settings
+from django.core.files.base import ContentFile
+from django.db import models
+from django.db.models.base import ModelBase
+from django.utils.translation import ugettext_lazy as _
+
+from imagekit import specs
+from imagekit.lib import *
+from imagekit.options import Options
+from imagekit.utils import img_to_fobj
+
+# Modify image file buffer size.
+ImageFile.MAXBLOCK = getattr(settings, 'PIL_IMAGEFILE_MAXBLOCK', 256 * 2 ** 10)
+
+
+class ImageModelBase(ModelBase):
+    def __init__(cls, name, bases, attrs):
+        parents = [b for b in bases if isinstance(b, ImageModelBase)]
+        if not parents:
+            return
+        user_opts = getattr(cls, 'IKOptions', None)
+        opts = Options(user_opts)
+        try:
+            module = __import__(opts.spec_module,  {}, {}, [''])
+        except ImportError:
+            raise ImportError('Unable to load imagekit config module: %s' % \
+                opts.spec_module)    
+        for spec in [spec for spec in module.__dict__.values() \
+                     if isinstance(spec, type) \
+                     and issubclass(spec, specs.ImageSpec) \
+                     and spec != specs.ImageSpec]:
+            setattr(cls, spec.name(), specs.Descriptor(spec))
+            opts.specs.append(spec)
+        setattr(cls, '_ik', opts)
+
+
+class ImageModel(models.Model):
+    """ Abstract base class implementing all core ImageKit functionality
+    
+    Subclasses of ImageModel can override the inner IKOptions class to customize
+    storage locations and other options.
+    
+    """
+    __metaclass__ = ImageModelBase
+
+    class Meta:
+        abstract = True
+        
+    class IKOptions:
+        pass
+        
+    def admin_thumbnail_view(self):
+        prop = getattr(self, self._ik.admin_thumbnail_spec, None)
+        if prop is None:
+            return 'An "%s" image spec has not been defined.' % \
+              self._ik.admin_thumbnail_spec
+        else:
+            if hasattr(self, 'get_absolute_url'):
+                return u'<a href="%s"><img src="%s"></a>' % \
+                    (self.get_absolute_url(), prop.url)
+            else:
+                return u'<a href="%s"><img src="%s"></a>' % \
+                    (self._imgfield.url, prop.url)
+    admin_thumbnail_view.short_description = _('Thumbnail')
+    admin_thumbnail_view.allow_tags = True
+    
+    @property
+    def _imgfield(self):
+        return getattr(self, self._ik.image_field)
+
+    def _clear_cache(self):
+        for spec in self._ik.specs:
+            prop = getattr(self, spec.name())
+            prop._delete()
+
+    def _pre_cache(self):
+        for spec in self._ik.specs:
+            if spec.pre_cache:
+                prop = getattr(self, spec.name())
+                prop._create()
+
+    def save(self, clear_cache=True, *args, **kwargs):
+        is_new_object = self._get_pk_val is None
+        super(ImageModel, self).save(*args, **kwargs)
+        if is_new_object:
+            clear_cache = False
+            spec = self._ik.preprocessor_spec
+            if spec is not None:
+                newfile = self._imgfield.storage.open(str(self._imgfield))
+                img = Image.open(newfile)
+                img = spec.process(img, None)
+                format = img.format or 'JPEG'
+                if format != 'JPEG':
+                    imgfile = img_to_fobj(img, format)
+                else:
+                    imgfile = img_to_fobj(img, format,
+                                          quality=int(spec.quality),
+                                          optimize=True)
+                content = ContentFile(imgfile.read())
+                newfile.close()
+                name = str(self._imgfield)
+                self._imgfield.storage.delete(name)
+                self._imgfield.storage.save(name, content)
+        if clear_cache:
+            self._clear_cache()
+            self._pre_cache()
+
+    def delete(self):
+        assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname)
+        self._clear_cache()
+        models.Model.delete(self)

imagekit/options.py

+# Imagekit options
+from imagekit import processors
+from imagekit.specs import ImageSpec
+    
+
+class Options(object):
+    """ Class handling per-model imagekit options
+
+    """
+    image_field = 'image'
+    crop_horz_field = 'crop_horz'
+    crop_vert_field = 'crop_vert'
+    preprocessor_spec = None
+    cache_dir = 'images'
+    save_count_as = None
+    cache_filename_format = "%(filename)s_%(specname)s.%(extension)s"
+    admin_thumbnail_spec = 'admin_thumbnail'
+    spec_module = 'imagekit.config'
+    
+    def __init__(self, opts):        
+        for key, value in opts.__dict__.iteritems():
+            setattr(self, key, value)
+            self.specs = []

imagekit/processors.py

+""" Imagekit Image "ImageProcessors"
+
+A processor defines a set of class variables (optional) and a 
+class method named "process" which processes the supplied image using
+the class properties as settings. The process method can be overridden as well allowing user to define their
+own effects/processes entirely.
+
+"""
+from imagekit.lib import *
+
+class ImageProcessor(object):
+    """ Base image processor class """
+    @classmethod
+    def process(cls, image, obj=None):
+        return image
+
+
+class Adjustment(ImageProcessor):
+    color = 1.0
+    brightness = 1.0
+    contrast = 1.0
+    sharpness = 1.0
+
+    @classmethod
+    def process(cls, image, obj=None):
+        for name in ['Color', 'Brightness', 'Contrast', 'Sharpness']:
+            factor = getattr(cls, name.lower())
+            if factor != 1.0:
+                image = getattr(ImageEnhance, name)(image).enhance(factor)
+        return image
+
+
+class Reflection(ImageProcessor):
+    background_color = '#FFFFFF'
+    size = 0.0
+    opacity = 0.6
+    
+    @classmethod
+    def process(cls, image, obj=None):
+        # convert bgcolor string to rgb value
+        background_color = ImageColor.getrgb(cls.background_color)
+        # copy orignial image and flip the orientation
+        reflection = image.copy().transpose(Image.FLIP_TOP_BOTTOM)
+        # create a new image filled with the bgcolor the same size
+        background = Image.new("RGB", image.size, background_color)
+        # calculate our alpha mask
+        start = int(255 - (255 * cls.opacity)) # The start of our gradient
+        steps = int(255 * cls.size) # the number of intermedite values
+        increment = (255 - start) / float(steps)
+        mask = Image.new('L', (1, 255))
+        for y in range(255):
+            if y < steps:
+                val = int(y * increment + start)
+            else:
+                val = 255
+            mask.putpixel((0, y), val)
+        alpha_mask = mask.resize(image.size)
+        # merge the reflection onto our background color using the alpha mask
+        reflection = Image.composite(background, reflection, alpha_mask)
+        # crop the reflection
+        reflection_height = int(image.size[1] * cls.size)
+        reflection = reflection.crop((0, 0, image.size[0], reflection_height))
+        # create new image sized to hold both the original image and the reflection
+        composite = Image.new("RGB", (image.size[0], image.size[1]+reflection_height), background_color)
+        # paste the orignal image and the reflection into the composite image
+        composite.paste(image, (0, 0))
+        composite.paste(reflection, (0, image.size[1]))
+        # return the image complete with reflection effect
+        return composite
+
+
+class Resize(ImageProcessor):
+    width = None
+    height = None
+    crop = False
+    upscale = False
+    
+    @classmethod
+    def process(cls, image, obj=None):
+        cur_width, cur_height = image.size
+        if cls.crop:
+            crop_horz = getattr(obj, obj._ik.crop_horz_field, 1)
+            crop_vert = getattr(obj, obj._ik.crop_vert_field, 1)
+            ratio = max(float(cls.width)/cur_width, float(cls.height)/cur_height)
+            resize_x, resize_y = ((cur_width * ratio), (cur_height * ratio))
+            crop_x, crop_y = (abs(cls.width - resize_x), abs(cls.height - resize_y))
+            x_diff, y_diff = (int(crop_x / 2), int(crop_y / 2))
+            box_left, box_right = {
+                0: (0, cls.width),
+                1: (int(x_diff), int(x_diff + cls.width)),
+                2: (int(crop_x), int(resize_x)),
+            }[crop_horz]
+            box_upper, box_lower = {
+                0: (0, cls.height),
+                1: (int(y_diff), int(y_diff + cls.height)),
+                2: (int(crop_y), int(resize_y)),
+            }[crop_vert]
+            box = (box_left, box_upper, box_right, box_lower)
+            image = image.resize((int(resize_x), int(resize_y)), Image.ANTIALIAS).crop(box)
+        else:
+            if not cls.width is None and not cls.height is None:
+                ratio = min(float(cls.width)/cur_width,
+                            float(cls.height)/cur_height)
+            else:
+                if cls.width is None:
+                    ratio = float(cls.height)/cur_height
+                else:
+                    ratio = float(cls.width)/cur_width
+            new_dimensions = (int(round(cur_width*ratio)),
+                              int(round(cur_height*ratio)))
+            if new_dimensions[0] > cur_width or \
+               new_dimensions[1] > cur_height:
+                if not cls.upscale:
+                    return image
+            image = image.resize(new_dimensions, Image.ANTIALIAS)
+        return image
+
+    
+class Transpose(ImageProcessor):
+    """ Rotates or flips the image
+    
+    Method should be one of the following strings:
+        - FLIP_LEFT RIGHT
+        - FLIP_TOP_BOTTOM
+        - ROTATE_90
+        - ROTATE_270
+        - ROTATE_180
+        
+    """
+    method = 'FLIP_LEFT_RIGHT'
+    
+    @classmethod
+    def process(cls, image, obj=None):
+        return image.transpose(getattr(Image, cls.method))

imagekit/specs.py

+""" ImageKit image specifications
+
+All imagekit specifications must inherit from the ImageSpec class. Models
+inheriting from ImageModel will be modified with a descriptor/accessor for each
+spec found.
+
+"""
+import os
+from imagekit.lib import *
+from imagekit.utils import img_to_fobj
+from django.core.files.base import ContentFile
+
+class ImageSpec(object):
+    pre_cache = False
+    quality = 70
+    increment_count = False
+    processors = []
+    
+    @classmethod
+    def name(cls):
+        return getattr(cls, 'access_as', cls.__name__.lower())
+        
+    @classmethod
+    def process(cls, image, obj):
+        processed_image = image.copy()
+        for proc in cls.processors:
+            processed_image = proc.process(processed_image, obj)
+        return processed_image
+        
+
+class Accessor(object):
+    def __init__(self, obj, spec):
+        self._img = None
+        self._obj = obj
+        self.spec = spec
+        
+    def _get_imgfile(self):
+        format = self._img.format or 'JPEG'
+        if format != 'JPEG':
+            imgfile = img_to_fobj(self._img, format)
+        else:
+            imgfile = img_to_fobj(self._img, format,
+                                  quality=int(self.spec.quality),
+                                  optimize=True)
+        return imgfile
+        
+    def _create(self):
+        if self._exists():
+            return
+        # process the original image file
+        fp = self._obj._imgfield.file.file
+        fp.seek(0)
+        self._img = self.spec.process(Image.open(fp), self._obj)
+        # save the new image to the cache
+        content = ContentFile(self._get_imgfile().read())
+        self._obj._imgfield.storage.save(self._path(), content)
+        
+    def _delete(self):
+        self._obj._imgfield.storage.delete(self._path())
+
+    def _exists(self):
+        return self._obj._imgfield.storage.exists(self._path())
+
+    def _name(self):
+        filename, ext =  os.path.splitext(os.path.basename(self._obj._imgfield.path))
+        return self._obj._ik.cache_filename_format % \
+            {'filename': filename,
+             'specname': self.spec.name(),
+             'extension': ext.lstrip('.')}
+
+    def _path(self):
+        return os.path.join(self._obj._ik.cache_dir, self._name())
+
+    @property
+    def url(self):
+        self._create()
+        if self.spec.increment_count:
+            fieldname = self._obj._ik.save_count_as
+            if fieldname is not None:
+                current_count = getattr(self._obj, fieldname)
+                setattr(self._obj, fieldname, current_count + 1)
+                self._obj.save(clear_cache=False)
+        return self._obj._imgfield.storage.url(self._path())
+        
+    @property
+    def file(self):
+        self._create()
+        return self._obj._imgfield.storage.open(self._path())
+        
+    @property
+    def image(self):
+        if self._img is None:
+            self._create()
+            if self._img is None:
+                self._img = Image.open(self.file)
+        return self._img
+        
+    @property
+    def width(self):
+        return self.image.size[0]
+        
+    @property
+    def height(self):
+        return self.image.size[1]
+
+
+class Descriptor(object):
+    def __init__(self, spec):
+        self._spec = spec
+
+    def __get__(self, obj, type=None):
+        return Accessor(obj, self._spec)

imagekit/tests.py

+import os
+import tempfile
+import unittest
+from django.conf import settings
+from django.core.files.base import ContentFile
+from django.db import models
+from django.test import TestCase
+
+from imagekit import processors
+from imagekit.models import ImageModel
+from imagekit.specs import ImageSpec
+from imagekit.lib import Image
+
+
+class ResizeToWidth(processors.Resize):
+    width = 100
+    
+class ResizeToHeight(processors.Resize):
+    height = 100
+    
+class ResizeToFit(processors.Resize):
+    width = 100
+    height = 100
+    
+class ResizeCropped(ResizeToFit):
+    crop = ('center', 'center')
+
+class TestResizeToWidth(ImageSpec):
+    access_as = 'to_width'
+    processors = [ResizeToWidth]
+
+class TestResizeToHeight(ImageSpec):
+    access_as = 'to_height'
+    processors = [ResizeToHeight]
+    
+class TestResizeCropped(ImageSpec):
+    access_as = 'cropped'
+    processors = [ResizeCropped]
+
+class TestPhoto(ImageModel):
+    """ Minimal ImageModel class for testing """
+    image = models.ImageField(upload_to='images')
+    
+    class IKConfig:
+        config_module = 'imagekit.tests'
+    
+
+class IKTest(TestCase):
+    """ Base TestCase class """
+    def setUp(self):
+        # create a test image using tempfile and PIL
+        self.tmp = tempfile.TemporaryFile()
+        Image.new('RGB', (800, 600)).save(self.tmp, 'JPEG')
+        self.tmp.seek(0)
+        self.p = TestPhoto()
+        self.p.image.save(os.path.basename('test.jpg'),
+                           ContentFile(self.tmp.read()))
+        self.p.save()
+        # destroy temp file
+        self.tmp.close()
+        
+    def test_setup(self):
+        self.assertEqual(self.p.image.width, 800)
+        self.assertEqual(self.p.image.height, 600)
+        
+    def test_to_width(self):
+        self.assertEqual(self.p.to_width.width, 100)
+        self.assertEqual(self.p.to_width.height, 75)
+        
+    def test_to_height(self):
+        self.assertEqual(self.p.to_height.width, 133)
+        self.assertEqual(self.p.to_height.height, 100)
+        
+    def test_crop(self):
+        self.assertEqual(self.p.cropped.width, 100)
+        self.assertEqual(self.p.cropped.height, 100)
+
+    def test_url(self):
+        tup = (settings.MEDIA_URL, self.p._ik.cache_dir, 'test_to_width.jpg')
+        self.assertEqual(self.p.to_width.url, "%s%s/%s" % tup)
+ 
+    def tearDown(self):
+        # make sure image file is deleted
+        path = self.p.image.path
+        self.p.delete()
+        self.failIf(os.path.isfile(path))

imagekit/utils.py

+""" ImageKit utility functions """
+
+import tempfile
+
+def img_to_fobj(img, format, **kwargs):
+    tmp = tempfile.TemporaryFile()
+    if format != 'JPEG':
+        try:
+            img.save(tmp, format, **kwargs)
+            return
+        except KeyError:
+            pass
+    img.save(tmp, format, **kwargs)
+    tmp.seek(0)
+    return tmp
+#/usr/bin/env python
+from setuptools import setup, find_packages
+ 
+setup(
+    name='django-imagekit',
+    version='0.1',
+    description='Automated image processing for Django models.',
+    author='Justin Driscoll',
+    author_email='justin.driscoll@gmail.com',
+    url='http://bitbucket.org/jdriscoll/django-imagekit/',
+    packages=find_packages(),
+    classifiers=[
+        'Development Status :: 3 - Alpha',
+        'Environment :: Web Environment',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: BSD License',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python',
+        'Framework :: Django',
+    ],
+    # Make setuptools include all data files under version control,
+    # svn and CVS by default
+    include_package_data=True,
+    zip_safe=False,
+    # Tells setuptools to download setuptools_git before running setup.py so
+    # it can find the data files under Hg version control.
+    setup_requires=['hg.setuptools'],
+)

src/imagekit/__init__.py

-"""
-
-Django ImageKit
-
-Author: Justin Driscoll <justin.driscoll@gmail.com>
-Version: 1.0
-
-"""
-VERSION = (1, 0, 'pre')
-
-
-
-        

src/imagekit/config.py

-""" Default ImageKit configuration """
-
-from imagekit.specs import ImageSpec
-from imagekit import processors
-    
-class ResizeThumbnail(processors.Resize):
-    width = 100
-    height = 50
-    crop = True
-    
-class EnhanceSmall(processors.Adjustment):
-    contrast = 1.2
-    sharpness = 1.1
-    
-class SampleReflection(processors.Reflection):
-    size = 0.5
-    background_color = "#000000"
-    
-class DjangoAdminThumbnail(ImageSpec):
-    access_as = 'admin_thumbnail'
-    processors = [ResizeThumbnail, EnhanceSmall, SampleReflection]

src/imagekit/lib.py

-# Required PIL classes may or may not be available from the root namespace
-# depending on the installation method used.
-try:
-    import Image
-    import ImageFile
-    import ImageFilter
-    import ImageEnhance
-    import ImageColor
-except ImportError:
-    try:
-        from PIL import Image
-        from PIL import ImageFile
-        from PIL import ImageFilter
-        from PIL import ImageEnhance
-        from PIL import ImageColor
-    except ImportError:
-        raise ImportError('ImageKit was unable to import the Python Imaging Library. Please confirm it`s installed and available on your current Python path.')

src/imagekit/management/__init__.py

-

src/imagekit/management/commands/__init__.py

-

src/imagekit/management/commands/ikflush.py

-from django.db.models.loading import cache
-from django.core.management.base import BaseCommand, CommandError
-from optparse import make_option
-from imagekit.models import ImageModel
-from imagekit.specs import ImageSpec
-
-
-class Command(BaseCommand):
-    help = ('Clears all ImageKit cached files.')
-    args = '[apps]'
-    requires_model_validation = True
-    can_import_settings = True
-
-    def handle(self, *args, **options):
-        return flush_cache(args, options)
-
-def flush_cache(apps, options):
-    """ Clears the image cache
-    
-    """
-    apps = [a.strip(',') for a in apps]
-    if apps:
-        print 'Flushing cache for %s...' % ', '.join(apps)
-    else:
-        print 'Flushing caches...'
-        
-    for app_label in apps:
-        app = cache.get_app(app_label)
-        models = [m for m in cache.get_models(app) if issubclass(m, ImageModel)]
-
-    for model in models:
-        for obj in model.objects.all():
-            for spec in model._ik.specs:
-                prop = getattr(obj, spec.name(), None)
-                if prop is not None:
-                    prop._delete()
-                if spec.pre_cache:
-                    prop._create()            

src/imagekit/models.py

-import os
-from datetime import datetime
-from django.conf import settings
-from django.core.files.base import ContentFile
-from django.db import models
-from django.db.models.base import ModelBase
-from django.utils.translation import ugettext_lazy as _
-
-from imagekit import specs
-from imagekit.lib import *
-from imagekit.options import Options
-from imagekit.utils import img_to_fobj
-
-# Modify image file buffer size.
-ImageFile.MAXBLOCK = getattr(settings, 'PIL_IMAGEFILE_MAXBLOCK', 256 * 2 ** 10)
-
-
-class ImageModelBase(ModelBase):
-    def __init__(cls, name, bases, attrs):
-        parents = [b for b in bases if isinstance(b, ImageModelBase)]
-        if not parents:
-            return
-        user_opts = getattr(cls, 'IKOptions', None)
-        opts = Options(user_opts)
-        try:
-            module = __import__(opts.spec_module,  {}, {}, [''])
-        except ImportError:
-            raise ImportError('Unable to load imagekit config module: %s' % \
-                opts.spec_module)    
-        for spec in [spec for spec in module.__dict__.values() \
-                     if isinstance(spec, type) \
-                     and issubclass(spec, specs.ImageSpec) \
-                     and spec != specs.ImageSpec]:
-            setattr(cls, spec.name(), specs.Descriptor(spec))
-            opts.specs.append(spec)
-        setattr(cls, '_ik', opts)
-
-
-class ImageModel(models.Model):
-    """ Abstract base class implementing all core ImageKit functionality
-    
-    Subclasses of ImageModel can override the inner IKOptions class to customize
-    storage locations and other options.
-    
-    """
-    __metaclass__ = ImageModelBase
-
-    class Meta:
-        abstract = True
-        
-    class IKOptions:
-        pass
-        
-    def admin_thumbnail_view(self):
-        prop = getattr(self, self._ik.admin_thumbnail_spec, None)
-        if prop is None:
-            return 'An "%s" image spec has not been defined.' % \
-              self._ik.admin_thumbnail_spec
-        else:
-            if hasattr(self, 'get_absolute_url'):
-                return u'<a href="%s"><img src="%s"></a>' % \
-                    (self.get_absolute_url(), prop.url)
-            else:
-                return u'<a href="%s"><img src="%s"></a>' % \
-                    (self._imgfield.url, prop.url)
-    admin_thumbnail_view.short_description = _('Thumbnail')
-    admin_thumbnail_view.allow_tags = True
-    
-    @property
-    def _imgfield(self):
-        return getattr(self, self._ik.image_field)
-
-    def _clear_cache(self):
-        for spec in self._ik.specs:
-            prop = getattr(self, spec.name())
-            prop._delete()
-
-    def _pre_cache(self):
-        for spec in self._ik.specs:
-            if spec.pre_cache:
-                prop = getattr(self, spec.name())
-                prop._create()
-
-    def save(self, clear_cache=True, *args, **kwargs):
-        is_new_object = self._get_pk_val is None
-        super(ImageModel, self).save(*args, **kwargs)
-        if is_new_object:
-            clear_cache = False
-            spec = self._ik.preprocessor_spec
-            if spec is not None:
-                newfile = self._imgfield.storage.open(str(self._imgfield))
-                img = Image.open(newfile)
-                img = spec.process(img, None)
-                format = img.format or 'JPEG'
-                if format != 'JPEG':
-                    imgfile = img_to_fobj(img, format)
-                else:
-                    imgfile = img_to_fobj(img, format,
-                                          quality=int(spec.quality),
-                                          optimize=True)
-                content = ContentFile(imgfile.read())
-                newfile.close()
-                name = str(self._imgfield)
-                self._imgfield.storage.delete(name)
-                self._imgfield.storage.save(name, content)
-        if clear_cache:
-            self._clear_cache()
-            self._pre_cache()
-
-    def delete(self):
-        assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname)
-        self._clear_cache()
-        models.Model.delete(self)

src/imagekit/options.py

-# Imagekit options
-from imagekit import processors
-from imagekit.specs import ImageSpec
-    
-
-class Options(object):
-    """ Class handling per-model imagekit options
-
-    """
-    image_field = 'image'
-    crop_horz_field = 'crop_horz'
-    crop_vert_field = 'crop_vert'
-    preprocessor_spec = None
-    cache_dir = 'images'
-    save_count_as = None
-    cache_filename_format = "%(filename)s_%(specname)s.%(extension)s"
-    admin_thumbnail_spec = 'admin_thumbnail'
-    spec_module = 'imagekit.config'
-    
-    def __init__(self, opts):        
-        for key, value in opts.__dict__.iteritems():
-            setattr(self, key, value)
-            self.specs = []

src/imagekit/processors.py

-""" Imagekit Image "ImageProcessors"
-
-A processor defines a set of class variables (optional) and a 
-class method named "process" which processes the supplied image using
-the class properties as settings. The process method can be overridden as well allowing user to define their
-own effects/processes entirely.
-
-"""
-from imagekit.lib import *
-
-class ImageProcessor(object):
-    """ Base image processor class """
-    @classmethod
-    def process(cls, image, obj=None):
-        return image
-
-
-class Adjustment(ImageProcessor):
-    color = 1.0
-    brightness = 1.0
-    contrast = 1.0
-    sharpness = 1.0
-
-    @classmethod
-    def process(cls, image, obj=None):
-        for name in ['Color', 'Brightness', 'Contrast', 'Sharpness']:
-            factor = getattr(cls, name.lower())
-            if factor != 1.0:
-                image = getattr(ImageEnhance, name)(image).enhance(factor)
-        return image
-
-
-class Reflection(ImageProcessor):
-    background_color = '#FFFFFF'
-    size = 0.0
-    opacity = 0.6
-    
-    @classmethod
-    def process(cls, image, obj=None):
-        # convert bgcolor string to rgb value
-        background_color = ImageColor.getrgb(cls.background_color)
-        # copy orignial image and flip the orientation
-        reflection = image.copy().transpose(Image.FLIP_TOP_BOTTOM)
-        # create a new image filled with the bgcolor the same size
-        background = Image.new("RGB", image.size, background_color)
-        # calculate our alpha mask
-        start = int(255 - (255 * cls.opacity)) # The start of our gradient
-        steps = int(255 * cls.size) # the number of intermedite values
-        increment = (255 - start) / float(steps)
-        mask = Image.new('L', (1, 255))
-        for y in range(255):
-            if y < steps:
-                val = int(y * increment + start)
-            else:
-                val = 255
-            mask.putpixel((0, y), val)
-        alpha_mask = mask.resize(image.size)
-        # merge the reflection onto our background color using the alpha mask
-        reflection = Image.composite(background, reflection, alpha_mask)
-        # crop the reflection
-        reflection_height = int(image.size[1] * cls.size)
-        reflection = reflection.crop((0, 0, image.size[0], reflection_height))
-        # create new image sized to hold both the original image and the reflection
-        composite = Image.new("RGB", (image.size[0], image.size[1]+reflection_height), background_color)
-        # paste the orignal image and the reflection into the composite image
-        composite.paste(image, (0, 0))
-        composite.paste(reflection, (0, image.size[1]))
-        # return the image complete with reflection effect
-        return composite
-
-
-class Resize(ImageProcessor):
-    width = None
-    height = None
-    crop = False
-    upscale = False
-    
-    @classmethod
-    def process(cls, image, obj=None):
-        cur_width, cur_height = image.size
-        if cls.crop:
-            crop_horz = getattr(obj, obj._ik.crop_horz_field, 1)
-            crop_vert = getattr(obj, obj._ik.crop_vert_field, 1)
-            ratio = max(float(cls.width)/cur_width, float(cls.height)/cur_height)
-            resize_x, resize_y = ((cur_width * ratio), (cur_height * ratio))
-            crop_x, crop_y = (abs(cls.width - resize_x), abs(cls.height - resize_y))
-            x_diff, y_diff = (int(crop_x / 2), int(crop_y / 2))
-            box_left, box_right = {
-                0: (0, cls.width),
-                1: (int(x_diff), int(x_diff + cls.width)),
-                2: (int(crop_x), int(resize_x)),
-            }[crop_horz]
-            box_upper, box_lower = {
-                0: (0, cls.height),
-                1: (int(y_diff), int(y_diff + cls.height)),
-                2: (int(crop_y), int(resize_y)),
-            }[crop_vert]
-            box = (box_left, box_upper, box_right, box_lower)
-            image = image.resize((int(resize_x), int(resize_y)), Image.ANTIALIAS).crop(box)
-        else:
-            if not cls.width is None and not cls.height is None:
-                ratio = min(float(cls.width)/cur_width,
-                            float(cls.height)/cur_height)
-            else:
-                if cls.width is None:
-                    ratio = float(cls.height)/cur_height
-                else:
-                    ratio = float(cls.width)/cur_width
-            new_dimensions = (int(round(cur_width*ratio)),
-                              int(round(cur_height*ratio)))
-            if new_dimensions[0] > cur_width or \
-               new_dimensions[1] > cur_height:
-                if not cls.upscale:
-                    return image
-            image = image.resize(new_dimensions, Image.ANTIALIAS)
-        return image
-
-    
-class Transpose(ImageProcessor):
-    """ Rotates or flips the image
-    
-    Method should be one of the following strings:
-        - FLIP_LEFT RIGHT
-        - FLIP_TOP_BOTTOM
-        - ROTATE_90
-        - ROTATE_270
-        - ROTATE_180
-        
-    """
-    method = 'FLIP_LEFT_RIGHT'
-    
-    @classmethod
-    def process(cls, image, obj=None):
-        return image.transpose(getattr(Image, cls.method))

src/imagekit/specs.py

-""" ImageKit image specifications
-
-All imagekit specifications must inherit from the ImageSpec class. Models
-inheriting from ImageModel will be modified with a descriptor/accessor for each
-spec found.
-
-"""
-import os
-from imagekit.lib import *
-from imagekit.utils import img_to_fobj
-from django.core.files.base import ContentFile
-
-class ImageSpec(object):
-    pre_cache = False
-    quality = 70
-    increment_count = False
-    processors = []
-    
-    @classmethod
-    def name(cls):
-        return getattr(cls, 'access_as', cls.__name__.lower())
-        
-    @classmethod
-    def process(cls, image, obj):
-        processed_image = image.copy()
-        for proc in cls.processors:
-            processed_image = proc.process(processed_image, obj)
-        return processed_image
-        
-
-class Accessor(object):
-    def __init__(self, obj, spec):
-        self._img = None
-        self._obj = obj
-        self.spec = spec
-        
-    def _get_imgfile(self):
-        format = self._img.format or 'JPEG'
-        if format != 'JPEG':
-            imgfile = img_to_fobj(self._img, format)
-        else:
-            imgfile = img_to_fobj(self._img, format,
-                                  quality=int(self.spec.quality),
-                                  optimize=True)
-        return imgfile
-        
-    def _create(self):
-        if self._exists():
-            return
-        # process the original image file
-        fp = self._obj._imgfield.file.file
-        fp.seek(0)
-        self._img = self.spec.process(Image.open(fp), self._obj)
-        # save the new image to the cache
-        content = ContentFile(self._get_imgfile().read())
-        self._obj._imgfield.storage.save(self._path(), content)
-        
-    def _delete(self):
-        self._obj._imgfield.storage.delete(self._path())
-
-    def _exists(self):
-        return self._obj._imgfield.storage.exists(self._path())
-
-    def _name(self):
-        filename, ext =  os.path.splitext(os.path.basename(self._obj._imgfield.path))
-        return self._obj._ik.cache_filename_format % \
-            {'filename': filename,
-             'specname': self.spec.name(),
-             'extension': ext.lstrip('.')}
-
-    def _path(self):
-        return os.path.join(self._obj._ik.cache_dir, self._name())
-
-    @property
-    def url(self):
-        self._create()
-        if self.spec.increment_count:
-            fieldname = self._obj._ik.save_count_as
-            if fieldname is not None:
-                current_count = getattr(self._obj, fieldname)
-                setattr(self._obj, fieldname, current_count + 1)
-                self._obj.save(clear_cache=False)
-        return self._obj._imgfield.storage.url(self._path())
-        
-    @property
-    def file(self):
-        self._create()
-        return self._obj._imgfield.storage.open(self._path())
-        
-    @property
-    def image(self):
-        if self._img is None:
-            self._create()
-            if self._img is None:
-                self._img = Image.open(self.file)
-        return self._img
-        
-    @property
-    def width(self):
-        return self.image.size[0]
-        
-    @property
-    def height(self):
-        return self.image.size[1]
-
-
-class Descriptor(object):
-    def __init__(self, spec):
-        self._spec = spec
-
-    def __get__(self, obj, type=None):
-        return Accessor(obj, self._spec)

src/imagekit/tests.py

-import os
-import tempfile
-import unittest
-from django.conf import settings
-from django.core.files.base import ContentFile
-from django.db import models
-from django.test import TestCase
-
-from imagekit import processors
-from imagekit.models import ImageModel
-from imagekit.specs import ImageSpec
-from imagekit.lib import Image
-
-
-class ResizeToWidth(processors.Resize):
-    width = 100
-    
-class ResizeToHeight(processors.Resize):
-    height = 100
-    
-class ResizeToFit(processors.Resize):
-    width = 100
-    height = 100
-    
-class ResizeCropped(ResizeToFit):
-    crop = ('center', 'center')
-
-class TestResizeToWidth(ImageSpec):
-    access_as = 'to_width'
-    processors = [ResizeToWidth]
-
-class TestResizeToHeight(ImageSpec):
-    access_as = 'to_height'
-    processors = [ResizeToHeight]
-    
-class TestResizeCropped(ImageSpec):
-    access_as = 'cropped'
-    processors = [ResizeCropped]
-
-class TestPhoto(ImageModel):
-    """ Minimal ImageModel class for testing """
-    image = models.ImageField(upload_to='images')
-    
-    class IKConfig:
-        config_module = 'imagekit.tests'
-    
-
-class IKTest(TestCase):
-    """ Base TestCase class """
-    def setUp(self):
-        # create a test image using tempfile and PIL
-        self.tmp = tempfile.TemporaryFile()
-        Image.new('RGB', (800, 600)).save(self.tmp, 'JPEG')
-        self.tmp.seek(0)
-        self.p = TestPhoto()
-        self.p.image.save(os.path.basename('test.jpg'),
-                           ContentFile(self.tmp.read()))
-        self.p.save()
-        # destroy temp file
-        self.tmp.close()
-        
-    def test_setup(self):
-        self.assertEqual(self.p.image.width, 800)
-        self.assertEqual(self.p.image.height, 600)
-        
-    def test_to_width(self):
-        self.assertEqual(self.p.to_width.width, 100)
-        self.assertEqual(self.p.to_width.height, 75)
-        
-    def test_to_height(self):
-        self.assertEqual(self.p.to_height.width, 133)
-        self.assertEqual(self.p.to_height.height, 100)
-        
-    def test_crop(self):
-        self.assertEqual(self.p.cropped.width, 100)
-        self.assertEqual(self.p.cropped.height, 100)
-
-    def test_url(self):
-        tup = (settings.MEDIA_URL, self.p._ik.cache_dir, 'test_to_width.jpg')
-        self.assertEqual(self.p.to_width.url, "%s%s/%s" % tup)
- 
-    def tearDown(self):
-        # make sure image file is deleted
-        path = self.p.image.path
-        self.p.delete()
-        self.failIf(os.path.isfile(path))

src/imagekit/utils.py

-""" ImageKit utility functions """
-
-import tempfile
-
-def img_to_fobj(img, format, **kwargs):
-    tmp = tempfile.TemporaryFile()
-    if format != 'JPEG':
-        try:
-            img.save(tmp, format, **kwargs)
-            return
-        except KeyError:
-            pass
-    img.save(tmp, format, **kwargs)
-    tmp.seek(0)
-    return tmp
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.