QImage vs QPixmap - まぁ、いわゆるQPaintDeviceのご紹介

＃はじめに

Qt Advent Calendar 2023　も二日目がやってまいりました。誰も埋める人がいなかったので勢いで二日目も登録しましたが、案の定あまり記事を書く時間が取れず・・・。本当は座標系の続きでQTransform - 行列計算について触れようかと思っていたのですが、時間もないので少し脱線してQPaintDeviceの方を見ていきましょう。

# QPaintDeviceとは

[昨日の記事](https://qiita.com/hermit4/items/a6fd561bdf587db61921)では、QPainterと座標系の基本について記事を出しましたが、その中で「QPainterは、QPaintDeviceを継承するクラスに対して描画命令を行うクラス」と紹介しました。つまり「QPaintDevice とは QPainter による２D描画の対象となる抽象クラス」です。

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のインスタンスを生成すると

```bash:
QPixmap: Must construct a QGuiApplication before a QPixmap
```

といって怒られます。なお、ネットで調べているとスレッドで描画するのは危険といった話が出てきます。たしかにQt4時代はX11でGUIスレッド以外でコンストラクトすると

```bash:
QPixmap: It is not safe to use pixmaps outside the GUI thread
```

として怒られていました。ただ、現在ではきちんと今のプラットフォームがスレッドでの利用に対応しているかをテストしていますので、警告がでなければ一応利用は可能と言うことになります。

```c++:pixmap.cpp
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の派生で主にマスクに利用される深さ１を保証するモノクロのオフスクリーンペイントデバイスになります。

## QSvgGenerator

QSvgGeneratorは、QPainterで行われた描画内容をSVGフォーマット形式に変換するためのペイントデバイスです。要するに描画をそのままSVGファイルへ変換できます。

## QPicture

QPixtureは、描画した結果ではなく描画命令をシリアライズしたいわゆるメタファイルを生成するクラスです。解像度に依存せず、プラットフォーム非依存ですが一般的な形式というわけではなく、Qt独自形式のフォーマットになります。

## QOpenGLWindow

QOpenGLWindow は　QOpenGLWidgetと互換性のあるAPIを利用できるウィンドウを生成するためのクラスです。このウィンドウでは直接OpenGLのAPIで描画するとともにQPainterでも描画できるようになります。

## QOpenGLPaintDevice

こちらもOpenGL向けのクラスで、QPaintDeviceにOpenGLコンテキストを追加しOpenGLでの描画も可能にするための派生クラスになっています。

## QPagedPaintDevice

こちらはPDFや印刷物のようにページの概念とページレイアウトなどをもつペイントデバイスの派生となります。


# まとめ
今回は時間がないため、QPainter関連にかこつけて中身のうっすい駄記事となりました。ごめんなさい。日曜日分の記事を書いて余力があれば手直しします・・・。

というわけで、明日こそはQTransformとウィンドウ・ビューポート周りの画像変換の記事を書きたいと