terom@85: """ terom@85: State for thumbnails; derivates of Images terom@85: """ terom@85: terom@125: import filesystem, exif terom@85: terom@85: import PIL.Image terom@85: terom@85: class Thumbnail (filesystem.File) : terom@85: """ terom@85: A Thumbnail is a derivate of an Image, usually resized to some other size. terom@85: """ terom@85: terom@115: def __init__ (self, image, subdir, target_size) : terom@85: """ terom@85: Initialize to link against the given `image`. terom@85: terom@85: `subdir` specifies the directory to store this thumbnail in. terom@115: `target_size` determines the target resolution of the output thumbnail. terom@85: """ terom@85: terom@95: # our file path, image name inside of the given subdir terom@95: super(Thumbnail, self).__init__(subdir, image.fsname) terom@85: terom@85: # store terom@85: self.image = image terom@115: self.target_size = target_size terom@85: terom@85: def stale (self) : terom@85: """ terom@85: Tests if this thumbnail is stale terom@85: """ terom@85: terom@95: if self.image.older_than(self) : terom@95: # both exist and this is newer terom@95: return False terom@95: terom@95: else : terom@95: # this thumb doesn't exist or is older terom@95: return True terom@115: terom@125: def calcsize (self, size) : terom@115: """ terom@125: Compute the *real* size of this thumbnail, from the give actual size and our target size. terom@115: terom@115: Preserves the aspect ratio etc. terom@115: """ terom@115: terom@125: # initial image size terom@125: width, height = size terom@115: terom@115: # target size terom@115: thumb_width, thumb_height = self.target_size terom@115: terom@115: # calc new size, preserving aspect ratio terom@125: # calculations ripped from PIL.Image.thumbnail :) terom@125: if width > thumb_width : terom@125: height = max(height * thumb_width / width, 1) terom@115: width = thumb_width terom@125: terom@125: if height > thumb_height : terom@125: width = max(width * thumb_height / height, 1) terom@115: height = thumb_height terom@115: terom@115: return width, height terom@123: terom@123: ## operations terom@122: def resize (self, img) : terom@122: """ terom@125: Resize the given image as needed. terom@122: """ terom@122: terom@125: return img.resize(self.calcsize(img.size), resample=PIL.Image.ANTIALIAS) terom@122: terom@125: def transform (self, img, orient_info) : terom@122: """ terom@125: Transform this image into the output version, resizing, rotating and mirroring in a single step. terom@122: """ terom@122: terom@125: # get sizes terom@125: img_width, img_height = img_size = img.size terom@125: terom@122: # unpack terom@122: mirroring, rotation = orient_info terom@125: terom@122: if mirroring : terom@125: # XXX: does anyone actually use this? Untested terom@125: # interaction with rotate is probably wrong terom@125: p0, p1 = { terom@125: exif.MIRROR_HORIZONTAL: ((0, img_height), (img_width, 0)), terom@125: exif.MIRROR_VERTICAL: ((img_width, 0), (0, img_height)), terom@125: } terom@125: terom@125: if rotation in (exif.ROTATE_90, exif.ROTATE_270) : terom@125: # flip the dimensions to take rotate into account terom@125: img_size = img_height, img_width terom@125: terom@125: # calc new size terom@125: thumb_size = self.calcsize(img_size) terom@125: terom@125: if rotation in (exif.ROTATE_90, exif.ROTATE_270) : terom@125: # flip the dimensions back before rotate terom@125: thumb_size = thumb_size[1], thumb_size[0] terom@125: terom@125: if mirroring : terom@125: # perform the transform, can't use ANTIALIAS here :/ terom@125: img = img.transform(thumb_size, PIL.Image.EXTENT, p0 + p1, PIL.Image.NEAREST) terom@125: terom@125: else : terom@125: # resize with ANTIALIAS terom@125: img = img.resize(thumb_size, resample=PIL.Image.ANTIALIAS) terom@122: terom@122: if rotation : terom@125: # transform can't rotate terom@122: # since these are in steps of 90 degrees, it should keep the right size terom@122: # but gah, PIL wants these as counter-clockwise! terom@122: img = img.rotate(360 - rotation) terom@122: terom@122: return img terom@85: terom@85: def update (self) : terom@85: """ terom@115: Render new output thumbnail. terom@85: """ terom@85: terom@122: # start with origional image terom@122: img = self.image.img terom@122: terom@125: if self.image.orientation and (self.image.orientation[0] or self.image.orientation[1]) : terom@125: # rotate terom@125: img = self.transform(img, self.image.orientation) terom@122: terom@125: else : terom@125: # just create resized copy of main image, using our size terom@125: img = self.resize(img) terom@85: terom@115: # write it out terom@122: img.save(self.path) terom@95: