mieki256's diary



2022/07/01(金) [n年前の日記]

#1 [python][cg_tools] ディザリング処理をするプログラムをPythonに移植中その5

任意のパレットを指定してディザリングをかけるサンプルプログラム群を ―― C++ で書かれてるソレを Python で書き直しているところ。

_Arbitrary-palette positional dithering algorithm

アルゴリズム3を移植中だけど、C++のソースの中に map というものが出現してきた。ググってみたら連想配列相当らしい。平衡二分木がどうとか書いてある…。それってPythonではどう書けばいいのか…。とりあえず、Pythonの辞書(dict)で代替して様子を見てみようか…。

2022/07/02() [n年前の日記]

#1 [python][cg_tools] ディザリング処理をするプログラムをPythonに移植中その6

任意のパレットを指定してディザリングをかけるサンプルプログラム群を ―― C++ で書かれてるソレを Python で書き直しているところ。

_Arbitrary-palette positional dithering algorithm

上記で紹介されている、Adobe Photoshop っぽいディザ処理を Python で書き直しているのだけど、かなりシンプルな処理なのに、ちゃんとそれっぽい画像に変換されるあたりがなんとも不思議。前回の計算処理で出てしまった誤差を、次の計算に持ち越すあたりがポイントなのだろうか…?

2022/07/03() [n年前の日記]

#1 [nitijyou] 自宅サーバ止めてました

雷が鳴ったので、16:15-19:15の間、自宅サーバ止めてました。申し訳ないです。

#2 [python] パレットデータを取り出すPythonスクリプトを書いてる

ここ最近、任意のパレットデータでディザ処理をかける実験をしているのだけど、そのパレットデータを別途指定できるようにしたいなと。今まではソース内に配列として書いてしまっていたので、パレットデータを変更して実験することが容易ではなかった。

Python の Pillow(PIL) を使って、インデックスカラーのPNGを読み込んで、パレット値を読み取る処理を書いてみたけど、使ってない色まで RGB=(0,0,0) で得られてしまう。インデックスカラー画像は256色あるものと決め打ちされているっぽい。16色や32色しか使ってないpngをパレット指定用画像として使いたいのだけどな…。

もしかして、GIMPのパレットデータファイル (.gpl) を読み込んで処理するのもアリかなと思えてきた。RGB値を正規表現で取り出せそうか少し試してみよう…。

2022/07/04(月) [n年前の日記]

#1 [nitijyou] 腹痛で大変だった

おそらくだけど、また尿路結石の痛みが出て苦しんでた。
ロキソニンを飲んでもこんなに痛いのか…と絶望的な気分になるぐらいの痛みだった。ただ、あらかじめ薬を飲んでいたせいか、前回のように何度も吐くほどの痛みにはならかった。とは言っても、布団の上でゴロゴロとのたうち回るぐらい痛かったけど…。

19:00頃、あまりに痛いので、ロキソニンを追加で飲んだ。本来最低でも4時間ほど間隔を空けて飲むこと、となっていたのだけど、どうにも耐えられなくて…。4時間も3時間もたいして変わらんだろと勝手に決めつけて飲んだけれど、何かしらの効果があったのかどうかは不明。追加で飲んでも、その後もずっと痛かった。

トイレで小さいほうをしたら血が出ていたので、やはりコレは尿路結石なんだろうなと。

前回この症状が出たのは… _2022/04/16 だったらしい。2ヶ月半で再発ということになるのだろうか。メモを確認したら、前回は19:10頃から痛み出して、21:40頃に病院を出たと書いてあった。自分の場合、2時間以上は痛みが続くようだなと。

2022/07/05(火) [n年前の日記]

#1 [nitijyou] 自宅サーバ止めてました

雷が鳴ったので、18:00-19:40の間、自宅サーバ止めてました。申し訳ないです。

かなり近くに雷が落ちたようで、普段聞いたことが無いような音が…。不安になってエアコンのコンセントまで抜いたりもして。この時期にエアコンが雷で壊されたら命に係わる…。

#2 [nitijyou] 一日中寝てた

昨日のダメージが尾を引いてしまったのか、眠くて眠くて、一日中寝てた。起きたら起きたで手足に力が入らず。考えてみたら昨晩素麺を御椀一杯程度食べて、それ以降何も食べてないなと…。夕飯をどうにか食べたら少し動くようになってきた。

2022/07/06(水) [n年前の日記]

#1 [gimp] GIMP を 2.10.32 に更新した

数日前に、Windows10 x64 21H2上にインストールしてあった GIMP 2.10.30 x64 Portable samj版を、GIMP 2.10.32 x64 Portable samj版に更新していたことをメモしてなかった気がするので一応メモ。

最初は上書きでインストールしてみたのだけど、GIMP を起動したら .dll が見つからない等のエラーが出てしまった。2.10.30 と 2.10.32 のフォルダ構成も結構違っていたようなので、別フォルダを作成してインストールすることにした。今後更新があった場合も、別フォルダを作成してインストールすることにしよう…。

ただ、別フォルダにインストールしてしまうと再設定が面倒で…。今回は、旧版インストールフォルダ\Preferences\ 内の、以下のファイルをコピーして再設定を避けてみた。

gimprc だけはテキストエディタで開いて、記述されているファイルパスを若干修正する必要があった。

APNGエクスポート用プラグインも一応導入しておいた。

_APNG (animated PNG) plug-in for Gimp 2.10 Windows64x - GIMP Chat

ただ、GIMP 2.10 はWebP画像フォーマットに標準で対応しているので、フルカラーのアニメ画像が欲しい場合は WebP でエクスポートしたほうがいいような気もする。APNG は拡張子が .png だから、一般的な画像ビューアは、普通のpngなのかAPNG なのか判別できなくて正常表示されないので…。WebPなら拡張子からして違うので、そういった問題は起きないのではないかと…。

2022/07/07(木) [n年前の日記]

#1 [python] Pillowのgetpixel()は本当に遅いのか気になったので確認してみた

Python で画像を扱える Pillow (PIL)モジュールについて、1ドット単位で値を読んだり書いたりできる .getpixel()/.putpixel() というメソッドがあるのだけど。巷の各種記事では処理速度が遅いと書いてあって、本当にそうなのかなと気になってきた。

そんなわけで、ベンチマークを取ってみた。環境は、Windows10 x64 21H2 + Python 3.9.13 64bit + Pillow 9.1.1。CPU は AMD Ryzen 5 5600X (6コア12スレッド、ベースクロック3.7GHz)。

4288x2848ドットの画像に対してひたすらドットを読むだけの処理をしてみた。ちなみに利用した画像は以下。

_十代の少女 可愛い 肖像画 - Pixabayの無料写真


ソースは以下。

_05getpixel_bench.py
from PIL import Image
from benchmarker import Benchmarker

infile = "teen-girl-4467541_4288x2848.jpg"


def main():
    im = Image.open(infile)
    width, height = im.size
    print("%d x %d" % (width, height))

    with Benchmarker(100) as bench:
        @bench(".getdata()")
        def _(bm):
            im = Image.open(infile)
            dt = im.getdata()
            for y in range(height):
                idx = width * y
                for x in range(width):
                    _ = dt[idx + x]

        @bench(".load()")
        def _(bm):
            im = Image.open(infile)
            src = im.load()
            for y in range(height):
                for x in range(width):
                    _ = src[x, y]

        @bench(".getpixel()")
        def _(bm):
            im = Image.open(infile)
            for y in range(height):
                for x in range(width):
                    _ = im.getpixel((x, y))


if __name__ == '__main__':
    main()

動作には、Pillow と benchmarker が必要。
pip install Pillow -U
pip install Benchmarker -U

結果は以下のような感じになった。

> py 05getpixel_bench.py
4288 x 2848
## benchmarker:         release 4.0.1 (for python)
## python version:      3.9.13
## python compiler:     MSC v.1929 64 bit (AMD64)
## python platform:     Windows-10-10.0.19044-SP0
## python executable:   C:\Python\Python39-64\python.exe
## cpu model:           AMD64 Family 25 Model 33 Stepping 2, AuthenticAMD
## parameters:          loop=100, cycle=1, extra=0

##                                       real    (total    = user    + sys)
.getdata()                             0.7155    0.7188    0.6875    0.0312
.load()                                0.7961    0.7969    0.7969    0.0000
.getpixel()                            6.2659    6.2656    6.2656    0.0000

## Ranking                               real
.getdata()                             0.7155  (100.0) ********************
.load()                                0.7961  ( 89.9) ******************
.getpixel()                            6.2659  ( 11.4) **

## Matrix                                real    [01]    [02]    [03]
[01] .getdata()                        0.7155   100.0   111.3   875.8
[02] .load()                           0.7961    89.9   100.0   787.1
[03] .getpixel()                       6.2659    11.4    12.7   100.0


.getdata() を使えばたしかに速くなるけれど、一次元配列に対してアクセスするような書き方になるので、可読性はほんのちょっと、若干かすかに、ビミョーに悪くなるような気もする。

対して、.load() を使ったアクセスなら、.getpixel() と同様に x, y を指定してアクセスできるし、しかも .getdata() を使った場合とそれほど処理速度も変わらないわけで…。

個人的には、.getdata() より、可読性と処理速度の両方をそこそこ得られる .load() を使ったほうがいいのではないかと思えてきた。

余談。手元で実験に使ってるスクリプトが、とにかく遅くて…。.load() を使って画像の各ドットにアクセスしていたのだけど、.getdata() を使ったらもっと速く処理できないかと少し期待しながらベンチマークを取ったわけで。ある意味、残念な結果になってしまった。今回、そこらへんを変えてみても結果は変わらないようだなと…。

#2 [python][cg_tools] ディザリング処理をするプログラムをPythonに移植中その7

任意のパレットを指定してディザリングをかけるサンプルプログラム群を ―― C++ で書かれてるソレを Python で書き直しているところ。

_Arbitrary-palette positional dithering algorithm

ある程度動くようになったので、一応アップロード。元記事内の、アルゴリズム1、2、3、及び Photoshopっぽいアルゴリズムを、全部1つのファイルにまとめてみた。

_Yliluoma's ordered dithering algorithm 1, 2, 3. Python version.

動作には、Pillow と tqdm が必要。
pip install Pillow -U
pip install tqdm -U

動作確認環境は、Windows10 x64 21H2 + Python 3.9.13 64bit + Pillow 9.1.1 + tqdm 4.64.0。

使い方。 :

使い方は以下のような感じ。--mode N でアルゴリズムを選べる。
py yliluoma_ordered_dither.py -i scene.png -o scenedither_m0_8x8.png --mode 0
py yliluoma_ordered_dither.py -i scene.png -o scenedither_m1_8x8.png --mode 1
py yliluoma_ordered_dither.py -i scene.png -o scenedither_m2_8x8.png --mode 2
py yliluoma_ordered_dither.py -i scene.png -o scenedither_m3_8x8.png --mode 3
py yliluoma_ordered_dither.py -i scene.png -o scenedither_m4_8x8.png --mode 4
py yliluoma_ordered_dither.py -i scene.png -o scenedither_m5_8x8.png --mode 5
py yliluoma_ordered_dither.py -i scene.png -o scenedither_m6_8x8.png --mode 6
py yliluoma_ordered_dither.py -i scene.png -o scenedither_m7_8x8.png --mode 7
py yliluoma_ordered_dither.py -i scene.png -o scenedither_m8_8x8.png --mode 8

一応、ヘルプ表示も載せておく。
> py yliluoma_ordered_dither.py --help
usage: yliluoma_ordered_dither.py [-h] -i INPUT -o OUTPUT [-p PALETTE] [-d DITHER] [-m MODE] [-c]

Yliluoma ordered dithering 1, 2, 3, 4

