crop機能を初めて使ったけど、縦か横どちらかに長い画像をサムネイル化する時は、cropした方が見栄えがいいですね。
相変わらず整理されてないソースだけど、メモメモ。
ライブラリは、Python Imaging Library と、
http://www.pythonware.com/products/pil/
インターレースPNGを扱えるpypngというのを利用している。
http://code.google.com/p/pypng/
モデルはこんな感じ。
毎回こんなクラスを作成するより、abstractなクラスを作った方がいいよね。
画像パス生成関数をなぜモデルの外に置く必要もなさそうだし。
# -*- coding: utf-8 -*- import re import os import png from PIL import Image as PILImage from django.db import models from django.contrib.auth.models import User from django.conf import settings from django.core.exceptions import ObjectDoesNotExist # Create your models here. def _get_image_upload_path(self, filename): file_path = "userfiles/%s/img" % self.user.username root_path = settings.MEDIA_ROOT + file_path if not os.path.exists(root_path): os.makedirs(root_path) m = re.search(r"(?P<ext>\.[a-zA-Z0-9]+$)", filename) ext = m.group("ext") newfilename = self.code + ext return file_path + "/" + newfilename class ImageModel(BaseModel): user = models.ForeignKey(User) code = models.SlugField("画像コード", max_length=100, unique=True) title = models.CharField("タイトル", max_length=255) image = models.ImageField("画像ファイル", upload_to=_get_company_image_upload_path) def get_resized_path(self, width, height, crop_mode=False): user_path = "userfiles/%s/img" % self.user.username user_dir_path = settings.MEDIA_ROOT + user_path if not os.path.exists(user_dir_path): os.mkdir(user_dir_path) file_name = os.path.split(self.image.name)[1] image_path = "%s/%dx%d_%s" % (user_path, width, height, file_name,) image_file_path = settings.MEDIA_ROOT + image_path if not os.path.exists(image_file_path): self.save_resized_image(image_file_path, width, height, crop_mode) return image_path def save_resized_image(self, filepath, width, height, crop_mode=False): if os.path.exists(self.image.path): self._convert_interlaced_png(self.image.path) pil_obj = PILImage.open(self.image.path) if pil_obj.mode !='RGB': pil_obj = pil_obj.convert('RGB') if crop_mode: w, h = pil_obj.size if w > h: pil_obj.thumbnail((w, int(height)), PILImage.ANTIALIAS) w, h = pil_obj.size pil_obj = pil_obj.crop((w/2-width/2, 0, w/2+width/2, h)) else: pil_obj.thumbnail((int(width), h), PILImage.ANTIALIAS) w, h = pil_obj.size pil_obj = pil_obj.crop((0, h/2-height/2, w, h/2+height/2)) else: pil_obj.thumbnail((int(width), int(height)), PILImage.ANTIALIAS) pil_obj.save(filepath, quality=100) def _convert_interlaced_png(self, filepath): "use pypng to disable interlace png. PIL cannnot use interlace pngs." if filepath.endswith(".png"): preader = png.Reader(file=open(filepath,'rb')) (base,exp) = filepath.split('.') imgdata = preader.asDirect() if bool(imgdata[3]['interlace']) == True: pwriter = png.Writer(imgdata[0],imgdata[1],interlace=False,bitdepth=imgdata[3]['bitdepth'],planes=imgdata[3]['planes'],alpha=imgdata[3]['alpha']) pngfile = open(filepath,'wb') pwriter.write(pngfile,imgdata[2]) pngfile.close() def get_resized_url(self, width, height, crop_mode=False): image_path = self.get_resized_path(width, height, crop_mode) image_url = "%s%s" % (settings.MEDIA_URL, image_path) return image_url @property def img_200x150_url(self): return self.get_resized_url(200, 150) @property def img_60x60_url(self): return self.get_resized_url(60, 60, crop_mode=True)
後はModelFormを作成して、views.pyでform.save()したらアップできる。
アップした時点ではサムネイル画像は生成されておらず、get_resized_urlやサムネイル画像URLを返すプロパティを呼び出すと生成される。