Qt 6 移行時の UI 差分と Python プラグイン移植差分を Krita 5.3.0 / 6.0.0 で確認した

Qt 6 移行時の UI 差分と Python プラグイン移植差分を Krita 5.3.0 / 6.0.0 で確認した

Krita 5.3.0 と Krita 6.0.0 を Windows 11 で比較し、Qt 5 から Qt 6 への移行に伴う UI 差分と、Python プラグイン移植差分を確認しました。
2026.03.26

はじめに

Krita 5.3.0 と Krita 6.0.0 の同時リリースが案内されました。 ただし、今回の関心は Krita 自体の新機能ではありません。Qt 利用者の立場から見ると、Krita 5.3.0 と Krita 6.0.0 は、Qt 5 系と Qt 6 系の違いによるアプリへの影響を観察しやすい比較対象といえます。 Krita 公式は、Krita 5.3.0 と Krita 6.0.0 について、ほぼ同じソースコードから生成されていると説明しています。つまり、両者を見比べると、Krita 固有の機能差というより、Qt 5 から Qt 6 への移行による変化を観察しやすいわけです。

今回は Windows 11 上で、Krita 5.3.0 と Krita 6.0.0 を比較しました。利用者目線の UI 差分と、開発者目線の Python プラグイン移植差分の 2 つを中心に見ました。先に結論を書くと、今回の検証で分かったことは次の通りです。

  • Windows 11 上では、Krita 5.3.0 と Krita 6.0.0 の見た目の差分はかなり小さく、起動直後、ファイルダイアログ、簡単な描画操作で、目視で分かる差はほぼない
  • PyQt5 から PyQt6 への移行差分、モジュール移動、API 変更のほうが現実的な移行コストになる

Krita とは

Krita とは、イラストやアニメーションなどの制作に使えるオープンソースのペイントアプリケーションです。Python スクリプトによる拡張機能を備えており、PyQt と Krita 独自 API に対応しています。

Qt とは

Qt とは、デスクトップ、組み込み、モバイル向けにアプリケーションと UI を構築するためのクロスプラットフォームフレームワークです。Qt 5 から Qt 6 への移行は、見た目だけでなく API やモジュール構成の差分にも関わります。

検証環境

  • OS: Windows 11
  • 比較対象: Krita 5.3.0、Krita 6.0.0
  • 配布形態: いずれもポータブル版の zip

スクリーンショット撮影時に使った初期ファイルは、256 x 256 px の白紙画像です。ズームは 100% にそろえています。

対象読者

  • Qt 5 系から Qt 6 系への移行影響を把握したい方
  • PyQt5 ベースの補助ツールやプラグインをメンテナンスしている方
  • 既存の Qt Widgets アプリで、利用者の見た目と開発者の移行コストの両方を知りたい方

参考

通常操作では大きな差は見えない

まず、起動直後の操作 UI を比較しました。ここで掲載しているのは、右クリックで表示したパレットを含む画面です。メニュー、ツールバー、ドッカー、ステータスバーを含めて見比べても、Krita 5.3.0 と Krita 6.0.0 の差はほとんど分かりませんでした。

キャンバス UI Krita 5.3.0

右クリックで起動したパレット (Krita 5.3.0)

キャンバス UI Krita 6.0.0

右クリックで起動したパレット (Krita 6.0.0)

ファイルダイアログについても同様です。見た目に差は見られませんでした。

ファイルダイアログ Krita 5.3.0

ファイルダイアログ (Krita 5.3.0)

ファイルダイアログ Krita 6.0.0

ファイルダイアログ (Krita 6.0.0)

簡単な描画操作も試しました。ブラシ描画やズームイン・アウトを試し、体感速度、拡大縮小時の違和感、ブラシカーソルの見え方に差がないのを確認しました。少なくとも今回の観測範囲では、Qt 6 へ移行しても Windows 11 上の Qt Widgets アプリの見た目が急に大きく変わるわけではありませんでした。

175% 表示ではわずかな差が出た

次に、Windows の表示スケールを 175% に切り替えて比較しました。175% 環境においても、目視で分かる差はほとんどありませんでした。

倍率 175 % 時のキャンバス UI (Krita 5.3.0)

倍率 175 % 時のキャンバス UI (Krita 5.3.0)

倍率 175 % 時のキャンバス UI (Krita 6.0.0)

倍率 175 % 時のキャンバス UI (Krita 6.0.0)

ただし、完全に同一ではありません。Krita 6.0.0 側では、次の差が確認できました。

  • UI のフォントサイズが小さい
  • ツールアイコンが大きい

