PaintFoo ver.0.68a [2025/04/03版]
ほぼ生存確認みたいなもの(笑)
...この1年それなりに手を入れたつもりだったが、1年前に上げたものと比較しても余り変わった印象が無いX-<
商用ソフトがメジャーバージョンアップの都度見た目を変える理由が良く判る気がする(笑)
見ての通り、高dpi環境でのGUIの可変表示に対応していないので実質先細りである、 この辺自前ライブラリでも根幹をなす部分であるので、どう設計したものか未だに自分の中でイメージが固まっていない:-<
後はまあTIFFとPNGの実装を自前の実装に切り替えて、アニメーション出力に絡むバグの修正と APNG対応に伴い減色ルーチンの全フレームを対象にした統計情報への対応、 ついでにwebpに対応したり、本当はavifも対応したかったが、ライブラリのコンパイルに色々インストールが必要で達成できていないX-<
後は念願(?)のガウスぼかしの選択領域によるぼかし量の変動を実装、色々近似はあるのだが
(マスクが低周波であれば畳み込み領域のコヒーレンシが期待出来、高周波の場合はノイズに畳み込まれるのでそれ程は目立たない)
状態により変なアーティファクトを生じる事も無く、多少メモリは食うが定数時間での処理になる;-)
その他色々修正
去年の最大の成果は念願であったラスター to ベクター変換を実装した事だが、上のソフトにはベクター機能は無いので一先ず倉庫行き.
実際に作ってみた所では、ベクトル座標での輪郭追跡後、同一直線状にある点を初期グループとしてまとめ グループ同士を最小二乗法でフィッティングして許容誤差内でグループ同士を連結させるような具合でそれなりに上手く行く模様である.
これが
こーなって
こうなる(最後の画像はSVG)
人間が編集する素材を作る想定なので形の正確さよりも制御点の扱い易さを優先し、ある程度勢いがある部分については許容誤差に加速度を付ける事で大きく間引いていたりする.
生成されたSVGを拡大して見ると、再現されているのはせいぜい元画像サイズの情報だけで、1pixelに満たない情報については 適当に補間されている事が判る. これはAIによる超解像でも同様の印象を受けるのだが 誤魔化されるのはせいぜい2倍程度の解像度までで、やはり情報量が増えたワケでは無いと実感する(面白くはあるのだが).
...折角作ったのだから、何とか面白い使い道が思いつけば良いのだけど:-<
# 余り似ていないのは、これでも一生懸命模写した結果なので許して欲しいorz
## ネットの漫画の転載やtwitterのアイコンで勝手に著作物を使っているのとか見ると、最近はそれ程神経質になる必要は無いのかもしれないが、 古い人間なせいか、公共のネットに上げる時点で個人的複製・翻案の範囲外という感覚が強い. でもその感覚で今のネットを取り締まったらそれこそ壊滅だろうし、明確な基準は無く空気感だけで、実際の所どうなのだろうかと結構真剣に悩んでしまう.
...というワケでも無いのだけど
自分用に作っているペイントソフトをこちらにも上げてみる
PaintFoo ver.0.68a [2024/01/30版]
Windows2000 SP4以降+SSE2対応のCPU向けの32bitアプリで、筆圧を使用する場合はWintab対応のタブレットが必要.
日付に"2007-2024"とあるのでもう17年作っているワケで、正直何の呪いだかという気もするが(苦笑)
今回自分の中で一区切りついたのでというか、そんなカンジで、まぁ一応昨今のペイント系ソフトと比較しても大概の事はできるようにしたつもり;-)
感覚的にはフィルタがあと20個程足りないカンジ、PhotoShopで言う所のアーティスティック系というか既成画像にディストーションを加えて素材として扱う部分がどうにも弱いのが悩ましい:-<
Exifなんかはどーしようかな、とか. ライブラリとしては完成しているのだが、どうにも見せ方が難しい. 何となくではあるけど9割のユーザーにとってはどうでも良い機能で、残り1割には必要でも、その為に残り9割がワリを食う (Exifに対し無頓着な事で発生するリスク) ような機能な気がして、その辺を解消する見せ方というのがどうにも思いつかない.
上のもこれからどう進めて行くかとか明確なビジョンがあってやっているワケでは無いのだけど、プログラムの学習や思いついたアルゴリズムやワークフローの検証なども兼ねているものの、結局は趣味でプラモデル組んでいるのと同じような感覚かもしれない.
# 作っている環境がちょっと古い(ほぼ20年前のマシン)なので画面が4:3に合わせてレイアウトされてたりはご愛嬌:-P
ネットで検索するとHDTV(ITU-R BT.709)に基づく係数として出てくる式で以下の式があるのだが
Y = 0.222015*R + 0.706655*G + 0.071330*B
※実際はこの前後にガンマ補正を含みリニアRGBとする運用が多いがここでは記述として省略している.
ここで使用されている係数の値に見覚えが無かったので、気になって計算してみた所、以下のxy値にて
W (D65) | 0.312713 | 0.329016 |
R | 0.64 | 0.33 |
G | 0.29 | 0.60 |
B | 0.15 | 0.06 |
この時の自前ライブラリでのRGB->XYZ変換マトリクスの計算結果がほぼ一致する値になる (なお参照光源はD65でD50への順応は無し).
0.43057387215726, 0.341550021754784, 0.178325324363055 0.222014652831087, 0.706655217423691, 0.071330129745222 0.0201831502573716, 0.129553456527677, 0.939180041645423 |
この式に基づくとすると上のxy値はHDTVの係数では無くPAL規格 (ITU-R BT.470) の係数値になる筈だが、過去にHDTV規格でもこの値が参照されていたのかについては残念ながら詳細不明.
ただ頻繁に計算する値では無いので、一応の自分用のメモとして.
# 但しHDTVとしても G=[0.30, 0.60] になるだけ (規格に合わせるなら白色点の桁数を合わせる場合もあるが) なので演算としてのズレはそれ程は大きくはないのも事実.
なお上の式はPhotoShopのグレースケール化方式として紹介される事もあるようなのだが、少なくともカラープロファイル対応以降の (ここ20年程の) PhotoShopについては誤りであるので注意.
PhotoShopの方式は作業RGBの色空間をCMMにおけるD50接続空間に変換したグレースケール変換になる、但し上の式はsRGBにおけるCMMでの変換に比較的近い結果を与えるのでPhotoShopの作業空間がsRGBに設定されている場合は (ガンマ補正も近いものを用いれば) 似た結果になるというだけの話(※1).
AdobeRGBやDisplay P3等の画像では上の式を使って変換するとPhotoShopの変換結果とは全く違った画像になるので注意.
※1) PhotoShopの変換ではRGB色空間からグレープロファイルの定義に従った色空間への変換とする為 (「絶対的な色域を維持」以外では) D50順応を前提とした変換が行われ、前後のガンマ補正もそれぞれ変換元・先のものが使われる、これは本質的には印刷用のグレー空間への変換であり、同一RGB空間内でのグレースケール変換とは明確に区別されるべきものとなる.
またグレーのガンマ形状が表示系と異なる場合 (例えばsRGBとGrayGamma22等) では表示における変換も含めて考えねば正確なグレー画像の表示は行えない事にも注意が必要.
表題の通り、調べモノをしていた所、俗に 「NTSC加重平均によるグレースケール変換」 として提示される以下の式
Y = R*0.298912 + G*0.586611 + B*0.114478
について日本の独自ルール (実際この係数を検索すると殆どが日本語のサイトなのは事実) とか根拠が無くWikipediaに紹介されている係数とは違っておかしいとか、経験則で作られた係数だとか、少々気になる内容が目に付いたので導出の根拠を書いておく、みたいな.
と言っても何らご大層な話ではなくてNTSCのxy定義について数値の精度を
W (C光源) | 0.310063 | 0.316158 |
R | 0.67 | 0.33 |
G | 0.21 | 0.71 |
B | 0.14 | 0.08 |
としてRGB->XYZ変換を求めればこの係数が出てくるというだけの話.
xy定義から変換行列を求める方法については省略するが(※1) 例えば自前の色変換ライブラリで計算した変換行列のダンプは以下の通り
0.606881244882208, 0.173504578784877, 0.200335840817129 0.298911657927057, 0.586610718748869, 0.114477623324074 0, 0.0660969823942388, 1.11615682740972 |
真の意味の有効桁数の評価はPC上での連続的な計算では非常に難しいが、このY成分に関する係数を前述のNTSC定義の白色点と同様6桁までで丸めると前述の
Y = R*0.298912 + G*0.586611 + B*0.114478
という式が出てくる、この場合係数の合計が 1.000001 となり1.0をオーバーするので、それが気持ち悪い場合には3成分いずれかの係数を0.000001減じる事になるが、上記計算の通り丸めの対象となる桁はいずれも 6,7,6 と近接しているので、何れかの成分に歪みを押し付けるのも気持ち悪い場合には微小値なので無視してしまう方法もある (実際にこの式を適用する値の精度や型に依る(※2))
なおこれを3桁で丸めると
Y = R*0.299 + G*0.587 + B*0.114
と、お馴染みの係数が出てくる.
上記に対応するYUV(or YCrCb)の変換式が欲しい場合は定義に従って (B-Y), (R-Y) を望む値域でスケールしてやれば良い (但し何らかの規格や仕様に合わせる場合は、その仕様に従う事).
※1) 簡単に書くと3刺激のxy座標からxyzのマトリクスを求め、それが白色点位置で白色点のXYZに合うようスケーリングしてやれば求める変換が得られる.
※2) まぁ非常に乱暴な表現なのだが (PC上での慣例的な) 有効桁数の表現としては正しいワケで、上の係数表現自体に瑕疵があるワケでは無いという所、ただ少々扱い難い係数である事は事実.
勿論、参照する元のxy値の精度によっては係数にズレを生じるワケで、例えば参照白色点の値を1桁減らして [ 0.31006, 0.31616 ] とした場合に前述のライブラリでの計算結果は
0.606863809295618, 0.173507280955537, 0.200334881408764 0.298903070250081, 0.586619854659197, 0.114477075090722 0, 0.0660980117925857, 1.11615148213454 |
となり、そこそこのズレが出てくる事になる. この辺りがともすると係数の定義が一致しないのでおかしいといった話が出てくる要因になっているのかなーとか思ってみたり.
なお最後になるがこの方法はあくまでNTSC係数を 「利用した」 輝度・色差分離変換であってカラープロファイル等を用いた絶対色空間での色厳密性を反映した NTSC->グレースケールの変換と 「等価では無い」 ので混同に注意、CMMにおける変換はガンマや照明の変換 (接続空間がD50であるのでD50照明に順応した空間で白色点に伸びる直線上の点としてグレースケールが計算される) が行われる為、単純に上記を適用したものとは異なる階調バランスの画像になる.
まー割とどうでも良い話ではあるし、殊更にあげつらうのもどうかどうかと思うのだが、この係数の数字が広まったのがミームであるなら、これについて根拠が無いとするデマが広まるのもミームであるので、一応ちゃんとした根拠があり、経験則では無く定義から明確に導出できる値だと書いている情報もあった方が良かろう、みたいなそんなカンジで.
エンジン的にやや特殊(※1)なので実装に難のあった指先ツールを (半ば無理矢理) 実装してみるテスト. 友人の話によると炎なんかの表現に便利らしい. 試しにやってみた所で適当に塗り潰したものを5分程かき回してこんな具合.
うん、クセはあるけどまぁ悪くないかもね. 他の部分で自分のソフト固有の特性を利用している部分があるので必ずしも指先ツールならどんなソフト・実装でもこうなるカンジでも無いのだけど、まあコントロール可能性のボーダーなのは認めるけどヨレるのとかも結構重要ですよ、みたいな;-)
なお指先ツールは通常のブラシ描画では意識しなくて良い部分が幾つか問題になるのだが、浮動小数座標とブラシのアンチエイリアスアルゴリズムとの相性の問題への対処は限定的、1pixとかだと強度100%でもかかりが弱く視認できなくなる問題があるが (現行のブラシ形状では2pix以下が影響が強い) 現行ではこれは対処しない、2pix以下でかつ強度100%の状態を使用するケースは極めて限定的と判断した為、一応これに対する対処も思いついてはいるのだが処理が2倍になる上にブラシ形状ごとでのチューニングが必要になる為、そこまでのメリットは無いと判断:-<
なお指先ツールというと色をぼかす用途が思いつくかもしれないが、これ以外に写真のレタッチで強度を100%とかにすると人物の目や口を大きくしたりするのに使えたりもする. というか単なる色の引きずりだけなら他の機能で (同じ表現にはならないものの) 代用できるのでこちらが無理を押して現行のブラシエンジンに実装した理由でもあったりする(笑)
これ以外には上手く使えば画像の塗りパターンの持つコンピュータによるペインティング特有のブラシアルゴリズムの (現実の画材に対する) 単純さや対称性を誤魔化すのにも使えるかもしれない、ただコントロールの観点からするとあくまで補助的に、ではあるが、といった所;-)
しかし今回ブラシエンジンに手を入れて、方式的にマズかった個所も幾つか修正した結果、現在のアプリとしてのデザイン (not 設計;-) ) では限界が見える部分も出てきているのでどーしたものかという所、部分的には見送りとしても良いかもしれないが、さてX-<
※1) 当初ペイントソフトの知識が無い状況で0から作ったので、今振り返ると結構硬直している方式で、今から作るとしたらこの方式は取らないだろーなというカンジなのだけど、この部分が実は色混合における色の伸び具合のキモに (結果的に) なっているので作り直せば良いというワケでも無いのが何とも何とも. 一般的な実装で同様の表現をやろうとすると今度はこの部分がややこしくなるのでセオリーには反するけど、こういうのもあっても良いかとも思ってみる. もう少しスケールアップさせるとこの方式だと頭打ちが出易いのも事実なのだが;-)
気が付けば先の日記から既に半年が経過している、年齢のせいか最近時間の経過がやけに速い. そろそろ真面目に色々なものを精算しながら動く事を考えた方が良いのかもしれない:-<
先の日記を書いて以来ここ数週間程Unicodeを正確に扱う為の方法論について色々考察中、正規等価性 + full casefolding(可変長変換)を扱う場合にどのように扱えば現状の方法論から大きくコストを逸脱しない形で扱えるかと考えているが、どうにも結論は芳しくない:-<
特に最悪なのはパーサの作成等の処理で、例えば文字をエスケープする場合通常なら'\'に続いて1文字読み、ここで 'a' が来れば '\a' としてベルコードに変換できるのだが、Unicode結合文字を扱う場合には1文字読んで 'a' が来た時点ではそれが 'a' であるか判らないという事になる、例えば 'a' の後に U+0304が来た場合 (かつ後続が結合文字では無い場合) は、これは U+0061 U+0304の文字であり U+0101の文字と等価になる、つまり表記としては '\ā' となっている.
---------------
...とまぁここまで書いて表題の件なのだが、Unicodeでは 基本文字+結合文字*n の組み合わせが正しい文字であるかは規定していない、つまりどのような文字も 基本文字+結合文字*n として修飾された、或いはある言語圏で異なる文字として扱われる1文字になり得る.
また先日書いた通り全ての文字について結合 (合成) 済みの独立した文字 (コードポイント) が定義されている訳でも無い.
よってUnicodeの結合文字仕様まで含めてサポートする場合1文字の単位は最低限 基本文字+結合文字*n として扱う必要がある (これ以外に拡張書記素クラスタというより広範な1文字の定義もあるが、こちらはユーザーの意識する1文字の概念に近いのでここでは省略(※1)) これは決して特殊な状況では無く、例えば日本語環境の場合に異字体セレクタ(IVS)や正規化で情報が劣化しないCJK互換漢字を 基本文字 + Standard Variant (VS1〜3) で表現する状況も含んでいる.
ここで上記のようにUnicodeにおける1文字の概念をより正確に扱おうとするライブラリなりミドルウェアが存在したとする. これに対し組み合わせて使用される、文字とコードポイントを等価としてしかサポートしないUnicodeに対応したライブラリなりミドルウェアも存在するものと考える.
ここで問題になるのは両者の文字の解釈が異なる事で、例えば前者で入力をサニタイズして後者に流す事を考えると、先のミドルウェアで異なる文字として扱われたものが、後のミドルウェアでは単一のコードポイントのみしか参照されない為にエスケープと判断されるケースで、これは十二分にセキュリティホール足り得る事になる (先に書いたベルコードのエスケープの例を参照)
無論エスケープの後ろには単一では表示不能な結合文字列が続く為安易な悪用は難しいと思われるが、これらをどのように扱うかは最終的にはミドルウェアなりバイパス層であるグルースクリプトの仕様であり、文字の認識が異なる事から発生する不確定要因自体は決して除去出来るものでは無い.
最悪なのはこの問題がUnicodeを扱う場合に 基本文字+結合文字*n を扱うか、コードポイントのみを扱うかの条件について排他的にしか成立しない点にある. 例えば入力のサニタイズをコードポイント単位で扱う場合、正常な 基本文字+結合文字*n のシーケンスの持つ情報も破壊されてしまう.
この選択においては常にどちらかを選択せねばならず、また現状においては殆どの場合Unicode仕様を完全にサポートしたソフトの方が圧倒的に少ない事を考えると、現実的にはコードポイントを文字単位として扱う選択は決して捨てる事は出来ない. よってUnicodeコンソーシアムの提唱するようなより広範なUnicodeサポートについては少なくともオプションとしての選択のみで、決してデフォルト動作として採用する訳にはいかない事になる.
無論実際には上記の問題に関係しない分野のソフトも大多数ある、しかし一方で上記が影響する分野も常に存在しており. 結果として常に「分断」が解消不能な形で横たわり、これらにおいてUnicodeの扱いが統一される事は決して無い事を示している.
...とまぁ、これが表題の「とんでもない事」に相当する話になる、小さな問題はセキュリティ上の懸念がある事、大きな問題はそれがUnicodeの根幹の仕様に食い込んでおり、安定した・根本的な解決策が (少なくとも現時点でのUnicode仕様では(※2)) 存在しない事 となる.
無論これは自分の杞憂或いは思い違いである可能性もあるが、一応の考察のメモとしてここに記載しておこうと思う;-)
---------------
※1) 拡張書記素クラスタについては確かにユーザーの認識する1文字の指標としては良いのだが、反面半角カナ + 濁点・半濁点が1文字と見なされる等の挙動を示す為、これが「編集」といったフェーズにおいて適しているかは一考の余地がある:-<
※2) 仕様に手を入れる限り対応策は無いワケでは無い、純粋なデータブロブとしての観点のみで文字コードが定義されている事が問題であり、これがコマンド+データの混在環境での問題を引き起こすワケで、コード体系或いは正規化の変形の一形態としてASCIIの絶対性のみ担保されていれば良いとも言える. ただ既にASCIIの通常文字と結合する文字も存在し、更にはトルコ語の'i'の問題などもあるので修正は難しいと思われる. サブセットを定義するなら余地もあるが、それ以外ではせいぜい譲歩ラインとして合成文字が割り当てられていないASCIIの記号についての (将来的な拡張でも影響されない) 絶対性を担保する位が限界かもしれない (単純なエスケープなら記号のみで良いが複雑な構造を記述する場合にはASCII文字全域が担保されていないと実用的では無いが):-<
...実は4年前にやった話、ネタにすると言っていながら、先日友人との話題に上がるまですっかり忘れていたorz
という事でこんな具合
習作なのでプログラムとしてのインターフェイス等は整備していないが、画面を見ての通りシンタックスハイライトや、画面幅での折り返し、可変ピッチフォントへの対応 (t1.cpp内のWM_CREATEでフォント指定している所を変更すればテスト可能) 等事前に想定していないとややこしくなる部分については一通り仕込みは完了していると思う...でも相変わらずunicode環境(※1)では無いのでShift_JISでの実装(苦笑) 実際はこれをベースに用途により肉付けして行くカンジ.
コードの規模は7000行程度だが半分は汎用のライブラリなんで、実質的には3000行ちょっと、ただ久しぶりに見たら何をやっているかサッパリで構造を思い出すのに作った人間が3日程かかっているので、余りサンプルとしては良くないかもしれないorz
特に目的は無いのだが、最低限のテキストエディタを作る場合のコストの見積もりが欲しかったという所、実際作った事無かったし(笑).当時ここまで組むのに割とガチでやって1ヶ月弱かかった記憶があるので、会社でやるならまぁ最低3ヶ月といった所、ここから機能を増やすと用途にも依るけど6ヶ月程度かなぁみたいな. 実際はゼロからのスタートなので、かかった時間の半分はデータモデルとAPIの用法の試行錯誤がメインではあるのだけど. ちなみに内部的には画面座標でカーソルを保持して必要になってから正規化しているような具合、色々試したけど画面のスクロールとかを考えた場合にこれが一番扱い易かったカンジ.
なお今回upするにあたってundo構造を暫定実装のリスト構造から、固定サイズのバッファにリング的に詰め込んで行く形に変更した. テキストエディタのような形式ならリストやファイルにマップ可能なストリームでの(ほぼ)無制限undoの方が良いのだが、部品として組み込む場合にはこちらの方がメモリの消費を安定して見積もれるので良いのではないかと思う (ring_undo.cpp内、ごく小さなコードだけどこういうちょっと気の利いたコード片は組んでいて別の楽しさがあるカンジ).
まー後は半分はIMMとカレット関係のAPIのサンプルになっているカンジかもしれない(苦笑)
実装については書き始めると色々あるのだが、最近は日記も書いていない事から判るように、何かを言いたいという感覚がほぼ皆無なので、この文章を書いているのも結構面倒だったりとかなり駄目駄目(sigh
まーそんな具合で、テキストエディタも安直な実装なら決して軽くは無いものの、それ程難しくも無いといった程度の話.
# まぁ、現状の実際自分の用途としては自作で使っている付箋ソフト(笑)の内蔵エディタや、自作言語のコマンドライン補助用の組み込みエディタ位しか使い所も無いので、プログラムとしてはいじっていてそこそこ面白いものの、他にやる事と比べるてそれ程モチベーションは出てこない所、まーそんな事言っているようじゃ駄目なのかもしれないけど(苦笑)
---------------
※1) unicodeもサロゲートペア程度までは良いのだけど、真面目に対応すると悪夢のような結合文字の仕様 (仕様上順不同で無制限に結合可能) とか、更にその等価性の問題とかを考えるとShift_JISなんか比較にならない位グダグダだったりするのは何とかならないものか:-< (等価性についてはCJK互換漢字なんかも考慮すると独立したコードポイントに加え、異字体セレクタ(IVS)とstandard variant(VS1〜VS3)の影響で同じ文字について複数の表現が可能だったりと最近は更に悪化しているし)
文字比較のcasefold(※2)なんかも仕様に合わせようとするとUCDを変換したものを組み込む必要があったりと、望む以上にバイナリサイズが肥大化するわ、メンテナンスではUCDのバージョンが問題になったりするわ、挙句文字の長さやインデックス位置も変わるわ (1文字が複数文字になる拡大変換だけならまだマシだが、リガチャとかが絡む場合は1文字が0.5文字等になるケースもあり中間一致状態をどう処理するべきかは非常に悩ましい) 正規等価性では記号の順序がソートされる事でインデックスの前後関係すら変わってくるわで、もう何とも何ともX-< (それでも比較でUCAまでサポートしなきゃならんソフトに比べればまだマシなんだろうけど)
※2) 余談だがUnicodeのcasefoldと正規化部分では古代ギリシャ文字の下付きi(イオタ)のみをサポートする為「だけ」に余分な処理が必要になる個所がある、日本語圏の自分なんかからすれば別にその位省いても良いんじゃないと思ったりするのだが、(自分はどーでも良いが) 時に日本人が旧字体なんかに拘るように古代ギリシャ文字ってのはヨーロッパ言語圏におけるロマンなのかねぇ、なんて思ってみたり. まー文字コードの問題はコンピュータ上での妥当な表現の模索というよりは寧ろ、それぞれの言語圏のエゴの投影の問題なのでここまでグダグダになるのだろうけどsigh.
※3) というかWin8で喧伝されていた異字体セレクタを含めたunicodeの結合文字の仕様やそれと絡めた正規等価性やfull casefoldingの仕様って文字列を単なる塊 (blob) として扱う場合は問題無いものの、そこから踏み込んで部分文字列やその編集・検索 (この過程で連続したバイト列が一塊の文字になるという考え方すら曖昧になる) といった話を混ぜ込むとどう考えても破綻している仕様としか思えないし、またこれをユーザーには透過的に1文字に見せようという考え方は後々の時代まで考えるととんでもない負債にしかならないような、控え目に言っても頭がおかしいとしか思えないのだけどねぇX-<
---------------
(2016/10/21追記)
※以下の内容は自分が実在の各言語圏についての十分な知識を持たない為、コンピュータ上の規格のみから類推した内容で、不確かな類推が含まれます.
先日のUnicodeに関する愚痴に絡んで、友人から「合成済み文字のみとして他は無視じゃ駄目なの?」という指摘が あったので追記.
まず合成文字だけでも全ての文字が扱えるかについてだが、確かに日本語の濁点・半濁点のようなものは対応する合成済みの文字も収録されているものの、ざっくり眺めただけではあるのだが、例えばデーヴァナーガリー文字 (インド,ヒンドゥー語?) についてはリンク先のキーボードレイアウトも考慮するとunicodeの通常文字+結合文字のみでしか扱えないように見え、実際にそれが標準である可能性も高い.
またUnicodeでは可能な限り合成済み文字で表現する(※1)形式 (NFC,NFKC正規化) も定義されているが、これについても揺らぎがあり、例えばヘブライ語の合成文字のU+FB38 (טּ) はU+05D8 U+05BC (טּ)に分解されるが、正規合成の例外文字になっており、NFC,NFKC正規化を通しても通常文字+結合文字のU+05D8 U+05BCのままで、U+FB38には戻らないので、こういった場合にも合成済み文字を前提とするのは極めて危険な可能性があるのではと推測する.
とまぁこんな具合、無論上記は結合文字だけに限定する場合のグダグダであってUnicodeの持つ扱い難さの一端のみ、検索や部分一致については結合文字以外にもリガチャ(合字)の等価性が更なる問題として含まれより話を複雑にするカンジX-<
# 上記グダグダについては直交した解は無い可能性もある、というのもかつてUnicodeコンソーシアムの提唱したUnicode対応の正規表現仕様ではcasefoldingを使用した一致についてfull casefolding (文字の1:1対応が崩れ可変になる) をサポートする事を要求していたが、この内容はその後取り下げ(Retracted)られている模様、正規等価性についても実際には難しいからテキストをNFDかNFKDで分解して、正規化に問題無いパターンを使うと何とかなるよ(意訳)みたいな書き方がされていて何とも何とも:-<
※1) 正確には正規合成を上記のような表現で表すのはやや語弊があり、正規化の本来の目的はUnicode表現の「一意性」を保証する事にあり、またバージョンによる影響で追加の合成済みの文字が追加された場合には過去の互換まで含めた一意性の方が優先されるので、必ずしも可能な限り合成済み文字で表現されるとは限らない事を注記しておく (具体的には正規化が定められたのがUnicode 3.0.0でありこれ以降に追加された合成済み文字は合成除外となる).
なおUnicode正規化について日本語圏に限定するとCJK互換文字 (日本語圏では一部の旧字体) の字体が失われる問題がある. これを避ける場合CJK互換文字をStandard Variantで定義される通常文字+VS1〜3(U+FF00〜U+FF02)に変換する必要がある. 但しこれとは別に同じ字体の文字が異字体セレクタ(IVS)にも定義されている場合があるので、問題は非常にややこしくなっている、無論同じ字体である筈の 基本文字+VS1〜3 と 基本文字+IVSはUnicodeの比較上異なるものとして判定される、IVSの方が一般にカバー範囲は広い反面、CJK互換漢字に対する変換が厳密に定義されているのはStandard VariantであるVS1〜3の方のみとなるX-<
いずれにせよ現時点ではUnicodeの正規化は一意性を保証する為の内部表現についてのみ適用し、マスターデータに適用するべきものでは無いものと思われる. なおこの類の等値性の比較をユーザー(プログラマ)に対して暗黙に行うのが良いかについては議論の余地がある事も注記しておく、明示的な利用はユーザーにUnicodeの正規化の具体的な変換まで含めた正確な知識を要求し、実質的には非常に厳しい反面、暗黙の変換を伴う言語にはMacのSwift等があるが、これと日本語の比較における混乱は検索してみれば幾つもリストアップされる:-<
なお正規化によりUnicodeの等値性は担保されるが、これに対し更に大小比較を行うものとしてUCAというのも定義されている、但し実際には大小関係は辞書順序等、言語圏に極めて密接に結びついたものであり、この定義は膨大になる (一応ICUライブラリとして提供しているプロジェクトは存在する). 実際この辺の膨大な定義ベースの処理が面倒だったのでUCAの基本テーブルに関する実装まではやってみたものの (これは役に立たない) 言語圏によるカスタマイズまで含めた実装については断念したカンジorz
ちなみに永続性を伴わない単純なUnicodeのロケールを加味した大小比較であれば標準ライブラリのwcscoll(), wcsxfrm()で可能、実際どの程度の正確さを持つかはライブラリの実装依存となるが...(なおwcsxfrmの結果は高速化を考慮した閉じたアプリ内の事前計算用で永続化を保証されたものでは無い(筈)なので注意) VC++環境ではこれはAPIのLCMapStringにLCMAP_SORTKEYを指定したものとして実装されており、ソート結果はエクスプローラのファイル名の順序とほぼ同様の結果になる (全てのケースで完全に一致するかは未確認).
...まぁUCAでも同一バージョン+言語圏でのソートキーは互換性を保てるものの、バージョンが変わるとキーの互換性は無い訳だが(苦笑)
相変わらずThompson NFA、幾つかテストを回していた所 worst caseテストの中で、対象文字列、正規表現状態数共にn段階に増加するもので、理論上 O(n^2) で完了すべき個所が O(n^3) かかってしまっていたorz
幸い心当たりはあったので、その部分を修正して完了. 原因は Thompson NFAの場合複数のスレッド (実行単位) が文字消費単位のニモニック(リテラル, 文字セット, マッチ完了)で同期的に実行され、この各実行単位で既にスケジュールされているスレッドと重複する状態を持つスレッドを棄却する事で高速な処理を実現しているが、同期単位のニモニックだけで無く、同期単位に到達するまでのニモニックにも既に実行した経路であれば棄却する処理が必要だったのを行っていなかったという話.
これでめでたく O(n^2) で完了し、どのような正規表現についてもキャッシュ効率等を除いて入力、パターンのそれぞれについて O(n) の安定性を確保出来る事になる. とまぁここまでは良いのだが、全てのニモニックを重複検出の対象とする事から、それ程複雑な照合処理は (速度的に) 行えない事になり、結局フラグのみとして、有限反復 {m,n} については全て展開処理し、各反復が全て異なる状態である事を保証しなければならなくなった.
つまり反復回数についてはかなり厳しい制限をかける必要があるという事で、他のエンジンでは大抵扱える65536回程度ですら厳しい状況で少々頭が痛い. 悩んだ末にGoogleのRe2はどのように解消しているのかと調べてみた所、Re2では単純に反復回数を1000回に制限しているだけだったりorz (※1)
まぁ、各ニモニックでのスレッドの同一照合を複雑な処理にしない事にはこの問題は解決出来そうに無かったので、自前のものでも上限を超えた反復はコンパイルエラーにする形にしてしまった (回数指定のある反復のみであるので、*,+等にはこの制限は当てはまらない) 前述の O(n^3) のコードではこの状態差をそれ程のコストでは無く区別出来るのだが、どのような worst case であってもリニアな処理を保証する方がメリットが大きいと判断した為 :-<
※1) まぁ実際には数1000程度までの回数指定が必要な正規表現は、自分の経験では言語処理系のシンボル長に対する制限位のもので、それにしても一旦無限長で取り込んでからプログラム上で制限長を判定しても良いワケで、ましてやO(n)安定性の保証と反復による消費メモリの増加 (back track stack) が無い状況ではそういうパターンを使用しても何のペナルティも無いのも事実 (使用する正規表現エンジンの性質に依存し、汎用性には欠けるのが難点だが)
---------------
上記の話は各論ではあるが、先日のスレッドの状態然り、Thompson NFAの枝刈りは現在の正規表現の状態とそれ「以降」のマッチ結果は、それ「以前」のマッチング経路に依存しない、という話を大前提としている事になる. これがそのまま後方参照や展開を伴わない有限反復(※2)が使えない理由であり (当然条件分岐やコールバックによる処理の変更等の実装も不可) 暗黙的にこのアルゴリズムに課せられた制限という話になっている.
実際上記O(n^3)のコードでグループまでスレッドの重複検出対象にすると、不用意な()を使用した場合に1回の照合で数秒〜数分程度はかかる状態数の爆発を伴った正規表現も発生させる事が可能になってしまう. このアルゴリズムが実際には暗黙的な省略を伴うものであり、バックトラック型で探索される全てのケースを包有するものでは無いというのは実に興味深い話だと思ってみたり.
まぁ、そーすると、Re2発表当初の 「Thompson NFAという優れた方法があるにも関わらず、back trackingでの実装ばかりで、それってどーなんだ?」 みたいな表現はやや一方向的であり、また事象を完全に公正には表現していないという点ではフェアでも無く、扇情的な表現だなぁ、なんて思ってみたり (無論そういった議論からback trackingが選択されてきたワケでは「無い」事は100も承知ではあるけれど;-)
なお殆どのマッチを早期棄却可能な日常的に使用するような正規表現では、現状の自前のThompson NFAのVM型の実装は以前ここに載せたnode型のback trackingを使用したものと同程度の速度、現在自分が使用しているnode型をチューニングしたものと比較すると1.5倍程度遅く、また現在試験中のVM型のback tracking実装 (一部最適化を除き.net, jdkの正規表現と同程度の速度) と比較すると2倍程度遅いカンジ. 実際Thompson NFA自体の処理自体、初期化や早期棄却に至るまでの処理が多く、重くなる傾向にあり、GoogleのRe2の速度の達成はon the flyのDFA化等を組み合わせた場合の速度で、go言語で使われている単純実装ではやはり遅いという話であるので致し方無い部分もあるのだが...うーん:-<
※2) これは少々分かり難いが、例えば/a?a{2}/という正規表現に"aa"をマッチさせる事を考える. この場合に「回数指定ループの展開を伴わないと」状態はa?とa{2}の2個所になる. まず1文字目のaを読み込んだ時点では選択肢a?とa{2}はそれぞれ1文字aを受け入れるが、次のaに進む段階でいずれの状態を待ち受けるのもa{2}(1文字目か2文字目)であり、優先はa?で1文字読んだ選択肢になるので、後続のa{2}でまず1文字読んだ選択肢は棄却されてしまうが、残った経路のa{2}は更に2文字aを読まないとマッチしない為、不一致という間違った結果になってしまう. これを避けるには回数指定が存在するループは全て違う状態として認識される必要があり、これを効率的に実現するには回数指定部を全て展開表現にする必要が出てくるというカンジ.
先日のThompson NFAの件、取り敢えずスレッドの同一性比較について、反復回数 (無限反復は除いた有限回の反復指定が必要なものに限定) の違うものは別のスレッドとして扱うようにする事で回避する. ここでグループ状態が違うものも異なるスレッドとして扱うべきか、という問題があるが、これを許容すると反復で不用意に()が使われた場合に幾何級数的な状態の増幅が生じてしまう.
ここでグループについて考えると、そもそもの問題は先行するスレッドがマッチせず、それより低い優先度の同一PCのスレッドで棄却されるものがマッチしてしまうケースであり、正規表現のマッチ機能がグループ状態に依存していない限りはグループレジスタを比較して、個別のものとして扱う必要は無い. 言い換えればこの状態で棄却され得る後続がマッチする場合には、先行するスレッドもマッチする事を保証すれば良く、これに矛盾するケースは (現状想定している正規表現の文法では) 有限回数かつ上限のある反復のみという事になる.
これがそのままThompson NFAの高速な実装では後方参照が使えない理由(※1)であり、また正規表現に機能を追加する場合には上記の制約が常に成立するように注意しなければならない (そういった意味ではバックトラックでの実装に比べると限定が出てしまう).
なおオプション(icase指定等)についても同様の議論があるが、一般的な正規表現文法としてインラインでのオプション変更がグループ外にまで波及する構文は無いので、これについても除外して良い事になる (文法を拡張した場合には考慮が必要).
※1) 例えばグループ状態のみが異なる同一の正規表現A,Bについて (A|B)C として、A,Bが共にマッチする場合にCを照合する段階で既にA経路がマッチしている為、同一PCで優先度の低いBは棄却される、ここで後方参照をサポートした場合で、C内に後方参照が存在すると、A経路はマッチしないがB経路はマッチするケースが考えられるが、既にB経路は棄却されているので、全体としてマッチしないという結果が返ってしまう.
グループ状態が異なるスレッドを異なるものにすれば上記は解決するが、この場合に()を使った反復のネストは幾何級数的な状態の爆発を招く為、そもそものThompson NFAでの利点が失われてしまう事になる.
---------------
直感的にはこの考え方で良いとは思うのだが、全てのケースを舐める事が出来ているか、少々不安は残る :-<
なお上の考え方は実質的には有限反復を全て展開する事に等しく、反復のネストはその反復の回数の総乗(Π)分だけの状態が生じる事になり、最悪のケースでは状態数爆発のリスクをゼロには出来ない事にもなる (無論バックトラック型ではこういったケースは更に遅くなるのでThompson NFAだけの欠点では無いのだが).
んー、完全なバカチョンに出来ない所が残念なカンジ、とは言え回数を指定された反復だけで無限ループはこの問題を含まない為、それ程極端な増加になる可能性は低い筈ではあるが...
なお現状の速度は別途バックトラック型で実装&チューニング (一般的な正規表現エンジンと同程度の速度) したものと比べ2倍強遅いカンジ. それでも1秒で数万回の照合は十分可能である事、殆どの場合に速度低下とメモリの膨大な消費が発生しない事で予測範囲内に処理が完了する事を考えると、全体の速度を落としてもこちらを使う方が良いケースというのは多々あるのも事実.
実際 (自分は携わりが無いが) 同一プロセスで多数のインスタンスが動作し、また開発人材も選別が出来ないようなエンプラ系のサーバ上で動作するものについては大幅にリスク回避が可能となる、また準不特定多数の人間が書いたプログラムを実行するブラウザ上のエンジンや、或いはテキストエディタ等エンドユーザーサイドに正規表現を公開するもの、webアプリ等の正規表現機能を公開するもの等、リスクよりも安定性を選択すべき個所は多々存在する為、今後の方針としては色々悩ましい所 :-<
Thompson NFAの実装、上手くいったかと思ったのだが、致命的なミスをしていた模様orz
具体的には異なる反復回数の同一PC(VM上のプログラムカウンタ)を同一stateと見なしていた良いかという所で、同一と見なしてしまっていた結果として、その後の照合でエラーになるものが存在する為に、その後の照合で可能性があり得るものまで棄却してしまっていたというカンジ.
割と設計の根幹に関わる個所であるので一旦白紙に戻して整理する必要もありそうで、頭が痛い:-<
何かこの所アレルギーで蕁麻疹は続くし (多分毛虫) PCは壊れるし (しかも乗り換え用に買っていた新しいPC、古いPCは未だ健在) 長雨で納戸が冠水して収納物が駄目になる (回復費用だけで10万円以上) と何とも踏んだり蹴ったりなカンジX-<
少々間が空いたが、相変わらず正規表現エンジンの実装.
取り敢えずバックトラック + VM型のエンジンのチューニングはJava,.NETの実装と比較して、ほぼ全てのケースでJavaより遅いが.NETより速いか、或いはその逆でJavaより速いが.NETよりは遅いかの、いずれかはコンスタントに出ている所までは持って行けたカンジ.
そもそもがサロゲートペア等を意識しないUnicodeに比べると、マルチバイトの処理がかなりのオーバーヘッドになっている (全体の30%程度) ので、まぁ大体こんな所かという所.
あくまで正規表現の通常処理の高速化であるので、特定の文字列を含まないものは予め弾くといった個別処理は実装していない. これを試験的に実装した所では行単位処理等はともすると10倍位高速になるが、まぁコードが肥大化するので、実装するとしても今回のチューニングとは別の話.
大体new/mallocを2回行うか1回行うかだけで全体の20%程度の速度に影響するようになってきているので、計算量的な問題としてはそろそろ限界か、という所でもある:-<
---------------
一方Thompson NFA + VMの構造の実装も完了、やはり懸念していた通り、アルゴリズム的に必ず1文字づつステップを切って照合が必要 (まとめて照合できない) な事や、事前のデータ構造が複雑になる事からのアロケーション処理の増加等で、反復のネストを伴わない正規表現では大体バックトラック型の2倍程度遅い印象.
実際 <Thompson NFAの上限照合回数> = <正規表現のステート数> x <探索文字列の長さ> は位置重複を伴わないだけで理論上の全数検索と等価であるので、バックトラック型で探索位置の重複が発生する反復のネストを伴う正規表現以外で効率的に処理できるかどうかはあくまで統計上の問題でしか無い.
但しバックトラック型で数分かかるような病的な正規表現についても線形時間で完了する為、テストベンチでは計測限界以下の0msecを叩き出してくれる辺り (一応Performance counterによる0msec以下の線形性も確認済) は、安定性としては悪くない. また反復においてもバックトラック型にあるような、極端な入力に対する消費メモリの爆発的増加 (反復回数に比例したバックトラックスタックを消費する) は無く、バックトラック型に比べややコストは大きいものの、同程度のスケール時間内ではある為、正規表現自体をユーザーに公開するようなソフトではこちらの方が望ましいと思われる.
まぁ一方アルゴリズムの限界から実装が難しい or 不可能な機能もあり、自分の実装でも結局GoogleのRe2と同様 後方参照, 先読み,
後読み, 独立部は実装していない、先読みと後読みはデータ構造の初期コストさえ改善出来れば不可能では無いが、後方参照と独立部は多分不可能と思われる
(ある一点を証明できれば、後方参照はやや考慮の余地があるかもしれないのだが(※1)、正直まだ見えていない:-<)
とまーそんな具合、いい加減飽きているのでモチベーションは地の底なのだが(苦笑)
Thompson NFAについてはもう少しチューニングの余地を検討する予定ではあるが、そもそもの作成動機が高速なライブラリでは無く、他のソフトの開発時にメンテナンスコストが限りなく低く見積もれる手軽なライブラリという事なので、上記の話からのフィードバックはあるものの、先に掲載した単純なノード型を改良して使う事になりそうな予感;-)
※1, 2014/6/9追記)
後方参照もやや複雑な処理をすればいけるかと思ったが、よくよく考えるとどうも無理っぽい. というのもある文字に至るまでに経路AとBがあり(Aが優先)、A,Bのどちらもほぼ等価の正規表現でその前までに一致するが、グルーピング等が異なる場合で、かつ現在のある文字以降がその後方参照のBにのみ一致する場合に、Aを消化した時点でBから来て現在の文字に至るまでの経路は同一PCを持つ為棄却される事になる、よってAが不一致の場合にBは既に刈り取られているので試行出来ない、という具合:-<
正規表現ライブラリについて仮想マシン(VM)型の実装をやってみる、コード生成部はほぼ完成、本来はここで紹介されている Thompson NFA (前回の日記に書いた幅優先型の検索) の検証の為だが、取り敢えず既に仕上がっているバックトラック型の照合について、前回のノード連結型のデータ構造とのパフォーマンス比較として実装してみた.
結果としては思っていた程では無く、およそ10〜30%程度の速度向上. 構文木とVMニモニックの生成が分離されているので、特定条件のみ必要な処理を省く等の最適化でおよそ1.5〜2倍程度向上するケースも見られた.
VM自体はストリングテーブルを参照する為、1回のアドレス間接が残っているが、前のものからそんなには速度の向上が見られなかった事から、最適化を含まない正規表現が現す構造そのままでの照合としては、劇的な速度向上は難しそうな雰囲気.
一部2倍程度の向上は見られ、幾つかのテストケースでは普及している (それ程チューニングされていない) 正規表現ライブラリと同程度か、一部はそれよりも高速に動作しており、最適化をもう少し詰めれば凡そにおいて一般的なライブラリと同程度には持って行けそうだが、コード自体は増加して行くので、本筋では無いライブラリにそこまで手をかけるのが良いかは悩ましい所.
正直な所、設定ファイルや単純な個人レベルのデータファイルの解析なら前回のものでも十分な気もして、どーにもやる気が出ない(笑)
Thompson NFAの方は検索位置の重複によるオーバーヘッドは省けるが、1文字ずつ選択肢を同期化しながら反復するコストがバックトラック型の処理よりも重いので、単純実装では比較的バックトラックの少ないパターンではバックトラック型の方が有利になりそうな雰囲気であるし...まぁ多分もう少し (ダラダラと) 続きそうな予感.
まー表題の通り
という事で今回組んだのは こんな具合 一応ほぼフルセットの実装になっている;-)
少々暇していたのと、いい加減C/C++で設定ファイルの解析で毎回同じようなparse処理を書くのにうんざりしていたが、小さなプログラムの場合に大掛かりなライブラリをリンクするのもアレなので、極力コンパクトな形の正規表現ライブラリを自作できないかという事でやってみた(※1)
利便性としては1000行程度で収まってくれるのが理想なのだが、機能を絞った習作では意味は無い為、実用に堪えるだけの機能を一通り乗せた所、残りは蛇足的に追加できそうな状況で、結局ほぼフルセットの機能を実装する形になる. 結果は正規表現のコアが2100行、スクリプト的な操作 (match,sub,split等) をサポートするラッパーが400行程度と、期待よりもやや大きめになってしまったのは悩ましい所ではある (残念ながら最低限実用に耐える機能だけ乗せても結局コアは1300行程度はあった)
出先でネット等での情報収集が出来ない状況であった為、実装方式はやや我流になってしまった感はあるが(※2) 「俗に」Traditional な NFAと称されるもので、基本的にはNFAで想定される構造をベースに、照合はバックトラックを使用したもの. 後読み構文 (?<=〜), (?<!〜)の制約がやや厳しい事を除けば、一般的な正規表現でサポートされる機能は殆ど網羅したカンジ、文字コードは現状はSJIS / rawバイナリ照合の切り替え式 (インラインオプションで部分適用も可能) でバイナリフリー + マルチスレッド対応となっている.
実行速度はやや遅く、メジャーな正規表現エンジンに比べ 1.5〜3倍程度遅いという所だが、単純さを最優先して最適化処理はほぼ全くと言って良い程行っていない為、まず一歩としては、こんなものかという所 (とは言っても1万行のソースに対する行単位grepでは50msec以下で終わる程度).
...と、まーこんな具合で仕上がったのだが、いざ作ってみるとなまじ中身を理解しているので、バックトラックを使用した正規表現エンジンが潜在的に持つオーバーヘッド(※3)が気になって使う気が起きなくなってしまった(えー
まー何とも何とも...間抜けなオチがついてしまったワケで(苦笑)
隅々まで把握していて扱い易いという事もあって、自前のスクリプト言語の正規表現のバックエンドとして組み込んでみたが、POSIX APIに比べ出来る事が増えたので正規表現周りのインターフェイスは再考が必要になりそう.
また正規表現エンジン本体については、独自拡張になってしまうものの再帰マッチの為の機能や正規表現内の式・変数体系のサポート等は考えてみても面白いかもしれない (例えば後方参照に対する演算、aがn個続いた後bがn個続くものにマッチさせるとか. また変数系はlexの状態遷移みたいに使える、まー何でも正規表現でやろうとするのも間違いだが) と、まーこんな具合でグダグダと今年もやって行く事になると思う、みたいなそんなカンジ (自分自身の器用貧乏的な部分はもう少し改めたい所ではあるが...:-<).
教科書の定番であるにも関わらず余り作っている人を見かけない正規表現ですが、ある程度のパフォーマンスで良ければ、規模的にも意外と誰でも作れるものなんで、一つ作ってみるのもそんなに悪くないですよみたいなそんな話;-)
余り興味のある分野でも無いのでこの程度で収めるつもりだったのだが、結局出先から帰ってきてから補講と称して色々調べているのは何とも何とも、他にやるべき事があるので続くか続かないかは現状未定.
---------------
※1) 最新のC++仕様では標準ライブラリとして正規表現がサポートされており、VS2010以降では使えるらしいのだが、VC8以降のランタイムはややオーバーヘッドが大きくなっていたり、Win2kで動くバイナリを吐けなくなっていたりで個人的には使いようが無いのも何とも何とも、実際はunicodeでないと日本語が扱えなかったりと決して用途が無いワケでもない、それ程積極的に意味があるとも言い難いが(苦笑)
また外部ライブラリを使った場合、外部依存関係が1つ増えるごとにソフトのリリース, メンテナンスにおけるコントロールの自由度が 1つ失われる事になるので (これは自前であっても例えば大企業でよくある他の自社製品を使う場合は直接修正可能な状況に無ければ同じ事、制約やバグが出た場合にそれが制御下にあるかどうかという話で、ライブラリやミドルウェアに合わせて仕様を決定するのは(本来は)本末転倒甚だしい) それが、開発における車輪の際発明の揶揄も、NIH症候群も主張の両極であり、何れの主張も個人的には疑問視しているが (本来の意味では研究畑の用語であるので、それを理解しないまま使う事を擁護しているものでは無い事も含めて、ね:-P) 何れにせよ選択肢の可能性がある事は常に良い事ではある;-)
※2) 一応NFA構造から来るデータの連結にはなっているが、文字単位の遷移では無く、照合機の連結としてグラフ構造を構築する形になっている (本来のNFAとは節点と枝が逆になっている) 文字単位の遷移は本来の意味での正規表現の機能から、一般的な「正規表現」と呼ばれる機能まで拡張した時に扱い難さが現れる気がした事と、構文解析とグラフ構造作成を 1passで行う (コードサイズ削減の為、最適化を考える場合は構文木とグラフ構造の生成は分けた方が良い) のに都合が良かった為だが、機能的な柔軟さは得易いものの、最適化の可能性を考えた場合に今回の実装方式が妥当であったかは不明.
後で調べた所では世間的にはVM(仮想マシン)での実装を取るケースが多いらしい、実際グラフを辿る方式ではメモリの局所性が失われるので、キャッシュの影響を期待できない事は事実ではある:-<
※3) (今回の実装に限らず) 俗にTraditional NFA (非決定性有限オートマトン) と呼ばれるバックトラック型の正規表現実装 (汎用のものは殆どこのタイプ) (※4)の問題として、単なる空白の読み飛ばしですら、1文字ごとにバックトラックステートをスタックに積む必要が出てくる (単なる読み飛ばしが成立しないのは/a+ab/のような正規表現をマッチさせる必要がある為) 例えば10000文字の空白があればマッチの成否に関わらず (ループ等の他のステートも含めると) 最低10000要素以上のメモリ書き込み、或いはステート保持の再帰処理(少なくともJavaの正規表現実装の選択処理にはこの手の問題がある) が発生する事になり、マッチ失敗のオーバーヘッドはよく論じられる話だが、成功時であっても専用にパーサを書くのに比べ遥かに大きなオーバーヘッドが発生する.
幾つかのパターンでパーサを書いて比較した所では、大体10倍程度は違う印象で、不一致条件についても早期に棄却出来る可能性が高い事を考えると、可能な時は何時でもパーサを書く方が良いとも言える. 実際には単なる数値のパース一つ取っても可能なパターンを列挙するとある程度の分量になる為、ちょっとした処理に使うのには手間がかかり過ぎるし、また文字単位での処理にオーバーヘッドがあり過ぎるインタプリタ等では、そもそもがどーにもならない話でもあるのだが...:-<
また文字列のスキャン処理自体は不一致の探索を含めるとO(n^2)であるので顕在化し難いものの、反復はその受領回数nについて*O(n)の選択肢を生成し、不一致条件で反復がネストすると暗黙的にO(n^3),O(n^4)...とコストが跳ね上がるリスクを常に伴う (これについては実装方式を変える事で回避可能な方法があるが)
そんなこんなで、どーにも使う前にお腹いっぱいになってしまった気分:-<
※4) この話はDFA (決定性有限オートマトン) での正規表現実装には当てはまらない、一方でDFA実装はグループ抽出や最小一致等、有用な機能の一部が実装不能 (DFAの場合正規表現の並びのままの照合では無く、正規表現の受理する文字入力で遷移可能な集合をまとめてしまう形で曖昧さを解消する反面、どの経路を辿ったかの情報は失われてしまう為) であったりと、ある程度の工夫を考えないと、自由度のあるものを実装するのは難しそうではある. (最適化過程として考えると部分的にDFAに変換する実装もあるが、全体としてDFAの考え方で殆どの機能を実装出来る方法があるのかは不明)
また、ここでのNFA,DFAという言葉は正規表現の実装の区分として、それ程専門的では無い界隈でよく使われる語彙で使用している為、本来の計算機学上で言うNFA,DFAの意味とはややズレがある. 本来は決定性という言葉は構文の遷移構造としての表現形式のみを指し、実際はNFAの評価についてもバックトラックが必須というワケでは無く、取り得る状態集合を全て管理しながら入力辿る (実質的にはDFA変換の状態集約の部分処理を評価時に行うのと等価) 方法もあり、バックトラックを用いた探索は(本来の)NFA構造を解釈する際の一解法に過ぎない. かなりの数の正規表現実装が上記の (not 専門的な意味の) NFA+バックトラックであるので一般化出来る話ではあるが、決して普遍的な話題では無い.
なおNFAの状態を保持しながら一致させる方法は(コンパイラ等の話ではこちらが言及されるものの)、グループ抽出等の正規表現まで拡張する方法については余り知られていなかったが、近年GoogleのRe2の実装に伴って見直されつつ、また比較的広く知られるようになってきた模様. この方式はグラフ探索における幅優先探索と同義であり、ループのネストによるスキャン位置の重複を棄却出来、良く知られる「病んだ」正規表現パターンによる大幅な速度低下の影響を受けない(筈、各分岐は入力文字位置で同期的に処理され、その瞬間の状態数は必ず正規表現全体の状態数以下になるので、照合回数はこれらの積となるが、実装したワケでは無いので、見落としがあるかどうかは不明).
またDFAについては制約が大きいものの、正規表現パターンの複雑さは空間コストにのみ転嫁され、照合コストに影響しない. 更に正規表現パターンについては複数の表現を結合可能でありlex等の複数規則でのtoken切り出し処理では1度のスキャンで複数のルールを判別可能になる、そういう意味では一般的な正規表現ライブラリのインターフェイスが常にこれらの凝った構文解析に対し代用可能という話でも無い.
---------------
余談だが、開発過程で出たバグを既存の実装と比較して確認していたらJavaの正規表現のバグを1つ見つけたっぽい、内容は
(?=(foo))...bar|\1[0-9]+
が
"foo99"
にマッチしてしまうというもので、先読みがmatchに成功した後、後続がmatchに失敗した場合にその先読み部のステートが成功状態のまま捨てられていないという話(JDK
1.6.0_20にて確認)
当然こんな正規表現を書くのは間違いだが、上記は独立部 (?>〜) や強欲な数量子 *+ 等でも発生しており、これらは実装として照合部から独立した正規表現を再帰的に適用する形になり得る箇所で、本質的な問題としては、再帰処理が成功した後で後続が失敗した場合に、再帰部のループ状態等の内部ステートが回復されないまま再度matchが試行される形になってしまうやも、というような一抹の不安を感じてみたり.
実装の内容次第なので問題にならない可能性もあるし、Oracle所有になってからは正直気持ち悪い(笑)事もあって最新のものは入れていないので、既にbugfixされているやもしれないが、ま、一応メモがてらという所 (という程Javaは使わないけど、Web屋じゃ無いので自分的には用途無いし(笑)
なおこれ以外にJavaの正規表現エンジンでは | で連結される要素の反復が続くとスタックオーバーフローを起こすというものもある、基本的なアルゴリズムとしては反復と選択は同様に照合の分岐を生じるのだが、単純な反復では発生しない事から、内部最適化の分かり難い制約として存在している模様. 本来選択部はアルゴリズム的には再帰を用いなくても記述可能なので、単純に実装上の制約という事になる.
フィルタは2個目実装完了、あと1つ気になるフィルタがあるが、その前に今回のフィルタで実装したアルゴリズムの副産物として考え得るものを検討.
こんな具合に使えるもので、選択範囲により任意の形状に対応可能だが、まともに表現の可能性を考えると3Dの質感設定とほぼ同様まで必要になりそうで少々頭が痛い、まだ試していないが内部情報の精度的には環境マップでの反射・屈折の表現も可能だと思われる. 但し透明体の場合にアルファを変動してもカラーガラスのような表現はレイヤーの合成モード的に単一レイヤーでは難しいと思われるので、所詮は限界はあるだろう.
またアルゴリズム的に弱点もあるので、別のパターンに対応する為のアルゴリズムも検討中だが、こちらはピクセル限界でのエラー含みの演算であるので、平均化等を用いても十分と呼べる品質に到達させられるか、また速度的にも難があったりと色々悩ましく、最終的にどのような形で統合させるかのイメージもまだ固まっていない.
まー、そんな具合で脇道にそれた形で色々検討中、以前KOJIさんと話した透明体や金属を表現する話は、所詮元データは2Dという事では多分この辺りが落とし所ではないかと思うがどんなものだろう?
まーしかし上の画像は何とも二流っぽいいかにもなデコレーションスタイルやね、センス無いわ(苦笑)
# 今月末に幾つか用事があり、その期間は十分にプログラムを書けるとは言い難い状況になるので、それまでにどの程度出来るかが勝負ではある、関連した論文も1本読まなきゃならないっぽいし、やはり突っ込んだ話というのは余り日本語のリソースが無いのが何とも厳しいやね:-<
---------------
少し前の話だが、自前の多倍長演算ライブラリで数学関数系の実装をやっていたが、余りに収束計算が遅かったので以前から気になっていたKaratsuba法を実装してみた、アルゴリズムだけ見た所では構造的にループに変換するのが困難そうな可変サイズの中間バッファを使用する形での再帰的な処理が必要なので、本当にこれで速くなるのかと懐疑的ではあったが、実装してみた所それなりに効果がある模様.
なお実装は再帰的にKaratusbaを適用し乗数の1方の有効桁が効率的に処理できる程度になった場合 or 全体の桁数の直接計算のコストが再帰のオーバーヘッドよりも軽くなる時点で直接計算に切り替えるという比較的シンプルなもので、計算コストは意識したが、再帰については素直に再帰呼び出しとして実装.
但し実装の問題もあるかもしれないが、RSAに使用するような1024〜4096bit (10進ではせいぜい300〜1200桁) 程度ではせいぜい1.5倍程度速くなる程度で、明確な恩恵が得られるのは数千〜数万桁程度以上という印象ではあった.
まーだからどーしたという程度の話ではあるが、会社時代 (もう10年も前になる) にRSAを検証した際Karatsubaは調べはしたが、実装にまでは至らなかったので、その10年前の補講といった所(笑)
実際には例え数万桁あっても除算を使えば結果は容易に無理数に落ち、計算は常に誤差を増幅し得るし、有限精度で整数無限の小数固定という考え方 (JavaのBigDecimalとか) は一般的な計算では悪くないが、技術計算系であれば (よくある計算の変形で) 逆数を取った時点で容易に破綻する. 四則演算系に有理数を用いる事を考えると通常の四則演算では誤差は含まないが数学関数や定数を使うと状況は大して変わらない (かつ潜在的な誤差は通常の多倍長よりも出方が奇妙な振る舞いになり得る、また正規化でEuclidの互除法を多用する事から基本要素は10進基数では無く、2進基数でなければ厳しいと思われ、2進化誤差も潜在的に包有する事になる) そんな具合でまだ自分の中で落とし所が定まらなかったりと、多倍長演算系は後2回位は作り直す必要がありそうな予感(苦笑)
# まーこの時の成果は一通りの数学関数の実装が多倍長域である程度実用的な速度で収束するような変形を得られた (但し1万桁とかになると数秒orderではある) 事(※1)であるので (浮動小数域では問題無い速度で収束するものでも、そのまま多倍長に適用すると収束が極めて遅くなるものがある) 上の話はその一環程度、あくまで気になっていた内容の消化という話でしか無いのだが、一応のメモがてら;-)
# なおこの時の関数計算は一部newton法だが、殆どは(広い意味での)Tayer展開と収束域への式変形を使用、場合によっては連分数展開の方が収束が早いものもあるが、精度に応じた漸化式を作成するのが少々面倒であるので今回は保留、実際連分数展開での精度検証では級数展開版も必要になるので取り敢えずはこれで良かろうという所、級数展開版は精度を変えた場合にも修正はほぼ不要であるので、シンプルなライブラリとしては扱い易い事もそれなりに利点ではあるというカンジ.
※1) これも元々は行列に対する超越関数の実装の前段としてスカラでの処理をもう一度見直しておこうという話で、行列に対する超越関数 (例: exp(A), Aは任意の正方行列) は行列の整数べき計算が定義される事から、極限的に指数・対数関数が考えられ、さらにその変形から任意数値・行列でのべき乗が定義され、また指数関数に対するオイラーの変形により三角関数も考える事が可能となる.
実際、行列の超越関数は級数展開と行列の固有値・固有ベクトル行列による対角化A==V.λ.inv(V)を考えるとべき演算とスカラ係数での線形結合を組み合わせる限り (つまり級数展開の演算が可能) 行列の固有値に対する演算と捉える事が可能で、この特性は実際に行列を用いた微分方程式の解として用いられる事になる. 但し上記は行列Aが対角化可能な場合にのみ固有値分解で直接計算が可能であるが、実際には対角化が不可能な場合にも定義される為、十分に収束させる事が可能であれば (実際はここが問題で、スカラの場合には単一の値のみ収束域に移せば良いが、行列の場合全ての固有値についてそれが成立しなければならない:-<) 変形・級数展開などから求める方が (誤差はともかくとして) 汎用性があるという話.
まー何かそんな具合に書くとややこしそうだが、この性質を以って行列による変換はn次元拡張され、個別の軸を持った掛け算として比較的直感的に捉える事が可能な気がしませんか?みたいなそんなカンジで;-)
クラウドが幻滅期に入ったという分析を調査会社が出したというニュースを見ていて、ふと思い立って検索してみたら...何か酷い事になっている.
それ以外にも「クラウドって何」で検索してみると山程出てくるが、何時の間にかクラウドがインターネットのような漠然としたものを介して、という意味になっているらしい. バズワードとして使われだした頃から、大体はそんな所に落ち着くだろうと思っていたし、またそうなってるなーとは感じていたのだけど、改めてちゃんと意識して見ると、何と言うか、まー呆れるを通り越して最早言葉も出ないカンジ.
誤解を恐れずに言うと、少なくともクラウドが話題になり始めた時の定義では分散コンピューティングと絡めて語られていたと思うのだが. 大まかな表現だが、総体として1つのコンピュータ (表現上の為やや簡略化した表現) として振舞うコンピュータの集合体であり、仮想化技術等を応用する事で多少の切り替えだけで1つのプログラムに対するリソース配分を極めて柔軟に (従来の仮想サーバ等と異なり仮想マシンの再起動等の明示的な切り替えも少なくとも表面上は行わないで) 更には実行時のプロファイル等を元に状況によってはリアルタイムでさえも柔軟にプログラムに対するリソース配分を変えられ、システムを増設する限り無限 (表現上のもので実際は極めて大きいと同義) の実行・記憶リソースを使用できるような代物を指していた筈.
但し実際には既存のシステム上のプログラムの実行形式・実行形態ではそういった自動的な分散化をサポート出来ない為、専用のシステム上の制約の下で1個のコンピュータとして振舞わせる為のレイヤを定義する必要があり、この抽象化階層がプログラム実行プラットフォームのどの階層から抽象化するかに応じてPaaS, SaaS, IaaSなどの分類が生じると言う話だったと記憶しているのだが、つまりcloudであると同時にcrowdであるシステムでなければクラウドとは普通言わないんじゃなかろーか?
色々クラウドと銘打ったサービスを検索すると最近はネットを介して後はせめて仮想化 (仮想化サーバのサービス自体はクラウドの前から使われているし) 技術程度でもクラウドと名乗って良いらしい、いや寧ろネットを介すだけでもクラウドであるらしい(∵ネット==クラウドである為). 後はせいぜいより細かい単位の課金体系で柔軟性を謳う (これも上の話の柔軟性とは別の話) とかって、そんなの昔からあった話だし. (まーこれも行き過ぎるとダム端とかX端末とか、或いはメインフレームにジョブを投げるのを指してクラウドは昔からあったなんてワケの分からない事も言えてしまうのかもしれないが(笑)).
何かwikipediaの記述すらカオスだし、コンピュータ業界ってやっぱり救い難いのかねぇ(※1)
...或いはおかしいのは自分の方だという可能性の基本二択で(苦笑)
※1) ま、コンピュータ業界に限らないけど、かなり昔だけどファジィという用語を多用した家電業界とか、アレもn段階テーブル引きをファジィ理論で言う所のファジィ制御とは言わねーみたいなシロモノだったし. 固有名詞で化学で言うのとは全く別物のマイナスイオンとか、イオン化した気体って、そのまま化学用語と照らし合わせるとまさかプラズマ (電離気体、電荷は逆だけど) かよ、みたいな (安定して分離した状態のプラズマが放出される空気清浄機とかが有ればそれはそれで面白いが(笑) なお一応メーカーは帯電気体を指して言っているらしい、効能については良くある似非科学の域を出ていない、人間やっぱり誠実に生きたいものだよね、なんて言ってみる:-P).
先日書いたPNGのライブラリ化はもう暫く保留、メモリの無駄が気になっていたが、圧縮ロジックが別なせいか、圧縮の方はいけるが展開部を完全にストリーム化するのが少々難しそうだという結論に. エンデコの場合望ましくは順次読み込みでラスタ単位のコールバックのような形であるのが望ましいのだが、ラスタなどの単位で圧縮データが分離されておらず、フィルタ処理などもあって展開部では一時的にある程度の展開画像を持つ必要があるのがかなり嫌なカンジ、特にインターレスまで考慮した際に綺麗な組み立てが思いつかず、細かくアロケーションが入るとシングルスレッドでは (呼び出し完了時には全てクリアされるので) 余り問題無くともマルチスレッド時のヒープのフラグメントが少々心配でもあったりと余り良い組み立てが思いつかない :-<
無理矢理やってもかなりルーチンを細切れにしなければならないカンジで、以前冗談で作ったC++でのyieldを使おうかとも本気で考えてしまった:-< なおコルーチンが欲しいと思ったのはこれが2回目、1回目はMIDIの演奏ドライバ (not デバイスドライバ) を作った際のチャンネル制御だったが、まぁ (ゲーム系以外では) 殆どのケースでは必要無い (通常の方法で遷移管理してもそんなに手間では無い) 代物だが、やっぱりあると便利な局面はわずかながらにあるものだなぁなんて思ってみたり.
この辺もう少し自前の言語に組み込んで色々試して見たい所だが、コルーチンを実装する場合関数内をフラットな擬似コードに変換できる構成にしていないと実装は難しく、現状の構文階層をそのまま辿っている実装では対応できない. まぁ、これはこれで色々な文法のテスト実装が楽だというメリットがあるのだが(笑) この辺色々もう少し自分の中で固まったら、あと1回程度は作り直しをする事になりそうな予感.
ここの所グラフィック系のプログラムが興に乗っているので、フィルタの残件を検証中、素材の為の素材作りでは無く、普通に使う程度では殆ど必要と思われるフィルタは実装してあるのだが、3〜4点程必要だと思うものの、実装方法が思いつかずに保留にしていたモノが有る.
まず一点目は取り敢えず上手い具合に計算式が出せたのだが、計算量がパラメータ値の2乗に露骨に比例するのが嫌なカンジ、特に省略出来ないフィルタで用いる基本特性の幾何計算で重いので、誤魔化しが効き難い個所であり悩ましい所. 全部やるかどうかは決めてないが、これと後一点位は実装に漕ぎ付けたい所ではある.
---------------
アプリ屋の友人とMicrosoftのXP頃からの迷走についてはよく話すが、少々ネタにしたので.
まぁ、自分が感じている危惧は若干違う話で、結局.NET辺りからMSがあたかも次世代APIとして喧伝しているテクノロジの殆どがオモチャなのでは無いかという所. これらの機能でSAIが作れるか、PhotoShopやAfterEffectが作れるか、LightWaveやMax,Maya etc. 或いは工業用のCAD、譲歩してもOfficeクラスのアプリ、また商用DBのコアエンジン等が作れるかという所. ダイアログに毛の生えた程度のソフトなんぞ、極論すればある程度いい加減に机上の空論だけでフレームワークをデザインしてもそれなりにつじつま合わせは出来るワケで、パフォーマンスなどを突き詰めた場合にも同様に結果が出せるもので無ければ「API」としての存在意義は無い (高い要求に見えるかもしれないが、ユーザーにとっては上記のようなソフトはごく「あたりまえ」の存在であるので、それに不十分であるというのは十二分に「APIとしては」論外と言える)
無論それなりには作れるのだが、自分が今まで触れてきたソフトでは、世に出ている.NET製のある程度複雑なアプリの大半が (見栄えはともかく) パフォーマンス等を含めた総合的には、Nativeで実装されたものに大きく劣ってしまう(※1).
それを見極める為にも色々新しい技術も調べるのだが、何と言うか、脳天気な若いプログラマの想定の甘さのような詰めの甘さと、そのその問題に対する回避策の選択肢の欠如、或いは極めて限定された選択肢しか無い事にそもそもの想定の甘さを感じる所が多々あり、正直真っ当なアプリ (最低線MS Office程度の複雑さのもの) も組んだ事の無い頭でっかちのフレームワーク屋の自己満足でデザインされているのではというような嫌な「感触」(※2)がある:-<
願わくば自分が年をとって頭の固い嫌な年寄りになっただけで、杞憂である事を祈りたい所. でもここ暫く新規のテクノロジについて友人と話してきた内容は結構当たっているのが何とも複雑な気分 (3DTV関係だけはちょっと外れたけど) 後で自分の逃げ道を塞ぐ為、或いは後で救われんが為(笑)にも、多分WinRTも駄目だろう(※3) とここに書いておく事にしよう(笑...って良いのやら複雑な気分:-<
※1) ソフトのユーザーに見える基準は常に競合製品との相対評価になる、つまり1つでもNativeで実装されたソフトがある限り、比較されるソフトはそれに劣っていてはならない事になり、標準APIへの要求は比較される他社製品と同水準のものを開発可能でなければならない事になる.
※2) 実際自分自身まだ人に何か言えるだけの開発経験があるとは言い難く、せいぜいようやく人並み (普通の人が見てソフトウェアだと認識できる) 程度のものが作れるようになったに過ぎない、その自分程度の経験で「すら」そう感じてしまう事が寧ろ大きく疑問を感じる:-<
※3) 理由はプラットフォームのデザインから開発自体を取り巻く周辺環境・条件や企業にとっての戦略の選択肢等々、文章にすると余りに多いので省略するけど (よく雑談で話している内容と同じ) Active Desktop Strikebacks!! になるんじゃなかろーか、みたいな.
# 上はあくまで標準「API」としてはの話、もう一つ悪い予感として、一連のテクノロジをMSは次世代のAPIのように喧伝しているが、実は大げさな単なるセールス文句で、その実はせいぜい非常に良く出来た (あの時代の) VisualBasic程度の代物なのではないかという予感も...ouch.
まー知らなかったのが悪いと言えば悪いのだが、表題の通り. .NETのWindows FormでToolTipを無引数コンストラクタで作ると明示的にDisposeしてやらんとリークするのだそーな、IDEのデザイナが吐いたコードではFormのメンバにContainerを暗黙的に作成して、それを介してDisposeのチェーンを実現している. まーIDE使っていれば気付いた話なので間抜けと言えば間抜け.
確かにToolTipなんかは自前で使っているC++のライブラリでも何処に紐付けするのか、少々悩ましい機能ではあるのだが、DBやネットワークコネクションなんかの比較的分かり易い切り分けのDisposeと違って、ある程度フレームワークとしてマネージドでラップされているように見えるGUIパーツで発生するというのは何とも何とも.
それ以外にはContextMenuはリークしないけどContextMenuStripは同じように呼ばないとリークするとか (つまり親子関係を後でいじれるようにしているせいか、popup系の実装が怪しげ) ADO.NET系のGUIパーツではデザイナを介してもデザイナの吐くコードがDisposeのチェーンを意識してなくて手動でDisposeを書いてやらないとリークするとか、細かく追ってないので先に書いたような話が影響しているかもしれないが (実際単純なテストプログラムでは再現しなかったので他の参照の結果である可能性もある) 子Formも表示した後でDisposeしないとリークの原因になっていた なんて話もあるらしい.
Disposeを呼ばなきゃリソースの解放が遅れて、場合によって問題になるというのなら分かるけど. 本来参照を外れている筈でしかも概念的にはプーリング等とも関係無い個所でGCかけても残るってのはどーなんだというカンジ. 挙動としては逆の話にはなるがActionScript(Flex)でも非同期イベントを変数で保持してないと消えるなんて話もあって大いに萎えたワケだけど (フレームワーク側でキューに参照持っておくようにでもしておけよと思ったり) そもそもがGC系言語のメリットって何よ、みたいなこういう本末転倒なのは正直全体の設計についても疑問(※1)を感じる.
まー一応GCを絡めた処理系の実装なんかもやってはいるので、システムがGCを前提に作られていない所でGCの挙動と合わせる為の界面の処理に色々難しい問題があるのは理解しているつもりだけど、出来るだけ隠蔽されて然るべきで、こういうあからさまな所に残っているのはどーなんだろうというカンジ.
自分に関して言えば、メインで使っているのはC++ + APIの組み合わせで安定 (自作言語は前段での理論の検証・解析が主用途であるので余り「実際の」開発に直結する個所では使ってない、インタプリタだし;-) しているので、特に拘る必要も無いと言えば無いが、前提知識の問題等で多人数開発では難があるのも事実、まーその辺会社時代のトラウマもあって、ある程度の人数・人材でもまともなものが作れる環境の検討として.NETも色々いじっていたワケだが...どーあがいても下の底上げだけで、上の品質でも頭打ちが早いのなら、そんな環境使う価値は無いのだよなぁ、なんて凹む事しきりorz
※1) 個別に対応すれば良いという話は、ライブラリの抽象階層を重ねていった時に容易に破綻する、それ自体には問題は無いが使っているパーツに問題がある場合、更にそれを包有するパーツでの挙動、と. 風が吹けば桶屋が儲かる的な(エラーの)カオス的発散を潜在的に内包する事になる.
---------------
真面目にリソースに埋め込んだbmpを自前のライブラリと併用する形で使う事を考えてみたので、取り敢えず据え置きにしていたRLEの展開及び(素材作成用として)圧縮処理を実装する. RLE圧縮自体は殆どの素材では使い物になるレベルでは無いし、ついでに言うとbmpのRLEのコードはもう少しマシな表記は無かったのかと思うような冗長さはあるものの、ヒットテストに使うようなアルファマスクではそれなりに(10〜15%程度まで)縮んでくれる.
安易に使うならいちいち作成しないで、グローバルな識別子として扱ってしまいたい所だが、起動時にmain/WinMainに制御が移る前に変換処理が入るのがコスト的に気になっていたのだが、実際やってみた所、200x200の画像をRLE展開込みで100枚程度では全然起動のもたつきは生じず、正直今更ながらにコンピュータも速くなったんだなぁ、などと感動してしまった. 一応変換は入っているので、小学生の頃最初に作った画像処理、というか色空間の変更 (MSXのScreen12のアレと言えば分かる人には分かるかも) は単純な変換ですら (BASICという事もあるが) 数時間かかったのが何とも感慨深い.
ただ圧縮効率としてはやはりpng(というかdeflate)等の「ちゃんとした」圧縮処理よりは格段に落ちるので、望ましくはpng形式等でリソースに格納しておくのが良いのかもしれない、ただlibpngをリンクするとちょっとしたプログラムでも200kb程度まで膨れてしまうので、png処理については以前フォーマットの解説用に作った自前のライブラリをちゃんと実用レベルまで持って行って組み込んだ方が良いかもしれない.
まーpng自体はともかくそーするとzlibのサイズも気になるのだが、まだ手持ちのdeflate実装はzlibを置き換えられる所まで十分とは言い難い、展開で3〜5倍、圧縮でも(最大圧縮率で)1.5〜2倍程度遅いし、圧縮処理はlazy match無しではzlibより圧縮率は0.5〜1%程度悪化するし、lazy matchの実装では10倍程度まで遅くなってしまう:-<
んー、どーしたものか.
先日のMDIの最大化切り替えのリサイズの問題、WM_SIZEで前のスクロールバーの可動範囲を記憶する形でいけるかと思ったのだが、ペイントソフトのようなものでは上手く行くが、その他の多数のソフトでは表示範囲以外にカレット位置があり、通常リサイズ時には スクロール範囲調整 -> Ensure Visibleな処理が入るので、Ensure Visible処理で自動スクロールが入ると流石にそこではスクロール情報を上書きしないワケにはいかない.
回避策として、これらの自動スクロールを要する種類のソフトではWM_SIZEではスクロール範囲のみを変更として、実際にユーザーから入力があるまでカレット位置への自動スクロールを遅延させる形が考えられるが...
試しに以前作ったテキストエディタのスクロールに同様の処理を組み込んで見た所...やっぱり少々使い辛いorz
どーしたものかと思ったが、そういえばVisualStudio6のテキストエディタもこんな挙動しているし (最新のは知らない、最近はGUIの作成も含めてコマンドライン+テキストエディタでの開発ばかりでIDE使ってないので:-P) 細かい仕様を把握している筈のMSの製品でもこの状況であるなら、やっぱり(綺麗な方法では※1)回避不能な問題という所なのかしらんorz
この挙動だと最大化での運用をメインとするか、マルチウインドウでの運用をメインとするか、明確な指針を設け他方を副次的にのみの使用を推奨するようなデザインを明確に取らないと、アプリのデザインとしては厳しそうではある.
ライブラリの機能がソフトのデザインを決めるという何とも本末転倒な状況、友人が 「標準機能のMDIはクズだから、自分で同じもの作った方が良いよ」 と言っていたのもさもありなんsigh.
# まー取り敢えず習作なんで、別にどーでも良いのだけどね、大抵のものを作るだけのイベントフローは掴めたし. タブ型が良ければ自前でタブ型のコンテナ作れば良いだけだし、ただ思っていたよりも選択肢としての自由度が低いのが残念だけでorz
※1) まーやろうと思えば子windowの作成前にフラグ立てて、WM_SIZEで子windowの作成中と他のwindowが最大化されている場合はスクロール領域の計算をpendingして、windowが最大化解除されてかつ子window作成中で無ければダミーのWM_SIZEを非activeなwindowに送信するとかやればできるのだけどね、ってそんな馬鹿みたいな事やるなら素直にカスタムコンテナ作った方がマシでしか無いワケで、オーナードローで無理矢理複雑な事をやろうとするのに似てるけど、正直損益分岐点割ってるがな、みたいな:-P
先日のMDIでのスクロールバーの件は色々試行錯誤をした結果、スクロールライブラリに手を入れる形にするのが一番綺麗にまとまるという結論に行き着く.
問題はクライアント領域のサイズが変わる事で、スクロールバーに設定された値がサイズ変更の前後でclippingされてしまう事が原因であるので、WM_SIZEで発生するスクロール領域の更新では以前のclippingされる前の値を保持しておき、再度サイズが変わった場合にはそのclippingされる前の値で補正を行う. スクロールが実際に実行された段階で初めて保存していた値を実際のclippingされたスクロールバーの位置に置き換えるといった具合.
この場合スクロールバーの端付近では、ウインドウを最大化から通常サイズに戻してスクロールバーを触らないで、再度最大化した場合と、スクロールを行ってから最大化した場合で復元されるスクロールバーの位置が変わる、つまり前後で1:1対応では無くなってしまうので、正直これも論理的な対称性からすると、正直どうかと思うし、またフルドラッグでリサイズしている途中の挙動が直感的では無い (復元前のサイズ以下の場合は端に張り付いたような動きをする) 気もするのだが、まぁここでやるのが一番安定した安定した方法だろうと結論付けた:-<
そこまでMDIを気にする事も無いし、個人的には特に複数のツールウインドウ + MDI子ウインドウの形式は決して使用シーンのデザインとしては決して良いものだとは思わないのだが、MDI + フローティングウインドウの利点として、取り敢えずソフトが手がかかっていそうに見える(ぉ 事や、また機能を追加した時にデザインやバランスを考えず無節操に機能を追加して後はユーザーに丸投げし易い(おい 事、その上でタブ型+独自コンテナよりはそれっぽく見え、定型化されているので当座の批判をかわし易い(おいおいおい 事などが魅力であると考えている.
などと書くと、ふざけているようにも見えるかもしれないが、結局の所は有限なコストとのバランスとして選択肢を持っておきたいという所で、極めて大真面目な話として考えている. 個人的な意見だが、本当の意味で適切にワークフローまで含めデザインされたGUI (に限らず道具全般) というのは本当に良いものではある. 反面 (個人的経験だが) 例えチェックボックス一つ追加するにも熟考を要する為、開発のパフォーマンスとしては決して良いものとは言い難い.
それに対し、コンピュータ業界にありがちな (そして今でも主流を占める) 多機能主義として後は (カスタマイズという名の) 丸投げという方針は実に楽な話で、それでいて本来の問題はユーザー次第と逃げる事で、開発側のエゴを満たし得るし、取り敢えずチェックリストとして揃っていれば感覚的なものは明確かつ適切に批判できる人が少ない事を考えると、当座問題が表面化する事は無い (馬鹿にしているように見えるかもしれないが、経験上この傾向は実社会における開発で大いに散見されると考える:-P
まぁ結局の所は提示されたコストに対する選択肢というトコロの話、本質的な問題解決としては決して正しいアプローチとは言えないが何も無いよりはマシじゃねー、みたいなそんなカンジ (実際内製ソフトの開発であれば、本来手間をかけるべき個所では無いのでこれでも十分だろう、不特定多数向けパッケージ開発で同じ事を言ったらトルチョックものだが) ;-)
---------------
で、上の話を踏まえて (箱庭遊びで使っている) .NET用のスクロールライブラリにも同じ修正を加える事にした. 該当個所を修正して動作を確認するが、修正前の動作と変わっていない (既にこの時点で勘の良い人は気付くかもしれないが) 30分近く悩んだ所で、ちょっと思い立って調べて見るとWM_SIZE相当でのスクロール設定の個所で、スクロールバーに値を設定した時点でScrollイベントが発生しWM_H/VSCROLLで行う処理相当が走っていたorz
まー結局処理中フラグを設ける事になるのだが、この挙動って正直バカナンジャネーノ(※1)なんて思ったりするのだが、むしろこう思うのは少数派なのだろーか? なおwin32では殆どのケースでプログラム側での変更はイベントを発行しない、これによりコントロールの状態をプログラムで設定した場合にはその状態でのプログラム上の状態は明示的に反映する必要があるが、反面非対称的あるいはコントロールと外部状態を同期させる場合にイベントフローの方向が明確に規定できる.
※1) 無論論理的 (あるいはプログラマ的に) GUIの状態に関わらずコード上での変更であってもイベントが発生する方が美しく「見える」事には同意する、実際昔まだダイアログに毛の生えた程度のプログラムしか作っていなかった時には自分もそう思っていたのも事実. ただある程度 (まだ十分とは言えないが) プログラムを書くようになって考えを改める、極論すれば設計上の「美しさ」なんてものはフレームワーク・ライブラリ屋の自己満足に過ぎず、どれだけ美しかろうが、実用においてむしろミスリーディングを誘発し得る設計はクズでしか無いと考えるに至る:-P
まー、今回のはそこまでご大層な話では無いのだが、設計上の「美しさ」に譲歩するとしても、それならばユーザーの入力を伴うイベントと変更検出のイベントは別系統として存在するべきだとも思ったりする;-) .NETに限らずVB時代まで遡っても余りこの件に触れる人がいないのは自分のこの考え方はマイノリティなのかなぁ(苦笑)
...まー色々文句が多いが、ここは私にとっての王様の耳はロバの耳の床屋の木の洞であるので、所詮こんなものだろう;-)
---------------
そういえば先日書いた.NETのdeflateの話は4.5になってからzlibベースに置き換わったらしい、これを以って良しとするか、或いは10年近く標準という肩書きの為に役立たずの機能がのさばる事になっていた、とするかは実に悩ましい所ではあるが;-)
減色ソフトの模擬開発(※1)という事でMDIを色々いじっているが、どーにもMDIのインターフェイス自体に幾つか難があるカンジ、最大のものは子Windowを最大化表示で切り替えて使うような運用を行った場合に、Windowの切り替えで非ActiveなWindowは勝手に最大化が解除されてしまう.
これの何が問題かと言えばウインドウのサイズによりScroll領域が変わる事で、結果的にユーザーとしてはドキュメントを切り替えているだけのつもりが、切り替えたwindowのスクロール位置が変わる事になってしまう (無論スクロール位置の重要性はプログラムの種類に依るので、これを以ってMDIが使えないと言っているのでは無い).
強引な方法としては幾らでも解決策はあるが、MDIでの模擬開発としてやっているので、そもそもがカスタムコントロールを作った方がマシな話にしかならないのであればMDIインターフェイスについてこれ以上入れ込むだけの価値は無いという判断にしかならない.
さて、どーしたものやら:-<
※1) 経験上、通常の開発と同レベルでイベントフローの存在するプログラムでなければ、設計上の問題は明確にならない. Toy Modelのプログラムばかり量産していても、結局まともに開発するとなるとその経験は全く役に立たなかったりするので実際の問題と同規模で捉えておく事は重要であると考えている.
---------------
上と絡んで減色ユニットについて幾つかのアルゴリズムパターンを変更して検証してみたが、現状ではまだパレット生成がxPadieからの派生系よりはやや弱いものの、ディザ部分でやや一般的では無い処理をしているので、そちらの精度で吸収しているというカンジ.
無印のPadieよりはまず良い結果は出せるが、xPadieとしてソースが公開されたものの派生の結果と比較すると目立つエラー (適用できる色の不足による局所的な荒れ) は出難いが、反面拡大するとややノイズ (階調の荒れ) が目立つ個所が発生する場合があり、使っている色空間が違う事や、局所的なパレットの集中が大域的な再現度を悪化させる事を考えると、一概には言い難い部分もあるが、やはりまだ少し詰めが甘い気がするsigh.
品質という事ではJPEGのチューニングもやらなきゃいけない気がしているのだが、SAIのJPEGエンコーダの結果とか、libjpegと比べて同一圧縮率でのモスキートノイズの量がかなり少ないので、一応結果はそのまま流用しても良いとの許可は貰ってるけど、全く何も自分で検証せずに使うのもどーかという気がするし. リサイズ周りも (特定条件下で) もう少し品質を上げられるネタはあるのだが、この辺も含めてやらねばならない事が山積しているカンジ、何とも何ともX-<
延々やっていた透明度付きの減色だが、「一応」程度には納得できるものが出来たので取り敢えずの区切りとする. 結局今回も1ヶ月程度いじっていた事になる. 減色ルーチンのサイズはたかだか1000〜1500行程度 (試行過程を残す為多くのコメントアウトが入っているので正確なサイズは見積もっていない) であるので分量的にはせいぜい2〜3日程度で書ける量である事を考えると、何とも馬鹿馬鹿しいのか面白いのか、まー結局コードの行数なんてアテにならんものなワケで (せいぜい役に立つのは制御可能性の指標としてだけで:-P
で、結果はこんな具合 (画像はWikipediaより転載・修正) IE6ではバグがある為、自分のメインブラウザではまともに表示されていない(笑)
![]() |
実際は上の画像は色の傾向が明確で、比較的均等に分布しているタイプの画像なので、減色の評価画像としては向いていない. またpng8では半透明部分の色分布が多いと圧倒的にパレットが不足する傾向があるので、そういった意味ではこの画像の様に半透明部分に画像としての複雑な階調が存在する画像も不向きではある ;-)
なお透明度無しの場合この程度まではいける (こちらは今更な人には今更な話だけど、一応対比の為;-)
自前のソフトへの組み込みも完了したのだが、試しに作ってみた重要領域をマーカーでweight付けする方式が非常に有用である事が判明 (当社比10倍程度). 問題は現状の自作ソフトのsaverプレビューにはそのままでは入れ込めない事で、専用のプレビュー付き保存画面を用意するのが妥当なのだが、そーするとAnimation GIFとJPEGも同様のインターフェイスである事が望ましいのでどーしたものやら、という所.
取り敢えず小休止で今回の減色ユニットを組み込んだMDIの習作 (設計遊び、考えて見ればtoy model程度でしかMDIはいじってないので、試しに某ソフトっぽいGUIでも作って、自分の中でイベント周りの体系化だけしておこうというトコロ) でもいじりながらぼちぼち考えている所、結局まだ暫く続くんだろーか(一応マーカーのweightバランスの調整は完了しているので、減色ユニット自体に手を入れる事は無いと思うのだけど) sigh.
表題の通り、レイヤーの移動(前後関係)のundoとredoの処理が逆になってた、これじゃレイヤーの移動をまたいだ処理のundoでは処理対象となるレイヤーが破綻するし、その前後でレイヤーの作成なんかがあると対象レイヤー自体が変わってしまいますorz
日常的に画像処理に使っているし、機能自体もごく頻繁に使うので気付きそうなものだが、意外と気付かないものだなぁ...undoの基本データ構造なんて何年も手を入れていない筈なので、バグっていたとしたら相当初期の段階からの筈なのだが(苦笑)
まー意外とこんな事もあるんですな的なカンジで、後は幾つか配布した個所があるんで、見ているかどうかは知らないけど一応の注意書きって所でX-<
# 後、先日直したバグでGIFの保存関連を高速化した時に特定データでlzwストリームの末尾が書き出されないバグも発生してました、この修正をしたバージョンを配った先はかなり限定されている筈なのでアレですが、ま一応orz
# つまりは自分のミスがあったのは表明したので、後はどーなっても知らんよーという自己の内的整合化過程なワケですな、免罪符を買うのは楽しい事ですよ(ヲイ
減色ユニットの作成中、内製で使っている.NET製の画像Utilでクリップボードを介して転送した画像が変にモアレが出るので調べてみたのだが、どうも.NET2.0ではClipboard.SetImage/GetImage()にて画像を転送すると、現在の画面の色深度設定が反映されてしまうらしい (つまりCF_DIBでは無く、CF_BITMAPのDDBになってしまっている).
控え目に言っても、これではビットマップデータを転送するには全く使い物にならないので恐らくはバグではないかと思うのだが (これを仕様というなら仕様を切ったエンジニアの脳疾患あるいは人格形成期における特異な影響や抑圧(ぉ を疑わねばならない:-P) 如何ともし難し.
まぁ仕方無いのでDIBを自前でDataFormats.DibについてSetData/GetData()として自前でカラーフォーマットを変換するルーチンに置き換えた(※1) せいぜい400行程度のコードなので、転用&テストで数時間程度の大した話では無いが、その殆どは画像ソフトのコードからの流用であるのでC++ to C#の変換程度(※2)で、これを0からとなるとそれなりに時間とテストのコストがかかる話ではある :-<
不思議なのは、対処しているらしきコードは海外のソフトのコード等では散見されるものの、この問題に言及している情報が殊の外少ない事だろうか、皆気にしないのか、或いはこの問題が問題になるような画像を扱うソフトを作る機会が実は少ないのか、さて.
※1 安易にやるならStreamに書き出してBITMAPFILEHEADER部分のオフセットを加えて無理矢理変換する方法もあるが、BMPだけでも幾つかのヘッダのバージョンが有り、ライブラリの書き出し仕様を暗黙的に期待する方法は実装としては非常に良くない&細かいバージョンチェックして変換する位なら同じ事なので結局自前を選択した;-)
※2 しかしC#はおろか世の殆どの「最近の」高級言語でのバイナリの扱い辛さはどうにかならないものか、DBとの接続やXML, Unicodeなども良いが、分野によっては (というか業務アプリ以外では) データとしてのバイナリの扱いはそれと同程度かそれ以上に重要な話である気がするのだが. あるいは自分が年をとっただけで、既にバイナリの重要性は失われているとかね(苦笑)
---------------
.NETは意外とこの手の問題があり、先日もAnimation GIFのテストをしていた所、標準のImageAnimatorのタイミング制御がダメダメ
(というかまともに動作していないレベルで不正確) で結局自前のスケジューラを作る事になったり、Deflate周りのアルゴリズム実装はそれこそ
(別途Deflateを実装してみると分かり易いが) 安易なLZSSの最速一致 (3文字一致する所でそれで終わり) ですら遥かにマシな圧縮率を出せるような酷い実装になっていたりと色々頭が痛い.
まぁDeflateの低性能は周辺特許まで考慮した結果、無難な実装のみに止めたとも好意的には考え得るが、それでもその圧縮性能はこれでDeflateを名乗ったら詐欺じゃないか、というレベルで非常に悪い(※3)
実際開発環境のテストとして作成している幾つかの.NET製の内製ソフトでも、結局細かい問題に対処する為にライブラリ相当の機能を自前で実装したり、かなりの部分をWin32やInteropServices系の呼び出し・変換が占めていたりで、まるでかつてのVBアプリが内部でやたらとWin32を呼ばないと使い物にならなかった状況の再現であり、決して額面通りの扱い易い代物とも言い難い (それでもGUIアプリについてはJavaやActionScriptの環境に比べたらまだマシだが)
ある一定規模までは使い易く、その想定を外れた所では本末転倒になってしまう所は、現在の開発環境のバベルを構築する中で、言語とライブラリの抽象化階層を積み上げ、より高度なプログラムを作れるようにするという発想そのものに根本的な欠陥があるのか...うーむ.
ま、実際の所ソフトの実現への要求は開発環境では無く、最低限周囲の他のソフトとの比較から生じるものだ、という所にこの問題のギャップの本質があるのかもしれない. 勿論これは最低限の状況を説明しただけの話で、本質的にはソフトの要求はソフトが体現する道具がどのようにあるべきか、という所から定められるべきであるのは言うまでも無いが:-P
※3 全てを調べたワケでは無いが、複数データで比較した所、一応カスタムハフマンテーブルが指定されているが、実際は固定のハフマンテーブルを使っている模様で、下手をすればDeflateで規定されている固定ハフマンテーブルを使用した場合 (これも圧縮性能は当然悪い) よりも悪化する場合すらある、同一方針で作成されているのかルーチンが共用なのか(※4)、Deflateを使うPNGの圧縮率についても.NETライブラリを通して生成したものは極めて圧縮率が悪い、というかデータに十分な偏りがあるデータで元データより大きくなるケースが多々ある時点で圧縮ライブラリとしては既に論外な気もするが :-<
※4 PNG圧縮の場合、インデックスを除くフルカラー及びグレイスケールの場合にはラスタ単位のフィルタ処理によるDPCMの結果、データの偏りが顕著になる為、素直にLZSSを適用するよりも、十分な長さに達していないデータについては後段のハフマン符号に任せてしまう方が圧縮率としては総じて良い結果を出す傾向にある、この為Deflate部については通常のDeflateとは少し違ったモードで動作させる方が良い事になる.
既にメンテナンスフェーズに入っているような具合で、思いついた時だけ進行中みたいなペイントソフト作成ですが (まー半分はアルゴリズムや理論・仮説のテストプラットフォームと化しているんでそんなものですが)
思い立ってAnimation GIFとPNG8の書き出しを追加してみた、でPNG8をサポートするなら2値透明度だけでなく半透過もサポートするのが当然だろーと思い、減色ユニットに手を入れたのが運の尽き、減色空間の4次元化は半日(というか数時間(笑)で完了し、それなりの結果は得られたのだが、各チャンネルの微妙なバランスが難しく、微調整やロジックの再検証を繰り返す事1週間余り、ちなみに現在も継続中.
...考えて見れば前の(半透明をサポートしない)減色ユニットを作った時も0ベースで勉強を始めてから 1〜2週間程度でモノは出来たのだが、結局色々調整して今の形になるには3ヶ月程度かかっているのだよなぁsigh
という事でネットで拾ってきた様々なタイプの画像を減色しては目を細めて全体のバランス確認、拡大して誤差拡散ノイズと階調のバランスを確認、更に他のソフトと比べてアルゴリズム的に何が起きているかの推測を延々繰り返す、正直目は疲れるし、延々同じ画像ばかり見ているとそのうち何が良いのか分からなくなってきて、といった具合.
そもそもロジック的に明確な解があるものならもっと楽なのだが、減色処理の場合、論理的に正しい最適化はむしろ、大局での認知的な違和感を増幅したり、全体のバランスが占有面積と色差の微妙な綱引きで成立しており、あちらの精度が上がればこちらの精度が下がるといった具合で、如何ともし難し、そもそもが閾値処理の塊なので、微分連続的なパラメータの変動がそのまま反映されるワケで無く、調整が直感的には行かないのが何とも難しい、先日やってたブラシエンジンの再調整も特異点付近の挙動の調整は経験則的で正直泥沼だと思っていた(笑)が、減色処理のバランス調整はほぼ全域がそんな具合orz 画像を延々見続ける作業と合わせ正に地獄のよーな状態が継続中.
---------------
取り敢えずまだ暫く続きそうだけど、一応書き出し機能で作ったAnimation GIFはこんな具合.
減色ユニットの性能に凝ったのだが、これじゃ全然分かりませんなorz
...半透明はまだそこまで詰められてないけどorz
ついでにAnimation GIFについて調べていると、(有名所まで含め) 公開されている殆どの画像Viewerで完全にAnimation GIFの仕様をサポートしたものが殆ど無い事に気付いたり、当然ブラウザでは表示できるので、落とし所はそれに合わせるのが妥当だと思うのだが、携帯のブラウザとかまで考えるとクオリティは正直アテにできないし、何ともはや...orz
そろそろうっとおしいヤツに(自分が)なってきている気がするので非常に恐縮なのですがm(_ _)m
BS_DEFPUSHBUTTONの扱いについては少々クセがあるようです、過去に調べたメモの抜粋ですが
・BS_DEFPUSHBUTTONスタイルを設定するのは1つのコントロールだけ
・IDにIDOK/IDCANCELを設定した場合はIDOKのボタンにBS_DEFPUSHBUTTONが設定されていればそのままIsDialogMessageで処理されるが、それ以外のidを使用する場合自前Window (not ダイアログ) ではDM_GETDEFIDに応答する必要がある.
なおDM_GETDEFID/DM_SETDEFIDはDefDlgProcで処理される想定のものであり、上は自前でモーダルループを実装する場合の手順になる、DM_GETDEFID/DM_SETDEFIDはWS_USER/WM_USER+1が割り当てられておりIsDialogMessageはこれを発生する可能性があるのでWM_USER/WM_USER+1は使うべきでは無い.
なおIDOK/Enterについてはこれで良いが、IDCANCEL/Escapeについての別idでの再現方法は不明.
という事で上の話を守らないとフォーカスの移動等でデフォルトの黒枠が表示されなかったりといった問題が発生します、というかこの話自体自分でもすっかり忘れてましたが(^^;;;;
IDCANCELの挙動の別IDでの再現が分からなかった&面倒なんで自分はIDOK/IDCANCELを指定する方法しか使ってないです(笑)、、、というかダイアログ自体がリソースとコードの2箇所の修正が必要になる&IDをいちいち定義しなければならないのが面倒という具合なんで最近は殆ど使ってなかったんですけど、そういえば自前のペイントソフトのOKボタンにBS_DEFPUSHBUTTON設定してないですね、完全にBS_DEFPUSHBUTTONの存在自体忘れてました(大汗)
しかし、、、フィルタのダイアログの修正だけで50個近くあるのでめんどい、ライブラリにもデフォルトボタンの作成で関数1個追加しなきゃいけないし、更に現在メンテナンス中の全てのソフトに反映させると、、、あ゛ーorz
引き続きTANEさん所の6/18の話(^^;;;
Trackbarの件はもう少し混み入った話の模様、挙動を確認しましたがソースを見た所かなりのパネルでhbrBackgroundを設定しているので背景まで含めた再描画が頻繁に走ってしまってのチラつきのようにも見えます、コントロールの親windowの再描画 -> コントロール自体の再描画というカンジで. common controlのバージョンによって微妙に挙動が違うのは基本的にver5/6は別コードなんで無効矩形と再描画の発生のタイミングや頻度が違う為じゃないかと思います.
自分的にはhbrBackgroundはNULLにしてしまって最低限の描画のみWM_ERASEBKGNDとWS_CLIPCHILDRENを組み合わせて使っているのですが、背景描画は可能な限り最前面の子windowのみにしてコンテナなどその領域が子windowで隠れてしまうものについては最低限のはみ出す領域のみの消去に止めておくようなカンジで設定すると再描画時のチラつきは軽減できます. WS_CLIPCHILDRENが便利ですが、スクロールなんかの都合上それが上手く動かない場合は親windowの再描画で子windowの領域を無効矩形から除外するような処理を行う場合もあります.
# ちなみに挙動の話は例えばANSIで扱うとコモンコントロールのver5/6でeditコントロールのメッセージ結果自体違うものを返します(笑、、、えなかったりするX-<
Windowのアクティブの件はソースを見たカンジModalWindowの破棄(DestoryWindow) -> EnableWindow(親Window)とやっている気がするのですが (間違っていたらスミマセン) これだと一時的にアプリに有効なWindowが存在しない為Windowsのフォーカスマネージャが上手く処理できなくなります、ので正解はEnableWindow(親Window)を行ってからModalWindowのDestroyWindowを呼ぶのがモーダルマネージャの実装のセオリーになるんだそうです (by MSの中の人が書いた本より).
上のような必要性があるのでModal Dialogの終了はDestroyWindowでは無く専用のAPI(EndDialog)があるのだよ、とゆーよーな話が書いてありました(笑)
---------------
こちらは全くPCが壊れる気配が無いです、Win2kなんでそろそろ対応ソフトも少ない (とは言え最近は市販ソフトは殆ど使いませんし、半分隠遁状態なんでそれ程新しい開発環境を追う必要性も無い(ってそれでいーのか(爆死) ) のですが、これはこれで困ったものです(苦笑)
TANEさん所の6月16日ネタ、標準コントロールのTrackbar、確かに操作性的にはイマイチな部分はあるのだけど再描画周りってそんな変な挙動したかなーと気になったのでテストプログラム これ見る限りある程度の再描画は発生するのだけど、 それ程フリッカーが激しいという程でも無い気もしますが、、、ただ余りTrackbarを配置したWindowでリサイズ可能なものは作っていないので、他のコントロールが大量にあった場合とかは未確認ですが(^^;; もしかしたらリサイズ周辺のイベント呼び出しで不要な再描画が入ってしまっているとか?
Rebar(CoolBar)はほぼ全く使ってないので分かりませんm(_ _)m 個人的にドッキングするUIって嫌い(※1)なので特にニーズが無ければまず使わないシロモノなので(笑)
# 後、実害を明示出来ず、実装者の主義の部分もあるので書こうか迷ったのですが、windowのモーダル実行終了時に親windowをBringWindowToTopにするのはやらない方が良い気もします、普通はWindowsの標準処理で以前のwindowが復帰しますし、フォーカス周りの挙動はともすると自プロセス以外にも複数Windowへの通知などが絡んで割とデリケートな部分なので、多分目立った問題は無いとは思うんですけど. アプリで無用にSetForegroundWindowを呼ぶのと同種の危うさがあるというか. まぁ、個人的意見という事で(^^;;;;;;;;;
※1) 嫌いというと感情的な表現ですが、UIの場合漠然とした相対位置で記憶していたりするので、それにより画期的にワークフローが改善するのでなければ中途半端なカスタマイズ要素は無い方が良いというのが個人的な開発方針. 特にツールバーの位置なんて画面サイズに合わせてコロコロ位置が変わったりするので、それを取り合えず何でも並べて後はユーザーに投げっぱってのは単純にプログラマーのデザインの放棄な気がするワケで;-)
---------------
ここ暫くは何となく思い立ってFlash ActiveXのホストなんぞ書いていたり、ExternalInterfaceの呼び出しはShell View(IE)をホストする場合のSetExternalDispatch/GetExternalと違ってイベントシンク経由で_IShockwaveFlashEvents::FlashCallが呼び出されるカンジ.
でもってリクエストはXMLの形式で来るのでその為だけにCOMのXMLパーサを使ったりライブラリを探したりが面倒だったので、結局自前でXMLパーサを書いたり. 右クリック/メニューキーは結局上手い方法が無かったのでメッセージループでswfコンテナの子孫の場合にメッセージそのものを殺す (実際には送り先を親windowに変更してしまう) 形で対処してみたり.
swfのステージサイズは結局COM経由では安定したものが取れないのでswfを解析する形で実装したり、なおswfの圧縮は先頭8バイトを除いてzlibのdeflateそのまま. ついでにswfの構造を色々調べてみたり、基本はチャンク型なんだけど、サイズが厳しかった時代の産物でそこかしこにビットストリームな扱いが必要でウンザリしてみたり. それでも一応swfからリソースをぶっこ抜く事ができるようになったり、まー色々グダグダと、ただそんなに面白いものじゃありませんな(苦笑) まぁせいぜいswfをexeにしてexe起動の場合はファイルの保存やゲームパッドのサポートを追加する程度かなーみたいなそんなカンジ、まぁ飽きるまでという具合.
TANEさんとこのWindowクラスの話、ざっくりとしか読んでないですが、気になった個所を少々
・Windowクラスの管理はWM_CREATEでは無くWM_NCCREATEでやる方が良いかも、順序としてはWM_NCCREATE -> WM_CREATE -> WM_DESTROY -> WM_NCDESTROYだったと思います.
・コントロールのフォントはGetStockObject(DEFAULT_GUI_FONT)が使えます、後はGetSysColorBrushなんかも破棄しなくて良いので便利.
・Windowクラスのインスタンスを明示的に削除している個所が見当たらなかったのですが (見落としてたらスミマセン) GC任せとするとデストラクタでハッシュから除去しているけど、そもそもハッシュに登録されている限りGCでは削除されないのでは? (この辺D言語の仕様も余り詳しくないので間違っていたらスミマセン)
・上の話と絡んで、Windowハンドルの寿命とハッシュ内の登録情報の管理が一致していないと、後から作られたWindowのハンドル値が、前の破棄したハンドルと重複してた場合にマズいのでは、とか.
まぁこんな所でしょうか(^^;;
最近は自分はついぞ不精になって、複雑な処理以外はwndProc内に直書きしてしまってます、ドラッグ処理とかマウスイベントが分かれていると面倒&可読性が下がるし、ツール切り替えの実装なんかはメインWindowのwndProcからツールのwndProcに分岐させて必要なイベントだけフックさせるのでon〜ハンドラ式だと分岐コードだけでえらい事になるとか、まーそんな具合で(苦笑)
---------------
CMYKの話と絡んで、色々フォーマットを調べていた所、何時の間にか16bit floatがIEEE仕様になっていた.
という事で変換コードを書いてみたけど、精度域が3桁しか無いので色々微妙. 何と言うかまぁ、積極的に使うものじゃないなーというかそんな具合 :-<
それ以外にはTIFFのサンプルフォーマット指定にSAMPLEFORMAT_COMPLEXIEEEFPなるものを見つけて、何でもアリだなーと妙に感心したり、でも昔は一部の特殊なプログラムでしか使われていなかったSAMPLEFORMAT_IEEEFPは最近はHDR向けとして使われているとか、PhotoShopもCS2から対応しているとか. それ以外には各画像形式のICMプロファイルの埋め込み方法を調べてたり、まーそんなカンジでグダグダと.
いやまー、関連しているんで良い機会だから調べてるだけで、実際にはCMYKやカラープロファイルは実装しないと思うけど、自作ペイントソフトのpngのガンマルーチンすら外してる位だから、ちなみに色変換はICM APIに丸投げです(笑)
ここ数日はCMYKでのプレビューをいじる、実装要素としてはほぼ全て揃い、テスト組み込みでも問題無く動いてはいるが、実際に運用するには単にCMYKのプロファイル指定だけで無く、ある程度厳密なカラープロファイルの運用が求められるのでどーしたものか、といったカンジ.
カラープロファイルなんぞ、実際グラフィックソフトを使っている総人口からすれば9割の人間には意味が無いもので、無用な混乱を引き起こすという事では1割にとって有用でも9割にとっては害悪にしかならない、実際生半可な知識でのカラープロファイルの運用なんて目も当てられない状況であるし (実例はCMYKなどのキーワードで検索すれば腐る程出てくる).
同様にAdobeRGBの編集とかも同じ要素で実装はできるけど、これもまた殆どの人にとっては事態を更に無用にややこしく、分かり難くするだけにしかならない (そしてそれを回避するには、などという無用なノウハウが立ち上げられる事になる).
さて、どーしたものか :-<
# まぁ、ある程度はCMYKプレビューも家庭用プリンタでの印刷で変色し易い色を確認するのにも使える (6色で+LC,LMの場合は傾向は似ている) ので、実用性皆無とも言えないが.
# 同じような話としては画像のピクセルサイズとdpiの話も無用な混乱の元だわな、所詮コンピュータにとってはdpiなんて実体の無いメタデータに過ぎないワケで、まぁこちらはカラープロファイルよりは単純な話なのでまだマシではあるが、実際には印刷しないならピクセルサイズだけでdpiなんて要らないし、家庭用印刷ではどうせ勝手に拡大縮小が入るので目安程度の自己満足程度にしかならんのだけど、殆どのソフトにあってそれが元で混乱があるのを見るとやっぱり何か馬鹿げている気がする、まぁ世界の断裂ってヤツやね :-P
任意矩形で表現される作図的なパース座標の任意分割と座標変換のテスト
まぁこんなものかというカンジ、日記に載せるので小さな画面に押し込める形でかなり歪んで見えるが、2・3分割の補助線を引いてみるとちゃんとパース分割として成立しているのが分かると思う、ただ多分パース定規は実装しないと思うが(※1) ;-)
※1) 何と言うか、パース作成に必要な要素を一通り乗っけるとCAD的というか事務的というか事前計画的というか、ソフトの持つ印象が何とも堅苦しいor息苦しい代物になりそうなんで、せいぜいグリッド程度で気軽に扱える程度の実装に落としこめるならアリなんですけどね :-<
実装完了
まー何と言うかダラダラと、最近は日記を書くのも面倒なカンジで、困ったものだ.
その後あるソフトの体験版で同様の機能を試す、メッシュ変形自体はありがちな機能のようで意外と実装しているソフトは少ない、正直ワクワクしながら試したのだが、実際にはその余りの実装の酷さに呆れ返る事しきり、現時点で自作のものは公開する意思は無いので余り言えた義理では無いが、それでもこれは開いた口が塞がらぬという印象で、このソフトに限らず、とりあえずのチェックリストを埋めました的な実装およびそのような実装を行うプログラマの存在・人生(ぉ については、いずれは粛清^H^H是正されねばならぬと思う事しきり :-<
猫でも分かるWindowsプログラミングの次にやるべき事
Win32の勉強で少し前に友人に聞かれて即答できなかった内容、ふと別の友人プログラマと話していた時に、単機能のAPIの把握から実際にプログラムを書けるようになる所の開きは何処にあるかという話で出た内容を幾つか羅列.
・ウインドウハンドルにデータを結び付ける方法とその寿命管理
クラススタイルでWin32プログラムをやるなら必須、Cであっても複雑なプログラムはこの辺をやっておかないとまず無理
・カスタムコントロールの自作
ボタンのようなものでも良いので素のWindowからEnableWindow,SetFocus等に反応し、マウス操作に対して適切にキャプチャを行い、ダイアログマネージャ等と併用してもちゃんとフォーカス等が動作した上でライブラリ化して複数作成する場合にも通常コントロールの作成・管理と同程度の手間で済むものを作る.
・入力と画面更新の手順
入力に合わせて内部状態を更新し、再描画を発行する事でWM_PAINT内で全て更新するモデルでの実装を行う、当たり前の話なんだけど結構この辺の話が見えてないプログラマも多い、間違っても非常時以外はGetDCで描画なんてしちゃいけない.
・オンメモリのフレームバッファとしてのdibの使用と画面更新
これまた意外と当たり前なようで詳細が少ない話、GUIやコントロール描画で凝ったものを作る際の基本中の基本. 個人的には80年代の8bitマイコン時代を語るプログラマで、自作のフレームバッファクラス/ライブラリを持ってない輩は須らく全てパチモンだと思って良いです:-P
・モーダルマネージャの自前実装とスレッドごとのWindowsのメッセージキュー実装の把握
これまた意外と当たり前のようで資料の少ない話、フレームワークのコード等でしか実際のサンプルを見る機会は少ないが、細かいGUIの挙動を実装する場合任意の場所でのモーダル実行の実装やその時Windowに何が起きているのかの把握は必須.
またSend/PostメッセージがWindowsによってどのようにキュー上やメッセージリスト に追加されて処理されているのかもちゃんと実装レベルで把握してないと変な落とし穴にはまり込んだりする羽目に.
とまーこんな所でしょうか、改めて見るとどれもこれも本当に基本的な、当たり前の話でしか無いのですが、意外とネットで見ていても情報が無い上に、自分の経験上アプリプログラマを名乗っていてこの辺全てちゃんと押さえているプログラマって余り見かけなかったりします.
(まぁ私の周囲が余りに悲惨なだけという可能性もあり全体の傾向とするには少々情報不足ではありますが)
それ以外には実際のライブラリの実装の例題として、作成されたコントロールの位置を記憶してWM_SIZEで自動で再配置を行うレイアウトマネージャとか、 仮想スクリーンの位置だけ設定しておけばスクロール座標だけでウインドウのスクロールが実装できるライブラリとか、もっと単純な所ではスプリッタコントロールの実装とか、まぁその辺をやっておくとダイアログまでは作れるんだけど、それ以上の 複雑なアプリとなると分からないといった状況を打開する鍵にはなるかもしれません.
実際慣れてくるとC#のGUIデザイナ程度でできる位のGUIはAPIでもそんなに手間がかからなくなったりします、ええ、いやマジで;-)
---------------
透明水彩としての表現力として例のレイヤーの計算式がどーのこーの、でこれが結果
元々の乗算合成での透明水彩表現だとこんな↓具合
なので、使い方によっては非常に効果的、透明水彩系の表現をした際に実際の絵の具に比べ発色が悪いという問題についてはほぼ解消可能&塗り込んだ時の予測という意味でも原色そのままなので問題無し. 無論万全というワケで無く、和紙等へのにじみという点はまだまだではあるが. ちなみに用法としては塗りのレイヤーと乾燥済みのレイヤーを同じモードで2枚用意して半透明描画 (SAIのマーカーにほぼ相当) で塗りレイヤーに描画、適当な所で乾燥済みのレイヤーに転写の繰り返しといった具合.
まぁ問題はPSDに保存するとレイヤーモードを再設定しなければならない所、そろそろ独自形式の保存の実装を検討する時期か.
---------------
まぁそんな具合で気付けば2011年、明けましておめでとうございます. 昨年は身内事で色々あった内容を書きたくなかったので殆ど日記は書いてませんでしたが、もうそれも過ぎた事、ぼちぼち以前のペースで動けるようにして行きたいと思ってますm(_ _)m
そうそう、車の免許取りました、東京では車を所有・維持するのは難しいですが、一応、ね.