optional arguments:
  -h, --help            show this help message and exit
  -i INPUT, --input INPUT
                        Input png filename
  -o OUTPUT, --output OUTPUT
                        Output png filename
  -p PALETTE, --palette PALETTE
                        Palette file (.png or .gpl)
  -d DITHER, --dither DITHER
                        Dither type 2,4,8 (2x2,4x4,8x8). default: 8
  -m MODE, --mode MODE  job kind 0 - 8. default: 3
  -c, --ciede2000       Enable CIEDE2000 (mode 6 only

mode の違いは以下のような感じ。元記事を読まないと分からないとは思うけど…。
  • mode 0: アルゴリズム1。2色の組み合わせを意識して差し替えるべき色を求める。かなり遅い。
  • mode 1: アルゴリズム1。mode 0 の改良版。突飛な色の組み合わせは除外する。かなり遅い。
  • mode 2: アルゴリズム1。mode 1 の改良版。ガンマ値を正しく反映させる。かなり遅い。
  • mode 3: アルゴリズム1。mode 2 の処理高速化版。ループを回さずに数式で近似値を求める。少し早い。
  • mode 4: アルゴリズム1。3色の組み合わせを意識して処理する。かなり遅い。
  • mode 5: アルゴリズム2。N色の組み合わせを意識しないで色を求める。かなり遅い。
  • mode 6: アルゴリズム2。mode 5 の改良版。ガンマ値を正しく反映させる。かなり遅い。
  • mode 7: アルゴリズム3。mode 6 の改良版。かなり遅い。
  • mode 8: Adobe Photoshop っぽいアルゴリズム。結構早い。

問題点。 :

このスクリプト、とにかく処理が遅い…。CPU: AMD Ryzen 5 5600X を使って、289x176ドットの画像に対して、16色でディザリングをかけるのに、最悪8分ぐらいかかる…。もちろん、使うアルゴリズムにもよるのだけど…。

比較的変換処理が速いのは、--mode 3 と --mode 8。
  • mode 3 は、ループを回して総当たりで相応しい色を探さずに、数式で大まかに合いそうな色を探すアルゴリズム。ただ、速くはなるけれど、生成画像の品質は落ちる。
  • mode 8 は、Adobe Photoshop で使われていたらしいアルゴリズム。品質と処理速度のバランスが取れている。ただ、生成画像は全体的にボケ気味な印象を受けた。

それ以外のアルゴリズムは、まあ、遅い。とんでもなく遅い。

改善策。 :

改善策は、いくつか思いつく。

元記事のソースは 8x8 のディザで処理しているけれど、見た感じ、4x4 のディザでも十分かなと思えた。8x8のディザだと、一番深いところで64回ループが回るけど、4x4なら16回のループで済むので、単純計算で約4倍の速さになりそう。

並列処理を導入するのも手かもしれない。Python で並列処理をさせる方法が分からなかったので今回は試してないけど、今時のCPUなら4コアだの6コアだの持ってるだろうから、1/4、1/6 の処理時間で済むのではなかろうか。実際、元々のC++版は、OpenMP(?) を有効にしてビルドして、並列処理する実行バイナリにしたら爆速になった。

画像の全てのドットに対して、毎回ループを回して、適切なパレットカラーを求めているあたりがアレだろうなという気もする。どれかしらの要素を事前に計算してキャッシュしておいて、そのキャッシュを使い回すようにすれば、もうちょっと改善されるかもしれない。例えばベタ部分が多い画像の場合、画像内に出てくる色の数は限定されるはずで、以前計算したことがある色が出現したら、その時の計算結果を再度使う、という作りにするだけでも効果はありそう。

元記事にも書いてあるけど、kd-tree 等を導入すれば改善される可能性もあるのだろうなと…。画像内のとある色について、差し替えに相応しい色はどれか、と探すあたりで時間がかかっているので、探索時間が短くなれば効果はあるはず。

余談。 :

このスクリプトで実験した後、減色ツールの OPTPiX や Yukari を使って減色をしてみたら、どれも一瞬で結果が返ってきて…。もしかすると1秒もかかってないのでは…。

それらはおそらくC/C++で書かれているのかなと想像するのだけど、それにしても処理時間が違い過ぎる。おそらく、アルゴリズムからして、もっと上手いやり方があるのだろう…。

2022/07/08(金) [n年前の日記]

#1 [python] Pythonの並行処理・並列処理について勉強中

Python の並行処理(マルチスレッド)、並列処理(マルチプロセス)について勉強中。

動作確認環境は、Windows10 x64 21H2 + Python 3.9.13。

巷の解説記事内で紹介されてる各サンプルを手元で動かしてみて、マルチスレッドについてはちゃんと動作することを確認できたのだけど。マルチプロセスのサンプルを動かそうとしたら、いきなりエラーが出て悩んでしまった。もしかして、Windows ではマルチプロセスは使えないのだろうか? 試しに Ubuntu Linux 20.04 LTS上で同じスクリプトを動かしてみたら、そちらではすんなり動いてしまった。

どうやら、マルチスレッドはともかく、Windows上ではPythonのマルチプロセスは動かないようだなと…。「Linux や Mac ならマルチプロセスが使えますよ」「Windows? 知らんがな」という状況なのだろう…。

と、一瞬思い込んでしまったのだけど、その後も他の解説記事を眺めて試していたら、Windows上でもマルチプロセスのサンプルが動いてくれた。「Linux でしか使えないよ」というわけではないらしい。

Windows上、かつ、マルチプロセスを使うスクリプトは、以下の記述が無いといかんようで。実処理をトップレベル(?)に書いてしまうとエラーが出てしまう。ただし、Linux上ではトップレベルに書いてしまっても動く、というオチだった模様。

def main():
    ...

if __name__ == "__main__":
    main()

ということで、巷の解説記事内でマルチプロセスのサンプルを見かけて、もし、処理がトップレベルに書かれてたら、上記の記述を追加して動作確認してみると良い、という話になるのだろうなと。

参考ページ。 :


2022/07/09() [n年前の日記]

#1 [python] Pythonの並行処理・並列処理についてまだ勉強中

昨日に引き続き、Python の並行処理・並列処理について勉強中。巷の解説記事のサンプルをコピペして動作確認。

2022/07/10() [n年前の日記]

#1 [nitijyou] 投票してきた

朝08:00頃、近所の公会堂まで電動自転車で行ってきて、参議院選挙の投票をしてきた。帰りにザ・ビッグに寄って夜食等を購入。

朝の時点でも暑いなと思ってたら、やはり昼間には30度を超えてた…。朝のうちに行っておいて良かった…。

2022/07/11(月) [n年前の日記]

#1 [xyzzy] xyzzyのスニペットについて調べてた

自分は普段 xyzzy という、操作感覚が emacsに近いWindows専用エディタを使っているのだけど。スニペット(Snippet)相当の機能は無いのかなと気になってきたので少し調べてみたり。

スニペット機能と言うのは…要は「定型文入力機能」という説明でいいのだろうか。プログラムソースを打ち込んでいく際、お決まりの一文を入力する場面がちょくちょくあるけれど、それらの定型文を登録しておいて挿入できれば作業が楽になる。

xyzzy の場合、abbrev と dabbrev という、2つの入力補完機能を持っていて。
スニペットは、前者の abbrev に相当するのだろうなと。

ただ、自分の手元の xyzzy では、abbrev の実行(?)が割り当てられてるはずの C-x ' が機能しなくて。設定ファイル ~/.xyzzy を確認したら、C-x ' には just-one-space が割り当てられていて、代わりに M-SPC に snippet-expand が割り当てられてた。昔、自分で設定したんだろうけど…何だっけコレ…。

どうやら自分、snippet.l という拡張を導入して、expand-abbrev の代わりに snippet-expand を使うようにしていたようだなと…。

snippet.lについて。 :

ググってみたら、snippet.l はもう入手不可能っぽい。Web Archive で解説ページは見つけられたけど、.zipファイルはDLできなかった。

_silog - script/snippet (Web Archive)


幸い、xyzzyインストールフォルダ\.netinst\src\ 以下で、snippet-2007.07.15.zip というファイルが見つかった。一応、バックアップも兼ねて置いておきます。

_snippet-2007.07.15.zip


snippet.l と abbrev の違いは、定型文を挿入した直後の動作だろうか…。abbrev は定型文を挿入して終わるけど、snippet.l は、挿入後に入力が必要な個所にカーソルを合わせてくれて、C-n や C-p で入力箇所を移動できるっぽい。

まあ、一般的には、abbrev が使えるだけでも十分かなという気もする。 *1

abbrevについて。 :

以下のページが分かりやすいだろうか…。

_単語補完機能 - xyzzy - emacs like editor in windows
_xyzzyで単語の自動補完(Static編) - tohokuaikiのチラシの裏
_abbrevの使い方 - himadatanode’s blog
_xyzzy - 覚書wiki - atwiki(アットウィキ)
_abbrev modeのやりかた (Web Archive)
_QuickTour/abbrev - XyzzyWiki (Web Archive)
_動的補完と静的補完の設定と使い方 | for programer | しょぼしょぼすくりぷと xyzzy (Web Archive)

消滅してしまったページが多いのがなんとも。

emacsの解説も参考になるかもしれず。

_abbrev


以下で、デフォルトのキー割り当てが紹介されてる。

_xyzzy機能分類別キーバインド一覧
  • C-x C-a : add-mode-abbrev 現在のモードのアブレブテーブルに登録(展開する文字列→略称)
  • C-x C-h : inverse-add-mode-abbrev 現在のモードのアブレブテーブルに登録(展開する文字列→略称)
  • C-x ' : expand-abbrev アブレブ略語を展開
  • C-x + : add-global-abbrev グローバルアブレブテーブルに登録(展開する文字列→略称)
  • C-x - : inverse-add-global-abbrev グローバルアブレブテーブルに登録(略称→展開する文字列)

abbrev の定義ファイルは ~/.abbrev_defs だけど、M-x list-abbrevs か M-x edit-abbrevs を実行すれば編集できる。

C-x C-a か C-x + を使って仮登録してから、M-x list-abbrevs でちゃんと修正、てな感じの登録の仕方になるのかな。たぶん。

*1: いやまあ、今から新規に xyzzy を使う人は居ないだろうから、一般的もへったくれも無いだろうけど。開発停止状態と言っていいほど枯れ切ってしまって、慣れ過ぎちゃって離れられない人しか使ってないエディタのはずだし…。

2022/07/12(火) [n年前の日記]

#1 [python] ディザをかけるPythonスクリプトを弄ってる

Pythonで並列処理(マルチプロセス)を使うやり方が大体分かってきたので、先日 Python で書いた、ディザをかけるスクリプトに並列処理を加えてみようと作業を始めたのだけど。特定のモードの処理に必要な関数だけを残して実行してみたら、以前のスクリプトで得られた生成画像と違う結果になってしまって悩んでいるところ。自分はどこでミスをしてしまったのか…。

2022/07/13(水) [n年前の日記]

#1 [python] ディザリング処理をするPythonスクリプトに並列処理を追加してみた

任意のパレットを指定してディザリングをかけるサンプルプログラム群を ―― C++ で書かれてるソレを Python で書き直してみたわけだけど。

_Arbitrary-palette positional dithering algorithm
_Yliluoma's ordered dithering algorithm 1, 2, 3. Python version.

あまりに処理時間がかかり過ぎるので、並列処理(マルチプロセス)も使えるように修正してみた。

動作確認環境は、Windows10 x64 21H2 + Python 3.9.13 + Pillow 9.1.1 + tqdm 4.64.0。

ソースは以下。

_yodither1.py (Yliluoma's dithering algorithm 1, slow, not tri-tone)
_yodither1tritone.py (Yliluoma's dithering algorithm 1, tri-tone)
_yodither3.py (Yliluoma's dithering algorithm 3)
_yodither4.py (adobe like algorithm)

動作には、Pillow、tqdm が必要。
pip install Pillow -U
pip install tqdm -U

スクリプトの使い方は以下。
python yodither1.py -i input.png -o out.png --mp
python yodither1tritone.py -i input.png -o out.png --mp
python yodither3.py -i input.png -o out.png --mp
python yodither4.py -i input.png -o out.png --mp

一応ヘルプも載せておきます。
> py yodither1.py --help
usage: yodither1.py [-h] -i INPUT -o OUTPUT [-p PALETTE] [-d DITHER] [--mp]

Yliluoma ordered dithering 1

optional arguments:
  -h, --help            show this help message and exit
  -i INPUT, --input INPUT
                        Input png filename
  -o OUTPUT, --output OUTPUT
                        Output png filename
  -p PALETTE, --palette PALETTE
                        Palette file (.png or .gpl)
  -d DITHER, --dither DITHER
                        Dither type 2,4,8 (2x2,4x4,8x8). default: 8
  --mp                  Enable multi process

コマンドラインオプションに --mp をつければ並列処理(マルチプロセス)が有効になる。

処理時間を実測。 :

CPU : AMD Ryzen 5 5600X (6コア12スレッド、3.7 - 4.6GHz) 上で、289 x 176 ドットの画像を変換して動作確認してみた。

Script name--mp (multi process)Time
yodither1.pyoff07:41
yodither1.pyon02:11
yodither1tritone.pyoff02:36
yodither1tritone.pyon01:11
yodither3.pyoff00:39
yodither3.pyon00:53
yodither4.pyoff00:30
yodither4.pyon00:49
  • yodither1.py, yodither1tritone.py は並列処理にすることで速くなった。
  • yodither3.py, yodither4.py は並列処理にしたらむしろ遅くなった。

ということで、並列処理にすることで速くなる場合と、遅くなる場合があると分かった。でもまあ、8分近くかかってた処理が2分で終わるようになった点はありがたい。

1ドット単位の計算をする際に並列処理を使ってしまったので、すぐに結果が得られるアルゴリズムの場合は、並列処理をするための下準備のほうがネックになってしまったのだろうけど。これがもし、1ラインずつ処理する形で並列処理を使っていたら、どのスクリプトでも効果が得られたのではないか、という気もする。

ただ、どのみち Python では、この手の処理は遅すぎてアレだなと…。

余談。 :

以前作成したスクリプトと生成画像が違ってしまって悩んでいたのだけど、以前作成したスクリプト内で、r2, g2, b2 と書くべきところを r2, b2, g2 と書いてしまっていたのが原因だった。以前のスクリプトの生成画像のほうが間違っていた…。

一応、修正して、Gist にアップロードし直しておいた。

_Yliluoma's ordered dithering algorithm 1, 2, 3. Python version.

2022/07/14(木) [n年前の日記]

#1 [prog] 減色ツール iZYINS をビルドしてみる

減色ツール xPadie のソースコードを参考にして、C# で書かれた iZYINS という減色ツールがあるそうで。ライセンスはGPL。

_iZYINS : Color Reducer on .NET Framework Project Top Page - OSDN

ただ、この iZYINS、ソースコードは公開されているけれど、実行バイナリは配布されてない。

ということで、ビルドできるか試してみた。

環境は、Windows10 x64 21H2 + Microsoft Visual Studio Community 2019。ちなみに今現在は Visual Studio 2022 が現行版らしい…。

_Visual Studio: ソフトウェア開発者とチーム向けの IDE およびコード エディター

ビルド手順。 :

iZYINS の公式サイトから、ソースコード、iZYINS100_src.zip をダウンロードした。解凍すると、中に iZYINS.sln というファイルがあった。これを Visual Studio で開けばいいのだろう…。「プロジェクト/ソリューションを開く」を選んで開けばいいのかな。たぶん。

開いたら、上のほうで「Debug」を「Release」にして、メニューの中から、ビルド → ソリューションのビルド、を選ぶ。

iZYINS\bin\Release\ 以下に、iZYINS.exe と iZYINS.pdb が出来上がった。これで実行バイナリが得られたのだと思う。たぶん。

.exeファイルは実行ファイルだけど、.pdbファイルは何だろう? ググってみたら、デバッグ時に必要なファイルらしい。プロジェクトのプロパティを設定すれば作らないようにできる模様。プロジェクト → xxxxのプロパティ → ビルド → 詳細 → デバッグ情報を「なし」にする。

以下、参考ページ。

_.pdb ファイルを生み出さない - rksoftware
_Visual StudioユーザーがReleaseビルドをするときに必ずやってほしい2つの設定 - Qiita
_PDB ファイルとは何か? - C# の開発環境 - C# 入門

使い方。 :

iZYINS の使い方は、大体以下のような感じ。
iZYINS.exe INPUT.png OUTPUT.png
iZYINS.exe INPUT.png OUTPUT.png -P16
iZYINS.exe INPUT.png OUTPUT.png -P16 -D4
iZYINS.exe INPUT.png OUTPUT.png -P16 -D4 -R1
  • iZYINS.exe 入力画像 出力画像、と指定する。
  • -Pxx で色数を指定。例えば -P16 と指定すれば、16色に減色する。
  • -Dx でディザパターンの種類を指定。-D4 と指定すれば、アニメ調とされるディザ(組織化ディザリング)になる。
  • -R1 をつけると、出力画像が既に存在していても上書きする。

問題に遭遇。 :

動作確認していたら問題に遭遇した。-D4 をつければアニメ調のディザがかかるはずだけど、エラーを出してしまう。

以下の投稿(2009/09/12)で、元々そういう状態でソースが配布されていると知った。

_チケット #18654: ディザモードの1と4について - iZYINS : Color Reducer on .NET Framework - OSDN
モード1は全体で単色で塗りつぶされてしまいモード4はエラーになるのですが、まだ実装されていないという事なのでしょうか?
モード4の方はエラーが出る部分だけ適当に弄ってみて(a2lの初期化と、Array.Sort(a2l,0,16)→Array.Sort(a2l,0,16,a2l0))一応は動作したのですが、少し試した感じではおかしな色が使われる事があるような気がします。

チケット #18654: ディザモードの1と4について - iZYINS : Color Reducer on .NET Framework - OSDN より


もう一つ、問題に遭遇。-Fオプションを使って、パレットデータ相当の画像を渡そうとするとエラーが出てしまう。これでは固定パレットで減色できない…。

ディザモード4でエラーが出る問題を修正。 :

自分はアニメ調のディザ、というか市松模様が大好きなので、ディザモード4を使えるようにしたい。前述の投稿記事を参考にしてソースを修正してみた。

問題を起こしてるソースは、iZYINS\Quant.cs だった。以下に、diff -u の結果を載せてみる。
  • 行頭に「+」がついている行が追加した行。
  • 行頭に「-」がついている行が削除した行。

_quant.cs.patch
--- iZYINS\Quant.cs.orig    Mon Jul 21 20:04:24 2008
+++ iZYINS\Quant.cs Thu Jul 14 04:59:57 2022
@@ -62,20 +62,21 @@
 
     }
 
-    public class Anime2line : IComparer
+    public class Anime2line
     {
         public int pal;
         public int d;
+    }
 
+    public class Anime2lineComparer : IComparer
+    {
         public int Compare(object a, object b)
         {
-
             int n = (((Anime2line)a).d) - (((Anime2line)b).d);
             if (n > 0) return 1;
             if (n < 0) return -1;
             return (((Anime2line)a).pal) - (((Anime2line)b).pal);
-
-        }    
+        }
     }
 
 
@@ -1185,8 +1186,20 @@
            Anime2line[] a2l= new Anime2line[32];
            Anime2line[] a2l2 = new Anime2line[16];
 
+            Anime2lineComparer a2lcomp = new Anime2lineComparer();
+
+            for (i = 0; i < 32; i++)
+                a2l[i] = new Anime2line();
+
+            for (i = 0; i < 16; i++)
+                a2l2[i] = new Anime2line();
 
-           Byte[] dit = { 0, 8, 2,10, 12, 4,14, 6, 3,11, 1, 9,15, 7,13, 5 };
+           Byte[] dit = {
+                0, 8, 2,10,
+                12, 4,14, 6,
+                3,11, 1, 9,
+                15, 7,13, 5
+            };
 
            for (y=0;y<height;y++) {
                for (x=0;x<width;x++) {
@@ -1208,8 +1221,9 @@
                            dz=(gz-gcstable[kk].Z)*ditherlevel/10;
                        }
 
+                        // Array.Sort(a2l, 0, 16);
+                        Array.Sort(a2l, a2lcomp);
 
-                        Array.Sort(a2l,0,16);
                        k=a2l[dit[(y & 3)*4+(x & 3)]].pal;
                    }
                    palbuf[x+y*picx]=(byte)k;

要するに、関数 Mtscan() 内の記述がおかしいらしい。
  • 元ソースでは、a2l, a2l2 のクラス配列サイズは確保されてるけど、クラスの実体(?)は確保されてないので、NULLポインタにアクセスしてエラーが出てしまう。改めて確保し直す。
  • Array.Sort() を使ってクラス配列をソートしようとしてるけど、記述がおかしいっぽい。ソート用に、Anime2lineComparer というクラスを作っておいて、それを渡しつつソートするように指定してみた。
C# は全然詳しくないので、ひたすらググってコピペして試してみたけど、一応これでディザモード4が動く状態にはなった。ただ、生成結果が正しいのかどうかは自信が無い…。

ところで、a2l2 は使ってないように見えるのだけど、気のせいだろうか…?

パレットデータ画像を指定するとエラーが出る問題を修正。 :

もう一つの問題、-Fオプションでパレットデータ相当の画像ファイルを渡すとエラーになる件は、どうやらオプション解析がバグってた模様。Program.cs 内の以下を修正したら解決した。

_program.cs.patch
--- iZYINS\Program.cs.orig  Mon Jul 21 20:04:24 2008
+++ iZYINS\Program.cs   Thu Jul 14 05:58:07 2022
@@ -60,7 +60,7 @@
             string s = iZYINSVersion + " : Color Reducer on .NET Framework or MONO" + "\n\r" +
                 "Copyright (C) 2005-2008 Y.Nomura all rights reserved." + "\n\r" +
                 "This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.";
-            
+
 
             int n;
             if (args.Length < 1)
@@ -209,7 +209,7 @@
                             }
                             break;
                         case 'F':
-                            s = args[i].Substring(3, args[i].Length - 2);
+                            s = args[i].Substring(2, args[i].Length - 2);
                             if (File.Exists(s))
                             {
                                 Bitmap bmp = new Bitmap(s);
@@ -224,7 +224,7 @@
                             }
                             break;
                         case 'H':
-                            s = args[i].Substring(3, args[i].Length - 2);
+                            s = args[i].Substring(2, args[i].Length - 2);
                             if (File.Exists(s))
                             {
                                 Bitmap bmp = new Bitmap(s);

要するに、コマンドラインオプション文字列の「-Fxxxx」の「-F」だけ削除すれば済むはずが、「-Fx」を削除しちゃって、文字数が足りなくなってエラーが出ていた模様。また、-Hオプションにも同じバグがあった。

実行バイナリ。 :

せっかくビルドしたので、Visual Studio Community 2019 のプロジェクトフォルダごとzipにして置いときます。bin\Release\ 以下に実行バイナリも入ってます。ライセンスはGPLだから、修正ソースも一緒に同梱しておけば配布しても問題無いのではないかと…。たぶん。

_iZYINS100_src_fork01.zip

ただ、おそらく他にもバグがありそうな気配がする。前述の投稿記事によると、ディザのモードによっては正常な結果が得られない、と報告されてたし…。

まあ、学習用ソースコードという扱いらしいので、バグを見つけて自力で直してみましょう、というお題だったりするのかも。

余談。 :

ちなみに、iZYINS 関連の情報をググっているうちに知ったのだけど、減色ツール Yukari にかつて同梱されていたコンソール版 Flan.exe は、iZYINS で加えられた改良点が反映されていて、かつ、iZYINS のバグも修正してあるそうで。単に減色ツールを使いたいだけなら Flan.exe を入手して利用したほうが良さそうではあるなと。

_結社「障泥烏賊ライブラリ」用地

ただ、Flan.exe は、ウイルスとして誤検出される問題を抱えていて…。ウイルス対策ソフト側で Flan.exe はチェックしないように除外設定をするのが若干面倒ではあるなと…。

#2 [prog] Visual Studio Community 2022をインストールしてみた

Windows10 x64 21H2上で、Microsoft Visual Studio Community 2022 をインストールしてみた。

_Visual Studio Tools のダウンロード - Windows、Mac、Linux 用の無料インストール

VisualStudioSetup.exe をDLして実行。C++とC#のソースをビルドできるように、「C++によるデスクトップ開発」「.NETデスクトップ開発」にチェックを入れてインストールを進めた。インストール場所はデフォルトの Cドライブのまま。

ちなみに、Visual Studio Community 2019 もインストールされている環境なのだけど、2019 と 2022 の共存はできる模様。

ググった感じでは、2019 と比べて、IDE が64bitになって、Windows7/8 や 32bit環境はサポート外になったらしい。Winodws10/11、64bit環境しかサポートしない、ということなのだな…。

旧バージョンの入手。 :

ふと、今から 2019 Community は入手できるのかどうかが気になってあちこち辿ってみたのだけど。「古いバージョンのダウンロード」というリンクを辿っていったら入手できそうなページに辿り着いた。であれば、旧バージョン決め打ちで配布されてるプロジェクトに遭遇しても大丈夫、ということかな…。

2022/07/15(金) [n年前の日記]

#1 [prog] FreeImageとDevILライブラリを試用

C/C++で、pngファイルを読み込んだり書き込んだりする処理をしてみたい。

画像処理用のライブラリとして、FreeImage、DevIL というライブラリがあるらしいと知ったので試用してみた。

_C/C++ にて 画像ファイルを読み込む - Qiita


動作確認環境は、Windows10 x64 21H2 + MSYS2 (MinGW64)。g++ 12.1.0。GNU Make 4.3。

FreeImageライブラリ。 :

C/C++ から利用できる画像処理用ライブラリとして、FreeImage というライブラリがあるらしい。

_The FreeImage Project


ライセンスは、GPL、または FreeImage Public License (FIPL)。どちらかを選べる、ということなのだろう。たぶん。

_GNU General Public License | Open Source Initiative
_freeimage.sourceforge.net/freeimage-license.txt


MSYS2 なら、パッケージが既に用意されていた。pacman -Ss hoge でパッケージ検索。
$ pacman -Ss freeimage
mingw32/mingw-w64-i686-freeimage 3.18.0-8
    Library project for developers who would like to  support popular graphics image formats (mingw-w64)
mingw64/mingw-w64-x86_64-freeimage 3.18.0-8
    Library project for developers who would like to  support popular graphics image formats (mingw-w64)
ucrt64/mingw-w64-ucrt-x86_64-freeimage 3.18.0-8
    Library project for developers who would like to  support popular graphics image formats (mingw-w64)
clang32/mingw-w64-clang-i686-freeimage 3.18.0-8
    Library project for developers who would like to  support popular graphics image formats (mingw-w64)
clang64/mingw-w64-clang-x86_64-freeimage 3.18.0-8
    Library project for developers who would like to  support popular graphics image formats (mingw-w64)

インストールしてみる。pacman -S hoge でインストールできる。
pacman -S mingw-w64-x86_64-freeimage
pacman -S mingw-w64-i686-freeimage

以下を参考にしてサンプルを用意した。pngファイルを読み込んで、1ドットずつグレースケール変換して、out.png として保存する、という処理をしている。

_FreeImageで画像の読み込みと保存 - Qiita
_画像変換ツールを自作する - Qiita
_FreeImage 3.13.1 documentation - FreeImage3131.pdf

_01_load_png01.cpp
// load png image with freeimage.

#include <stdio.h>
#include <iostream>
#include <FreeImage.h>

const char *outfile = "out.png";

int main(int argc, char *argv[])
{
  if (argc != 2)
  {
    printf("Usage:\n\t01_load_png01.exe IN.png\n");
    return -1;
  }

  FreeImage_Initialise(FALSE);

  printf("FreeImage ver. %s\n", FreeImage_GetVersion());
  printf("%s\n\n", FreeImage_GetCopyrightMessage());

  try
  {
    auto filename = argv[1];

    // get input image format
    FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
    int flag = 0;

    fif = FreeImage_GetFileType(filename);
    if (fif == FIF_UNKNOWN)
      fif = FreeImage_GetFIFFromFilename(filename);

    if (fif == FIF_PNG)
      flag = PNG_DEFAULT;
    else if (fif == FIF_BMP)
      flag = BMP_DEFAULT;
    else if (fif == FIF_GIF)
      flag = GIF_DEFAULT;
    else if (fif == FIF_JPEG)
      flag = JPEG_DEFAULT;

    // load image
    FIBITMAP *image = FreeImage_Load(fif, filename, flag);
    if (image)
      printf("Load : %s\n", filename);
    else
      throw std::runtime_error("Load failed");

    // convert to 24bit image
    FIBITMAP *image24 = FreeImage_ConvertTo24Bits(image);

    // convert greyscale
    int width = FreeImage_GetWidth(image24);
    int height = FreeImage_GetHeight(image24);
    FIBITMAP *newimg = FreeImage_Allocate(width, height, 24);
    RGBQUAD col;

    for (int y = 0; y < height; y++)
    {
      for (int x = 0; x < width; x++)
      {
        FreeImage_GetPixelColor(image24, x, y, &col);

        double ncol = 0.299 * col.rgbRed + 0.587 * col.rgbGreen + 0.114 * col.rgbBlue;
        if (ncol < 0)
          ncol = 0;
        if (ncol > 255)
          ncol = 255;
        col.rgbRed = int(ncol);
        col.rgbGreen = int(ncol);
        col.rgbBlue = int(ncol);
        
        FreeImage_SetPixelColor(newimg, x, y, &col);
      }
    }

    // save png image
    if (FreeImage_Save(FIF_PNG, newimg, outfile, PNG_DEFAULT))
      printf("Save : %s\n", outfile);
    else
      throw std::runtime_error("Save failed");

    FreeImage_Unload(image);
    FreeImage_Unload(newimg);
  }
  catch (std::exception &e)
  {
    std::cout << "exception!\n"
              << e.what() << std::endl;
  }

  FreeImage_DeInitialise();

  return 0;
}

コンパイルは以下。
g++ 01_load_png01.cpp -o 01_load_png01.exe -lfreeimage

実行。
./01_load_png01.exe in.png

グレースケールになった out.png が得られた。

ただ、ldd 01_load_png01.exe と打って、使用している .dll を確認したところ、かなり多い。ここまで多いのでは、おそらく静的リンク(スタティックリンク)はできない予感がする。

DevILライブラリ。 :

C/C++から利用できる画像処理用ライブラリとして、DevILというライブラリがあるらしい。

_DevIL - A full featured cross-platform Image Library
_DevILを用いた画像ファイルの読み込み - 月とスカラベ
_How To Load and Display an Image with DevIL and OpenGL | Geeks3D

MSYS2では、FreeImage と同様にパッケージが用意されていた。
$ pacman -Ss devil
mingw32/mingw-w64-i686-devil 1.8.0-8
    Library for reading several different image formats  (mingw-w64)
mingw64/mingw-w64-x86_64-devil 1.8.0-8
    Library for reading several different image formats  (mingw-w64)
ucrt64/mingw-w64-ucrt-x86_64-devil 1.8.0-8
    Library for reading several different image formats  (mingw-w64)
clang32/mingw-w64-clang-i686-devil 1.8.0-8
    Library for reading several different image formats  (mingw-w64)
clang64/mingw-w64-clang-x86_64-devil 1.8.0-8
    Library for reading several different image formats  (mingw-w64)

インストール。
pacman -S mingw-w64-x86_64-devil
pacman -S mingw-w64-i686-devil

あちこちからコピペしてサンプルを書いてみた。画像を読み込んで画像サイズを出力するだけのスクリプト。

_01_load_image_devil.cpp
// DevIL sample.

#include <IL/ilu.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#undef USE_WCHAR_T

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        printf("Usage:\n\thoge.exe IN.png\n");
        return 0;
    }

    char *filename = argv[1];

    ILuint img;
    int width, height;

    // DevIL init
    ilInit();
    iluInit();

    // generate image ID
    ilGenImages(1, &img);

    ilBindImage(img);

#ifdef USE_WCHAR_T
    // char to wchar_t
    size_t newsize = strlen(filename) + 1;
    wchar_t *wcfilename = new wchar_t[newsize];
    size_t convertedChars = 0;
    mbstowcs_s(&convertedChars, wcfilename, newsize, filename, _TRUNCATE);
#endif

    ILboolean fg;

    if (1)
    {
        // not use ilLoadImage()

        ILubyte *lump;
        ILuint size;
        FILE *fp;

        fp = fopen(filename, "rb");
        fseek(fp, 0, SEEK_END);
        size = ftell(fp);

        lump = (ILubyte *)malloc(size);
        fseek(fp, 0, SEEK_SET);
        fread(lump, 1, size, fp);
        fclose(fp);

        fg = ilLoadL(IL_PNG, lump, size);

        free(lump);
    }
    else
    {
        // use ilLoadImage()

        fg = ilLoadImage(filename);
    }

    if (fg)
    {
        // load success
        printf("Load : %s\n", filename);

        width = ilGetInteger(IL_IMAGE_WIDTH);
        height = ilGetInteger(IL_IMAGE_HEIGHT);
        printf("image size : %d x %d\n", width, height);

        if (ilGetInteger(IL_IMAGE_ORIGIN) != IL_ORIGIN_LOWER_LEFT)
        {
            iluFlipImage();
        }
    }
    else
    {
        // load failure
        ILenum err = ilGetError();
        std::cerr << iluErrorString(err) << std::endl;
    }

    ilDeleteImages(1, &img);

#ifdef USE_WCHAR_T
    delete wcfilename;
#endif

    return 0;
}

