PythonとPILで、サブピクセルレンダリング縮小をする

ちょっと今更な話ですが、ライフネット生命副社長である岩瀬大輔さん著、文春新書『生命保険のカラクリ』が、全編PDFで公開されています。

これを印刷して通勤時間を利用して読もうと思ったのですが、綺麗に印刷することができないよう制限がかけられていました。なので、携帯電話で読んでやろうと思い、私の携帯電話に適したサイズに変換することにしました。
なお、私の携帯電話のスペックは、480x800の3インチ液晶です。

縮小の手順

  1. PDFをビットマップ変換
  2. ビットマップを、サブピクセルレンダリング縮小する
  3. 余白をトリミングする。トリミング結果が、480x800になるように、1,2の工程を工夫する

という手順を取ることにしました。
1のPDFビットマップ変換はGhostScriptを使用し、2と3のビットマップ画像を縮小とトリミングにはPythonとPILを使いました

PDFをビットマップに変換

色々試した結果、PDFを150dpiでラスタライズ(ビットマップ変換)した画像から余白をトリミングしたら、丁度480x800になることがわかりました。ということで、PDFからPNGへの変換は以下のようにして変換しました。

% gs -dTextAlphaBits=4 -sDEVICE=pnggray -dBATCH -q -dNOPAUSE -sOutputFile=seiho/%03d.png -r451x150 seiho.pdf

後ほどRGBのサブピクセルを計算する余地として、横方向の解像度を3倍としておきます。
変換結果:

ビットマップをサブピクセルレンダリング縮小する

単純な実装

元の画像はグレースケールにアンチエイリアシングしてあり、横方向に3倍の解像度を持っています。これを、以下のようにRGBの各サブピクセルに割り当ててみました。

  • a: ある行のグレースケールの値
  • b: それを、rgbにそのまま割り当てた場合の、サブピクセルの模式図
  • c: bのrgbの値をピクセルにしたもの

aを1/3に縮めてbのように表示するデータがc、という感じかな……。

さきほどのページをこの方法で縮小した画像がはこちら。

曲線部分は綺麗に出ていますが、縦画の左右に偽色が出ているのがわかると思います。単純に考えると、

のように見えるはずなのですが……。

左右のピクセルとの平均を取るようにした実装

エッジが目立ちすぎているので、アンチエイリアシングの基本に立ち返り、左右のピクセルとの平均を取るようにしてみました。
言葉で説明すると難しいですが、左からグレースケールの値(a)が以下のようなデータの場合、

00,55,ff,ff,ff,ff,00,00,00,00,ff,ff,ff,ff,...

左から順に、00,55,ffの平均の71、55,ff,ffの平均のc6、ff,ff,ffでff、ff,ff,ffでff、ff,ff,00でaa、としていきます.。


ということで、綺麗になりました。
携帯電話でもとても綺麗に表示されます。3インチでも問題なく読むことができます。

今回の例は、元データがモノクロのグレースケール画像だったのが、処理を非常に簡単にしています。
また、今回の実装は実装の簡単さと速度を考慮して、3ピクセルの算術平均にしましたが、アンチエイリアスの平均の取得方法には色々なアルゴリズムがあるようです。
最後に、おまけとして、Python + PILのコードを掲載しておきます。元画像や変換後のサイズに汎用性がない、私専用のものではありますが……。それにしても、PILは簡単でいいですね。

あと、言葉で説明するのが難しくて、支離滅裂でわかりにくかったらすいません。

Python + PIL のコード

#! /usr/bin/env python
# -*- coding: utf-8 -*-

from PIL import Image, ImageChops

# 縮小後のサイズ
destSize = (480, 800)

# 元サイズでのトリミングの起点(左上原点)
geom = (240, 115)

def main(sourceFile):
    sourceImage = Image.open(sourceFile)
    shrinked = shrink(sourceImage)
    shrinked.save("out/%s" % sourceFile, "PNG")

def shrink(sourceImage):
    targetImage = Image.new('RGB', destSize)
    targetPix = targetImage.load()
    sourcePix = sourceImage.load()

    (l, t) = geom
    for y in range(destSize[1]):
        l = geom[0]
        gg = sourcePix[l-2, t] + sourcePix[l-1, t] + sourcePix[l, t]
        for x in range(destSize[0]):
            r = gg = gg - sourcePix[l-2, t] + sourcePix[l+1, t]
            g = gg = gg - sourcePix[l-1, t] + sourcePix[l+2, t]
            b = gg = gg - sourcePix[l  , t] + sourcePix[l+3, t]
            targetPix[x, y] = (r/3, g/3, b/3)
            l += 3
        t += 1
    return targetImage
        
