codingjoe / django-stdimage

Django Standardized Image Field
MIT License
259 stars 63 forks source link

Rotate variations or keep exif info for JPEG #66

Open codingjoe opened 8 years ago

codingjoe commented 8 years ago

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

baxeico commented 8 years ago

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
josefnorlin commented 6 years ago

@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 commented 6 years ago

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?

codingjoe commented 6 years ago

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

jckw commented 5 years ago

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.

codingjoe commented 5 years ago

@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?

baxeico commented 5 years ago

@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?

jckw commented 5 years ago

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.

codingjoe commented 5 years ago

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.

IgorCode commented 3 years ago

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