以下でコンパイル。
g++ 01_load_image_devil.cpp -o 01_load_image_devil.exe -lIL -lILU

一応動いたものの、ldd 01_load_image_devil.exe で使用DLLを確認したら、これまたかなりの .dllを利用すると分かった。

試しに -static をつけて静的リンクを試みたところ、-lIL -ILU が見つからないとエラーが出てしまった。ググった感じでは、静的リンクをするなら DevILライブラリ自体をビルドし直さないといけないらしい。

wchar_t と char の変換。 :

Visual Studio Code 上で、DevIL のサンプルを書いていた際、ilLoadImage() に char* を渡す書き方をしたところ、「この関数は wchar_t* を要求している」と警告が表示されてしまった。

色々ググって、char* を wchar_t* に変換するやり方を記述したのだけど…。MSYS2 + MinGW64 でビルドしてみたら、今度は「これは char* を要求する関数だぞ」と言われてエラーが出てしまった。

どうやら、Visual Studio Code 上の警告は無視して、そのまま char* を渡せば良かったらしい。

ちなみに、char* から wchar_t* に変換する処理は以下。wcfilename に wchar_t の文字列が入る。はず。また、使い終わったら delete で消去(解放)しておかないといけない。
    // char to wchar_t
    size_t newsize = strlen(filename) + 1;
    wchar_t *wcfilename = new wchar_t[newsize];
    size_t convertedChars = 0;
    mbstowcs_s(&convertedChars, wcfilename, newsize, filename, _TRUNCATE);
