#はじめに
Qt Advent Calendar 2023 も二日目がやってまいりました。誰も埋める人がいなかったので勢いで二日目も登録しましたが、案の定あまり記事を書く時間が取れず・・・。本当は座標系の続きでQTransform - 行列計算について触れようかと思っていたのですが、時間もないので少し脱線してQPaintDeviceの方を見ていきましょう。
QPaintDeviceとは
昨日の記事では、QPainterと座標系の基本について記事を出しましたが、その中で「QPainterは、QPaintDeviceを継承するクラスに対して描画命令を行うクラス」と紹介しました。つまり「QPaintDevice とは QPainter による2D描画の対象となる抽象クラス」です。
Qt6でのQPaintDeviceは昨日も紹介しましたが省略していた派生クラスも少し書き足してみました。
- QWidget
- 派生は多数あるので省略
- QPaintDeviceWindow
- QOpenGLWindow
- QRasterWindow
- QOpenGLPaintDevice
- QPagedPaintDevice
- QPdfWriter
- QPrinter
- QImage
- QPixmap
- QBitmap
- QPicture
- QSvgGenerator
QPixmap v.s. QImage
先ほどの QPaintDevice の中でもQtユーザーが一番よく目にするのは QWidget かと思いますが、その次によく使う候補として争うのは QPixmap か QImage かのいずれかになるかと思います。この二つのクラスは画像ファイルをQtで読み込んだり書き出したりするために利用するクラスです。最初の頃はどちらを使うのが良いのかわからず悩んだりしたことが多いのではないでしょうか。
デフォルトでサポートしているフォーマット
以下は、QPixmapのドキュメントに記載されているサポートしている画像フォーマットです。
- BMP (Read/Write)
- GIF (Read)
- JPG/JPEG (Read/Write)
- PNG (Read/Write)
- PBM (Read)
- PGM (Read)
- PPM (Read/Write)
- XBM (Read/Write)
- XPM (Read/Write)
と書きましたが、実はQImageでも同じ記載になっています。そして両者ともサポートされているファイル形式の完全なリストは、以下で調べるようにと記載されています。
- QImageReader::supportedImageFormats()
- QImageWriter::supportedImageFormats()
では一体何が違うのか
大きな違いは、QPixmapはプラットフォーム依存で保持するイメージデータが決まり、QImageはプラットフォーム非依存で、指定したフォーマットあるいは読み込んだ画像ファイルのフォーマットでイメージを保持します。
どういうことかと言うと、QPixmapは可能な限りそのプラットフォーム(より正確には描画に利用しているQPAプラグイン)でそのまま描画に利用できるイメージで画像を保存するよう努めます。これはプラットフォームに依存するため、同じ画像を保持する場合でも、プラットフォームにより利用するメモリサイズなどには差がでてきます。その画像保持を誰が行うのかもプラットフォーム依存と決められています。
このためQPixmapの利用の前には必ずQGuiApplicationのインスタンスの生成が必要です。これがないとそのプラットフォームで何を利用すべきなのかが確定しないためです。QGuiApplicationのインスタンス生成前にQPixmapのインスタンスを生成すると
QPixmap: Must construct a QGuiApplication before a QPixmap
といって怒られます。なお、ネットで調べているとスレッドで描画するのは危険といった話が出てきます。たしかにQt4時代はX11でGUIスレッド以外でコンストラクトすると
QPixmap: It is not safe to use pixmaps outside the GUI thread
として怒られていました。ただ、現在ではきちんと今のプラットフォームがスレッドでの利用に対応しているかをテストしていますので、警告がでなければ一応利用は可能と言うことになります。
static bool qt_pixmap_thread_test()
{
if (Q_UNLIKELY(!QCoreApplication::instance())) {
qFatal("QPixmap: Must construct a QGuiApplication before a QPixmap");
return false;
}
if (QGuiApplicationPrivate::instance()
&& qApp->thread() != QThread::currentThread()
&& !QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedPixmaps)) {
qWarning("QPixmap: It is not safe to use pixmaps outside the GUI thread on this platform");
return false;
}
return true;
}
QPixmapは内部的には以下のようなQPlatformPixmapのサブクラスを使って生成されます。
- QX11PlatformPixmap
- QWindowsDirect2DPlatformPixmap
- QBlittablePlatformPixmap
- QRasterPlatformPixmap
もう一つの特徴としてGUIスレッド上でQPixmapを使いファイルをロードした場合、QPixmapCacheへデータをキャッシュします。つまり一度ファイルから読み込むとキャッシュの状況次第では次の読み込みも高速に行える可能性があります。
共通の特徴としてはQPixmapもQImageも暗黙のデータ共有機構を利用しています。つまりコピーの段階では共有オブジェクトになっており変更の時点でディープコピーが発生することで変更を伴わないコピー時の負荷を削減しています。
その他のPaintDevice
昨日の記事では QRasterWindowを扱いました。QWidgetはこの記事を読むような物好きな方には説明不要でしょう。QImageとQPixmapは同じフォーマットのイメージを読み書きできるものの内部的なイメージデータ保持が異なることを開設しました。
そこで、他のPaintDeviceも駆け足て見ておきましょう。
QBitmap
QBitmapはQPixmapの派生で主にマスクに利用される深さ1を保証するモノクロのオフスクリーンペイントデバイスになります。
QSvgGenerator
QSvgGeneratorは、QPainterで行われた描画内容をSVGフォーマット形式に変換するためのペイントデバイスです。要するに描画をそのままSVGファイルへ変換できます。
QPicture
QPixtureは、描画した結果ではなく描画命令をシリアライズしたいわゆるメタファイルを生成するクラスです。解像度に依存せず、プラットフォーム非依存ですが一般的な形式というわけではなく、Qt独自形式のフォーマットになります。
QOpenGLWindow
QOpenGLWindow は QOpenGLWidgetと互換性のあるAPIを利用できるウィンドウを生成するためのクラスです。このウィンドウでは直接OpenGLのAPIで描画するとともにQPainterでも描画できるようになります。
QOpenGLPaintDevice
こちらもOpenGL向けのクラスで、QPaintDeviceにOpenGLコンテキストを追加しOpenGLでの描画も可能にするための派生クラスになっています。
QPagedPaintDevice
こちらはPDFや印刷物のようにページの概念とページレイアウトなどをもつペイントデバイスの派生となります。
まとめ
今回は時間がないため、QPainter関連にかこつけて中身のうっすい駄記事となりました。ごめんなさい。日曜日分の記事を書いて余力があれば手直しします・・・。
というわけで、明日こそはQTransformとウィンドウ・ビューポート周りの画像変換の記事を書きたいと