Esempio n. 1
0
def save_image(image, destination=None, filename=None, **options):
    """
    Save a PIL image.
    """
    if destination is None:
        destination = BytesIO()
    filename = filename or ''
    # Ensure plugins are fully loaded so that Image.EXTENSION is populated.
    Image.init()
    format = Image.EXTENSION.get(os.path.splitext(filename)[1].lower(), 'JPEG')
    if format in ('JPEG', 'WEBP'):
        options.setdefault('quality', 85)
    saved = False
    if format == 'JPEG':
        if settings.THUMBNAIL_PROGRESSIVE and (
                max(image.size) >= settings.THUMBNAIL_PROGRESSIVE):
            options['progressive'] = True
        try:
            image.save(destination, format=format, optimize=1, **options)
            saved = True
        except IOError:
            # Try again, without optimization (PIL can't optimize an image
            # larger than ImageFile.MAXBLOCK, which is 64k by default). This
            # shouldn't be triggered very often these days, as recent versions
            # of pillow avoid the MAXBLOCK limitation.
            pass
    if not saved:
        image.save(destination, format=format, **options)
    if hasattr(destination, 'seek'):
        destination.seek(0)
    return destination
Esempio n. 2
0
def save_image(image, destination=None, filename=None, **options):
    """
    Save a PIL image.
    """
    if destination is None:
        destination = BytesIO()
    filename = filename or ''
    # Ensure plugins are fully loaded so that Image.EXTENSION is populated.
    Image.init()
    format = Image.EXTENSION.get(os.path.splitext(filename)[1].lower(), 'JPEG')
    if format in ('JPEG', 'WEBP'):
        options.setdefault('quality', 85)
    saved = False
    if format == 'JPEG':
        if settings.THUMBNAIL_PROGRESSIVE and (max(image.size) >=
                                               settings.THUMBNAIL_PROGRESSIVE):
            options['progressive'] = True
        try:
            image.save(destination, format=format, optimize=1, **options)
            saved = True
        except IOError:
            # Try again, without optimization (PIL can't optimize an image
            # larger than ImageFile.MAXBLOCK, which is 64k by default). This
            # shouldn't be triggered very often these days, as recent versions
            # of pillow avoid the MAXBLOCK limitation.
            pass
    if not saved:
        image.save(destination, format=format, **options)
    if hasattr(destination, 'seek'):
        destination.seek(0)
    return destination
Esempio n. 3
0
def save(filename, im, options, destination=None):
    pil_options = options.copy()
    if destination is None:
        destination = BytesIO()
    # Ensure plugins are fully loaded so that Image.EXTENSION is populated.
    Image.init()
    fmt = Image.EXTENSION.get(
        os.path.splitext(filename)[1].lower(), 'JPEG')
    if fmt in ('JPEG', 'WEBP'):
        pil_options.setdefault('quality', 85)
    saved = False
    if fmt == 'JPEG':
        progressive = pil_options.pop('progressive', 100)
        if progressive:
            if progressive is True or max(im.size) >= int(progressive):
                pil_options['progressive'] = True
        try:
            im.save(destination, format=fmt, optimize=1, **pil_options)
            saved = True
        except IOError:
            # Try again, without optimization (PIL can't optimize an image
            # larger than ImageFile.MAXBLOCK, which is 64k by default).
            # This shouldn't be triggered very often these days, as recent
            # versions of pillow avoid the MAXBLOCK limitation.
            pass
    if not saved:
        im.save(destination, format=fmt, **pil_options)
    if hasattr(destination, 'seek'):
        destination.seek(0)
    return destination
Esempio n. 4
0
 def test_nearly_image(self):
     """
     Broken images raise an exception.
     """
     data = self.create_image(None, None)
     trunc_data = BytesIO()
     trunc_data.write(data.read()[:-10])
     trunc_data.seek(0)
     self.assertRaises(IOError, source_generators.pil_image, data)
 def test_nearly_image(self):
     """
     Broken images raise an exception.
     """
     data = self.create_image(None, None)
     trunc_data = BytesIO()
     trunc_data.write(data.read()[:-10])
     trunc_data.seek(0)
     self.assertRaises(IOError, source_generators.pil_image, data)
 def test_nearly_image(self):
     """
     Broken images are passed silently.
     """
     data = self.create_image(None, None)
     trunc_data = BytesIO()
     trunc_data.write(data.read()[:-10])
     trunc_data.seek(0)
     self.assertEqual(source_generators.pil_image(data), None)
