So-net無料ブログ作成

QPaintEngineの呼ばれ方の確認 [Qt]

前回、試した中で、呼ばれている関数について、どのような実装が必要か確認しておきます。

begin書き込み先のQPaintDevice(ここではQtlDrawDataで保持するQPixmapが来る予定)を受け取るので、保持しておく。metricでそのパラメータを使用。
metric1(width), 2(height), 8(縦方向の解像度DPI)が要求されている。
updateStateQPaintEngineの状態が変わった時に通知されるそうです。QPaintEngineStateに何が変わったかが含まれているそうで、QPaintEngineState::state()で何が変わったかを取得せよとのこと。
drawPathQPainterPathが渡されます。QPainterPathは描画要素が含まれるコンテナだとのこと。QPainterPath::elementCount()で要素数を取得し、QPainterPath::elementAt()で要素を取りだすことができる様子。
drawPolygon線を引くために複数の点を指定して呼ばれる様子。引数の点にQPointFを利用しているものとQPointを利用しているものがありますが、QPointFの方が呼ばれていました。
end終了の関数ですね。ま、必要な処理をすることにしましょう。


これらを踏まえて、どういう値で呼ばれているのかを調べたところ、metric()は上述の通り。

updateState()がQPaintEngineState::state()の値が、0x203、0x011、0x002、0x011、以下0x002と0x011の繰り返しで、最後に0x20bで終わってます。これはQPaintEngine::DirtyFlagとして定義されている値で、表にするとこんな感じですか・・。

0x203Hints | Brush | Pen
0x011Background | Pen
0x002Brush
0x20bHints | Font | Brush | Pen


描画の際は特にPenなどの変更はしていないはずですが、頻繁に変わっているようですね・・。実装の際にはこの辺も注意していく必要があるかもしれません。

続いて、drawPath()。解析のため、保存する画像を横線一本にしているのですが、その場合で、要素が5個。最初の要素のQPainterPath::ElementTypeが0(MoveToElement)で、以降が1(LineToElement)となっています。同時にこれらのコンポーネントの座標が取得できるのですが、5つの座標がそれぞれ、(410.5, 175.5) (411.5, 175.5) (411.5, 176.5) (410.5, 176.5) (410.5, 175.5) となっています。これらは、線の終点の周辺の様な気がしていますが、正確にはよくわかりません・・。SVGデータと比較する必要がありますね・・。

最後に drawPolygon()。2点づつ数回呼ばれています。modeは「PolylineMode」ですね。drawPath()の内容が(458.5, 174.5) (459.5, 174.5) (459.5, 175.5) (458.5, 175.5) (458.5, 174.5)の場合の内容は下記の通り。

modepoints
3(119, 164)-(133, 157)
3(133, 157)-(141, 156)
3(141, 156)-(149, 157)
3(149, 157)-(164, 160)
3(164, 160)-(178, 163)
3(178, 163)-(226, 167)
3(226, 176)-(248, 167)
3(248, 167)-(269, 169)
3(269, 169)-(295, 176)
3(295, 176)-(319, 180)
3(319, 180)-(342, 182)
3(342, 182)-(359, 183)
3(359, 183)-(375, 187)
3(375, 187)-(393, 187)
3(393, 187)-(411, 184)
3(411, 184)-(424, 184)
3(424, 184)-(441, 183)
3(441, 183)-(451, 179)
3(451, 179)-(459, 175)


drawPath() の点は終点なんですかねぇ・・。整数と浮動小数点数で多少の誤差が出ていると言う感じでしょうか。

ま、何となく drawPolygon() から抜き取ることで何とかなりそうな気もしますが、ちゃんと動作を確かめたいので、SVGのファイルと見比べながら解析することにしますか・・。特に updateState() の通知内容がなぜ発生しているのかも見る必要がありますしね・・。


つづく。






QPaintEngineの利用 [Qt]

さて、QPainterの派生に行き詰って、どうしようかとQtのPaint Systemに関するドキュメントを読み直してみたところ、QPaintEngineと言うクラスがQPainterQPaintDeviceの間にかんでます。

