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

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

Open koddr opened this issue 7 years ago • 4 comments

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.

koddr avatar Nov 20 '17 09:11 koddr

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

vstoykov avatar Nov 20 '17 09:11 vstoykov

@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) ^_^

koddr avatar Nov 20 '17 09:11 koddr

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.

vstoykov avatar Nov 20 '17 10:11 vstoykov

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

koddr avatar Nov 20 '17 10:11 koddr