if __name__ == "__main__":
    import sys
    for sourceFile in sys.argv[1:]:
        print sourceFile
        main(sourceFile)

EclipseでのJava開発 with Maven2 and Bazaar

対象: Eclipse-3.5, Maven-2.2.x, Bazaar-2.0.x

JavaでSwingアプリの開発をやっています。
具体的には、Maven2で構成管理をし、Bazaarでソースコード管理をし、NetBeansGUIを作成し、Eclipseでプログラムを書いています。開発の上で試行錯誤の末辿りついたBazaarとMaven2Eclipseの構成を紹介します。(EclipseNetBeansの連携の話は後日また)
なお、私のBazaar使用歴は短かいです(2.0からのユーザです)。以下で紹介する構成には、誤解や誤りに基く内容が含まれている可能性がありますのでご注意ください。識者のご指導ご鞭撻を期待しつつ、エントリを上げている次第です。

続きを読む

vboxapiでVirtualBoxをPythonで操作その4

対象: VirtualBox-3.1, Python-2.6

ということで、調査中に見つけた情報源を以下に記述しつつ、VirtualBoxシリーズは一旦ここまにしたいと思います。

http://www.virtualbox.org/

VirtualBoxの公式サイトです。

公式サイトのダウンロードページには以下のようなものが含まれます。

続きを読む

vboxapiでVirtualBoxをPythonで操作その3

対象: VirtualBox-3.1, Python-2.6

前回、前々回の続きです。
VMを起動したり電源をブチっと切ってみたり、一時停止したりしてみます。これらの操作はIMachineのメソッドでははありません。m.pause() とかできたら簡単そうなのですが。
まず、VMがどのような状態を持つのかを確認しておきましょう。

VMが持つ状態

ドキュメントのenum MachineStateに状態遷移図がありますので、以下に引用します。

(略)
+-> PoweredOff --+-->[powerUp()]--> Starting --+      | +-----[resume()]-----+
|                |                             |      | V                    |
|   Aborted -----+                             +--> Running --[pause()]--> Paused
|                                              |      ^ |                   ^ |
(略)

非常にワクワクする図ですね。

VMの起動

ドキュメント通りにpowerUp()で起動するとなぜか失敗してしまいますので、前回ちらりと書いたように、VMの起動はopenRemoteSessionでRemoteSessionを開く方法を使います。
ドキュメントと違いますが、vboxshell.pyもopenRemoteSessionでやっていますので気にしないようにします。

続きを読む

vboxapiでVirtualBoxをPythonで操作その2

対象: VirtualBox-3.1, Python-2.6

前回の続きです。

昨日は、VM一覧の取得と簡単な内容表示まで書きました。
machinesというのは、リファレンスでいうところの、IMachineインターフェースを実装したオブジェクトのリストになっています。

import vboxapi
vbm = vboxapi.VirtualBoxManager(None, None)
vbox = vbm.vbox
machines = vbm.getArray(vbox, 'machines')

for m in machines:
    print "uuid =", m.id

IMachineが保持している情報のうち、特に重要なものがid属性です。idには各VMwikipedia:UUIDを保持しています。これを使ってVirtualBoxに「このVM」を指定する仕様になっています。

続きを読む

vboxapiを使ってVirtualBoxをPythonから操作その1

対象: VirtualBox-3.1, Python-2.6

日常的にデスクトップ用途としてUbuntu 9.10を使っています。たまに他のOSを使う用事があって、そういうときにはVirtualBoxから使っています。

デスクトップOSとして使っているだけなのでVMの起動も終了もGUIからやってしまうのですが、プログラムからVirtualBoxの機能にアクセスできるAPIが用意されているので、何ができるのか調べてみました。
APIにはPythonC++用が用意されているのですが、Python用を使うことにします。

続きを読む

Bazaar on Redmine でコメントが「?」になる件の応急対応

対象: Redmine-0.8.7, Bazaar 2.0.2, on Ubuntu 9.10

ファイル名、コミットメッセージのマルチバイト対応が良好、とのことで Bazaar を使い初めました。

同じく最近使い初めた RedmineSCM 連携機能で、Bazaar との連携機能を使ってみたところ、コミットコメント中のマルチバイト部分(日本語)が「?」になって表示されていまいました。

色々調査してみたところ、Redmine を ホストしている Webサーバの locale が C で動いていることが原因だとわかりました。

続きを読む