Изменить размер изображения при сохранении
Как я могу легко изменить размер изображения после его загрузки в Django? Я использую Django 1.0.2, и я установил PIL.
Я думал о переопределении метода save() Модели, чтобы изменить его размер, но я действительно не знаю, как начать и переопределить его.
Может ли кто-нибудь указать мне правильное направление? Спасибо:-)
@ Guðmundur H: Это не будет работать, потому что пакет django-stdimage не работает в Windows:-(
7 ответов
Вы должны использовать метод для обработки загруженного файла, как показано в документации Django.
В этом методе вы можете объединить фрагменты в переменной (а не записывать их непосредственно на диск), создать образ PIL из этой переменной, изменить размер изображения и сохранить его на диск.
В PIL, вы должны посмотреть на Image.fromstring
а также Image.resize
,
Я рекомендую использовать StdImageField из django-stdimage, он должен обрабатывать всю грязную работу за вас. Он прост в использовании, вы просто указываете размеры измененного изображения в определении поля:
class MyModel(models.Model):
image = StdImageField(upload_to='path/to/img', size=(640, 480))
Проверьте документы - он также может делать миниатюры.
Я использую этот код для обработки загруженных изображений, изменения их размера в памяти (без сохранения их на диске), а затем сохраняю большой палец на Django ImageField. Надежда может помочь.
def handle_uploaded_image(i):
import StringIO
from PIL import Image, ImageOps
import os
from django.core.files import File
# read image from InMemoryUploadedFile
image_str = “”
for c in i.chunks():
image_str += c
# create PIL Image instance
imagefile = StringIO.StringIO(image_str)
image = Image.open(imagefile)
# if not RGB, convert
if image.mode not in (“L”, “RGB”):
image = image.convert(“RGB”)
#define file output dimensions (ex 60x60)
x = 130
y = 130
#get orginal image ratio
img_ratio = float(image.size[0]) / image.size[1]
# resize but constrain proportions?
if x==0.0:
x = y * img_ratio
elif y==0.0:
y = x / img_ratio
# output file ratio
resize_ratio = float(x) / y
x = int(x); y = int(y)
# get output with and height to do the first crop
if(img_ratio > resize_ratio):
output_width = x * image.size[1] / y
output_height = image.size[1]
originX = image.size[0] / 2 - output_width / 2
originY = 0
else:
output_width = image.size[0]
output_height = y * image.size[0] / x
originX = 0
originY = image.size[1] / 2 - output_height / 2
#crop
cropBox = (originX, originY, originX + output_width, originY + output_height)
image = image.crop(cropBox)
# resize (doing a thumb)
image.thumbnail([x, y], Image.ANTIALIAS)
# re-initialize imageFile and set a hash (unique filename)
imagefile = StringIO.StringIO()
filename = hashlib.md5(imagefile.getvalue()).hexdigest()+’.jpg’
#save to disk
imagefile = open(os.path.join(‘/tmp’,filename), ‘w’)
image.save(imagefile,’JPEG’, quality=90)
imagefile = open(os.path.join(‘/tmp’,filename), ‘r’)
content = File(imagefile)
return (filename, content)
#views.py
form = YourModelForm(request.POST, request.FILES, instance=profile)
if form.is_valid():
ob = form.save(commit=False)
try:
t = handle_uploaded_image(request.FILES[‘icon’])
ob.image.save(t[0],t[1])
except KeyError:
ob.save()
Я настоятельно рекомендую приложение sorl-thumbnail для легкого и прозрачного изменения размера изображения. Это входит в каждый проект Django, который я начинаю.
Вот полное решение для вас, используя форму. Я использовал вид администратора для этого:
class MyInventoryItemForm(forms.ModelForm):
class Meta:
model = InventoryItem
exclude = ['thumbnail', 'price', 'active']
def clean_photo(self):
import StringIO
image_field = self.cleaned_data['photo']
photo_new = StringIO.StringIO(image_field.read())
try:
from PIL import Image, ImageOps
except ImportError:
import Image
import ImageOps
image = Image.open(photo_new)
# ImageOps compatible mode
if image.mode not in ("L", "RGB"):
image = image.convert("RGB")
image.thumbnail((200, 200), Image.ANTIALIAS)
image_file = StringIO.StringIO()
image.save(image_file, 'png')
image_field.file = image_file
return image_field
Моя модель инвентаря выглядит так:
class InventoryItem(models.Model):
class Meta:
ordering = ['name']
verbose_name_plural = "Items"
def get_absolute_url(self):
return "/products/{0}/".format(self.slug)
def get_file_path(instance, filename):
if InventoryItem.objects.filter(pk=instance.pk):
cur_inventory = InventoryItem.objects.get(pk=instance.pk)
if cur_inventory.photo:
old_filename = str(cur_inventory.photo)
os.remove(os.path.join(MEDIA_ROOT, old_filename))
ext = filename.split('.')[-1]
filename = "{0}.{1}".format(uuid.uuid4(), ext)
return os.path.join('inventory', filename)
#return os.path.join(filename)
def admin_image(self):
return '<img height="50px" src="{0}/{1}"/>'.format(MEDIA_URL, self.photo)
admin_image.allow_tags = True
photo = models.ImageField(_('Image'), upload_to=get_file_path, storage=fs, blank=False, null=False)
thumbnail = models.ImageField(_('Thumbnail'), upload_to="thumbnails/", storage=fs, blank=True, null=True)
....
Я переписал функцию сохранения модели вместо того, чтобы сохранить фотографию и большой палец вместо того, чтобы просто изменить размер фотографии:
def save(self):
# Save this photo instance first
super(InventoryItem, self).save()
from PIL import Image
from cStringIO import StringIO
from django.core.files.uploadedfile import SimpleUploadedFile
# Set our max thumbnail size in a tuple (max width, max height)
THUMBNAIL_SIZE = (200, 200)
# Open original photo which we want to thumbnail using PIL's Image object
image = Image.open(os.path.join(MEDIA_ROOT, self.photo.name))
if image.mode not in ('L', 'RGB'):
image = image.convert('RGB')
image.thumbnail(THUMBNAIL_SIZE, Image.ANTIALIAS)
# Save the thumbnail
temp_handle = StringIO()
image.save(temp_handle, 'png') # image stored to stringIO
temp_handle.seek(0) # sets position of file to 0
# Save to the thumbnail field
suf = SimpleUploadedFile(os.path.split(self.photo.name)[-1],
temp_handle.read(), content_type='image/png') # reads in the file to save it
self.thumbnail.save(suf.name+'.png', suf, save=False)
#Save this photo instance again to save the thumbnail
super(InventoryItem, self).save()
Оба отлично работают, хотя в зависимости от того, что вы хотите сделать:)
Я знаю, что это старый, но для любого, кто наткнулся на это, есть пакет, django-thumbs
at Django-thumbs - Легкие мощные миниатюры для Django, интегрированные с StorageBackend, который автоматически генерирует миниатюры в указанных вами размерах или ни одного, если вы этого не сделаете. Затем вы вызываете нужную миниатюру с нужными вам размерами.
Например, если вы хотите, чтобы изображение имело эскизы размером 64x64 и 128x128, вы просто импортируете thumbs.models.ImageWithThumbsField
и использовать его вместо ImageField
, Добавить параметр sizes=((64,64),(128,128))
к определению поля, то из вашего шаблона вы можете позвонить:
{{ ClassName.field_name.url_64x64 }}
а также
{{ ClassName.field_name.url_128x128 }}
для отображения миниатюр. Вуаля! Вся работа выполнена в этом пакете для вас.
Если вы используете Django Rest Framework, это может использовать:
Сначала определите функцию для сжатия и изменения размера изображения
def compress_image(photo):
# start compressing image
image_temporary = Image.open(photo)
output_io_stream = BytesIO()
image_temporary.thumbnail((1250, 1250), Image.ANTIALIAS)
# change orientation if necessary
for orientation in ExifTags.TAGS.keys():
if ExifTags.TAGS[orientation] == 'Orientation':
break
exif = dict(image_temporary._getexif().items())
# noinspection PyUnboundLocalVariable
if exif.get(orientation) == 3:
image_temporary = image_temporary.rotate(180, expand=True)
elif exif.get(orientation) == 6:
image_temporary = image_temporary.rotate(270, expand=True)
elif exif.get(orientation) == 8:
image_temporary = image_temporary.rotate(90, expand=True)
# saving output
image_temporary.save(output_io_stream, format='JPEG', quality=75, optimize=True, progressive=True)
output_io_stream.seek(0)
photo = InMemoryUploadedFile(output_io_stream, 'ImageField', "%s.jpg" % photo.name.split('.')[0],
'image/jpeg', getsizeof(output_io_stream), None)
return photo
Во-вторых, теперь вы можете использовать функцию в сериализаторах:
class SomeSerializer(serializers.ModelSerializer):
def update(self, instance, validated_data):
# сжимаем рисунок
if 'photo' in validated_data:
validated_data.update({'photo': compress_image(validated_data['photo'])})
return super(SomeSerializer, self).update(instance, validated_data)
def create(self, validated_data):
# сжимаем рисунок
if 'photo' in validated_data:
validated_data.update({'photo': compress_image(validated_data['photo'])})
return super(SomeSerializer, self).create(validated_data)