matthewwithanm / django-imagekit

Automated image processing for Django. Currently v4.0
http://django-imagekit.rtfd.org/
BSD 3-Clause "New" or "Revised" License
2.26k stars 276 forks source link

How to get my custom processor with parameters from saved Django model? #442

Open koddr opened 6 years ago

koddr commented 6 years ago

Hello and thanks for awesome package!

Django 1.11.7

I use custom processor for text overlay. This is my code:

# ./example/processors.py

from django.conf import settings
from imagekit import ImageSpec, register
from PIL import Image, ImageDraw, ImageFont

_default_font = ImageFont.truetype(settings.TEXT_OVERLAY_FONT, 24)

def add_text_overlay(image, text, font=_default_font):
    rgba_image = image.convert('RGBA')
    text_overlay = Image.new('RGBA', rgba_image.size, (255, 255, 255, 0))

    image_draw = ImageDraw.Draw(text_overlay)
    text_size_x, text_size_y = image_draw.textsize(text, font=font)
    text_xy = ((rgba_image.size[0] / 2) - (text_size_x / 2), (rgba_image.size[1] / 2) - (text_size_y / 2))

    image_draw.text(text_xy, text, font=font, fill=(255, 255, 255, 255))
    image_with_text_overlay = Image.alpha_composite(rgba_image, text_overlay)

    return image_with_text_overlay

class TextOverlayProcessor(object):
    def __init__(self, text=None):
        """
        :param text: The overlay text, string.

        """
        self.text = text

    def process(self, img):
        return add_text_overlay(image=img, text=self.text)

And add him to Django model:

# ./example/models.py

from django.db import models
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
from .processors import TextOverlayProcessor

class ExampleModel(models.Model):
    title = models.CharField('Image title', max_length=25, default=None, blank=False)
    image = models.ImageField('Image source', default=None)
    image_800x800 = ImageSpecField(
        source='image',
        processors=[ResizeToFill(800, 800), TextOverlayProcessor(text='Test text')],
        format='JPEG',
        options={'quality': 100}
    )

Everything ok. After save this model, I have image with overlayed text on center Test text. But how to get value of title field fromExampleModel model and save this image after save model?

Help me please.

vstoykov commented 6 years ago

You want instead of hardcoded text to use the value of the title field right?

koddr commented 6 years ago

@vstoykov yep. I'll explain a little why this is necessary.

The moderators of this site create a new entry in which they write a description (text field) of the photo (for display on the photo as overlay text) and attach a photo (which is cropped in size 800 to 800).

Next, this photo is being posted to Twitter (but that's another story) ^_^

vstoykov commented 6 years ago

By default Processors are not intended to know anything about Specs and for that reason you can't tell that info.

As a workaround I'm thinking of property that will return the new image by using modified snipped from the README (https://github.com/matthewwithanm/django-imagekit#defining-specs-outside-of-models).

from django.db import models
from imagekit import ImageSpec
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
from .processors import TextOverlayProcessor

class ExampleModel(models.Model):
    title = models.CharField('Image title', max_length=25, default=None, blank=False)
    image = models.ImageField('Image source', default=None)

    @property
    def image_800x800(self):

        class Thumbnail(ImageSpec):
            processors = [ResizeToFill(800, 800), TextOverlayProcessor(text=self.title)]
            format = 'JPEG'
            options = {'quality': 60}

        image_generator = Thumbnail(source=source_file)
        result = image_generator.generate()
        return result

Probably this code will not work exactly as you want (if it works at all, I have not tested it). But I think that it can give you an idea.

You can also look at the code for ImageSpec and ImageSpecField to see if you can pass the needed information somehow and create your own subclass of ImageSpecField that will pass current instance or only some fields of it to processors (or some of them).

There is currently no such functionality built in because it's not so trivial.

koddr commented 6 years ago

@vstoykov oh, great! Thanks for fast feedback, btw 👍 I'll test this code later and write if it's worked fine.

Would be nice to have included class/method, like ImageSpecField(), but for overlay and watermark elements (and hide all magic for users — use only dummy parameters). For example:

class ExampleModel(models.Model):
    image = models.ImageField('Image source', default=None)

    overlay_text = models.CharField('Image for overlay text', max_length=25, default=None, blank=False)
    watermark_image = models.ImageField('Image for watermark', default=None)

    image_text_overlay = ImageTextOverlayField(
        source_image='image'
        source_text='overlay_text',
        processors=[TextOverlayProcessor(placed='center', font='...', font_size=24, opacity=0.6, ...)]
    )

    image_watermark = ImageWatermarkField(
        source_image='image'
        source_watermark='watermark_image',
        processors=[WatermarkProcessor(placed='center', opacity=0.6, ...)]
    )

And next in templates and/or views (as usual):

{{ example.image_text_overlay.url }}
{{ example.image_watermark.url }}