将使用 wand 生成的图像保存到 django ImageField
Save an image generated with wand to django ImageField
我正在尝试为存储在 django 模型中的 "overlay" 配置生成预览,稍后将应用于其他模型。我没有太多使用 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'
怎么了?
无论何时将文件保存到 ImageField
或 FileField
,您都需要确保它是 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
模型时执行此操作,即使原始图像相同。但如果很少保存,应该不是问题。
已解决!
例如@Alexey Kuleshevich 说django FileField
需要一个文件objeto
,但缺少的是我们必须首先将图像保存到磁盘或内存中的文件中,正如我们猜测的那样这是更好的记忆...所以这是最终的解决方案。我认为可以改进不使用两步 "conversion"
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
?
以防万一,这里有一个更优雅(在我看来)的实现。首先,它需要这个应用程序: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)
我正在尝试为存储在 django 模型中的 "overlay" 配置生成预览,稍后将应用于其他模型。我没有太多使用 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'
怎么了?
无论何时将文件保存到 ImageField
或 FileField
,您都需要确保它是 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
模型时执行此操作,即使原始图像相同。但如果很少保存,应该不是问题。
已解决!
例如@Alexey Kuleshevich 说django FileField
需要一个文件objeto
,但缺少的是我们必须首先将图像保存到磁盘或内存中的文件中,正如我们猜测的那样这是更好的记忆...所以这是最终的解决方案。我认为可以改进不使用两步 "conversion"
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
?
以防万一,这里有一个更优雅(在我看来)的实现。首先,它需要这个应用程序: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)