Esempio n. 7
0
 def test_nearly_image(self):
     """
     Broken images are passed silently.
     """
     data = self.create_image(None, None)
     trunc_data = BytesIO()
     trunc_data.write(data.read()[:-10])
     trunc_data.seek(0)
     self.assertEqual(source_generators.pil_image(data), None)
Esempio n. 8
0
def saveObscurement(image):
    """
    Return a 1-bit PNG byte string from the PIL image
    """
    output = StringIO()
    output.name = '__obscurement.png'
    image.save(output, optimize=True, transparency=TRANS, bits=1)
    output.seek(0)

    return output.read()
Esempio n. 9
0
def saveObscurement(image):
    """
    Return a 1-bit PNG byte string from the PIL image
    """
    output = StringIO()
    output.name = '__obscurement.png'
    image.save(output, optimize=True, transparency=TRANS, bits=1)
    output.seek(0)

    return output.read()
Esempio n. 10
0
 def test_nearly_image(self):
     """
     Truncated images *don't* raise an exception if they can still be read.
     """
     data = self.create_image(None, None)
     reference = source_generators.pil_image(data)
     data.seek(0)
     trunc_data = BytesIO()
     trunc_data.write(data.read()[:-10])
     trunc_data.seek(0)
     im = source_generators.pil_image(trunc_data)
     # im will be truncated, but it should be the same dimensions.
     self.assertEqual(im.size, reference.size)
Esempio n. 11
0
 def test_nearly_image(self):
     """
     Truncated images *don't* raise an exception if they can still be read.
     """
     data = self.create_image(None, None)
     reference = source_generators.pil_image(data)
     data.seek(0)
     trunc_data = BytesIO()
     trunc_data.write(data.read()[:-10])
     trunc_data.seek(0)
     im = source_generators.pil_image(trunc_data)
     # im will be truncated, but it should be the same dimensions.
     self.assertEqual(im.size, reference.size)
Esempio n. 12
0
    def create_image(self, storage, filename, size=(800, 600), image_mode="RGB", image_format="JPEG"):
        """
        Generate a test image, returning the filename that it was saved as.

        If ``storage`` is ``None``, the BytesIO containing the image data
        will be passed instead.
        """
        data = BytesIO()
        Image.new(image_mode, size).save(data, image_format)
        data.seek(0)
        if not storage:
            return data
        image_file = ContentFile(data.read())
        return storage.save(filename, image_file)
Esempio n. 13
0
    def create_image(self,
                     storage,
                     filename,
                     size=(800, 600),
                     image_mode='RGB',
                     image_format='JPEG'):
        """
        Generate a test image, returning the filename that it was saved as.

        If ``storage`` is ``None``, the BytesIO containing the image data
        will be passed instead.
        """
        data = BytesIO()
        Image.new(image_mode, size).save(data, image_format)
        data.seek(0)
        if not storage:
            return data
        image_file = ContentFile(data.read())
        return storage.save(filename, image_file)
Esempio n. 14
0
def save_image(image, destination=None, filename=None, **options):
    """
    Save a PIL image.
    """
    if destination is None:
        destination = BytesIO()
    filename = filename or ''
    format = Image.EXTENSION.get(os.path.splitext(filename)[1], 'JPEG')
    if format == 'JPEG':
        options.setdefault('quality', 85)
        try:
            image.save(destination, format=format, optimize=1, **options)
        except IOError:
            # Try again, without optimization (PIL can't optimize an image
            # larger than ImageFile.MAXBLOCK, which is 64k by default)
            pass
    image.save(destination, format=format, **options)
    if hasattr(destination, 'seek'):
        destination.seek(0)
    return destination
Esempio n. 15
0
def save_image(image, destination=None, filename=None, **options):
    """
    Save a PIL image.
    """
    if destination is None:
        destination = BytesIO()
    filename = filename or ''
    format = Image.EXTENSION.get(os.path.splitext(filename)[1], 'JPEG')
    if format == 'JPEG':
        options.setdefault('quality', 85)
        try:
            image.save(destination, format=format, optimize=1, **options)
        except IOError:
            # Try again, without optimization (PIL can't optimize an image
            # larger than ImageFile.MAXBLOCK, which is 64k by default)
            pass
    image.save(destination, format=format, **options)
    if hasattr(destination, 'seek'):
        destination.seek(0)
    return destination