...
    delete wcfilename;

さておき。前述の問題の解決策が分かった。#define _UNICODE を無効にすれば、wchar_t* ではなく char* が使われる状態になるらしい。そのあたり、以下の韓国語ページで解説されていた。

_c++ devil library char または wchar_t の問題

vscode(Visual Studio Code)上で、この定義、_UNICODE を無効にするためには、以下の解説に従って作業する。

_Visual Studio Code C++ remove 'define' in c_cpp_properties.json - Stack Overflow

まず、vscode-preinclude.h というファイルをワークスペースフォルダ内に作って、以下の一文を書いておく。
#undef _UNICODE

vscode上で、Ctrl + Shift + P → C/C++:構成の編集(UI) C/C++: Edit Configurations (UI)。設定画面が出てくるので下のほうまでスクロールして「詳細設定」をクリック。「強制インクルード」の欄に以下を記述。
${workspaceFolder}/vscode-preinclude.h

これで、vscode に vscode-preinclude.h が読み込まれて、そこに _UNICODE の定義を削除する一文が書いてあるから、DevIL の各関数が、wchar_t* ではなく char* を要求するものとして vscode に認識されて、おかしな警告が表示されなくなる。

あるいは、以下で紹介されているように、C++ の標準機能でファイルを開いてメモリに格納してから、そのメモリ領域を ilLoadL() に渡すのもアリかもしれない。

_The Developer's Image Library (DevIL) Tutorials

#2 [nitijyou] 体調が悪い

朝、妙な息苦しさを感じた。喉にも痛みが。なんだろうコレは…。

実のところ、昨日一昨日から息苦しさを感じる瞬間はあって。寝ようとして横になったら急に息苦しくなったりとか。

症状からして、もしやコロナだろうか…。外出、というか密になる場面はできるだけ少なくしてるつもりなのだけど…。 *1
*1: 夜食を買いに、数日に1回、近所のスーパー(ザ・ビッグ)、もしくはドラッグストア(カワチ)に入るぐらいで。できるだけ素早く買い物して早く店から出るように心掛けてはいるつもり。

2022/07/16() [n年前の日記]

#1 [prog] 画像処理ライブラリを探してた

昨日、FreeImage と DeviIL という、C/C++で利用できる画像処理ライブラリを試用してみたものの、静的リンク(スタティックリンク)が難しそうなあたりがちょっと厳しかったので、他に良さそうな画像処理ライブラリは無いものかとググってた。

興味が湧いたのは、stbライブラリだろうか…。ヘッダファイルの形で提供しているので、#inlcude するだけで利用できてしまうそうで。

_nothings/stb: stb single-file public domain libraries for C/C++
_でらうま倶楽部 : stbライブラリが便利すぎる!!
_C++で超簡単に画像出力+サンプル - Qiita
_Reading an image file in C/C++ - Stack Overflow
_stbライブラリを使おうとするとLNK2001(外部シンボルは未解決です)エラーになる
_c++ - stb_image.h in Visual Studio - unresolved external symbol - Stack Overflow
_stbライブラリで画像読み込み | ぬの部屋(仮)

他にも、シングルファイルに近い形で提供されてるライブラリがいくつか存在しているらしい。

_nothings/single_file_libs: List of single-file C/C++ libraries.

CImgというライブラリも気になった。

_The CImg Library - C++ Template Image Processing Library
_dtschump/CImg: The CImg Library is a small and open-source C++ toolkit for image processing
_CImg を使う C++ プログラム例
_The CImg Library - C++ Template Image Processing Toolkit - Reference Documentation

2022/07/17() [n年前の日記]

#1 [prog] stbライブラリを試用

_昨日調べて 存在を知った、C/C++で画像処理をするためのstbライブラリを少しだけ試用。

_nothings/stb: stb single-file public domain libraries for C/C++

動作確認環境は Windows10 x64 21H2 + MSYS2 + MinGW64。g++ 12.1.0。GNU Make 4.3。

今回は画像のロードとセーブを試してみたかったので、stb_image.h と stb_image_write.h をDL。メインとなるソースコードと同じ場所に置いて利用した。

画像ロード。 :

stb_image.h の使用例。画像(.png)を読み込んで、画像サイズと1ピクセルあたりのバイト数を出力する。

_01_loadimage.cpp
// load image with stb_image.h

#include <stdio.h>

#define STB_IMAGE_STATIC
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

int main(int argc, char *argv[])
{
  if (argc != 2)
  {
    printf("Usage\n\t01_loadimage.exe IN.png");
  }

  auto filename = argv[1];

  unsigned char *pixels;
  int width;
  int height;
  int bpp;

  pixels = stbi_load(filename, &width, &height, &bpp, 0);

  printf("Image size %d x %d\n", width, height);
  printf("Image bpp %d\n", bpp);

  stbi_image_free(pixels);
}

コンパイルは以下。ここでは静的リンク(スタティックリンク)をしている。
g++ 01_loadimage.cpp -o 01_loadimage.exe -static -lstdc++ -lgcc -lwinpthread

以下で実行。画像サイズと Byte per pixel が出力された。
$ ./01_loadimage.exe in.png
Image size 512 x 512
Image bpp 3

stb_image.h の使い方としては、最初に以下を書いておく。
#define STB_IMAGE_STATIC
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
  • stb_image.h を使う場合、必ず、「#define STB_IMAGE_IMPLEMENTATION」と「#include "stb_image.h"」を記述する。
  • STB_IMAGE_STATIC を定義しておくと、各関数の頭に static がつく、らしい。

  • stbi_load() で画像のロード。
  • 画像を使い終わったら stbi_image_free() で解放する。

画像セーブ。 :

stb_image_write.h の使用例。画像(.png)を読み込んで、グレースケールに変換して、out.png というファイル名で保存する。

_02_writeimage.cpp
// write png image with stb_image_write.h

#include <stdio.h>

#define STB_IMAGE_STATIC
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

#define STB_IMAGE_WRITE_STATIC
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

int main(int argc, char *argv[])
{
  if (argc != 2)
  {
    printf("Usage\n\t02_writeimage.exe IN.png");
  }

  auto filename = argv[1];

  unsigned char *pixels;
  int width;
  int height;
  int bpp;

  // load image
  pixels = stbi_load(filename, &width, &height, &bpp, 0);

  printf("Image size %d x %d\n", width, height);
  printf("Image bpp %d\n", bpp);

  for (int y = 0; y < height; y++)
    for (int x = 0; x < width; x++)
    {
      int idx = (y * width + x) * bpp;

      unsigned int r = (unsigned int)pixels[idx + 0];
      unsigned int g = (unsigned int)pixels[idx + 1];
      unsigned int b = (unsigned int)pixels[idx + 2];

      // convert greyscale
      if (1)
      {
        double grey = 0.299 * (double)r + 0.587 * (double)g + 0.114 * (double)b;
        if (grey < 0)
          grey = 0;
        if (grey > 255)
          grey = 255;
        unsigned int ui_grey = (unsigned int)grey;

        pixels[idx + 0] = ui_grey;
        pixels[idx + 1] = ui_grey;
        pixels[idx + 2] = ui_grey;
      }
    }

  // write image
  if (stbi_write_png("out.png", width, height, 3, pixels, width * 3))
  {
    printf("Save out.png\n");
  }
  else
  {
    printf("Failure save.\n");
  }

  stbi_image_free(pixels);
}

コンパイル。
g++ 02_writeimage.cpp -o 02_writeimage.exe -static -lstdc++ -lgcc -lwinpthread

実行。
$ ./02_writeimage.exe in.png
Image size 512 x 512
Image bpp 3
Save out.png

グレースケールの画像が得られた。

stb_image_write.h の使い方としては、最初に以下を書いておく。
#define STB_IMAGE_WRITE_STATIC
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
  • stb_image_write.h を使う場合、必ず、「#define STB_IMAGE_WRITE_IMPLEMENTATION」と「#include "stb_image_write.h"」を記述する。
  • STB_IMAGE_WRITE_STATIC を定義しておくと、各関数の頭に static がつく、らしい。

  • stbi_write_png() で、png画像の保存。
与える引数は…ちょっとよく分からない。たぶん以下なのかなと思うけど。
stbi_write_png(保存ファイル名, 画像横幅, 画像縦幅, bpp, ピクセルデータ群へのポインタ, 1ライン分のバイト数)
ただ、巷のサンプルを見ると、一番最後で「0」を指定してる場合が多くて…。

ちなみに、stb_image_write.h 内のコメントによると、ソースコードの量を減らすためにpng画像の圧縮率は低い状態になっているそうで。zlib関係の関数を STBIW_ZLIB_COMPRESS に定義することで圧縮率は改善できると書いてあった。もちろん、zlib を使えるようにビルドしないといかんのだろうけど。

何にせよ、stb_image.h にしろ、stb_image_write.h にしろ、比較的簡単な記述で画像のロードやセーブができる上に、静的リンクもすんなりできてしまうとは…。これは便利だなと…。

stbi_write_png() の最後の引数について。 :

stbi_write_png() の最後の引数がよく分からなかったけど、ソースを眺めた感じでは 0 を指定しても良かったらしい。

stbi_write_png() は、内部で stbi_write_png_to_mem() を呼んでいるけれど、この stbi_write_png_to_mem() の中で、値が 0 なら width * bpp を代入、みたいなことをしている。

なので、自分で指定してもいいし、面倒臭ければ 0 を指定してもよい。どちらも同じ結果になるはず。たぶん。

2022/07/18(月) [n年前の日記]

#1 [prog] getoptについて調べてる

C/C++で書かれているコマンドラインツール(?)の中で、コマンドラインオプションの解析をしたい。

ググってみた感じでは、getopt() や getopt_long() を使うと、多少は簡単に処理を書けるらしい。試してみた。

