読者です 読者をやめる 読者になる 読者になる

LuaJIT Libraries on LuaJITTeX de 画像処理したりする話

この記事はTeX & LaTeX Advent Calendar 2016の10日目の記事です。
昨日はyuracoさん、明日はkaizen_nagoyaさんです。

発端

まずはこちらのつぶやきをご覧ください。

以前、このツイートを拝見した際に思っていました。
画像処理で似たことが出来ないかなと。

TeX 出来ないかなと。

そこで、「TeX で画像処理するなら今こそ LuaTeX の出番!」と思いたち*1、LuaTeX でトランプ風アートを出力する私の旅は始まったのでした……

コンテンツ

この記事で掲載する内容は以下になります。

  • Torch7: Lua ライブラリの話
  • texluajit で Lua ライブラリを使う話
  • luajittex から Lua ライブラリを使う話(ホンキのLuaTeX)
    • require の動作を修正する話
  • 画像処理を行う話

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と同じ探索を行う

という感じです。

require の動作を修正する話

前述の通りなので、requireで読み込むときの動作を修正するモジュールを作成します。

次のページの回答で出ているコードを利用しますが、LuaTeXのLuaがこれとは少し違うようで、修正を加えています。*4

tex.stackexchange.com

以下のコードを、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で利用すると、こんな感じのが出力されます。

f:id:vraisamis:20161210194007p:plain


この画像だと512幅しかないので、このサイズで平均化すると中身がわかりませんね。
サイズを変更するといい感じになるかもしれません。

*1:PyTeX などがあるというツッコミはご容赦ください

*2:らしい

*3:JITではないインストールもできるようですが今回はしていないです

*4:Lua5.2でloadersがserchersに変更されたのが原因のようです。Lua 5.2 リファレンスマニュアル: 8.2節を参照。LuaTeXで5.2が使われた場面ってありましたかね?

*5:File:Lenna.png - Wikipedia