【问题标题】:Save an image generated with wand to django ImageField将使用 wand 生成的图像保存到 django ImageField
【发布时间】:2015-03-30 13:27:17
【问题描述】:

我正在尝试为存储在 django 模型中的“叠加”配置生成预览,而不是稍后应用于其他模型。我没有太多使用 python 操作文件的经验... =(

这是我的代码:

import io
from django.conf import settings
from django.db import models
from wand.image import Image
from PIL.ImageFile import ImageFile, Parser, Image as PilImage

class Overlay(models.Model):
    RELATIVE_POSITIONS = (...)
    SIZE_MODES = (...)

    name = models.CharField(max_length=50)
    source = models.FileField(upload_to='overlays/%Y/%m/%d')
    sample = models.ImageField(upload_to='overlay_samples/%Y/%m/%d', blank=True)
    px = models.SmallIntegerField(default=0)
    py = models.SmallIntegerField(default=0)
    position = models.CharField(max_length=2, choices=RELATIVE_POSITIONS)
    width = models.SmallIntegerField(default=0)
    height = models.SmallIntegerField(default=0)
    size_mode = models.CharField(max_length=1, choices=SIZE_MODES, default='B')
    last_edit = models.DateTimeField(auto_now=True)

    def generate_sample(self):
        """
        Generates the sample image and saves it in the "sample" field model
        :return: void
        """
        base_pic = Image(filename=os.path.join(settings.BASE_DIR, 'girl.jpg'))
        overlay_pic = Image(file=self.source)
        result_pic = io.BytesIO()
        pil_parser = Parser()

        if self.width or self.height:
            resize_args = {}
            if self.width:
                resize_args['width'] = self.width
            if self.height:
                resize_args['height'] = self.height
            overlay_pic.resize(**resize_args)
            base_pic.composite(overlay_pic, self.px, self.py)
            base_pic.save(file=result_pic)

        result_pic.seek(0)
        while True:
            s = result_pic.read(1024)
            if not s:
                break
            pil_parser.feed(s)

        pil_result_pic = pil_parser.close()
        self.sample.save(self.name, pil_result_pic, False)

    def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
        self.generate_sample()
        super(Overlay, self).save(force_insert, force_update, using, update_fields)

但是我在这里读取的 AttributeError 是我的 django 调试数据的一部分:

 /usr/local/lib/python2.7/dist-packages/django/core/files/utils.py in <lambda>

    """
    encoding = property(lambda self: self.file.encoding)
    fileno = property(lambda self: self.file.fileno)
    flush = property(lambda self: self.file.flush)
    isatty = property(lambda self: self.file.isatty)
    newlines = property(lambda self: self.file.newlines)
    read = property(lambda self: self.file.read)
    readinto = property(lambda self: self.file.readinto)
    readline = property(lambda self: self.file.readline)
    readlines = property(lambda self: self.file.readlines)
    seek = property(lambda self: self.file.seek)
    softspace = property(lambda self: self.file.softspace)
    tell = property(lambda self: self.file.tell)

▼ 局部变量 变量值

self    <File: None>



/usr/local/lib/python2.7/dist-packages/PIL/Image.py in __getattr__

        # numpy array interface support
        new = {}
        shape, typestr = _conv_type_shape(self)
        new['shape'] = shape
        new['typestr'] = typestr
        new['data'] = self.tobytes()
        return new
    raise AttributeError(name)

def __getstate__(self):
    return [
        self.info,
        self.mode,
        self.size,

▼ 局部变量 变量值

self    <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=1080x1618 at 0x7F1429291248>
name    'read'

怎么了?

【问题讨论】:

    标签: python django imagemagick django-models wand


    【解决方案1】:

    每当您将文件保存到 ImageFieldFileField 时,您需要确保它是 Django 的 File 对象。这里参考文档:https://docs.djangoproject.com/en/1.7/ref/models/fields/#filefield-and-fieldfile

    from django.core.files import File
    

    在方法内:

    def generate_sample(self):
        ...
        pil_result_pic = pil_parser.close()
        self.sample.save(self.name, File(pil_result_pic), False)
    

    否则它看起来不错,虽然我可能错过了一些东西。试试看它是否能解决问题,如果没有,我会进一步研究。

    编辑

    您实际上不需要解析器。我认为应该可以解决它:

    from django.core.files import ContentFile
    
    class Overlay(models.Model):
        ...
    
        def generate_sample(self):
            base_pic = Image(filename=os.path.join(settings.BASE_DIR, 'girl.jpg'))
            overlay_pic = Image(file=self.source)
            result_pic = io.BytesIO()
    
            if self.width or self.height:
                resize_args = {}
                if self.width:
                    resize_args['width'] = self.width
                if self.height:
                    resize_args['height'] = self.height
                overlay_pic.resize(**resize_args)
            base_pic.composite(overlay_pic, self.px, self.py)
            base_pic.save(file=result_pic)
    
            content = result_pic.getvalue()
            self.sample.save(self.name, ContentFile(content), False)
            result_pic.close()
            base_pic.close()
            overlay_pic.close()
    

    有一件事可能是一个潜在的问题,它会在每次保存Overlay 模型时执行此操作,即使原始图像相同。但如果很少保存,应该不是问题。

    【讨论】:

    • 尝试使用“from django.core.files import File”和“from django.core.files.images import ImageFile”并尝试了许多其他方法,但似乎没有任何反应。提前一千感谢
    • 也尝试过:pil_result_pic = pil_parser.close() django_file = File(open(pil_result_pic)) self.sample.save(self.name, django_file, False) 现在得到以下错误:强制到 Unicode:需要字符串或缓冲区,找到实例。我不知道,但不知何故我觉得我更接近@alexey-kuleshevich
    • 我认为强制转换为 Unicode 是因为它没有以二进制模式打开,但我不确定 100%。无论如何,如果有新提议的解决方案适合您,请告诉我。
    • 哈哈谢谢!!我们达成了相同的解决方案,但您最好跳过“pil_parser” 建议的解决方案效果很好! =D
    • 是的,很有趣,我们几乎同时得到了相同的解决方案 :) 我还可以向您展示一种仅在 source 字段更改时生成样本的方法,而不是每次模型已保存。如果你想让我在这里输入,请告诉我。
    【解决方案2】:

    解决了!

    例如@Alexey Kuleshevich 说django FileField 需要一个文件objeto,但缺少的是我们必须首先将图像保存到磁盘或内存中的文件中,因为会猜测它是更好的内存...所以这是最终的解决方案。我认为不使用两步“转换”可以改进

    from django.core.files.base import ContentFile
    

    在方法内:

        result_pic = io.BytesIO()
        pil_parser = Parser()
    
        ...
        overlay_pic.resize(**resize_args)
        base_pic.composite(overlay_pic, self.px, self.py)
        base_pic.save(file=result_pic)
    
        result_pic.seek(0)
        while True:
            s = result_pic.read(1024)
            if not s:
                break
            pil_parser.feed(s)
    
        result_pic = io.BytesIO()
        pil_result_pic = pil_parser.close()
        pil_result_pic.save(result_pic, format='JPEG')
        django_file = ContentFile(result_pic.getvalue())
        self.sample.save(self.name, django_file, False)
    

    感谢这个答案: How do you convert a PIL Image to a Django File?

    【讨论】:

      【解决方案3】:

      以防万一,这里有一个更优雅的(在我看来)实现。首先它需要这个应用程序:django-smartfields。这个解决方案如何更好:

      • 仅在 source 字段发生更改时更新 sample 字段,并且仅在保存模型之前。
      • 如果keep_orphans 被省略,旧的source 文件将被清理。

      实际代码:

      import os
      from django.conf import settings
      from django.db import models
      from django.utils import six
      
      from smartfields import fields
      from smartfields.dependencies import FileDependency
      from smartfields.processors import WandImageProcessor
      from wand.image import Image
      
      class CustomImageProcessor(WandImageProcessor):
      
          def resize(self, image, scale=None, instance=None, **kwargs):
              scale = {'width': instance.width, 'height': instance.height}
              return super(CustomImageProcessor, self).resize(
                  image, scale=scale, instance=instance, **kwargs)
      
          def convert(self, image, instance=None, **kwargs):
              base_pic = Image(filename=os.path.join(settings.BASE_DIR, 'girl.jpg'))
              base_pic.composite(image, instance.px, instance.py)
              stream_out = super(CustomImageProcessor, self).convert(
                  image, instance=instance, **kwargs):
              if stream_out is None:
                  stream_out = six.BytesIO()
                  base_pic.save(file=stream_out)
              return stream_out        
      
      
      class Overlay(models.Model):
          RELATIVE_POSITIONS = (...)
          SIZE_MODES = (...)
      
          name = models.CharField(max_length=50)
          source = fields.ImageField(upload_to='overlays/%Y/%m/%d', dependencies=[
              FileDependency(attname='sample', processor=CustomImageProcessor())
          ], keep_orphans=True)
          sample = models.ImageField(upload_to='overlay_samples/%Y/%m/%d', blank=True)
          px = models.SmallIntegerField(default=0)
          py = models.SmallIntegerField(default=0)
          position = models.CharField(max_length=2, choices=RELATIVE_POSITIONS)
          width = models.SmallIntegerField(default=0)
          height = models.SmallIntegerField(default=0)
          size_mode = models.CharField(max_length=1, choices=SIZE_MODES, default='B')
          last_edit = models.DateTimeField(auto_now=True)
      

      【讨论】:

      • 酷,如果您对此解决方案或 smartfields 应用程序有任何疑问,请告诉我。
      猜你喜欢
      • 2018-08-10
      • 1970-01-01
      • 2012-02-08
      • 2011-12-15
      • 2013-02-13
      • 1970-01-01
      • 2010-11-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多