環境は Windows10 x64 21H2 + MSYS2 + MinGW64 + g++ 12.1.0。

_getopt_sample.cpp
// getopt() sample.

#include <stdio.h>
#include <getopt.h>

void usage(char *name)
{
    printf("Usage:\n");
    printf("  %s -i IN.png -o OUT.png [-d N]\n", name);
    printf("\n");
    printf("  -i FILE, --input FILE   input png image file.\n");
    printf("  -o FILE, --output FILE  output png image file.\n");
    printf("  -d N, --dither N        dither map. N = 2/4/8 (2x2, 4x4, 8x8)\n");
    printf("  -h, --help              display help message\n");
    printf("\n");
}

int main(int argc, char **argv)
{
    // parse option
    char *infile = NULL;
    char *outfile = NULL;
    int dither = 4;

    struct option longopts[] = {
        {"help", no_argument, NULL, 'h'},
        {"input", required_argument, NULL, 'i'},
        {"output", required_argument, NULL, 'o'},
        {"dither", required_argument, NULL, 'd'},
        {0, 0, 0, 0}};

    int opt;
    int longindex;
    while ((opt = getopt_long(argc, argv, "hi:o:d:", longopts, &longindex)) != -1)
    {
        switch (opt)
        {
        case 'i': // input image filename
            infile = optarg;
            break;

        case 'o': // output image filename
            outfile = optarg;
            break;

        case 'd': // dither map size, 2 or 4 or 8
            switch (optarg[0])
            {
            case '2':
                dither = 2;
                break;

            case '4':
                dither = 4;
                break;

            case '8':
                dither = 8;
                break;

            default:
                printf("Error : Unknown dither mode = %s\n\n", optarg);
                dither = -1;
                break;
            }
            break;

        case 'h': // help
            usage(argv[0]);
            return 0;

        default:
            break;
        }
    }

    if (optind < argc)
    {
        for (; optind < argc; optind++)
            printf("Unknown option : %s\n", argv[optind]);
        usage(argv[0]);
        return 1;
    }

    printf("infile  : %s\n", infile);
    printf("outfile : %s\n", outfile);
    printf("dither  : %d\n", dither);

    if (infile == NULL || outfile == NULL || dither == -1)
    {
        usage(argv[0]);
        return 1;
    }
}

コンパイル。
g++ getopt_sample.cpp -o getopt_sample.exe

オプションを指定して実行してみる。
$ ./getopt_sample.exe --help
Usage:
  D:\...\getopt_sample.exe -i IN.png -o OUT.png [-d N]

  -i FILE, --input FILE   input png image file.
  -o FILE, --output FILE  output png image file.
  -d N, --dither N        dither map. N = 2/4/8 (2x2, 4x4, 8x8)
  -h, --help              display help message

$ ./getopt_sample.exe -i in.png -o out.png -d 2
infile  : in.png
outfile : out.png
dither  : 2


ヘルプメッセージは自分で列挙するしかないのだろうか…?

参考ページ。 :


2022/07/19(火) [n年前の日記]

#1 [prog] cmdline.hを少し試用

C++で書いたコマンドラインツールに、コマンドラインオプションを渡して、処理が変わるようにしたい。昨日、getopt() や getopt_long() を使って試してみたけど、それらは記述が面倒だなと…。ヘルプメッセージは自分で用意しないといけないし。

もっと便利なライブラリは無いのかなとググったら、cmdline.h というライブラリがあることを知った。ヘッダーファイル1つを inlcude するだけで使えるらしい。

_C++向け簡易コマンドラインパーザ cmdline - 純粋関数型雑記帳
_tanakh/cmdline: A Command Line Parser
_C++版のgetopt - Qiita

興味が湧いたので、手元で動作確認。動作確認環境は、Windows10 x64 21H2 + MSYS2 + MinGW64 + g++ 12.1.0。

_getopt_sample2.cpp
#include "cmdline.h"

#include <stdio.h>
#include <iostream>

int main(int argc, char *argv[])
{
    // parse options
    cmdline::parser a;
    a.add<std::string>("input", 'i', "Input png image", true, "");
    a.add<std::string>("output", 'o', "Output png image", true, "");
    a.add<std::string>("palette", 'p', "Palette (.png or .gpl)", false, "");
    a.add<int>("dither", 'd', "Dither map 2/4/8 (2x2/4x4/8x8)", false, 4);
    a.parse_check(argc, argv);

    // get options
    auto infile = a.get<std::string>("input").c_str();
    auto outfile = a.get<std::string>("output").c_str();
    auto palette = a.get<std::string>("palette").c_str();
    auto dither = a.get<int>("dither");

    if (0)
    {
        std::cout << "input   : " << infile << std::endl;
        std::cout << "output  : " << outfile << std::endl;
        std::cout << "palette : " << palette << std::endl;
        std::cout << "dither  : " << dither << std::endl;
    }
    else
    {
        printf("input   : [%s]\n", infile);
        printf("output  : [%s]\n", outfile);
        printf("palette : [%s]\n", palette);
        printf("dither  : %d\n", dither);
    }

    if (strlen(infile) == 0 || strlen(outfile) == 0)
    {
        // Not set infile or outfile
        printf("%s\n", a.usage().c_str());
        return 1;
    }

    if (strlen(palette) == 0)
    {
        // Not set palette
        printf("Use default palette\n");
    }

    if (!(dither == 2 || dither == 4 || dither == 8))
    {
        printf("Error : Unknown dither mode = %d\n\n", dither);
        printf("%s\n", a.usage().c_str());
        return 1;
    }

    return 0;
}

コンパイルは以下。
g++ getopt_sample2.cpp -o getopt_sample2.exe -static -lstdc++ -lgcc -lwinpthread -lm

実行。
$ ./getopt_sample2.exe --help
usage: D:\...\getopt_sample2.exe --input=string --output=string [options] ...
options:
  -i, --input      Input png image (string)
  -o, --output     Output png image (string)
  -p, --palette    Palette (.png or .gpl) (string [=])
  -d, --dither     Dither map 2/4/8 (2x2/4x4/8x8) (int [=4])
  -?, --help       print this message

$ ./getopt_sample2.exe -i in.png -o out.png -d 8
input   : [in.png]
output  : [out.png]
palette : []
dither  : 8
Use default palette

$ ./getopt_sample2.exe --input in.png --output out.png --dither 8
input   : [in.png]
output  : [out.png]
palette : []
dither  : 8
Use default palette

記述量も少なくて済むし、ヘルプメッセージも自動生成してくれるし、ヘッダファイル1つだから使うのも楽だし、静的リンクもしやすい。これは便利だなと…。

#2 [prog] パレットデータ相当を読み込みたい

C++で、パレットデータ相当を読み込みたい。以下のような処理をしたい。
ということで、一応そういう処理を書いてみた。

動作確認環境は、Windows10 x64 21H2 + MSYS2 + MinGW64 + g++ 12.1.0。

画像の読み込みには、stbライブラリ(stb_image.h) を使わせてもらった。ありがたや。

_nothings/stb: stb single-file public domain libraries for C/C++

ソース。 :

ソースは以下。

_loadpalette.cpp
// load palette (.gpl or .png)

#include <map>
#include <string.h>

/*
 use stb library

 nothings/stb: stb single-file public domain libraries for C/C++
 https://github.com/nothings/stb
 */
#define STB_IMAGE_STATIC
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

#define CBUF_N 1024
#define TMP_PAL_NUM 1024

unsigned int *load_palette_from_gpl(char *infile, int *len_pal)
{
    unsigned int *pal = nullptr;
    unsigned int tmp_pal[TMP_PAL_NUM];
    FILE *fp;
    char str[CBUF_N];
    unsigned int r, g, b;
    int idx = 0;

    for (int i = 0; i < TMP_PAL_NUM; i++)
        tmp_pal[i] = 0;

    fp = fopen(infile, "r");
    if (fp == NULL)
    {
        printf("Error : Can not open %s\n", infile);
        *len_pal = 0;
        return nullptr;
    }

    while (fgets(str, CBUF_N, fp) != NULL)
    {
        str[strlen(str) - 1] = '\0';
        if (strncmp("GIMP Palette", str, 12) == 0)
            continue;
        if (strncmp("Name:", str, 5) == 0)
            continue;
        if (strncmp("Columns", str, 7) == 0)
            continue;
        if (strncmp("#", str, 1) == 0)
            continue;

        sscanf(str, "%d %d %d", &r, &g, &b);
        // printf("%s", str);
        // printf("\t\tRGB : (%d, %d, %d)\n", r, g, b);

        tmp_pal[idx] = (r << 16) | (g << 8) | b;
        idx++;
    }
    fclose(fp);

    *len_pal = idx;
    pal = new unsigned int[idx];
    for (int i = 0; i < idx; i++)
        pal[i] = tmp_pal[i];

    return pal;
}

unsigned int *load_palette_from_img(char *infile, int *len_pal)
{
    unsigned int *pal = nullptr;
    std::map<unsigned int, unsigned int> pal_map;

    // load image
    unsigned char *pixels;
    int w;
    int h;
    int bpp;

    pixels = stbi_load(infile, &w, &h, &bpp, 0);
    if (pixels == NULL)
    {
        printf("Error : Can not load %s\n", infile);
        *len_pal = 0;
        return nullptr;
    }

    for (int y = 0; y < h; y++)
    {
        int yofs = y * w * bpp;
        for (int x = 0; x < w; x++)
        {
            int i = x * bpp + yofs;
            unsigned int col = (pixels[i + 0] << 16) | (pixels[i + 1] << 8) | pixels[i + 2];
            pal_map[col] += 1;
        }
    }
    stbi_image_free(pixels);

    int n = pal_map.size();

    // pal = (unsigned int *)malloc(sizeof(unsigned int) * n);
    pal = new unsigned int[n];

    int j = 0;
    for (auto i = pal_map.begin(); i != pal_map.end(); i++)
    {
        pal[j] = i->first;
        j++;
    }

    // printf("Count palette = %d\n", j);
    *len_pal = j;
    return pal;
}

unsigned int *load_palette(char *infile, int *len_pal)
{
    unsigned int *pal = nullptr;
    int n;

    char *ext = strrchr(infile, '.');
    if (stricmp(".gpl", ext) == 0)
    {
        // load from .gpl (GIMP palette file)
        pal = load_palette_from_gpl(infile, &n);
    }
    else
    {
        // load from .png
        pal = load_palette_from_img(infile, &n);
    }
    *len_pal = n;
    return pal;
}

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        printf("Usage:\n    %s PALETTE\n\n  PALETTE (.png or .gpl)\n", argv[0]);
        return 0;
    }

    auto palfile = argv[1];
    printf("Palette : %s\n", palfile);

    int len_pal = 0;
    unsigned int *my_pal = load_palette(palfile, &len_pal);
    if (my_pal == nullptr)
    {
        printf("Error : Load failure palette [%s]\n", palfile);
        return 1;
    }

    printf("Palette size = %d\n", len_pal);

    // dump palette data
    for (int i = 0; i < len_pal; i++)
        printf("%06X, ", my_pal[i]);
    printf("\n");

    for (int i = 0; i < len_pal; i++)
    {
        unsigned int col = my_pal[i];
        unsigned int r = (col >> 16) & 0x0ff;
        unsigned int g = (col >> 8) & 0x0ff;
        unsigned int b = col & 0x0ff;
        printf("{%d, %d, %d}, ", r, g, b);
    }
    printf("\n");

    delete my_pal;
}

コンパイルは以下。
g++ loadpalette.cpp -o loadpalette.exe -static -lstdc++ -lgcc -lwinpthread -lm

実行は以下。
$ ./loadpalette.exe def16.gpl
Palette : def16.gpl
Palette size = 16
080000, 201A0B, 432817, 492910, 234309, 5D4F1E, 9C6B20, A9220F, 2B347C, 2B7409, D0CA40, E8A077, 6A94AB, D5C4B3, FCE76E, FCFAE2,
{8, 0, 0}, {32, 26, 11}, {67, 40, 23}, {73, 41, 16}, {35, 67, 9}, {93, 79, 30}, {156, 107, 32}, {169, 34, 15}, {43, 52, 124}, {43, 116, 9}, {208, 202, 64}, {232, 160, 119}, {106, 148, 171}, {213, 196, 179}, {252, 231, 110}, {252, 250, 226},

$ ./loadpalette.exe def16.png
Palette : def16.png
Palette size = 16
080000, 201A0B, 234309, 2B347C, 2B7409, 432817, 492910, 5D4F1E, 6A94AB, 9C6B20, A9220F, D0CA40, D5C4B3, E8A077, FCE76E, FCFAE2,
{8, 0, 0}, {32, 26, 11}, {35, 67, 9}, {43, 52, 124}, {43, 116, 9}, {67, 40, 23}, {73, 41, 16}, {93, 79, 30}, {106, 148, 171}, {156, 107, 32}, {169, 34, 15}, {208, 202, 64}, {213, 196, 179}, {232, 160, 119}, {252, 231, 110}, {252, 250, 226},

.gpl も .png も読み込めているように見える。

動作確認に使った .gpl、.png は以下。

_def16.gpl

def16.png
_def16.png

余談。 :

.gpl内のRGB値をどうやって取り込めばいいのか悩んだけれど、sscanf() であっさり取り込めてしまった。ただ、.gpl内の記述の仕方によっては、sscanf() で対応できなくてエラーになる場面もありそう。

そもそも .gplの記述ルールがアバウト過ぎる気もする。空白無し、区切り文字は「,」にでもしておけばよかったのに。あるいは xml にしておくとか。

2022/07/20(水) [n年前の日記]

#1 [cg_tools][tic80] TIC-80のパレットデータを手元に残そうとして少しハマった

TIC-80 は、ある時期からパレットデータを SWEETIE 16 というデータに差し替えていたらしいのだけど。 *1

_Palette - nesbox/TIC-80 Wiki
_Sweetie 16 Palette - lospec.com

この SWEETIE 16 パレットデータを手元に残そうとして、少しハマってしまった。

TIC-80 の Wiki にアップロードされているパレットデータ画像は、ページに記述されてる数値とビミョーに違っているようで…。具体的には、水色がちょっとずれてる。作業していてなんだか変だなと思ったら、そういうオチだった…。

