def test_transparent_thumbnailing(self): thumb_file = self.thumbnailer.get_thumbnail({"size": (100, 100)}) thumb_file.seek(0) thumb = Image.open(thumb_file) self.assertFalse(utils.is_transparent(thumb), "%s shouldn't be transparent." % thumb_file.name) thumb_file = self.transparent_thumbnailer.get_thumbnail({"size": (100, 100)}) thumb_file.seek(0) thumb = Image.open(thumb_file) self.assertTrue(utils.is_transparent(thumb), "%s should be transparent." % thumb_file.name) thumb_file = self.transparent_greyscale_thumbnailer.get_thumbnail({"size": (100, 100)}) thumb_file.seek(0) thumb = Image.open(thumb_file) self.assertTrue(utils.is_transparent(thumb), "%s should be transparent." % thumb_file.name)
def generate_thumbnail(self, thumbnail_options, high_resolution=False): """ Return an unsaved ``ThumbnailFile`` containing a thumbnail image. The thumbnail image is generated using the ``thumbnail_options`` dictionary. """ if high_resolution: orig_size = thumbnail_options["size"] # remember original size thumbnail_options = thumbnail_options.copy() thumbnail_options["size"] = (orig_size[0] * 2, orig_size[1] * 2) image = self.generate_source_image(thumbnail_options) if image is None: raise exceptions.InvalidImageFormatError("The source file does not appear to be an image") thumbnail_image = engine.process_image(image, thumbnail_options, self.thumbnail_processors) quality = thumbnail_options.get("quality", self.thumbnail_quality) if high_resolution: thumbnail_options["size"] = orig_size # restore original size filename = self.get_thumbnail_name( thumbnail_options, transparent=utils.is_transparent(thumbnail_image), high_resolution=high_resolution ) img = engine.save_image(thumbnail_image, filename=filename, quality=quality) data = img.read() thumbnail = ThumbnailFile( filename, file=ContentFile(data), storage=self.thumbnail_storage, thumbnail_options=thumbnail_options ) thumbnail.image = thumbnail_image thumbnail._committed = False return thumbnail
def autocrop(im, autocrop=False, **kwargs): """ Remove any unnecessary whitespace from the edges of the source image. This processor should be listed before :func:`scale_and_crop` so the whitespace is removed from the source image before it is resized. autocrop Activates the autocrop method for this image. """ if autocrop: # If transparent, flatten. if utils.is_transparent(im) and False: no_alpha = Image.new('L', im.size, (255)) no_alpha.paste(im, mask=im.split()[-1]) else: no_alpha = im.convert('L') # Convert to black and white image. bw = no_alpha.convert('L') # bw = bw.filter(ImageFilter.MedianFilter) # White background. bg = Image.new('L', im.size, 255) bbox = ImageChops.difference(bw, bg).getbbox() if bbox: im = im.crop(bbox) return im
def get_thumbnail(self, thumbnail_options, save=True): """ Return a ``ThumbnailFile`` containing a thumbnail. It the file already exists, it will simply be returned. Otherwise a new thumbnail image is generated using the ``thumbnail_options`` dictionary. If the ``save`` argument is ``True`` (default), the generated thumbnail will be saved too. """ opaque_name = self.get_thumbnail_name(thumbnail_options, transparent=False) transparent_name = self.get_thumbnail_name(thumbnail_options, transparent=True) if opaque_name == transparent_name: names = (opaque_name, ) else: names = (opaque_name, transparent_name) for filename in names: if self.thumbnail_exists(filename): thumbnail = ThumbnailFile(name=filename, storage=self.thumbnail_storage) return thumbnail thumbnail = self.generate_thumbnail(thumbnail_options) if save: save_thumbnail(thumbnail, self.thumbnail_storage) # Ensure the right thumbnail name is used based on the transparency # of the image. filename = (utils.is_transparent(thumbnail.image) and transparent_name or opaque_name) self.get_thumbnail_cache(filename, create=True, update=True) return thumbnail
def generate_thumbnail(self, thumbnail_options): """ Return an unsaved ``ThumbnailFile`` containing a thumbnail image. The thumbnail image is generated using the ``thumbnail_options`` dictionary. """ image = self.generate_source_image(thumbnail_options) if image is None: raise exceptions.InvalidImageFormatError( "The source file does not appear to be an image") thumbnail_image = engine.process_image(image, thumbnail_options, self.thumbnail_processors) quality = thumbnail_options.get('quality', self.thumbnail_quality) filename = self.get_thumbnail_name(thumbnail_options, transparent=utils.is_transparent(thumbnail_image)) data = engine.save_image(thumbnail_image, filename=filename, quality=quality).read() thumbnail = ThumbnailFile(filename, ContentFile(data), storage=self.thumbnail_storage) thumbnail.image = thumbnail_image thumbnail._committed = False return thumbnail
def generate_thumbnail(self, thumbnail_options): """ Return an unsaved ``ThumbnailFile`` containing a thumbnail image. The thumbnail image is generated using the ``thumbnail_options`` dictionary. """ image = self.generate_source_image(thumbnail_options) if image is None: raise exceptions.InvalidImageFormatError( "The source file does not appear to be an image") thumbnail_image = engine.process_image(image, thumbnail_options, self.thumbnail_processors) quality = thumbnail_options.get('quality', self.thumbnail_quality) filename = self.get_thumbnail_name( thumbnail_options, transparent=utils.is_transparent(thumbnail_image)) data = engine.save_image(thumbnail_image, filename=filename, quality=quality).read() thumbnail = ThumbnailFile(filename, ContentFile(data), storage=self.thumbnail_storage) thumbnail.image = thumbnail_image thumbnail._committed = False return thumbnail
def get_thumbnail(self, thumbnail_options, save=True): """ Return a ``ThumbnailFile`` containing a thumbnail. It the file already exists, it will simply be returned. Otherwise a new thumbnail image is generated using the ``thumbnail_options`` dictionary. If the ``save`` argument is ``True`` (default), the generated thumbnail will be saved too. """ opaque_name = self.get_thumbnail_name(thumbnail_options, transparent=False) transparent_name = self.get_thumbnail_name(thumbnail_options, transparent=True) if opaque_name == transparent_name: names = (opaque_name,) else: names = (opaque_name, transparent_name) for filename in names: if self.thumbnail_exists(filename): thumbnail = ThumbnailFile(name=filename, storage=self.thumbnail_storage) return thumbnail thumbnail = self.generate_thumbnail(thumbnail_options) if save: save_thumbnail(thumbnail, self.thumbnail_storage) # Ensure the right thumbnail name is used based on the transparency # of the image. filename = (utils.is_transparent(thumbnail.image) and transparent_name or opaque_name) self.get_thumbnail_cache(filename, create=True, update=True) return thumbnail
def get_thumbnail(self, thumbnail_options, save=True, generate=None): """ Return a ``ThumbnailFile`` containing a thumbnail. If a matching thumbnail already exists, it will simply be returned. By default (unless the ``Thumbnailer`` was instanciated with ``generate=False``), thumbnails that don't exist are generated. Otherwise ``None`` is returned. Force the generation behaviour by setting the ``generate`` param to either ``True`` or ``False`` as required. The new thumbnail image is generated using the ``thumbnail_options`` dictionary. If the ``save`` argument is ``True`` (default), the generated thumbnail will be saved too. """ opaque_name = self.get_thumbnail_name(thumbnail_options, transparent=False) transparent_name = self.get_thumbnail_name(thumbnail_options, transparent=True) if opaque_name == transparent_name: names = (opaque_name,) else: names = (opaque_name, transparent_name) for filename in names: if self.thumbnail_exists(filename): return ThumbnailFile( name=filename, storage=self.thumbnail_storage, thumbnail_options=thumbnail_options) if generate is None: generate = self.generate if not generate: signals.thumbnail_missed.send( sender=self, options=thumbnail_options) return thumbnail = self.generate_thumbnail(thumbnail_options) if save: save_thumbnail(thumbnail, self.thumbnail_storage) # BEGIN: cache thumbnail modified time utils.invalidate_easy_cache(thumbnail.name) # END: signals.thumbnail_created.send(sender=thumbnail) # Ensure the right thumbnail name is used based on the transparency # of the image. filename = (utils.is_transparent(thumbnail.image) and transparent_name or opaque_name) self.get_thumbnail_cache(filename, create=True, update=True) if self.thumbnail_high_resolution: thumbnail_2x = self.generate_thumbnail(thumbnail_options, high_resolution=True) save_thumbnail(thumbnail_2x, self.thumbnail_storage) # BEGIN: cache thumbnail modified time utils.invalidate_easy_cache(thumbnail.name) # END: return thumbnail
def generate_thumbnail(self, thumbnail_options, high_resolution=False, silent_template_exception=False): """ Return an unsaved ``ThumbnailFile`` containing a thumbnail image. The thumbnail image is generated using the ``thumbnail_options`` dictionary. """ thumbnail_options = self.get_options(thumbnail_options) orig_size = thumbnail_options['size'] # remember original size # Size sanity check. min_dim, max_dim = 0, 0 for dim in orig_size: try: dim = int(dim) except (TypeError, ValueError): continue min_dim, max_dim = min(min_dim, dim), max(max_dim, dim) if max_dim == 0 or min_dim < 0: raise exceptions.EasyThumbnailsError( "The source image is an invalid size (%sx%s)" % orig_size) if high_resolution: thumbnail_options['size'] = (orig_size[0] * 2, orig_size[1] * 2) image = engine.generate_source_image( self, thumbnail_options, self.source_generators, fail_silently=silent_template_exception) if image is None: raise exceptions.InvalidImageFormatError( "The source file does not appear to be an image") icc_profile = image.info.get("icc_profile") thumbnail_image = engine.process_image(image, thumbnail_options, self.thumbnail_processors) if high_resolution: thumbnail_options['size'] = orig_size # restore original size filename = self.get_thumbnail_name( thumbnail_options, transparent=utils.is_transparent(thumbnail_image), high_resolution=high_resolution) quality = thumbnail_options['quality'] subsampling = thumbnail_options['subsampling'] img = engine.save_image( thumbnail_image, filename=filename, quality=quality, subsampling=subsampling, icc_profile=icc_profile) data = img.read() thumbnail = ThumbnailFile( filename, file=ContentFile(data), storage=self.thumbnail_storage, thumbnail_options=thumbnail_options) thumbnail.image = thumbnail_image thumbnail._committed = False return thumbnail
def generate_thumbnail(self, thumbnail_options, high_resolution=False, silent_template_exception=False, keep_file_open=True): """ Return an unsaved ``ThumbnailFile`` containing a thumbnail image. The thumbnail image is generated using the ``thumbnail_options`` dictionary. """ thumbnail_options = self.get_options(thumbnail_options) orig_size = thumbnail_options['size'] # remember original size # Size sanity check. min_dim, max_dim = 0, 0 for dim in orig_size: try: dim = int(dim) except (TypeError, ValueError): continue min_dim, max_dim = min(min_dim, dim), max(max_dim, dim) if max_dim == 0 or min_dim < 0: raise exceptions.EasyThumbnailsError( "The source image is an invalid size (%sx%s)" % orig_size) if high_resolution: thumbnail_options['size'] = (orig_size[0] * 2, orig_size[1] * 2) image = engine.generate_source_image( self, thumbnail_options, self.source_generators, fail_silently=silent_template_exception, keep_file_open=keep_file_open) if image is None: raise exceptions.InvalidImageFormatError( "The source file does not appear to be an image") thumbnail_image = engine.process_image(image, thumbnail_options, self.thumbnail_processors) if high_resolution: thumbnail_options['size'] = orig_size # restore original size filename = self.get_thumbnail_name( thumbnail_options, transparent=utils.is_transparent(thumbnail_image), high_resolution=high_resolution) quality = thumbnail_options['quality'] subsampling = thumbnail_options['subsampling'] img = engine.save_image( thumbnail_image, filename=filename, quality=quality, subsampling=subsampling) data = img.read() thumbnail = ThumbnailFile( filename, file=ContentFile(data), storage=self.thumbnail_storage, thumbnail_options=thumbnail_options) thumbnail.image = thumbnail_image thumbnail._committed = False return thumbnail
def test_transparent_thumbnailing(self): thumb_file = self.thumbnailer.get_thumbnail({'size': (100, 100)}) thumb_file.seek(0) thumb = Image.open(thumb_file) self.assertFalse(utils.is_transparent(thumb), "%s shouldn't be transparent." % thumb_file.name) thumb_file = self.transparent_thumbnailer.get_thumbnail( {'size': (100, 100)}) thumb_file.seek(0) thumb = Image.open(thumb_file) self.assertTrue(utils.is_transparent(thumb), "%s should be transparent." % thumb_file.name) thumb_file = self.transparent_greyscale_thumbnailer.get_thumbnail( {'size': (100, 100)}) thumb_file.seek(0) thumb = Image.open(thumb_file) self.assertTrue(utils.is_transparent(thumb), "%s should be transparent." % thumb_file.name)
def get_thumbnail(self, thumbnail_options, save=True, generate=None): """ Return a ``ThumbnailFile`` containing a thumbnail. If a matching thumbnail already exists, it will simply be returned. By default (unless the ``Thumbnailer`` was instanciated with ``generate=False``), thumbnails that don't exist are generated. Otherwise ``None`` is returned. Force the generation behaviour by setting the ``generate`` param to either ``True`` or ``False`` as required. The new thumbnail image is generated using the ``thumbnail_options`` dictionary. If the ``save`` argument is ``True`` (default), the generated thumbnail will be saved too. """ opaque_name = self.get_thumbnail_name(thumbnail_options, transparent=False) transparent_name = self.get_thumbnail_name(thumbnail_options, transparent=True) if opaque_name == transparent_name: names = (opaque_name, ) else: names = (opaque_name, transparent_name) for filename in names: if self.thumbnail_exists(filename): return ThumbnailFile(name=filename, storage=self.thumbnail_storage, thumbnail_options=thumbnail_options) if generate is None: generate = self.generate if not generate: signals.thumbnail_missed.send(sender=self, options=thumbnail_options) return thumbnail = self.generate_thumbnail(thumbnail_options) if save: save_thumbnail(thumbnail, self.thumbnail_storage) signals.thumbnail_created.send(sender=thumbnail) # Ensure the right thumbnail name is used based on the transparency # of the image. filename = (utils.is_transparent(thumbnail.image) and transparent_name or opaque_name) self.get_thumbnail_cache(filename, create=True, update=True) if self.thumbnail_high_resolution: thumbnail_2x = self.generate_thumbnail(thumbnail_options, high_resolution=True) save_thumbnail(thumbnail_2x, self.thumbnail_storage) return thumbnail
def colorspace(im, bw=False, replace_alpha=False, **kwargs): """ Convert images to the correct color space. A passive option (i.e. always processed) of this method is that all images (unless grayscale) are converted to RGB colorspace. This processor should be listed before :func:`scale_and_crop` so palette is changed before the image is resized. bw Make the thumbnail grayscale (not really just black & white). replace_alpha Replace any transparency layer with a solid color. For example, ``replace_alpha='#fff'`` would replace the transparency layer with white. """ if im.mode == 'I': # PIL (and pillow) have can't convert 16 bit grayscale images to lower # modes, so manually convert them to an 8 bit grayscale. im = im.point(list(_points_table()), 'L') is_transparent = utils.is_transparent(im) is_grayscale = im.mode in ('L', 'LA') new_mode = im.mode if is_grayscale or bw: new_mode = 'L' else: new_mode = 'RGB' if is_transparent: if replace_alpha: if im.mode != 'RGBA': im = im.convert('RGBA') base = Image.new('RGBA', im.size, replace_alpha) base.paste(im, mask=im) im = base else: new_mode = new_mode + 'A' if im.mode != new_mode: im = im.convert(new_mode) return im
def generate_thumbnail(self, thumbnail_options, high_resolution=False, silent_template_exception=False): """ Return an unsaved ``ThumbnailFile`` containing a thumbnail image. The thumbnail image is generated using the ``thumbnail_options`` dictionary. """ if high_resolution: orig_size = thumbnail_options['size'] # remember original size thumbnail_options = thumbnail_options.copy() thumbnail_options['size'] = (orig_size[0] * 2, orig_size[1] * 2) image = engine.generate_source_image( self, thumbnail_options, self.source_generators, fail_silently=silent_template_exception) if image is None: raise exceptions.InvalidImageFormatError( "The source file does not appear to be an image") thumbnail_image = engine.process_image(image, thumbnail_options, self.thumbnail_processors) quality = thumbnail_options.get('quality', self.thumbnail_quality) if high_resolution: thumbnail_options['size'] = orig_size # restore original size filename = self.get_thumbnail_name( thumbnail_options, transparent=utils.is_transparent(thumbnail_image), high_resolution=high_resolution) img = engine.save_image( thumbnail_image, filename=filename, quality=quality) data = img.read() thumbnail = ThumbnailFile( filename, file=ContentFile(data), storage=self.thumbnail_storage, thumbnail_options=thumbnail_options) thumbnail.image = thumbnail_image thumbnail._committed = False return thumbnail
def colorspace(im, bw=False, replace_alpha=False, **kwargs): """ Convert images to the correct color space. A passive option (i.e. always processed) of this method is that all images (unless grayscale) are converted to RGB colorspace. This processor should be listed before :func:`scale_and_crop` so palette is changed before the image is resized. bw Make the thumbnail grayscale (not really just black & white). replace_alpha Replace any transparency layer with a solid color. For example, ``replace_alpha='#fff'`` would replace the transparency layer with white. """ is_transparent = utils.is_transparent(im) if bw: if im.mode in ('L', 'LA'): return im if is_transparent: return im.convert('LA') else: return im.convert('L') if im.mode in ('L', 'RGB'): return im if is_transparent: if im.mode != 'RGBA': im = im.convert('RGBA') if not replace_alpha: return im base = Image.new('RGBA', im.size, replace_alpha) base.paste(im, mask=im) im = base return im.convert('RGB')
def ensure_srgb(image): """ Process an image file of unknown color profile. Transform to sRGB profile and return the result. """ if not SRGB_PROFILE or image.mode in (u'L', u'LA', u'I'): return image with NamedTemporaryFile(suffix='.icc', delete=True) as icc_file: profile = image.info.get('icc_profile') if profile: icc_file.write(profile) icc_file.flush() try: output_mode = 'RGBA' if is_transparent(image) else 'RGB' image = ImageCms.profileToProfile(image, icc_file.name, SRGB_PROFILE, outputMode=output_mode) except PyCMSError: log.error("Unable to apply color profile!") pass return image
def generate_thumbnail(self, thumbnail_options, high_resolution=False, silent_template_exception=False): """ Return an unsaved ``ThumbnailFile`` containing a thumbnail image. The thumbnail image is generated using the ``thumbnail_options`` dictionary. """ thumbnail_options = self.get_options(thumbnail_options) orig_size = thumbnail_options['size'] # remember original size # Size sanity check. min_dim, max_dim = 0, 0 for dim in orig_size: try: dim = int(dim) except (TypeError, ValueError): continue min_dim, max_dim = min(min_dim, dim), max(max_dim, dim) if max_dim == 0 or min_dim < 0: raise exceptions.EasyThumbnailsError( "The source image is an invalid size (%sx%s)" % orig_size) if high_resolution: thumbnail_options['size'] = (orig_size[0] * 2, orig_size[1] * 2) image = engine.generate_source_image( self, thumbnail_options, self.source_generators, fail_silently=silent_template_exception) if image is None: raise exceptions.InvalidImageFormatError( "The source file does not appear to be an image") thumbnail_image = None IS_GIF = False if isinstance(image, GifImageFile): IS_GIF = True import images2gif gif_image, gif_params = images2gif.readGif(image, False) frames = [] for frame in gif_image: thumbnail_frame = engine.process_image( frame, thumbnail_options, self.thumbnail_processors) frames.append(thumbnail_frame) if thumbnail_image is None: thumbnail_image = frame else: thumbnail_image = engine.process_image(image, thumbnail_options, self.thumbnail_processors) if high_resolution: thumbnail_options['size'] = orig_size # restore original size filename = self.get_thumbnail_name( thumbnail_options, transparent=utils.is_transparent(thumbnail_image), high_resolution=high_resolution) quality = thumbnail_options['quality'] subsampling = thumbnail_options['subsampling'] if IS_GIF: try: # try save with speedup img = images2gif.writeGif(frames, **gif_params) saved = True except: # if not saved, simple save gif_params.update({ 'subRectangles': False, }) img = images2gif.writeGif(frames, **gif_params) else: img = engine.save_image(thumbnail_image, filename=filename, quality=quality, subsampling=subsampling) data = img.read() thumbnail = ThumbnailFile(filename, file=ContentFile(data), storage=self.thumbnail_storage, thumbnail_options=thumbnail_options) thumbnail.image = thumbnail_image thumbnail._committed = False return thumbnail
def generate_thumbnail(self, thumbnail_options, high_resolution=False, silent_template_exception=False): """ Return an unsaved ``ThumbnailFile`` containing a thumbnail image. The thumbnail image is generated using the ``thumbnail_options`` dictionary. """ thumbnail_options = self.get_options(thumbnail_options) orig_size = thumbnail_options['size'] # remember original size # Size sanity check. min_dim, max_dim = 0, 0 for dim in orig_size: try: dim = int(dim) except (TypeError, ValueError): continue min_dim, max_dim = min(min_dim, dim), max(max_dim, dim) if max_dim == 0 or min_dim < 0: raise exceptions.EasyThumbnailsError( "The source image is an invalid size (%sx%s)" % orig_size) if high_resolution: thumbnail_options['size'] = (orig_size[0] * 2, orig_size[1] * 2) image = engine.generate_source_image( self, thumbnail_options, self.source_generators, fail_silently=silent_template_exception) if image is None: raise exceptions.InvalidImageFormatError( "The source file does not appear to be an image") self.open() source_image = Image.open(self.file) is_animated_gif = self.name.lower().endswith('.gif') \ and source_image.format == 'GIF' \ and source_image.info.get('duration') != None from io import BytesIO if is_animated_gif: frame_index = 0 palette = source_image.getpalette() from easy_thumbnails.utils import images2gif images = images2gif.readGif(self.file, asNumpy=False) # convert the durations to 0.1 secs durations = [single_duration / 1000.0 for single_duration in images2gif.getDurationsOfGif(self.file)] else: images = [engine.generate_source_image( self, thumbnail_options, self.source_generators, fail_silently=silent_template_exception)] thumbnail_images = [] if high_resolution: thumbnail_options['high_resolution'] = True for image in images: if image is None: raise exceptions.InvalidImageFormatError( "The source file does not appear to be an image") thumb = engine.process_image(image, thumbnail_options, self.thumbnail_processors) thumbnail_images.append(thumb) thumbnail_image = thumbnail_images[0] quality = thumbnail_options.get('quality', self.thumbnail_quality) if high_resolution: thumbnail_options['size'] = orig_size # restore original size del thumbnail_options['high_resolution'] filename = self.get_thumbnail_name( thumbnail_options, transparent=utils.is_transparent(thumbnail_image), high_resolution=high_resolution) quality = thumbnail_options['quality'] subsampling = thumbnail_options['subsampling'] if is_animated_gif: from tempfile import NamedTemporaryFile try: output_temp_file = NamedTemporaryFile('rw', suffix='.gif') images2gif.writeGif(output_temp_file.name, thumbnail_images, duration=durations) output_temp_file = open(output_temp_file.name) data = output_temp_file.read() finally: output_temp_file.close() del output_temp_file else: img = engine.save_image( thumbnail_image, filename=filename, quality=quality) data = img.read() thumbnail = ThumbnailFile( filename, file=ContentFile(data), storage=self.thumbnail_storage, thumbnail_options=thumbnail_options) thumbnail._committed = False return thumbnail
def get_thumbnail(self, thumbnail_options, save=True, generate=None): """ Return a ``ThumbnailFile`` containing a thumbnail. If a matching thumbnail already exists, it will simply be returned. By default (unless the ``Thumbnailer`` was instanciated with ``generate=False``), thumbnails that don't exist are generated. Otherwise ``None`` is returned. Force the generation behaviour by setting the ``generate`` param to either ``True`` or ``False`` as required. The new thumbnail image is generated using the ``thumbnail_options`` dictionary. If the ``save`` argument is ``True`` (default), the generated thumbnail will be saved too. """ opaque_name = self.get_thumbnail_name(thumbnail_options, transparent=False) transparent_name = self.get_thumbnail_name(thumbnail_options, transparent=True) if opaque_name == transparent_name: names = (opaque_name,) else: names = (opaque_name, transparent_name) #### # If this thumbnail not exists, maybe errors. # TODO: check it try: obj = models.Thumbnail.objects.filter(Q(name=names[0]) | Q(name=names[1]))[0] name = obj.name except: name = "" return ThumbnailFile(name=name, storage=self.thumbnail_storage, thumbnail_options=thumbnail_options) #### for filename in names: if self.thumbnail_exists(filename): return ThumbnailFile(name=filename, storage=self.thumbnail_storage, thumbnail_options=thumbnail_options) if generate is None: generate = self.generate if not generate: signals.thumbnail_missed.send(sender=self, options=thumbnail_options) return thumbnail = self.generate_thumbnail(thumbnail_options) if save: save_thumbnail(thumbnail, self.thumbnail_storage) signals.thumbnail_created.send(sender=thumbnail) # Ensure the right thumbnail name is used based on the transparency # of the image. filename = (utils.is_transparent(thumbnail.image) and transparent_name or opaque_name) self.get_thumbnail_cache(filename, create=True, update=True) return thumbnail
def iiif_image_api(request, identifier_param, region_param, size_param, rotation_param, quality_param, format_param): """ Image repurposing endpoint for IIIF Image API 2.1 """ ik_image, image = _get_image_or_404(identifier_param, load_image=True) is_transparent = et_utils.is_transparent(image) is_grayscale = image.mode in ('L', 'LA') # Map format names used for IIIF URL path extension to proper name format_mapping = { 'jpg': 'jpeg', 'tif': 'tiff', } try: # Parse region x, y, r_width, r_height = parse_region(region_param, image.width, image.height) # Parse size s_width, s_height = parse_size(size_param, r_width, r_height) # Parse rotation is_mirrored, rotation_degrees = \ parse_rotation(rotation_param, s_width, s_height) # Parse quality quality = parse_quality(quality_param) # Parse format # TODO Add support for unsupported formats (see `parse_format`) image_format = os.path.splitext(ik_image.image.name)[1][1:].lower() output_format = parse_format(format_param, image_format) corrected_format = format_mapping.get(output_format, output_format) # Redirect to canonical URL if appropriate, per # http://iiif.io/api/image/2.1/#canonical-uri-syntax canonical_path = make_canonical_path( identifier_param, image.width, image.height, (x, y, r_width, r_height), # Region (s_width, s_height), # Size (is_mirrored, rotation_degrees), # Rotation quality, output_format) if request.path != canonical_path: return HttpResponseRedirect(canonical_path) # Determine storage file name for item if iiif_storage: storage_path = build_iiif_file_storage_path( canonical_path, ik_image, iiif_storage) else: storage_path = None # Load pre-generated image from storage if one exists and is up-to-date # with the original image (per timestampt info embedded in the storage # path) # TODO The exists lookup is slow for S3 storage, cache metadata? # TODO Detect when original image would be unchanged & use it directly? if (storage_path and iiif_storage.exists(storage_path)): if is_remote_storage(iiif_storage, storage_path): return HttpResponseRedirect(iiif_storage.url(storage_path)) else: return FileResponse( iiif_storage.open(storage_path), content_type='image/%s' % corrected_format, ) ################## # Generate image # ################## # Apply region if x or y or r_width != image.width or r_height != image.height: box = (x, y, x + r_width, y + r_height) image = image.crop(box) # Apply size if s_width != r_width or s_height != r_height: size = (s_width, s_height) image = image.resize(size) # TODO Apply rotation # Apply quality # Much of this is cribbed from easythumbnails' `colorspace` processor # TODO Replace with glamkit-imagetools' sRGB colour space converter? if quality in ('default', 'color') and not is_grayscale: if is_transparent: new_mode = 'RGBA' else: new_mode = 'RGB' elif is_grayscale or quality == 'gray': if is_transparent: new_mode = 'LA' else: new_mode = 'L' if new_mode != image.mode: image = image.convert(new_mode) # Apply format and "save" result_image = BytesIO() image.save(result_image, format=corrected_format) # Save generated image to storage if possible if storage_path: iiif_storage.save(storage_path, result_image) if iiif_storage and is_remote_storage(iiif_storage, storage_path): return HttpResponseRedirect(iiif_storage.url(storage_path)) else: result_image.seek(0) # Reset image file in case it's just created return FileResponse( result_image.read(), content_type='image/%s' % corrected_format, ) # Handle error conditions per iiif.io/api/image/2.1/#server-responses except ClientError, ex: return HttpResponseBadRequest(ex.message) # 400 response
def generate_thumbnail(self, thumbnail_options, high_resolution=False, silent_template_exception=False): """ Return an unsaved ``ThumbnailFile`` containing a thumbnail image. The thumbnail image is generated using the ``thumbnail_options`` dictionary. """ thumbnail_options = self.get_options(thumbnail_options) orig_size = thumbnail_options['size'] # remember original size # Size sanity check. min_dim, max_dim = 0, 0 for dim in orig_size: try: dim = int(dim) except (TypeError, ValueError): continue min_dim, max_dim = min(min_dim, dim), max(max_dim, dim) if max_dim == 0 or min_dim < 0: raise exceptions.EasyThumbnailsError( "The source image is an invalid size (%sx%s)" % orig_size) if high_resolution: thumbnail_options['size'] = (orig_size[0] * 2, orig_size[1] * 2) image = engine.generate_source_image( self, thumbnail_options, self.source_generators, fail_silently=silent_template_exception) if image is None: raise exceptions.InvalidImageFormatError( "The source file does not appear to be an image") thumbnail_image = None IS_GIF = False if isinstance(image, GifImageFile): IS_GIF = True import images2gif gif_image, gif_params = images2gif.readGif(image, False) frames = [] for frame in gif_image: thumbnail_frame = engine.process_image(frame, thumbnail_options, self.thumbnail_processors) frames.append(thumbnail_frame) if thumbnail_image is None: thumbnail_image = frame else: thumbnail_image = engine.process_image(image, thumbnail_options, self.thumbnail_processors) if high_resolution: thumbnail_options['size'] = orig_size # restore original size filename = self.get_thumbnail_name( thumbnail_options, transparent=utils.is_transparent(thumbnail_image), high_resolution=high_resolution) quality = thumbnail_options['quality'] subsampling = thumbnail_options['subsampling'] if IS_GIF: try: # try save with speedup img = images2gif.writeGif(frames, **gif_params) saved = True except: # if not saved, simple save gif_params.update({'subRectangles': False,}) img = images2gif.writeGif(frames, **gif_params) else: img = engine.save_image( thumbnail_image, filename=filename, quality=quality, subsampling=subsampling) data = img.read() thumbnail = ThumbnailFile( filename, file=ContentFile(data), storage=self.thumbnail_storage, thumbnail_options=thumbnail_options) thumbnail.image = thumbnail_image thumbnail._committed = False return thumbnail