諸般の事情
私の住んでいるマンションの管理組合では、各棟に掲示物を貼り出すことができます(極端に政治的に偏ったものとか、公序良俗に反するものでない限りはOK)。これを管理センターの職員さんが複合機で電子データにしてgmailに送ってもらってそれをFC2ブログでも公開しています。
しかし、その電子データがPDFで、場合によって複数ページにくっついて送られてくるので、PDFの画像データを取り出して2MB以下のpngファイルにして…というのが重かったりします。最終的にはこの処理をほぼ自動化したいのですが、手始めに「PDF画像から画像を取得して、適当な名前でセーブする」までを行いました1, 2。
Python環境
用意したもの
- Python3.6
- Wand
- ImageMagick
- ghostscript
もっとエレガントなモジュールもありそうな気はしたけれど、簡単に済ませられるやつで…。
Wand自体はpip installで入りますが、外部プログラムでImageMagickとghostscriptは必須なので、別途インストールが必要です。
ソースコード
import os
from wand.image import Image
from wand.display import display
from wand.sequence import Sequence
class ImageNotFound(Exception):
def __init__(self, filename):
self.filename = filename
class ImagesForUpload:
def __init__(self, filename):
self.filename = filename
self.tnFilename = None
self.img = []
self.thumbnail = []
self.sequence = 0
def openImage(self, formatType='png'):
img = Image(filename=self.filename, resolution=200)
if len(img.sequence) > 1:
for i in img.sequence:
nImg = Image(i)
nImg.format = formatType
nImg.quantize(16, 'rgb', 0, True, False)
self.img.append(nImg)
else:
img.format = formatType
img.quantize(16, 'rgb', 0, True, False)
self.img.append(img)
self.sequence = len(self.img)
self.filename = os.path.splitext(os.path.basename(self.filename))[0]
return self.img
def makeThumbnailImage(self, width =500, formatType= 'png'):
if len(self.img) == 0:
raise ImageNotFound(self.filename)
for (i, img) in enumerate(self.img):
print('%d / %d consective images...' % (i+1, len(self.img)))
tImg = img.clone()
w = tImg.width
h = tImg.height
height = int((width * h) / w)
tImg.resize(width = width, height = height)
tImg.format = formatType
self.thumbnail.append(tImg)
self.tnFilename = os.path.splitext(os.path.basename(self.filename))[0] + '-s'
return self.thumbnail
def saveImage(self, saveAs=None):
for (i, img) in enumerate(self.img):
img.save(filename=self.filename+'-'+str(i) +'.'+img.format)
def saveThumbnailImage(self, saveAs=None):
for (i, img) in enumerate(self.thumbnail):
img.save(filename = self.tnFilename + '-' + str(i) + '.' + img.format)
はまりポイント
SimpleImageオブジェクトの罠
Wandは、複数ページあるPDFデータをSimpleImageクラスに保存して、そのリストをImage.sequenceに保存しているのですが、SimpleImageクラスそのものにresizeなどを適用しても反映されません。正確には「最初の1ページだけ反映されて、残りは無視されている」ような結果になります。
ImageクラスのコンストラクタにはSimpleImageクラスを渡せるので、一個一個渡していますImageにしてしまえば何でもありにできるので。
色の減色に関するドキュメントがなかった
Wandは下回りは、古くからのUnixユーザならおなじみのImageMagickを使っていますが、画像中の色の減色をすることができたはずなのに、Wand側にちゃんとしたドキュメントがありませんでした。
Image.quantizeメソッドというのがあるそうですが、ここはソースがドキュメントになっていますね…。
def quantize(self, colors, colorspace=None, tree_depth=1, dither=False, measure_error=False):
''' Limit the colours present in an image to a fixed amount.
colors - The number of colors in the new image.
colorspace - Perform color reduction in this colorspace, defaults to the image's colorspace.
tree_depth - Default is to choose an optimal tree depth of
Log4(colors). A tree of this depth generally allows
the best representation of the reference image with
the least amount of memory and the fastest
computational speed. In some cases, such as an image
with low color dispersion (a few number of colors), a
value other than Log4(number_colors) is required. To
expand the color tree completely, use a value of 8.
dither - If True, the image is dithered. Defaults to False.
measure_error - If True, measures the difference between the
original and quantized images as an error.
Defaults to False.'''
通常は、tree_depthはデフォルト値で大丈夫です。ditherはTrue、measure_errorは今回の用途には不要なのでFalseを与えています。
マンションの掲示物なんて、そんなにフルカラー要求されることはないので、16色くらいまで豪快に落としています。
よく考えれば、2MB以下になるまでquantizeし続けて一番良い色数にする、というのもありですが、めんどくさかったので決め打ちにしてます。
おわりに
- Wand、当初の目論見よりめんどくさかった(きっぱり)。
- 実はGmail APIでメール取得とFC2ブログにXML-RPC経由で投稿する、というところまでは作ったけれど、完成度が全然なので、もう少しこなれてから。