django-stdimage icon indicating copy to clipboard operation
django-stdimage copied to clipboard

Rotate variations or keep exif info for JPEG

Open codingjoe opened this issue 9 years ago • 10 comments

http://stackoverflow.com/a/11543365/649951

codingjoe avatar Nov 11 '15 13:11 codingjoe

I'm using the following function as the callable in render_variations keyword argument of StdImageField. Maybe this can be useful for this issue.

from django.core.files.base import ContentFile

from stdimage.utils import render_variations

def resize_and_autorotate(file_name, variations, storage):
    with storage.open(file_name) as f:
        with Image.open(f) as image:
            file_format = image.format
            exif = image._getexif()

            # if image has exif data about orientation, let's rotate it
            orientation_key = 274 # cf ExifTags
            if exif and orientation_key in exif:
                orientation = exif[orientation_key]

                rotate_values = {
                    3: Image.ROTATE_180,
                    6: Image.ROTATE_270,
                    8: Image.ROTATE_90
                }

                if orientation in rotate_values:
                    image = image.transpose(rotate_values[orientation])

            with BytesIO() as file_buffer:
                image.save(file_buffer, file_format)
                f = ContentFile(file_buffer.getvalue())
                storage.delete(file_name)
                storage.save(file_name, f)

    # render stdimage variations
    render_variations(file_name, variations, replace=True, storage=storage)

    return False  # prevent default rendering

baxeico avatar Feb 05 '16 11:02 baxeico

@baxeico How did you call your render_variations function? I've tried this to no avail:

img = StdImageField(upload_to=UploadToUUID(path='posters'), blank=True, render_variations ,variations={
    'app': (179, 260),
    'thumbnail': (207, 299),
    'thumbnail_2x': (413, 598),
    'thumbnail_4x': (826, 1196),
    'full': (427, 616),
    'full_2x': (854, 1232),
})

josefnorlin avatar Dec 20 '17 17:12 josefnorlin

Solved it.

Put your function in the models.py and called it with render_variations=resize_and_autorotate in the img declaration

img = StdImageField(upload_to=UploadToUUID(path='posters'), blank=True, render_variations=resize_and_autorotate ,variations={
    'app': (179, 260),
    'thumbnail': (207, 299),
    'thumbnail_2x': (413, 598),
    'thumbnail_4x': (826, 1196),
    'full': (427, 616),
    'full_2x': (854, 1232),
})

But then I get an error, claiming: NameError name 'Image' is not defined

What happened there? Am I implementing this wrong @baxeico?

josefnorlin avatar Dec 20 '17 17:12 josefnorlin

@baxeico you will need to import pil.Image ;)

codingjoe avatar Dec 22 '17 14:12 codingjoe

I know this is an old issue.. but is there a chance this will be implemented into the library? I would like to avoid using a custom callable everywhere where I've used a StdImageField.

jckw avatar Sep 30 '19 19:09 jckw

@jckw it's not implemented yet, but we have the JPEGField now. Sooo I guess this would be a really cool feature to add. Would you mind creating the PR yourself?

codingjoe avatar Oct 02 '19 06:10 codingjoe

@jckw it's not implemented yet, but we have the JPEGField now. Sooo I guess this would be a really cool feature to add. Would you mind creating the PR yourself?

@codingjoe I think I should be able to have a look on this and create a PR. I briefly looked at the code and I think I could override the render_variations method on JPEGField and rotating the image before calling the parent render_variations. What do you think?

baxeico avatar Oct 02 '19 07:10 baxeico

It would be amazing if a "check for JPEG deal with EXIF tags" could be option on the regular ImageField too!

I assume we could just use whatever validator JPEG fields use to check that JPEGs are being used.

jckw avatar Oct 02 '19 21:10 jckw

I think it would be a great feature for both fields, but I would start with the JPEGField and take it from there.

@baxeico yes, be we should not rotate the original image, or at least not save a rotated version. It should remain unmodified. However, we can rotate it in memory before passing it to the render_variations function. We should also consider how to keep the EXIT information. I believe currently we drop in some cases for smaller variations.

codingjoe avatar Oct 03 '19 19:10 codingjoe

Updated @baxeico's solution with exif_transpose:

def render_variations_and_autorotate(file_name, variations, storage):
    with storage.open(file_name) as f:
        with Image.open(f) as image:
            transposed_image = ImageOps.exif_transpose(image)

            if not getattr(storage, 'file_overwrite', False):
                storage.delete(file_name)
            with BytesIO() as file_buffer:
                transposed_image.save(file_buffer, format=image.format)
                f = ContentFile(file_buffer.getvalue())
                storage.save(file_name, f)

    # Allow default render variations
    return True

IgorCode avatar Feb 22 '21 14:02 IgorCode