そんなわけで、画像はちょっと当てにならないので、記述されている数値を元にしてパレットデータ画像を新規に生成するPythonスクリプトを書いてみた。

いやまあ、losepc.com からダウンロードできる画像や .gpl を使えば問題無さそうだけど…。ざっと調べた感じでは、そちらはちゃんと数値が合ってるように見えた。TIC-80 の Wiki に載ってる画像のみがおかしい。

_Sweetie 16 Palette - lospec.com

でもまあ、「Python + Pillow を使えばこういうこともできるよね」的サンプルの一つ、みたいな感じで一応アップロードしておきます。

生成した .gpl や .png。 :

生成した .gpl (GIMP Palette ファイル) や .png は以下。

_sweetie-16_tic80new_.gpl
_sweetie-16_tic80new.png
sweetie-16_tic80new.png

生成Pythonスクリプト。 :

動作確認環境は、Windows10 x64 21H2 + Python 3.9.13 64bit + Pillow 9.1.1。

sweetie-16_tic80new_.gpl と sweetie-16_tic80new.png を生成する。

動作には Pillow のインストールが必要。pip install Pillow -U でインストールできるはず。

_create_sweetie-16_image.py
from PIL import Image
from PIL import ImageDraw


out_gpl_file = "sweetie-16_tic80new_.gpl"
out_img_file = "sweetie-16_tic80new.png"

title = "Sweetie-16 (TIC-80 new)"

tbl = [
    ["Black", 0, "1A1C2C"],
    ["Purple", 1, "5D275D"],
    ["Red", 2, "B13E53"],
    ["Orange", 3, "EF7D57"],
    ["Yellow", 4, "FFCD75"],
    ["Light Green", 5, "A7F070"],
    ["Green", 6, "38B764"],
    ["Dark Green", 7, "257179"],
    ["Dark Blue", 8, "29366F"],
    ["Blue", 9, "3B5DC9"],
    ["Light Blue", 10, "41A6F6"],
    ["Cyan", 11, "73EFF7"],
    ["White", 12, "F4F4F4"],
    ["Light Grey", 13, "94B0C2"],
    ["Grey", 14, "566C86"],
    ["Dark Grey", 15, "333C57"],
]


def main():

    pal = []

    # create .gpl
    lst = []
    lst.append("GIMP Palette")
    lst.append("Name: %s" % title)
    lst.append("Columns: 8")
    lst.append("#")
    lst.append("#")

    for t in tbl:
        name, index, hexstr = t
        col = int(hexstr, 16)
        r = (col >> 16) & 0x0ff
        g = (col >> 8) & 0x0ff
        b = col & 0x0ff
        s = "%-8d%-8d%-8d%06x  %d  %s" % (r, g, b, col, index, name)
        lst.append(s)
        pal.append((r, g, b))

    for s in lst:
        print(s)

    # save .gpl file
    with open(out_gpl_file, mode="w") as f:
        f.write("\n".join(lst))
        f.write("\n")

    print("# Save %s" % out_gpl_file)

    # create image and save
    xcnt, ycnt = 8, 2
    # xcnt, ycnt = 16, 1

    w, h = 32, 32
    img_w, img_h = w * xcnt, h * ycnt

    im = Image.new("P", (img_w, img_h), (0, 0, 0))
    paldata = []
    for c in pal:
        r, g, b = c
        paldata.append(r)
        paldata.append(g)
        paldata.append(b)

    im.putpalette(paldata, rawmode="RGB")
    
    draw = ImageDraw.Draw(im)

    x, y = 0, 0
    for c in range(len(pal)):
        draw.rectangle((x, y, x + w - 1, y + h - 1), fill=c)
        x += w
        if x >= img_w:
            x = 0
            y += h

    im.save(out_img_file)
    print("# Save %s" % out_img_file)


if __name__ == '__main__':
    main()

py create_sweetie-16_image.py で実行すれば、.gpl と .png が得られる。

*1: 最初の頃は、DB16 (DawnBringer 16) というパレットデータが割り当てられてた。

#2 [pc] お袋さん用ノートPCをセットアップ

作業メモ。お袋さんが会社で使ってたノートPCが、社内のPC管理の都合で不要になったそうで、しばらく前に、お袋さんがそのノートPCを自宅に持って帰ってきた。型番は、DELL Vostro 15 3558、と書いてある。

しばらく放置してしまっていたけど、さすがにそろそろ使えるようにしなければと、夕食の後にセットアップ作業をしてみた。と言っても、MACアドレスを調べて、無線LANルータにMACアドレスを登録して、Wi-Fi接続しただけ。とりあえず、これでお袋さん用のノートPCもネット接続できるようになった。

その他の詳細はGRPでメモ。 そもそも、MACアドレスフィルタリングって今となっては無意味、という話も見かけたような…。

_高木浩光@自宅の日記 - 無線LANのMACアドレス制限の無意味さがあまり理解されていない
_MACアドレスフィルタリングって何?【“Wi-Fiの困った”を解決:基本編 第8回】 - INTERNET Watch
_「意味ない」どころか「悪影響」!?Wi-FiのセキュリティとMACアドレスフィルタリング再考|そだフラ!
_MACアドレス制限は“効果ゼロ”今さら聞けない「無線LAN認証」の基本【1/4】 | 大塚商会
「進入禁止」と書かれた規制テープのようなものであり、一定の抑止効果はあるものの悪意を持った攻撃者はそれを気にもとめず乗り越えてくることを理解したうえで導入すべきでしょう。

MACアドレス制限は“効果ゼロ”今さら聞けない「無線LAN認証」の基本【1/4】 | 大塚商会 より


まあ、そういうものと思っておくことにしよう…。

2022/07/21(木) [n年前の日記]

#1 [prog] C++の勉強中

ディザパターン画像に変換するC++のソースを弄って、ディザのマップサイズを変更したり、パレットデータ相当を読み込んだりする処理を追加しているところ。

C++は使ったことがないので、関数に引数を渡すあたりですら躓く始末。二次元配列ってどうやって渡せばいいのだ…。勉強しないと…。

2022/07/22(金) [n年前の日記]

#1 [prog][cg_tools] ディザパターンに変換するプログラムを修正

以下のページで紹介されている、ディザパターンアルゴリズムのサンプルソースに、色々と手を加えて機能追加をしてみた。

_Arbitrary-palette positional dithering algorithm

変更点は以下。
コンパイルに使った環境は、Windows10 x64 21H2 + MSYS2 + MinGW64。g++ 12.1.0 + GNU Make 4.3。

png画像の読み込みや書き込みは stbライブラリの stb_image.h と stb_image_write.h を使わせてもらった。*.cpp と同じ場所に置いておけばコンパイルできるようになる。

_nothings/stb: stb single-file public domain libraries for C/C++

ソース。 :

ソースは以下。

_yodither1.cpp
_yodither3.cpp
_yodither4.cpp

Makefile は以下。

_Makefile

基本的には、以下でコンパイルできるはず。
g++ -O3 yodither1.cpp -fopenmp -o yodither1.exe -static -lstdc++ -lgcc -lwinpthread -lm

実行バイナリ。 :

一応、実行バイナリも含めた一式を .zip にして置いときます。各 .exe を別PC (Windows 10 x64機) にコピーしても動いたので、他のPC上でも動いてくれるのではないかと…。たぶん。

_yodither_20220722.zip

使用例は以下。--help をつければ使い方の簡単な説明が表示されるはずなので…。
yodither1.exe --help
yodither1.exe -i INPUT.png -o OUTPUT.png
yodither1.exe -i INPUT.png -o OUTPUT.png -d 4
yodither1.exe -i INPUT.png -o OUTPUT.png -d 4 -p db32.gpl
yodither1.exe -i INPUT.png -o OUTPUT.png -d 4 -p db32.png

yodither4.exe --help
yodither4.exe -i INPUT.png -o OUTPUT.png
yodither4.exe -i INPUT.png -o OUTPUT.png -d 8
yodither4.exe -i INPUT.png -o OUTPUT.png -d 8 -p db32.gpl
yodither4.exe -i INPUT.png -o OUTPUT.png -d 8 -p db32.png
> yodither1.exe --help
Usage:
  yodither1.exe -i IN.png -o OUT.png [-d N] [-p PALETTE] [-t] [-v] [-h]

  -i FILE, --input FILE    input png image file.
  -o FILE, --output FILE   output png image file.
  -d N, --dither N         dither map. N = 2/4/8(2x2,4x4,8x8) [default=8]
  -p FILE, --palette FILE  Set palette (.gpl or .png)
  -t, --trimode            Enable tri-mode.
  -v, --verbose            Print verbose
  -h, --help               print this message
> yodither4.exe --help
Usage:
  yodither4.exe -i IN.png -o OUT.png [-d N] [-p PALETTE] [-v] [-h]

  -i FILE, --input FILE    input png image file.
  -o FILE, --output FILE   output png image file.
  -d N, --dither N         dither map. N = 2/4/8 (2x2/4x4/8x8) [Default=4]
  -v, --verbose            Print verbose
  -h, --help               print this message

  • yodither1.exe : 処理時間が結構かかる。生成結果の品質はそこそこ良い。
  • yodither3.exe : パレットデータを変更すると妙な生成結果になりがち。あまり使えない感じがする。
  • yodither4.exe : 処理時間はそれほどかからない。品質は yodither1.exe よりちょっと落ちる。

16〜32色程度のパレットデータでしか動作確認してません。256色あたりを指定すると、とんでもなく処理時間がかかりそうなので、怖くて試してないです。

2022/07/23() [n年前の日記]

#1 [comic] 漫画を読んでた

弟が帰省した際、漫画の単行本をダンボール箱で数箱分ほど持ってきてくれるのだけど、そのあたり手つかずだったので目を通そうとしているところ。今日は二冊ほど読んでみた。

「ぴっかり職業訓練中」1巻。野村宗弘著。職業訓練校の様子を漫画で解説した作品。農業高校を舞台にした「銀の匙」、大学の獣医学部を舞台にした「動物のお医者さん」のようなジャンルになるのだろうか。かなり勉強になった…。

「カナリアたちの舟」。全1巻。高松美咲著。ミステリー、いや、SFミステリー作品だろうか。ある日空から何かがやってきて、気づけばヒロインとおじさんの二人だけで見知らぬ場所に、みたいな。なんとなくだけど、藤子F先生のSF短編、あるいは星新一作品を思い出す、そんな感じの基本設定。今の時代の漫画の絵柄・見せ方ならおそらくこうなるのだろうな、みたいな。

2022/07/24() [n年前の日記]

#1 [comic] 漫画を読んでた

漫画単行本を消化中。

「あまんちゅ!」天野こずえ著、1〜7巻を読了。現代日本を舞台にして、スキューバダイビングにハマっていく高校生(主に女子高校生)達の日常を描いた作品、という説明でいいのだろうか。アニメ版は一応見ていたはずなのだけど内容をすっかり忘れていたので新鮮な(?)感覚で読めた。なんとも独特な雰囲気を持つ漫画だなと…。この雰囲気をアニメ化するのは、そりゃ難しいよな…。

2022/07/25(月) [n年前の日記]

#1 [comic] 漫画を読んでた

漫画単行本を消化中。

「あまんちゅ!」天野こずえ著、8巻から、最終巻の17巻まで読了。ヒロインが高校に入学してから卒業するまでを描いていて、構成としては奇麗にまとまった気がする。見ていて嫌な気分にさせられるキャラが一人も出てこないあたり、癒し系作品に分類されるのだろうか、とも。ググったところ、2008年から2021年まで連載していたとのことで…いやはやお疲れさまでした…。

2022/07/26(火) [n年前の日記]

#1 [comic] 漫画を読んでた

漫画単行本を消化中。

「雑草家族」「10歳かあさん」「ブーゲンビリア 小路啓之短編集」小路啓之著を読了。2016年に46歳の若さでお亡くなりになった漫画家、小路啓之氏の作品群。「雑草家族」「10歳かあさん」は未完の状態で単行本化されているらしい。絵柄はともかく、極めて淡白というか、独特な台詞回しを感じる作家性で、帯や巻末で異才と称して紹介されていることに納得した。なんとも惜しい…。

「プラネットガール」大石日々緒、1巻を読了。事故を起こした宇宙船の回収カプセルを開けたら、そこに居たのは…。宇宙開発が絡んだSFファンタジー or SFミステリー、という分類でいいのだろうか。と思ったけど再度表紙を眺めたみたら、帯に日常系SFアドベンチャーと書いてあった。キャラの線は多いけれど、各コマは整理されていて、とても読みやすい漫画のように感じた。

「アイゼンフリューゲル 弾丸の歌よ龍に届いているか」原作: 虚淵玄、原作イラスト: 中央東口、作画: 七竈アンノ、1巻を読了。ドラゴンが存在する世界で、ジェット機の開発ストーリーを描くという基本設定、なのだろうか。原作は小説らしい。まるでイラスト集を見ているような感覚になるぐらいに絵は上手いのだけど、えてしてそういった絵柄の作家さんが描いた漫画は、漫画としては読みづらいわけで…。とは言え、こういう絵柄じゃないと、この世界観には合わないのかもしれないなと…。

「戦国機甲伝クニトリ」あさりよしとお著、1巻を読了。日本の戦国時代に巨大ロボットが存在して、それらが合戦で活躍していたら、という一風変わった設定の作品。ロボットは米(→酒→アルコール)を燃料にして動くという設定が秀逸だなと…。それはつまり各地の米の生産高が軍事力と直結する上、米を食料に回すのか、それとも軍備に回すのかと言う選択が戦国武将に絶えず突き付けられるわけで…。何のためにわざわざロボットを動かすのか、一体誰がそんな状況を作り上げたのか、といった話になってきて…。さすがあさりよしとお先生…。面白い設定を考える…。

「クラッシャージョウ REBIRTH」原作: 高千穂遥、キャラ原案: 安彦良和、作画: 針井佑、1〜2巻を読了。スペースオペラ小説「クラッシャージョウ」の漫画化作品。絵柄、コマ割り、擬音の類まで、かなり安彦良和先生のソレに寄せているように見えて感心してしまった。安彦先生は漫画を筆で描いてるらしいけど、そのタッチをペンで再現したのだろうか。妙なタイミングで何故かコミカルな表現を入れてくる安彦先生のソレまで再現してるように思えて…。いやはや、よくまあここまで…。

