ほんじゃらねっと

ダイエット中プログラマのブログ

crop機能付きでサムネイル化できる画像管理モデルを作る

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を返すプロパティを呼び出すと生成される。