このあたりは、Qt 6 の高 DPI まわりの既定動作変更と関係している可能性があります。Qt 公式の High DPI ドキュメント では、Qt 6 では高 DPI 対応が常時有効であり、スケール係数の丸め方の既定値も Qt 5 系とは異なることが説明されています。

また、細かな違いとして、ウィンドウタイトルも異なっていました。Krita 5.3.0 側は [未保存] (256.0 KiB) * - Krita と表示される一方、Krita 6.0.0 側は単に Krita と表示されました。

設定画面では余白の印象が少し違う

設定画面も比較しました。スクロールバー、チェックボックスなどに差はありませんでした。ただし、左側のカテゴリ選択 UI や余白に少し違いがありました。

設定画面 (Krita 5.3.0)

設定画面 (Krita 5.3.0)

設定画面 (Krita 6.0.0)

設定画面 (Krita 6.0.0)

Krita 6.0.0 では、カテゴリ選択 UI に縁がありません。また、上下左右の明るい灰色の余白も少なめでした。Krita 5.3.0 のほうが整って見える印象です。決して大きな差ではありませんが、Qt 6 への移行によって一部 UI の余白やコンポーネントの見え方にはわずかな差が出ることが分かります。

Qt 利用者にとって重要なのは Python プラグイン移植差分

ここからが本記事の本命です。Krita 5.3.0 には PyQt5 が、Krita 6.0.0 には PyQt6 が同梱されています。これらの挙動を確認するため、最小の Krita Python プラグインを 2 種類用意しました。ひとつは PyQt5 前提の probe、もうひとつは PyQt6 前提の probe です。

pyqt5_probe.py
pyqt5_probe.py
from pathlib import Path

from krita import Extension

RESULT_PATH = Path(__file__).resolve().parent / 'result.txt'

def append_result(label, ok, detail):
    with RESULT_PATH.open('a', encoding='utf-8') as f:
        status = 'OK' if ok else 'NG'
        f.write(f'[{status}] {label}: {detail}\n')

# Module import / attribute phase
for label, code in [
    ('PyQt5.QtWidgets.QAction', 'from PyQt5.QtWidgets import QAction'),
    ('PyQt5.QtWidgets.QOpenGLWidget', 'from PyQt5.QtWidgets import QOpenGLWidget'),
    ('PyQt5.QtCore.Qt.AlignLeft', 'from PyQt5.QtCore import Qt\n_ = Qt.AlignLeft'),
    ('PyQt5.QApplication.exec_', 'from PyQt5.QtWidgets import QApplication\n_ = QApplication.exec_'),
    ('PyQt5.QApplication.exec', 'from PyQt5.QtWidgets import QApplication\n_ = QApplication.exec'),
    ('PyQt5.QtWinExtras.QWinTaskbarButton', 'from PyQt5.QtWinExtras import QWinTaskbarButton'),
]:
    try:
        exec(code, {})
        append_result(label, True, 'import or attribute lookup succeeded')
    except Exception as e:
        append_result(label, False, repr(e))

class PyQt5ProbeExtension(Extension):
    def __init__(self, parent):
        super().__init__(parent)

    def setup(self):
        append_result('setup', True, 'extension setup called')

    def createActions(self, window):
        action = window.createAction('pyqt5_probe_action', 'PyQt5 Probe', 'tools/scripts')
        action.triggered.connect(self.action_triggered)
        append_result('createActions', True, 'action created')

    def action_triggered(self):
        append_result('action_triggered', True, 'action triggered')
pyqt6_probe.py
pyqt6_probe.py
from pathlib import Path

from krita import Extension

RESULT_PATH = Path(__file__).resolve().parent / 'result.txt'

def append_result(label, ok, detail):
    with RESULT_PATH.open('a', encoding='utf-8') as f:
        status = 'OK' if ok else 'NG'
        f.write(f'[{status}] {label}: {detail}\n')

# Module import / attribute phase
for label, code in [
    ('PyQt6.QtGui.QAction', 'from PyQt6.QtGui import QAction'),
    ('PyQt6.QtOpenGLWidgets.QOpenGLWidget', 'from PyQt6.QtOpenGLWidgets import QOpenGLWidget'),
    ('PyQt6.QtCore.Qt.AlignmentFlag.AlignLeft', 'from PyQt6.QtCore import Qt\n_ = Qt.AlignmentFlag.AlignLeft'),
    ('PyQt6.QApplication.exec_', 'from PyQt6.QtWidgets import QApplication\n_ = QApplication.exec_'),
    ('PyQt6.QApplication.exec', 'from PyQt6.QtWidgets import QApplication\n_ = QApplication.exec'),
    ('PyQt6.QtWinExtras.QWinTaskbarButton', 'from PyQt6.QtWinExtras import QWinTaskbarButton'),
]:
    try:
        exec(code, {})
        append_result(label, True, 'import or attribute lookup succeeded')
    except Exception as e:
        append_result(label, False, repr(e))