2022/07/27(水) [n年前の日記]

#1 [nitijyou] 自宅サーバ止めてました

雷が鳴ったので、11:55-21:40の間、自宅サーバ止めてました。申し訳ないです。

昼頃から雷が鳴り始めて、夕方には鳴り止んだかなと思ったら、そこからまた夜までずっと鳴り続ける状態で…。幸い近くには落ちなかったようだけど…。なんともはや…。

#2 [ubuntu][linux] xscreensaver経由で動画を再生

Linux上で動くメジャーなスクリーンセーバ、xscreensaver を経由して動画を再生してみたいなと思い立った。

もし動画を再生できるなら、わざわざスクリーンセーバのプログラムを作らなくても、何かしらのCGツールを使って動画を作ってソレを流しとけばいいじゃん、ってことになりそうだよなと…。

そんなわけで試してみた。動作確認環境は、Ubuntu Linxu 20.04 LTS。xscreensaver が利用できる状態になっていること。

参考ページ。 :

mpvのインストール。 :

動画再生用ツールとして mpv をインストールする。Ubuntu の場合はパッケージが用意されていた。ありがたや。
sudo apt install mpv

プレイリストファイルを作成。 :

mpv に渡すためのプレイリストファイルを作成する。前述の参考ページに倣って、~/.config/screensaver.m3u というファイル名にしておく。
vi ~/.config/screensaver.m3u
#EXTM3U

/home/USER/Videos/hoge.mp4
/home/USER/Videos/
  • 1行目には、「#EXTM3U」と記述する。これで、m3u形式のプレイリストと判別されるらしい。
  • 3行目あたりから、動画ファイル(.mp4)のパスを記述。あるいは、動画が入っているディレクトリのパスを記述しても良い。
  • 更に、youtube-dl なるツールが利用できる場合は、YouTube の動画のURLを記述して、それを再生することも可能らしい。ただ、今回は、youtube-dl を導入してないので、そのあたりの記述は削除した。

以下は、参考ページで紹介されていた記述例。
#EXTM3U

/path/to/some_video.mp4
/path/to/dir_with_many_videos/

# any youtube video or other sites supported by `youtube-dl`
https://www.youtube.com/watch?v=Xjs6fnpPWy4

xscreensaver の設定ファイルを編集。 :

xscreensaver の設定ファイル、~/.xscreensaver を編集する。programs: 以下に mpv起動用の項目を追加する。
vi ~/.xscreensaver
"MPV"  mpv --really-quiet --no-stop-screensaver      \
          --fs --wid=$XSCREENSAVER_WINDOW             \
          --no-audio --loop=inf --shuffle             \
          --ytdl-format="bestvideo[height<=1080]"     \
          $HOME/.config/screensaver.m3u             \n\
上記はスペース文字で記述してしまっているけれど、実際に追加する際は、TAB文字有効、かつ、TABは8スペースでインデント等をしたほうが良さそう。

まあ、一旦は1行で記述してしまって、xscreensaver の設定ウインドウで何かしら値を変更すれば、~/.xscreensaver が整形された状態で上書き保存されるので、手作業でインデントを整えなくてもいいのかもしれない。

オプションの意味は以下。
  • --really-quiet : stdout(標準出力)への出力を抑制。
  • --no-stop-screensaver : mpv自体をスクリーンセーバにする、らしい。
  • --fs : フルスクリーン表示
  • --wid=$XSCREENSAVER_WINDOW : 環境変数 XSCREENSAVER_WINDOW で与えられたウインドウIDに描画する。
  • --no-audio : 無音にする。
  • --loop=inf : ループ再生
  • --shuffle : シャッフル再生
  • --ytdl-format="bestvideo[height<=1080]" : YouTubeの動画を再生する場合の指定
  • $HOME/.config/screensaver.m3u : プレイリストファイルのパスを指定

xscreensaver は、スクリーンセーバを実行する際、環境変数 XSCREENSAVER_WINDOW に、スクリーンセーバ画面のウインドウIDを設定してくれるらしい。

つまり、件の環境変数を読み取って、そのウインドウIDを管理して描画ができる言語であれば、xscreensaver用のスクリーンセーバを作れるはず…。なのだけど、ソースコードに添付されていたドキュメントには、「まあ、そんなことができるのってC/C++ぐらいだから、実際にはC/C++で書くことになるだろうね」みたいな感じの一文が書いてあった気がする。

ただ、mpv は、自身が利用するウインドウを --wid オプションで指定できるので…。故に mpv を使えば好きな動画をスクリーンセーバとして再生できるのだろうなと。

動作確認。 :

xscreensaver の設定画面を出して、「MPV」を選ぶ。

これで、スクリーンセーバが起動する際、プレイリストファイル ~/.config/screensaver.m3u 内に記述した動画が再生された。

余談。 :

手元の環境ではデスクトップとして Xfce を使っているけど、デフォルトでは xfce4-screensaver というスクリーンセーバがインストールされていて、~/.xscreensaver を修正しても修正内容が反映されなかった。xscreensaver をインストールしてそちらを使う状態にしないと、~/.xscreensaver は読まれないらしい。

xfce4-screensaver のアンインストールと、xscreensaver のインストールは以下。
sudo apt remove xfce4-screensaver
sudo apt install xscreensaver

今時の Ubuntu は、xscreensaver を使わずに、gnome-screensaver 等々、違うスクリーンセーバがデフォルトで入っているので、そのあたりはちょっと注意。各スクリーンセーバがどの設定ファイルを読み込むのかが分かれば、xscreensaver に切り替えずに済むのだろうけど…。

#3 [comic] 漫画を読んでた

漫画単行本を消化中。

「川島芳子は男になりたい」田中ほさな著、全3巻を読了。大正末期の上海を舞台に、男になりたいと願うヒロインがスパイとして活躍する冒険活劇漫画、という説明であってるのだろうか。なんとなくだけど、リアル版らんま1/2、みたいなものかもしれないと思ったりもして。さておき、川島芳子という名前はどこかで見たなとググってみて、なるほどそういうことかと納得。それにしても史実のソレは状況が複雑過ぎて何が何だか…。ガンダムやSTAR WARSの設定並みに把握するのが難しい…。ただ、この作品は、自分でも分かる程度の設定量に抑えてあって助かった。

「ディエンビエンフー」復刊版(?)、全6巻、「ディエンビエンフー TRUE END」全3巻、西島大介著を読了。ベトナム戦争を舞台にして繰り広げられる、ハイスピードスプラッタ超人バトルアクションラブストーリー、という分類でいいのだろうか。なんだその分類意味わからんって言われそうだけど、おそらく読めば納得してもらえそうな気もする。とにかく全てが独特…。漫画と言う表現手法の幅の広さ、自由奔放さを痛感させられた。漫画ならこういうスタイルだって全然アリなのだと学生時代のうちに知っておきたかった、とすら思えた。もし知ってたら、「漫画はこういう絵じゃないといけない」「こういうスタイルで仕上げないといけない」等々、視野の狭い勝手な思い込みを振り回して周囲に迷惑(?)をかけずに済んでいた、かもしれない。こういう方向でまとめていく選択肢もあったのだよな…。このスタイルに自力で辿り着いたのであろう作者の非凡さに感服。また、巻末の参考文献の量にも驚かされた。ベトナム戦争を描くならここまで調べないといかんのだな…。

2022/07/28(木) [n年前の日記]

#1 [xscreensaver][python] xscreensaverからPythonスクリプトを起動できないか模索

xscreensaver用のスクリーンセーバを作る際に、C/C++ で書くのはちょっとハードルが高いなと。せめてPythonスクリプトを起動できたら楽になりそうだなと。

一応、そういったこともできるらしいのだけど…。

_A Python screensaver for xscreensaver (Linux) | alvinalexander.com

紹介されているソースは昔のPython用のソースだったようで。Ubuntu Linux 20.04 LTS ではもう利用できなくなった PyGTK を使ってたりする…。PyGObject を使うように書き換えないと…。よく見たら print も print() になってない…。

色々ググってたら、PyGTKCompat を使えばソースの改変を最小限にして過去の資産を活かせる、と知ったのだけど。

_Projects/PyGObject/PyGTKCompat - GNOME Wiki!

試してみたものの、gtk.gdk.Window() でエラーが出てしまう。Gtk2 と Gtk3 で渡す引数が違うのだろうか。

ちなみに、if self.flags() & gtk.REALIZED: は、if self.get_realized(): に変更してみた。それで合ってるのかどうか分からんけど…。

#2 [python] vscode + autopep8がimportの順番を変更してしまう。

Visual Studio Code を使ってPythonのスクリプトソースを書いていたのだけど、autopep8 を呼んでソース整形をしてみたらimport行の順番が変更されてしまって、PyGObject を使ったソースが動かなくなってしまった。import行は変更しないように設定したいのだけど…。

以下の設定で、import行には変更を加えない状態にできるらしい。

_【VS Code】モジュールの自動整形(並び替え)をオフする方法 | peachdotpy
_python - Allow statements before imports with Visual Studio Code and autopep8 - Stack Overflow
_autopep8がsys.path.append()行をImportの下に持ってきてしまう問題 - Qiita

変更されたくない行の最後に「#nopep8」と書いておくのが一番簡単だろうか。

あるいは、Visual Studio Code の設定画面で、autopep8Args に「--ignore E402」を追加しておけばいいようで。

#3 [comic] 漫画を読んでた

漫画単行本を消化中。

「フルメタル・パニック! 0 -ZERO-」原作:賀東招二、キャラクター原案:四季童子、作画:カサハラテツロー、1〜5巻を読了。近未来を舞台にしたSFロボットバトル漫画、だろうか。「RIDEBACK」で独特なロボットやメカ描写を見せてくれたカサハラテツロー先生が漫画化しただけあって、ロボットバトルシーンがイイ感じに思えたし、途中で挿入されるロボットの内部図解や、搭乗する際の各手順を何ページも使って見せていくあたりもリアリティを高めていたように感じた。自分、フルメタル・パニックシリーズはアニメ版の一部を少し目にした程度なのだけど、結構面白い内容のシリーズなのだなと…。いや、この漫画版は結構アレンジされてるのかもしれないけれど…。

2022/07/29(金) [n年前の日記]

#1 [nitijyou] 自宅サーバ止めてました

雷が鳴ったので、15:55-19:10の間、自宅サーバ止めてました。

かなり近くで何度も雷が落ちていた上に、普段は聞かない「バリバリ」という音も…。後から落雷情報を確認したら、一帯が真っ赤になってた。

#2 [python] PyGTKのドキュメントが見つからない

PyGTK を使ってるスクリプトを PyGObject を使うように書き直したいのだけど、PyGTK のAPIドキュメントが見つからなくて作業が進まず。どのリンクを辿っても PyGObject のドキュメントしか出てこない。たしかに今現在は PyGObject を使うことが推奨されてるけど、だからといって昔のドキュメントを PyGObject のソレに差し替えなくてもええやろ…。PyGTK時代の仕様が分からないから、PyGObject の何で置き換えたらいいのか分からん…。

#3 [comic] 漫画を読んでた

漫画単行本を消化中。

「海帝」星野之宣著を読んだ。全9巻、なのだけど、4,5巻だけ何故か箱に入ってなかったのでそこだけ未読。中国が明だった頃に南の諸外国まで大艦隊を率いて航海した主人公の生涯を描く、みたいな内容だろうか。帯に海洋冒険ロマンと書いてあったけど、たしかにそういうジャンルだなと…。ラストのあたり、浪漫溢れる展開としか言いようがない…。イカだの猿だの写楽くんだの、あちこちのエピソードでSFっぽい設定が入ってくるあたりもなかなか。いや、これはSFなんだろうか…? 奇想天外な設定ではあろうけど…。でも、それぞれの設定にそれらしい理屈をつけながら描いてあるからやはりSFのような気もする。

2022/07/30() [n年前の日記]

#1 [python][pygtk] PyGObjectがよく分からない

Python + PyGObject を勉強中だけど、どうもよく分からない…。

背景色すら指定できない。 :

Widget の背景色を変更しようとして、win.modify_bg(Gtk.StateType.NORMAL, Gdk.Color(0, 0, 0)) と書いてみたら、Ubuntu Linux 20.04 LTS 上では特に何も言われなかったけど、Windows10 x64 21H2 + MSYS2 上では DeprecationWarning: Gtk.Widget.modify_bg is deprecated と言われてしまった。どうやら廃止予定らしい…。

代わりに何を使うのかを調べてみたら、Gtk.Widget.override_background_color() を使え、と書いてある。

_Gtk.Widget - Classes - Gtk 3.0

ならばと、win.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(0.3, 0.3, 0.3)) と書いてみたら、これまた DeprecationWarning: Gtk.Widget.override_background_color is deprecated と言われる。これも廃止予定なのか…。

_Gtk.Widget - Classes - Gtk 3.0

独自のCSSスタイルを使え、とか言い出した…。どんどん複雑になってきてないか…。

PyGTKのドキュメントは見つかった。 :

PyGObject ではなく、PyGTK時代のドキュメントを探していたけど、ようやく見つけた。WebArchive に残ってた。

_PyGTK (WebArchive)
_PyGTK Reference Manual (WebArchive)
_gtk.Widget (WebArchive)

でも、公式サイトで PyGObject のドキュメントしか置いてないのは困る…。昔のドキュメントが無かったら、昔のスクリプトを今風に書き直すのも難しくなるやん…。

変換ツールがあるらしい。 :

以下のページで、pygi-convert.sh というシェルスクリプトが紹介されていた。

_Porting from Static Bindings - PyGObject
_Projects/PyGObject/IntrospectionPorting - GNOME Wiki!

./pygi-convert.sh hoge.py と打てば、PyGTK時代のスクリプトを PyGObject向けに変換してくれるらしい。中では Perl を呼び出して、PyGTK時代の書き方を PyGObject向けの書き方に置換している模様。

Ubuntu Linux 20.04 LTS 上で、以下のページのスクリプトに対して試してみたけど…。

_A Python screensaver for xscreensaver (Linux) | alvinalexander.com