QPaintEngineを見たところ、ほぼ全てのメンバ関数が仮想関数となっています。これが使えるかなとより詳しく読んで行ったところ、どうやらQPainterはQPaintDevice::paintEngine()からQPaintEngineを取得してそれを利用する様子。実際に描画を行っているのはQPaintEngineみたいですね。QPaintEngineのメンバ関数はほとんどが仮想関数なため、派生したクラスを渡せば問題無く自作の関数が呼ばれそうです。と言うより、自前のQPaintDeviceを作成する際には、自前のQPaintEngineを作成して描画を実現するのが、まっとうなやり方の様です。よく見たら、QSvgGeneratorもまさにその方法を使っている様ですね。

*

と言うことで、QSvgRendererからの指示を内部データに変換するためにダイレクトにQPainterを派生しようとしていたのを、QPaintEngineの派生クラスと、その派生クラスを利用する変換用のQPaintDeviceの派生クラスを作成して実現することにします。一応、新たにクラスを作り直し、前のQPainterの派生クラスで流用できるところは流用していくことにしましょう。QPaintEngineはQPainterに比べて、メンバ関数が簡素になっています。QPainterでオーバーロードされていた便利関数が全て無くなっているようです。

さて、実装していてふと気になったのが、QPaintDeviceが返しているwidth()とかheight()など、QPaintDeviceの状態を返す関数です。すべて仮想関数になっていないため、これらをオーバーライドしても正しく応答できません・・。どうすんのかな〜と探していたら、metricと言う関数がprotectedな仮想関数として用意されていました。内部情報を返す関数だとか。これを実装すれば良さそうですね。

*

相変わらず、初歩的なエラーを出しながら、なんとかコードが完了して実行することができました。線を引いただけのSVGデータで、呼ばれた関数は「QtlSvgConvertor::metric()」「QtlSvgConvEngine::begin()」「QtlSvgConvEngine::updateState()」「QtlSvgConvEngine::drawPath()」「QtlSvgConvEngine::drawPolygon()」、そして「QtlSvgConvEngine::end()」の様です。

「QtlSvgConvertor::metric()」は一応、何が設定されて呼ばれるのかログを仕込んどいたのですが、width、heightとDpiYが設定されています。そして割と頻繁に呼ばれています。DpiがYだけなのが不思議ですね・・。後、「QtlSvgConvEngine::drawPath()」と「QtlSvgConvEngine::drawPolygon()」はどういう使い分けをされているのでしょうか?まあ、点の多さですかね。後、「QtlSvgConvEngine::updateState()」って、なんじゃ?という観点で調べていきたいと思います。


つづく。






gccとg++ [C++]

前回の記事の内容を確認するために次のようなコードを書きました。

virtual.cpp
#include 

class Base {
public:
	void normal_func() {
		std::cout << "Base::normal_func()" << std::endl;
	};
	
	virtual void virtual_func() {
		std::cout << "Base::virtual_func()" << std::endl;
	};
};

class Sub : public Base {
public:
	void normal_func() {
		std::cout << "Sub::normal_func()" << std::endl;
	};
	
	virtual void virtual_func() {
		std::cout << "Sub::virtual_func()" << std::endl;
	};
};

int main()
{
	Base base;
	Base *pBase1 = new Base();
	Base *pBase2 = new Sub();
	
	Sub sub;
	Sub *pSub = new Sub();
	
	base.normal_func();
	base.virtual_func();
	pBase1->normal_func();
	pBase1->virtual_func();
	pBase2->normal_func();
	pBase2->virtual_func();
	
	sub.normal_func();
	sub.virtual_func();
	pSub->normal_func();
	pSub->virtual_func();
	
	return 0;
}