class PyQt6ProbeExtension(Extension):
    def __init__(self, parent):
        super().__init__(parent)

    def setup(self):
        append_result('setup', True, 'extension setup called')

    def createActions(self, window):
        action = window.createAction('pyqt6_probe_action', 'PyQt6 Probe', 'tools/scripts')
        action.triggered.connect(self.action_triggered)
        append_result('createActions', True, 'action created')

    def action_triggered(self):
        append_result('action_triggered', True, 'action triggered')

これらを実際に各バージョンの Krita に配置し、Python プラグインマネージャー経由で有効化したうえで、プラグイン内で import 結果を result.txt に出力させました。

用意したプラグイン 2 種

結果として、Krita 5.3.0 では PyQt5 前提の probe が動作し、Krita 6.0.0 では PyQt6 前提の probe が動作しました。一方で、逆方向の probe はどちらも ModuleNotFoundError で失敗しました。検証により、 移行時に影響するのは単純な import 名の違いだけではない ことを、Krita 上で直接確認できました。

# Qt 5 系で確認できた書き方
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QAction, QApplication, QOpenGLWidget
print(Qt.AlignLeft)
_ = QApplication.exec_

# Qt 6 系で確認できた書き方
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import QApplication
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
print(Qt.AlignmentFlag.AlignLeft)
_ = QApplication.exec

QAction の所属モジュールが異なる

Qt 5 系では、次の import が成功します。

from PyQt5.QtWidgets import QAction

一方、Qt 6 系では次の import が失敗します。

from PyQt6.QtWidgets import QAction

Qt 6 系で成功するのは、こちらです。

from PyQt6.QtGui import QAction

QOpenGLWidget の所属モジュールが異なる

Qt 5 系では、次の import が成功します。

from PyQt5.QtWidgets import QOpenGLWidget

しかし、Qt 6 系ではこの import は失敗します。Qt 6 系では、次のように QtOpenGLWidgets から import する必要があります。

from PyQt6.QtOpenGLWidgets import QOpenGLWidget

enum の参照方法が異なる

Qt 5 系では Qt.AlignLeft が使えますが、Qt 6 系ではこの名前がありません。Qt 6 系では、Qt.AlignmentFlag.AlignLeft のような書き方が必要です。

QApplication.exec_() の扱いが異なる

Krita 5.3.0 側では、QApplication.exec_QApplication.exec のどちらも参照できました。一方、Krita 6.0.0 側では QApplication.exec は参照できましたが、QApplication.exec_AttributeError になりました。Qt 5 系のコードで exec_() に依存している場合は、Qt 6 系へ移すときに見直しが必要です。

ただし、これは Krita プラグイン本体で頻繁に使う差分というより、周辺の PyQt 補助コードや単体ツールを含めて移植時に注意したい差分です。

見た目よりコードのほうが移行差分は大きい

今回の検証で分かったのは、利用者の画面に出る差分が小さい一方、開発者が触るコード側の差分は無視できないという点です。Krita 5.3.0 と Krita 6.0.0 は、キャンバス、ファイルダイアログ、描画ではほぼ同じ挙動に見えました。175% 表示や設定画面でわずかな差はあるものの、アプリ全体の印象を大きく変えるほどではありません。

一方で、Python プラグインや周辺ツールは別です。PyQt5 前提のコードをそのまま持ち込むと、import 文、モジュール移動、enum の参照方法、exec_() の扱いなどで問題が起きる可能性があります。

まとめ

Krita 5.3.0 と Krita 6.0.0 を Windows 11 上で比較した結果、利用者目線では差分はかなり小さいと分かりました。Qt 6 へ移行しても、少なくとも今回の範囲では、見た目が急に大きく変わるわけではありません。ただし、Qt 利用者の視点では話が変わります。見た目の差よりも、PyQt5 から PyQt6 への移行差分、モジュール移動、API 変更のほうが現実的な移行コストになります。

Krita は、Qt 6 移行が利用者画面には影響しにくい一方で、開発者コードには影響することを見る題材として面白い比較対象でした。Qt 5 系のアプリを Qt 6 系へ移すときは、まず UI 崩れを疑いたくなります。しかし実際には、プラグインや補助コードの移植計画をメインに据えるのがよさそうです。

この記事をシェアする

FacebookHatena blogX

関連記事