Esempio n. 16
0
def save_image(image, destination=None, filename=None, **options):
    """
    Save a PIL image.
    """
    if destination is None:
        destination = BytesIO()
    filename = filename or ''
    # Ensure plugins are fully loaded so that Image.EXTENSION is populated.
    Image.init()
    format = Image.EXTENSION.get(os.path.splitext(filename)[1].lower(), 'JPEG')
    if format in ('JPEG', 'WEBP'):
        options.setdefault('quality', 85)
    if format == 'JPEG':
        try:
            image.save(destination, format=format, optimize=1, **options)
        except IOError:
            # Try again, without optimization (PIL can't optimize an image
            # larger than ImageFile.MAXBLOCK, which is 64k by default)
            pass
    image.save(destination, format=format, **options)
    if hasattr(destination, 'seek'):
        destination.seek(0)
    return destination
Esempio n. 17
0
def save_image(image, destination=None, filename=None, **options):
    """
    Save a PIL image.
    """
    if destination is None:
        destination = BytesIO()
    filename = filename or ''
    # Ensure plugins are fully loaded so that Image.EXTENSION is populated.
    Image.init()
    format = Image.EXTENSION.get(os.path.splitext(filename)[1].lower(), 'JPEG')
    if format in ('JPEG', 'WEBP'):
        options.setdefault('quality', 85)
    if format == 'JPEG':
        try:
            image.save(destination, format=format, optimize=1, **options)
        except IOError:
            # Try again, without optimization (PIL can't optimize an image
            # larger than ImageFile.MAXBLOCK, which is 64k by default)
            pass
    image.save(destination, format=format, **options)
    if hasattr(destination, 'seek'):
        destination.seek(0)
    return destination
Esempio n. 18
0
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
Esempio n. 19
0
def writeGif(images, duration=0.1, repeat=True, dither=False,
                nq=0, subRectangles=True, dispose=None):
    """ writeGif(images, duration=0.1, repeat=True, dither=False,
                    nq=0, subRectangles=True, dispose=None)

    Write an animated gif from the specified images.

    Parameters
    ----------
    images : list
        Should be a list consisting of PIL images or numpy arrays.
        The latter should be between 0 and 255 for integer types, and
        between 0 and 1 for float types.
    duration : scalar or list of scalars
        The duration for all frames, or (if a list) for each frame.
    repeat : bool or integer
        The amount of loops. If True, loops infinitetely.
    dither : bool
        Whether to apply dithering
    nq : integer
        If nonzero, applies the NeuQuant quantization algorithm to create
        the color palette. This algorithm is superior, but slower than
        the standard PIL algorithm. The value of nq is the quality
        parameter. 1 represents the best quality. 10 is in general a
        good tradeoff between quality and speed. When using this option,
        better results are usually obtained when subRectangles is False.
    subRectangles : False, True, or a list of 2-element tuples
        Whether to use sub-rectangles. If True, the minimal rectangle that
        is required to update each frame is automatically detected. This
        can give significant reductions in file size, particularly if only
        a part of the image changes. One can also give a list of x-y
        coordinates if you want to do the cropping yourself. The default
        is True.
    dispose : int
        How to dispose each frame. 1 means that each frame is to be left
        in place. 2 means the background color should be restored after
        each frame. 3 means the decoder should restore the previous frame.
        If subRectangles==False, the default is 2, otherwise it is 1.

    """
    # Check PIL
    if PIL is None:
        raise RuntimeError("Need PIL to write animated gif files.")

    # Check images
    images = checkImages(images)

    # Instantiate writer object
    gifWriter = GifWriter()
    gifWriter.transparency = False # init transparency flag used in GifWriter functions

    # Check loops
    if repeat is True:
        loops = 0 # zero means infinite
    elif repeat is False or repeat == 1:
        loops = -1
    else:
        loops = int(repeat-1)

    # Check duration
    if hasattr(duration, '__len__'):
        if len(duration) == len(images):
            duration = [d for d in duration]
        else:
            raise ValueError("len(duration) doesn't match amount of images.")
    else:
        duration = [duration for im in images]

    # Check subrectangles
    if subRectangles:
        images, xy, images_info = gifWriter.handleSubRectangles(images, subRectangles)
        defaultDispose = 1 # Leave image in place
    else:
        # Normal mode
        xy = [(0,0) for im in images]
        defaultDispose = 2 # Restore to background color.

    # Check dispose
    if dispose is None:
        dispose = defaultDispose
    if hasattr(dispose, '__len__'):
        if len(dispose) != len(images):
            raise ValueError("len(xy) doesn't match amount of images.")
    else:
        dispose = [dispose for im in images]

    # Make images in a format that we can write easy
    images = gifWriter.convertImagesToPIL(images, dither, nq)

    # Write
    try:
        from cStringIO import cStringIO as BytesIO
    except ImportError:
        from django.utils.six import BytesIO
    fp = BytesIO()
    gifWriter.writeGifToFile(fp, images, duration, loops, xy, dispose)
    fp.seek(0)
    return fp