残念ながら、すんなり動く状態にはならなかった。self.window なんて無い、と言われてしまう。
$ ./01_xscrsav.py 
...
  File "./01_xscrsav.py", line 50, in realize
    if self.window == None:
AttributeError: 'ScreenSaverWindow' object has no attribute 'window'

一応あちこちの単語を書き換えてくれているようではあるけれど…。

PyGTKを無理矢理インストールしてみた。 :

Ubuntu Linux 20.04 LTS 上で、Ubuntu Linux 18.04 用のパッケージ、python-gtk2 と python-gtk2-dev をDLして無理矢理インストールしてみた。

_bluetooth - "No package 'pygtk-2.0' found" on Ubuntu 20.04 even after installing python-gtk2 from archives - Ask Ubuntu

cd ~/Downloads
wget -c http://archive.ubuntu.com/ubuntu/pool/universe/p/pygtk/python-gtk2_2.24.0-5.1ubuntu2_amd64.deb
wget -c http://archive.ubuntu.com/ubuntu/pool/universe/p/pygtk/python-gtk2-dev_2.24.0-5.1ubuntu2_all.deb

sudo apt-get update
sudo apt-get install ./python-gtk2-dev_2.24.0-5.1ubuntu2_all.deb  ./python-gtk2_2.24.0-5.1ubuntu2_amd64.deb

一応インストールはできた。

以下のスクリプトを動かしてみたけど…。

_A Python screensaver for xscreensaver (Linux) | alvinalexander.com

一応ウインドウは表示されたものの、内部に何も表示されない。本来は Hello World という文字列が表示されるはずなのだけど…。やはり無理矢理インストールしたのがよろしくないのだろうか。

そもそも、Ubuntu Linux 20.04 LTS 上では PyGTK は廃止されて PyGObject を使えということになっているわけで…。python-gtk2* はアンインストールしておいた。

sudo apt remove python-gtk2 python-gtk2-dev

2022/07/31() [n年前の日記]

#1 [ruby] ruby-gtk2がWindows10上で動かない

ruby-gtk2 (Ruby-GNOME2?) がWindows10 x64 21H2上で動かなくて悩んでいるところ。環境は、RubyInstaller版の Ruby 2.3.3 x86、Ruby 2.6.10 x86、Ruby 3.0.4 x86。

_RubyInstaller for Windows
_RubyGems.org

結論を先に書くと、上記の環境では ruby-gtk2 をインストールすることができなかった。ただ、Ruby 1.8.7 x86 + gtk2 なら、かろうじてインストールできた。

ruby-gtk2を動かしたい理由。 :

どうして ruby-gtk2 を動かそうとしているのかというと、ruby-gtk2 を使えば xscreensaver用のスクリーンセーバを書けそうだなと思いついたから。

ここ数日、Python を使って xscreensaver用のスクリーンセーバを書けないものかと試行錯誤していたのだけど、Python + PyGTK で書かれた雛形ソースは存在するものの、Python + PyGObject で書かれたものは見当たらなくて手詰まりになっていて。

_A Python screensaver for xscreensaver (Linux) | alvinalexander.com

今時の Linux は、Python から GTK2 を制御するモジュール、PyGTK を本気で殺しにかかっていて、各ディストリの公式リポジトリにパッケージが用意されてない上に、「PyGTK を使っているPythonスクリプトは GTK3 を制御する PyGObject で書き直せ」という風潮にもなっている。しかし、自分の知識では、前述のスクリプトを PyGObject を使った形に書き直すことができず…。

何かヒントが無いかとググっていたら、ruby-gtk2 を使って xscreensaver用のスクリーンセーバを書いてる事例を見かけたわけで。

_zhum/rubysaver: Xscreensaver module, displaying weather, forecast, clock and now playing song title and author.

「そうか。Python がダメなら Ruby と GTK2 を使えばいいんじゃん」と気づいたわけで。できればメジャーな Python を使って書きたかったけれど、マイナーな Ruby を使ったとしても C/C++ で書くより随分と楽になるはずだよなと。

しかし…。まずは Windows10 上で ruby-gtk2 を勉強すべく環境を整えようとしたところ、そもそも ruby-gtk2 のインストールすらできない状態になっていることに気づいたという…。

ruby-gtk2のインストールができない。 :

再度書くけど、環境は Windows10 x64 21H2 + Ruby 2.3.3、2.6.10、3.0.4 x86。

_gtk2 | RubyGems.org

  • Ruby 2.6.10 p210 i386-minge32 で gem install gtk2 をしたら msys2利用のビルド時にエラーが出る。
  • Ruby 3.0.4 p208 i386-mingw32 でも同様。
  • Ruby 2.3.3 p222 i386-mingw32 でも同様。最新版はビルド時にエラーが出る。

ただ、日記を検索したら、昔は Windowsでも ruby-gtk2 のインストールができて、かつ、動作していたようでもあり…。

_2018/02/27 Ruby-GNOME2を試用
_2019/04/20 green_shoes を動かせないか試したり

2018年、2019年頃なら、Ruby 2.3.3 p222 x86 上で、gtk2 3.2.1、glib2 3.2.1、cairo 1.15.11 x86-mingw32版をインストールできていた模様。x86-mingw32 の記述があるパッケージはバイナリ(.soファイル)も含まれているから、msys2 でビルドしなくて済むし、インストール時にエラーも出なかったのだろう。たぶん。

であれば、バージョンと platform を指定すればインストールできるのではないかと、Ruby 2.3.3 p222 x86上で試してみたのだけど。

gem install pkg-config -v 1.2.9
gem install native-package-installer -v 1.0.6

gem install cairo -v 1.15.11 --platform=x86-mingw32
gem install glib2 -v 3.2.1 --platform=x86-mingw32
gem install atk -v 3.2.1 --platform=x86-mingw32
gem install gobject-introspection -v 3.2.1 --platform=x86-mingw32
gem install gio2 -v 3.2.1 --platform=x86-mingw32
gem install gdk_pixbuf2 -v 3.2.1 --platform=x86-mingw32

gem install cairo-gobject -v 3.2.1 --platform=x86-mingw32
gem install pango -v 3.2.1 --platform=x86-mingw32
gem install gtk2 -v 3.2.1 --platform=x86-mingw32

何故か、最後の3つ、cairo-gobject、pango、gtk2 のインストール時に、cairo 1.17.6 をビルド + インストールしようとしてエラーが出てしまう。既に cairo 1.15.11 をインストールしてあるから、cairo 1.17.6 のビルドはしなくてもよさそうなものだけど…。

> gem list | grep cairo
cairo (1.15.11 x86-mingw32)

> gem install cairo-gobject -v 3.2.1 --platform=x86-mingw32
Temporarily enhancing PATH to include DevKit...
Building native extensions. This could take a while...
ERROR:  Error installing cairo-gobject:
        ERROR: Failed to build gem native extension.

    current directory: C:/Ruby/Ruby23-x86/lib/ruby/gems/2.3.0/gems/cairo-1.17.6/ext/cairo
C:/Ruby/Ruby23-x86/bin/ruby.exe -I C:/Ruby/Ruby23-x86/lib/ruby/site_ruby/2.3.0 -r ./siteconf20220731-10340-gvbbgt.rb extconf.rb
checking for GCC... yes
checking for Homebrew... no
checking for cairo version (>= 1.2.0)... no
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
        --with-opt-dir
        --without-opt-dir
        --with-opt-include
        --without-opt-include=${opt-dir}/include
        --with-opt-lib
        --without-opt-lib=${opt-dir}/lib
        --with-make-prog
        --without-make-prog
        --srcdir=.
        --curdir
        --ruby=C:/Ruby/Ruby23-x86/bin/$(RUBY_BASE_NAME)
        --with-pkg-config
        --without-pkg-config
        --with-override-variables
        --without-override-variables

To see why this extension failed to compile, please check the mkmf.log which can be found here:

  C:/Ruby/Ruby23-x86/lib/ruby/gems/2.3.0/extensions/x86-mingw32/2.3.0/cairo-1.17.6/mkmf.log

extconf failed, exit code 1

Gem files will remain installed in C:/Ruby/Ruby23-x86/lib/ruby/gems/2.3.0/gems/cairo-1.17.6 for inspection.
Results logged to C:/Ruby/Ruby23-x86/lib/ruby/gems/2.3.0/extensions/x86-mingw32/2.3.0/cairo-1.17.6/gem_make.out


であればと、rubygems から、バージョン 3.2.1 の 各 .gem をWebブラウザ経由でダウンロードして、ローカルでインストールしてみたところ、そのやり方ならインストールはできた。

gem install cairo-gobject-3.2.1-x86-mingw32.gem
gem install pango-3.2.1-x86-mingw32.gem
gem install gtk2-3.2.1-x86-mingw32.gem

しかし、動作確認してみたら別の問題が発生。require "cairo" をすると、「LoadError: cannot load such file -- cairo.so」とエラーが出る…。

> irb
irb(main):001:0> require "cairo"
LoadError: cannot load such file -- cairo.so
        from C:/Ruby/Ruby23-x86/lib/ruby/site_ruby/2.3.0/rubygems/core_ext/kernel_require.rb:85:in `require'
        from C:/Ruby/Ruby23-x86/lib/ruby/site_ruby/2.3.0/rubygems/core_ext/kernel_require.rb:85:in `require'
        from C:/Ruby/Ruby23-x86/lib/ruby/gems/2.3.0/gems/cairo-1.15.11-x86-mingw32/lib/cairo.rb:54:in `rescue in <top (required)>'
        from C:/Ruby/Ruby23-x86/lib/ruby/gems/2.3.0/gems/cairo-1.15.11-x86-mingw32/lib/cairo.rb:50:in `<top (required)>'
        from C:/Ruby/Ruby23-x86/lib/ruby/site_ruby/2.3.0/rubygems/core_ext/kernel_require.rb:160:in `require'
        from C:/Ruby/Ruby23-x86/lib/ruby/site_ruby/2.3.0/rubygems/core_ext/kernel_require.rb:160:in `rescue in require'
        from C:/Ruby/Ruby23-x86/lib/ruby/site_ruby/2.3.0/rubygems/core_ext/kernel_require.rb:170:in `require'
        from (irb):1
        from C:/Ruby/Ruby23-x86/bin/irb.cmd:19:in `<main>'

cairo-1.15.11-x86-mingw32/lib/ 以下に、2.3/cairo.so はあるのだけどな…。何にせよ、require "gtk2" をした時も「cairo.so が読み込めない」とエラーが出てしまうので、これでは ruby-gtk2 が使えない。

Ruby本体がおかしくなっているのかと疑って、Ruby 2.3.3 p222 x86 をアンインストール・再インストールして、その後、ローカルに保存した .gem を順々にインストールして再度動作確認してみたけれど、やはり cairo.so が読み込めないと言ってくる。うーん。

ちなみに、Ruby 2.3.3 のインストールは以前のメモを参考にして作業した。

_Windows10上で Ruby 2.3.3 をインストールしようとして少しハマった

余談。Ruby 2.6.10 x86 上で gem install cairo をしたところ、cairo 1.17.6 のビルドに成功してインストールできたように見えた。require "cairo" もできている。ただ、Ruby 2.6.10 は gtk2 のビルドでエラーを出してインストールできないのだけど…。

Debian Linux の ruby-gtk2 パッケージを確認したら 3.4.3 を配布しているように見えた。ひょっとすると 3.4.3 ならインストールできるのではないかと思えてきた。そこで、Ruby 2.6.10 x86 上で、glib2 や gtk2 に対して -v 3.4.3 を指定してインストールを試みてみた。

gem install cairo
gem install glib2 -v 3.4.3
gem install atk -v 3.4.3
gem install gobject-introspection -v 3.4.3
gem install gio2 -v 3.4.3
gem install gdk_pixbuf2 -v 3.4.3
gem install cairo-gobject -v 3.4.3
gem install pango -v 3.4.3
gem install gtk2 -v 3.4.3

しかし、最後の最後、gtk2 3.4.3 のビルドでエラーが大量に出てしまう。やはり Windows10 上で ruby-gtk2 のインストールはできない模様。

何が何でもruby-gtk2を動かす。 :

glib2 や gtk2 のバージョンを色々変えて何度試してもエラーが出てインストールできないので、もはや意地になってきた。何が何でも Windows10上で ruby-gtk2 を動かしてみたい。

もしかすると、Ruby 1.8 + ruby-gtk2 なら動かせるかもしれない…。Ruby 1.8 の時代なら、GTK2のランタイムバイナリも含めた形のセットアップファイルが配布されていたらしいので…。

そんなわけで、Windows10 x64 21H2 + Ruby 1.8.7 mswin32 版上で利用できるか試してみた。Ruby 1.8.7 mswin32 がインストールされている環境で、以下から、ruby-gnome2-0.16.0-1-i386-mswin32.exe を入手する。

_Ruby-GNOME 2 - Browse /ruby-gnome2/ruby-gnome2-0.16.0 at SourceForge.net

ちなみに、この ruby-gnome2-0.16.0-1-i386-mswin32.exe は Ruby 1.8 のみに対応。Ruby 1.9 や 2.x では動かない。Ruby 1.8 用のバイナリしか同梱されてない状態なので…。

  • 実行すると、GUIでインストールウイザード画面が開く。
  • Choose Components 画面では、Ruby-GNOME2 と GTK2 Runtime にチェックを入れて、Register Environment Variables はチェックを外した状態に。
  • インストールフォルダは、Ruby 1.8.7 インストールフォルダを指定。(例 : C:\Ruby187 , C:\Ruby\Ruby187p330mswin32 等々)
  • 先に進んで、Installボタンをクリックすればインストールされる。

一応、ruby-gnome2-0.16.0-1-i386-mswin32.exe を実行した際の、セットアップ画面のスクリーンショットも載せておく。

ss_001.png

ss_002.png

ss_003.png

ss_004.png

ss_005.png


インストール後、以下を実行できれば gtk2 のインストールは成功している。

> ruby -r gtk2 -e "p Gtk::VERSION"
[2, 10, 6]

Rubyのコマンドラインオプションについては以下が参考になる。

_Rubyの起動 - Rubyリファレンスマニュアル

以上、31 日分です。

過去ログ表示

Prev - 2022/07 - Next
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project