この記事はTeX & LaTeX Advent Calendar 2016の10日目の記事です。
昨日はyuracoさん、明日はkaizen_nagoyaさんです。
発端
まずはこちらのつぶやきをご覧ください。
昨日東京駅構内で買ったトランプ、これすごい!表も裏もドット絵みたいになってるから透けてるんだけど相手からは見えないってやつ!オサレェ… pic.twitter.com/xzSlkUgfOQ
— しゅんか (@shunkatt) November 22, 2015
以前、このツイートを拝見した際に思っていました。
画像処理で似たことが出来ないかなと。
TeX で 出来ないかなと。
そこで、「TeX で画像処理するなら今こそ LuaTeX の出番!」と思いたち*1、LuaTeX でトランプ風アートを出力する私の旅は始まったのでした……
コンテンツ
この記事で掲載する内容は以下になります。
Torch7: Lua ライブラリの話
とりあえず画像処理ができないことには目標が達成できません。
処理自体は、単純に範囲ごとに平均を取る方法を利用しようと思っていたので、画像から各ピクセルのRGBが取得できれば問題ありませんでした。
探してみて思ったのですが、あまりLuaの画像処理できるライブラリがないのですね。
最初に見つけたのがTorch7という機械学習などができる*2ライブラリでした。
torchの中にimageというそのままの名前のライブラリがあり、こちらを使うこととしました。
本題とは関係がないですが、このライブラリがあれば更に面白いことができそうですね。
texluajit で Lua ライブラリを使う話
Torch7 が luajit のライブラリ*3なので、まずはluajittexエンジンでluaプログラムを動かす、texluajitコマンドで動作確認です。
require('image') image.test()
テスト出力がされれば成功です。
luajittex から Lua ライブラリを使う話
texluajitからは動作したので、今度はlatex文章に埋め込んで試しますが動きませんでした。
\documentclass{ltjsarticle} \directlua{ require('image') image.test() } \begin{document} imageモジュールのテストをパス%しません \end{document}
これの原因は次のリンク先を参照してください。要約すると、
- requireはpackage.loaders( or serchers)を利用して探索する
- lua(jit)texではパスの探索はkpseの探索範囲で行う
- ただし、texlua(jit)コマンドで起動した場合は、普通のluaと同じ探索を行う
という感じです。
- 資料-Lua5.1のモジュール機能 - LuaTeX-ja Wiki - LuaTeX-ja - OSDN: 「モジュールローダーの取得」の節が今回関連のある部分です。
require の動作を修正する話
前述の通りなので、requireで読み込むときの動作を修正するモジュールを作成します。
次のページの回答で出ているコードを利用しますが、LuaTeXのLuaがこれとは少し違うようで、修正を加えています。*4
以下のコードを、luaモジュールを使いたいtexファイルと同じディレクトリにというlualoader.lua名前で配置しました。
local make_loader = function(path, pos, loadfunc) local target = package.searchers or package.loaders local default_loader = target[pos] local loader = function(name) local file, _ = package.searchpath(name,path) if not file then local msg = "\n\t[lualoader] Search failed" local ret = default_loader(name) if type(ret) == "string" then return msg ..ret elseif type(ret) == "nil" then return msg else return ret end end print("[lualoader][info]found file: " .. file .. "; (with name: " .. name ..")") local loader,err = loadfunc(file, name) if not loader then return "\n\t[lualoader] Loading error:\n\t"..err end return loader end target[pos] = loader end local binary_loader = function(file, name) local symbol = name:gsub("%.","_") return package.loadlib(file, "luaopen_"..symbol) end make_loader(package.path,2, function(file, name) return loadfile(file) end) make_loader(package.cpath,3, binary_loader)
これが動くことを確認するため、以下のtexファイルを実行します。
\documentclass{ltjsarticle} \directlua{ require('lualoader') require('image') image.test() } \begin{document} imageモジュールのテストをパス \end{document}
pdfが出力すれば成功です。これでようやく画像処理ができますね。
画像処理を行う話
画像処理内容は単純で、100x100マスごとにRGBの値を平均したものを計算します。
その色をtikzで塗っていきます。tikzのfillオプションにRGB指定するのがよくわからなかったので、xcolorで毎回色を作っています。
\documentclass{ltjsarticle} \usepackage{xcolor,tikz,pgf} \begin{document} \begin{tikzpicture}[x=5mm,y=5mm] \directlua{ print() print(package) for k,v in pairs(package) do print(k,v) end print() for k,v in pairs(package.loaders) do print(k,v) end print() print(package.path) print(package.cpath) require "lualoader" require('image') img = image.load("./lenna.png", 3, 'byte') size = 100 rgb = torch.Tensor( 4, %-- r,g,b, count math.ceil(img:size(2) / size), %-- y math.ceil(img:size(3) / size) %-- x ):fill(0) %-- sum color value for y = 1,img:size(2) do for x = 1,img:size(3) do local vy = math.ceil(y / size) local vx = math.ceil(x / size) for n = 1, 3 do rgb[n][vy][vx] = rgb[n][vy][vx] + img[n][y][x] end rgb[4][vy][vx] = rgb[4][vy][vx] + 1 end print(y) end %-- avl color value for y = 1, rgb:size(2) do for x = 1, rgb:size(3) do local px = torch.Tensor(3) for n = 1, 3 do %-- rgb[n][y][x] = math.floor(rgb[n][y][x] / rgb[4][y][x]) px[n] = math.floor(rgb[n][y][x] / rgb[4][y][x]) end tex.print("\string\\definecolor{mycolor}{RGB}{" .. px[1] .. "," .. px[2] .. "," .. px[3] .. "}") tex.print("\string\\draw[draw=mycolor,fill=mycolor] (" .. (x - 1) .. ",-" .. (y - 1) .. ") rectangle (" .. (x-0.2) .. ",-" .. (y-0.2) .. ");") end end } \end{tikzpicture} \end{document}
これを、Lennaの画像*5で利用すると、こんな感じのが出力されます。
この画像だと512幅しかないので、このサイズで平均化すると中身がわかりませんね。
サイズを変更するといい感じになるかもしれません。