degal/thumbnail.py
author Tero Marttila <terom@fixme.fi>
Wed, 01 Jul 2009 20:40:00 +0300
changeset 141 9387da0dc183
parent 125 74f135774567
permissions -rw-r--r--
move .config from filesystem to gallery/folder/image, rename degal_dir to app_dir
"""
    State for thumbnails; derivates of Images
"""

import filesystem, exif

import PIL.Image

class Thumbnail (filesystem.File) :
    """
        A Thumbnail is a derivate of an Image, usually resized to some other size.
    """

    def __init__ (self, image, subdir, target_size) :
        """
            Initialize to link against the given `image`.
            
            `subdir` specifies the directory to store this thumbnail in.
            `target_size` determines the target resolution of the output thumbnail.
        """

        # our file path, image name inside of the given subdir
        super(Thumbnail, self).__init__(subdir, image.fsname)

        # store
        self.image = image
        self.target_size = target_size

    def stale (self) :
        """
            Tests if this thumbnail is stale
        """
        
        if self.image.older_than(self) :
            # both exist and this is newer
            return False
        
        else :
            # this thumb doesn't exist or is older
            return True
    
    def calcsize (self, size) :
        """
            Compute the *real* size of this thumbnail, from the give actual size and our target size.

            Preserves the aspect ratio etc.
        """
        
        # initial image size
        width, height = size

        # target size
        thumb_width, thumb_height = self.target_size

        # calc new size, preserving aspect ratio
        # calculations ripped from PIL.Image.thumbnail :)
        if width > thumb_width : 
            height = max(height * thumb_width / width, 1)
            width = thumb_width
 
        if height > thumb_height :
            width = max(width * thumb_height / height, 1)
            height = thumb_height
        
        return width, height 
   
    ## operations
    def resize (self, img) :
        """
            Resize the given image as needed.
        """

        return img.resize(self.calcsize(img.size), resample=PIL.Image.ANTIALIAS)
    
    def transform (self, img, orient_info) :
        """
            Transform this image into the output version, resizing, rotating and mirroring in a single step.
        """
        
        # get sizes
        img_width, img_height = img_size = img.size
        
        # unpack
        mirroring, rotation = orient_info
 
        if mirroring :
            # XXX: does anyone actually use this? Untested
            # interaction with rotate is probably wrong
            p0, p1 = {
                exif.MIRROR_HORIZONTAL: ((0, img_height), (img_width, 0)),
                exif.MIRROR_VERTICAL:   ((img_width, 0), (0, img_height)),
            }

        if rotation in (exif.ROTATE_90, exif.ROTATE_270) :
            # flip the dimensions to take rotate into account
            img_size = img_height, img_width

        # calc new size
        thumb_size = self.calcsize(img_size)
 
        if rotation in (exif.ROTATE_90, exif.ROTATE_270) :
            # flip the dimensions back before rotate
            thumb_size = thumb_size[1], thumb_size[0]
       
        if mirroring :
            # perform the transform, can't use ANTIALIAS here :/
            img = img.transform(thumb_size, PIL.Image.EXTENT, p0 + p1, PIL.Image.NEAREST)

        else :
            # resize with ANTIALIAS
            img = img.resize(thumb_size, resample=PIL.Image.ANTIALIAS)

        if rotation :
            # transform can't rotate
            # since these are in steps of 90 degrees, it should keep the right size
            # but gah, PIL wants these as counter-clockwise!
            img = img.rotate(360 - rotation)
        
        return img

    def update (self) :
        """
            Render new output thumbnail.
        """

        # start with origional image
        img = self.image.img

        if self.image.orientation and (self.image.orientation[0] or self.image.orientation[1]) :
            # rotate
            img = self.transform(img, self.image.orientation)

        else :
            # just create resized copy of main image, using our size
            img = self.resize(img)

        # write it out
        img.save(self.path)