これを「gcc -o virtual virtual.cpp」でコンパイルしようとしたところ、「undefined reference to ...」と言うエラーがたくさん出て、「collect2: ld はステータス 1 で終了しました」とのこと。コンパイルできません。これはNetWalkerでのお話ですが、同じことをMacで実行したところ「Undefied symbols:」と出た後にずらずら〜っとメッセージが表示され、「ld: symbol(s) not found」「collect2: ld returned 1 exit status」だとのこと。同じくコンパイル出来ませんでした。iostreamに関連するライブラリのリンクがうまく行っていない様子で、検索していたところ、明確なお答えが・・・。

「g++ -o virtual virtual.cpp」と実行したところ、問題なくコンパイルが通り、実行結果も得ることができました。

*

う〜ん、昔は、gccが拡張子を見て適切に処理してくれたような気がしますが、いつからこうなったんでしょうか・・?


【参考】
undefined reference to `std::cout' - Ubuntu Forum




   

flagsと派生クラス・親クラスの関数の呼び出し方 [C++]

さて、QPainterを派生しようとしていましたが、この方法は根本的に不適当である可能性が高くなってきたので、このまま行っても大丈夫なのかを確かめるため、またC++の基礎的な仕様を調べておこうと思います。

前回出たのは「namespace」、「flags」、そして、今回の試みの根本的なところとなると思われる「派生クラスと親クラスの関数の呼び出し方」ですね。「namespace」は一本の記事に出来そうなので、おいおい別記事にすることにします。

*

「flags」・・・。ちょっと検索してみましたが、引っかかりませんね。こちらも追々必要になったら調べることにします。

残るは「派生クラスと親クラスの関数の呼び出し方」ですね。すごく基本的な事項だとは思うんですが・・。QPainterの派生クラスを作成した際、ほとんど何も考えずにさくさくと作って言ったんですが、QPainterの公開関数はすべて、「virtual」指定されていませんね。基本的にこれらの関数はオーバーライドされることを考慮していない(と言うか、望んでいない?)と言うことでしょうか。

こちらの説明で全て網羅されている様ですが、ここの情報を元に呼び出し関係をまとめてみます。サンプルとするクラスのソースは下記の通りです。

#include 

class Base {
public:
    void normal_func() {
        std::cout << "Base::normal_func()" << std::endl;
    }
	
    virtual void virtual_func() {
        std::cout << "Base::virtual_func()" << std::endl;
    }
};

class Sub : public  Base {
    void normal_func() {
        std::cout << "Sub::normal_func()" << std::endl;
    }
	
    virtual void virtual_func() {
        std::cout << "Sub::virtual_func()" << std::endl;
    }
};


これらのインスタンスとその呼び出し結果は下記のとおりとなりました。

クラスインスタンス呼び出し出力
class BaseBase base;base.normal_func()Base::normal_func()
base.virtual_func()Base::virtual_func()
Base *pBase = new Base();pBase->normal_func()Base::normal_func()
pBase->virtual_func()Base::virtual_func()
Base *pBase = new Sub();pBase->normal_func()Base::normal_func()
pBase->virtual_func()Sub::virtual_func()
class SubSub sub;sub.normal_func()Sub::normal_func()
sub.virtual_func()Sub::virtual_func()
Sub *pSub = new Sub();pSub->normal_func()Sub::normal_func()
pSub->virtual_func()Sub::virtual_func()


前回の記事で問題になっているのは、緑色の箇所ですね。インスタンスの実体はサブクラスのものなのですが、それを格納している変数が親クラスとなっていて、しかもオーバーライドしている関数が仮想関数で無いため、意図しているサブクラスの関数ではなく、親クラスの関数が呼ばれています。


う〜ん、困りましたね。


【参考】
C++ Reference - cplusplus.com
7. 仮想関数 - 目指せプログラマー! C++言語入門






VYM [Qt]

今回ちょっと、休憩です。NetWalkerでアイデア出しにVYM(View Your Mind)と言うマインドマップのツールを使っているのですが、Macでも見れないのかなと探していたところ、なんとMac版も配布されていました。バージョンは1.12.6で、ソースのみですけどね。

よく見てみるとユーザインタフェースがQtだとのこと。Qtは開発環境をMacにも入れていますし、試しにビルドしてみたところ、たくさん警告が出てたものの、ビルドは完了し、問題なく動いたりしました。

10080100.png
何事も無く起動


せっかくなので、特にMac用とも書かれていませんが、最新バージョンの 1.12.7 のソースも試しに落としてみました。ビルドしたところ、こちらもたくさん警告は出たもののビルド完了。問題なく動きました。

これ、1.12.7は特にMacを意識せずにアップデートしたけど、Macでも問題がでないということなんですかね?たぶん、コンフィギュレーションなどはMac用の記述も含まれているんじゃないかと思いますが、これが、Qtのおかげならかなり魅力的に感じたりします。


早く、自由にかけるようになりたいですねぇ・・。




  

QPainterの派生クラスの作成 [Qt]

さて、実際に実装していきたいと思いますが、まずは、QPainterのどの関数が呼ばれるのかを調べるために、各メンバ関数が呼ばれた時にログを出すだけとしたいと思います。戻り値が必要なものについては、個別に検討します。

ログ出しには、以前試した「qDebug()」を利用します。調査の後、呼ばれているものから順に処理を考えていくことにします。データのロードタイミングをどうしようか悩みましたが、とりあえず、canvasがリサイズされるときにロードすることにしました。かなり乱暴ですけどね・・。この辺、処理フローをちゃんと考える必要がありますね。

*

コンパイルしたところ、エラーがでました。まずは、「boundingRect ( const QRectF & rectangle, const QString & text, const QTextOption & option = QTextOption() )」・・・。あ〜、クラスの宣言のところにデフォルト値の指定が書かれていますね・・。Qtのリファレンスの所をそのままコピーしたので、こういう事になってました。これは取り除く必要がありますね。他にもありそう・・。検索してみると結構ありましたので、修正しときました。

次は「CompositionMode compositionMode () const」ですね。「CompositionMode」が型名じゃないとか・・。調べたところ、「enum QPainter::CompositionMode」と言うQPainter内の列挙型の様です。とりあえず、関数の型名の前に「QPainter::」をつけることに・・。これって、namespaceとかが使えるんですかね・・?namespaceって、なんとなくわかりますが、いまいち使い方がわかってなかったりするんですが・・。

次は「RenderHints renderHints () const」です。これも先程のものと同様ですね。でも説明には列挙の「enum」以外に「flags」と言うキーワードも使われてますね。「flags」って知らないんですが・・。

*

修正した後、ビルドが通ったので、実行してみましたところ、データ変換用に今回用意した「QtlSvgInternalDataConv」と言うクラスのコンストラクタが呼ばれた後に「QPainter::save: Painter not active」と言うエラーが表示されました・・。

QPainter::save: Painter not active


この「save()」って、誰が呼んでるんですかね・・。しかも作成したクラスではなく、元クラスのQPainterのものが呼ばれているようです・・。

*

どうも、「QSvgRenderer::render()」の中で呼ばれている様です。そういや、この関数を呼ぶ前に変換クラスをbegin()していませんでした。その際にQPaintDeviceを渡す必要があります。ここは、QtlDrawDataで保持しているcanvas(QPixmap型)を渡すことにします。

・・結果は同じですね。元クラスの「QPainter::begin()」をなんとか呼ぶ必要があるのでしょうか?? 一々元のQPainterとの絡みが出てくるとすると、この計画はダメですね・・。

親クラスの関数はどう呼べばいいのか調べていたときに思いついたんですが、もしかしたら、QSvgRendrerで勝手に親クラスの「save」を呼んでる?? そう言えば、親クラスとサブクラスの間の関数の呼び出し規則ってどうだったんでしたっけ? C++の基本中の基本だと思いますが、これは押さえとかないとやってることが全く無駄になってしまいますね・・。と言うより、この方法はダメな気がしてきました。


つづく。



   

ブログを作る(無料) powered by So-netブログ

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。

×

この広告は1年以上新しい記事の更新がないブログに表示されております。