Esempio n. 20
0
def writeGif(images,
             duration=0.1,
             repeat=True,
             dither=False,
             nq=0,
             subRectangles=True,
             dispose=None):
    """ writeGif(images, duration=0.1, repeat=True, dither=False,
                    nq=0, subRectangles=True, dispose=None)

    Write an animated gif from the specified images.

    Parameters
    ----------
    images : list
        Should be a list consisting of PIL images or numpy arrays.
        The latter should be between 0 and 255 for integer types, and
        between 0 and 1 for float types.
    duration : scalar or list of scalars
        The duration for all frames, or (if a list) for each frame.
    repeat : bool or integer
        The amount of loops. If True, loops infinitetely.
    dither : bool
        Whether to apply dithering
    nq : integer
        If nonzero, applies the NeuQuant quantization algorithm to create
        the color palette. This algorithm is superior, but slower than
        the standard PIL algorithm. The value of nq is the quality
        parameter. 1 represents the best quality. 10 is in general a
        good tradeoff between quality and speed. When using this option,
        better results are usually obtained when subRectangles is False.
    subRectangles : False, True, or a list of 2-element tuples
        Whether to use sub-rectangles. If True, the minimal rectangle that
        is required to update each frame is automatically detected. This
        can give significant reductions in file size, particularly if only
        a part of the image changes. One can also give a list of x-y
        coordinates if you want to do the cropping yourself. The default
        is True.
    dispose : int
        How to dispose each frame. 1 means that each frame is to be left
        in place. 2 means the background color should be restored after
        each frame. 3 means the decoder should restore the previous frame.
        If subRectangles==False, the default is 2, otherwise it is 1.

    """
    # Check PIL
    if PIL is None:
        raise RuntimeError("Need PIL to write animated gif files.")

    # Check images
    images = checkImages(images)

    # Instantiate writer object
    gifWriter = GifWriter()
    gifWriter.transparency = False  # init transparency flag used in GifWriter functions

    # Check loops
    if repeat is True:
        loops = 0  # zero means infinite
    elif repeat is False or repeat == 1:
        loops = -1
    else:
        loops = int(repeat - 1)

    # Check duration
    if hasattr(duration, '__len__'):
        if len(duration) == len(images):
            duration = [d for d in duration]
        else:
            raise ValueError("len(duration) doesn't match amount of images.")
    else:
        duration = [duration for im in images]

    # Check subrectangles
    if subRectangles:
        images, xy, images_info = gifWriter.handleSubRectangles(
            images, subRectangles)
        defaultDispose = 1  # Leave image in place
    else:
        # Normal mode
        xy = [(0, 0) for im in images]
        defaultDispose = 2  # Restore to background color.

    # Check dispose
    if dispose is None:
        dispose = defaultDispose
    if hasattr(dispose, '__len__'):
        if len(dispose) != len(images):
            raise ValueError("len(xy) doesn't match amount of images.")
    else:
        dispose = [dispose for im in images]

    # Make images in a format that we can write easy
    images = gifWriter.convertImagesToPIL(images, dither, nq)

    # Write
    try:
        from cStringIO import cStringIO as BytesIO
    except ImportError:
        from django.utils.six import BytesIO
    fp = BytesIO()
    gifWriter.writeGifToFile(fp, images, duration, loops, xy, dispose)
    fp.seek(0)
    return fp