Qt Graphics Viewの要!mapFromScene()で実現する正確なグラフィック描画

2025-05-27

もう少し詳しく説明します。

QtのGraphics View Frameworkは、大量の2Dグラフィックスアイテムを管理し、表示するための強力な仕組みです。主に以下の3つのクラスで構成されます。

  1. QGraphicsScene:

    • すべてのグラフィックスアイテム(QGraphicsItemのサブクラス、例: QGraphicsRectItem, QGraphicsEllipseItemなど)が配置される仮想的なキャンバスです。
    • アイテムの追加、削除、管理、イベントの伝播などを担当します。
    • このシーン上の座標系を「シーン座標」と呼びます。
  2. QGraphicsView:

    • QGraphicsSceneの内容を表示するためのウィジェットです。
    • 一つのシーンに対して複数のビューを持つことができます。
    • ズーム、パン(画面移動)、回転などの変換を適用できます。
    • このビューポート(表示領域)上の座標系を「ビュー座標」と呼びます。これは、QGraphicsViewが持つQWidgetとしてのクライアント座標に相当します。
  3. QGraphicsItem:

    • シーン上に配置される個々のグラフィックスオブジェクトです。
    • 自身の位置、サイズ、描画方法などを定義します。
    • 各アイテムは独自の「アイテム座標」を持ちます。

ユーザーがマウスをクリックしたり、何かを描画したりする際に、その位置がQGraphicsViewビュー座標で得られます。しかし、実際にシーン上のアイテムとインタラクトしたり、シーン上の特定の位置にアイテムを追加したりするには、そのビュー座標をシーン座標に変換する必要があります。

QGraphicsView::mapFromScene()は、この逆を行います。つまり、シーン座標で指定された点を、QGraphicsViewビュー座標に変換します。

使用例



QGraphicsView::mapFromScene() の一般的なエラーとトラブルシューティング

期待する座標値が得られない(オフセット、スケール、回転のずれ)

  • トラブルシューティング:

    • 変換の確認:
      • qDebug() << view->transform(); を使って、ビューの現在の変換行列を確認します。意図した変換が適用されているか確認してください。
      • view->resetTransform(); を一時的に呼び出して、変換をリセットし、初期状態での mapFromScene() の結果を確認します。これで正しくなるなら、変換の適用方法に問題がある可能性が高いです。
    • 座標系の理解を深める:
      • シーン座標、ビュー座標、アイテム座標のそれぞれの定義と、それらがどのように関連しているかを再確認します。
      • アイテムの座標をビュー座標に変換したい場合は、item->mapToScene(itemPoint) (アイテム座標 -> シーン座標) -> view->mapFromScene(scenePoint) (シーン座標 -> ビュー座標) の手順を踏むようにします。
    • アライメントの統一:
      • 通常、view->setAlignment(Qt::AlignLeft | Qt::AlignTop); を設定して、ビューポートの左上が常にシーンの左上(またはそれに近い)に対応するようにすると、理解しやすくなります。
    • setSceneRect() の確認:
      • シーンにアイテムを追加する前に scene->setSceneRect(QRectF(0, 0, width, height)); のように明示的にシーンの有効範囲を設定します。または、scene->itemsBoundingRect() の結果を確認し、意図した範囲になっているかを確認します。
  • 原因:

    • ビューの変換が正しく適用されていない: QGraphicsViewsetTransform(), scale(), rotate(), translate() などの関数で変換行列を保持しています。これらの変換が mapFromScene() の計算に影響します。例えば、setTransform() で一度設定した変換を、その後 scale()translate() で変更している場合、意図しない変換が累積されている可能性があります。
    • シーンの原点とアイテムの原点の混同: QGraphicsScene の原点 (0,0) と、QGraphicsItem のローカル座標系における原点 (0,0) を混同している場合があります。mapFromScene() はあくまでシーン座標をビュー座標に変換するので、アイテムのローカル座標を直接渡すと誤った結果になります。アイテムの座標を変換したい場合は、まず QGraphicsItem::mapToScene() でアイテム座標をシーン座標に変換してから、QGraphicsView::mapFromScene() を使う必要があります。
    • ビューポートの配置 (Alignment): QGraphicsView::setAlignment()Qt::AlignLeft | Qt::AlignTop 以外に設定されている場合、ビューポート内にシーンがセンタリングされたり、別の位置に配置されたりします。これにより、mapFromScene() の結果が直感と異なることがあります。特に、シーン全体がビューポートに収まる場合、このアライメントが影響します。
    • シーンの矩形 (Scene Rect) の不適切: QGraphicsScene::setSceneRect() が正しく設定されていない場合、スクロールバーの範囲や、ビューがシーン全体をどう扱うか (特にズームがない場合) に影響を与え、座標変換の解釈にずれが生じることがあります。
    • ビューの初期化タイミング: QGraphicsViewshow() される前や、シーンがビューに設定される前に mapFromScene() を呼び出すと、意味のない結果が返されることがあります。ビューが完全に初期化され、表示可能な状態になってから呼び出すことが重要です。
  • エラーの症状:

    • mapFromScene() で変換したビュー座標が、実際の画面上の表示位置とずれている。
    • ズームイン・ズームアウト、パン(画面移動)、回転などの操作を行った後に特にずれが顕著になる。

スクロールバーの動きと座標変換の不一致

  • トラブルシューティング:

    • スクロールポリシーの確認: スクロールバーが適切に表示されるように、Qt::ScrollBarAsNeededQt::ScrollBarAlwaysOn を設定して、スクロールの状態を可視化します。
    • ensureVisible() の活用: 特定のシーン上の領域やアイテムをビューポートに確実に見せるには、QGraphicsView::ensureVisible() を使うと便利です。これにより、ビューが自動的にスクロール位置を調整してくれます。
  • 原因:

    • QGraphicsView::mapFromScene() は内部的にスクロールバーのオフセットも考慮して計算されます。しかし、QGraphicsView のスクロールバーの挙動が、開発者の意図と異なる設定になっている場合に混乱が生じることがあります。
    • 例えば、QGraphicsView::setHorizontalScrollBarPolicy()QGraphicsView::setVerticalScrollBarPolicy()Qt::ScrollBarAlwaysOff に設定されているが、シーンがビューポートより大きい場合に、スクロール動作が内部的に行われてもスクロールバーが表示されないため、位置関係が把握しにくいことがあります。
  • エラーの症状:

    • スクロールバーを動かすと、mapFromScene() で得られるビュー座標と、実際に画面に表示される位置との間にずれが生じる。

浮動小数点誤差 (Floating Point Precision Issues)

  • トラブルシューティング:

    • 計算の回数を減らす: 頻繁な座標変換の繰り返しは避けるようにします。
    • 原点に近い場所で操作する: 可能な限り、座標系の原点 (0,0) に近い範囲で作業を行うようにします。大きな座標値を扱う場合は、一時的に座標系をシフトすることを検討することもできます。
    • QGraphicsItem のローカル座標系を最大限活用する: アイテム内の描画やインタラクションは、可能な限りアイテムのローカル座標系で行うことで、シーン座標やビュー座標への変換回数を減らすことができます。
  • 原因:

    • QPointFQRectF などの浮動小数点数(qreal、通常は double)を使用しているため、計算のたびに微小な誤差が発生する可能性があります。
  • エラーの症状:

    • mapFromScene()mapToScene() を繰り返し呼び出したり、非常に大きな座標値や非常に小さなスケールで操作したりすると、わずかなずれが累積して最終的に目に見えるずれになることがある。

イベントハンドラ内での座標変換のずれ

  • トラブルシューティング:

    • 適切な座標系を使用する:
      • QGraphicsView のイベントハンドラ内:
        • ビューポートのクリック位置をシーン座標に変換: QPointF scenePos = mapToScene(event->pos());
        • ビューポートのクリック位置をシーン座標に変換し、それを再びビュー座標に変換: QPointF viewPos = event->pos(); // これは既にビュー座標
      • QGraphicsItem のイベントハンドラ内:
        • アイテムのクリック位置をシーン座標に変換: QPointF scenePos = mapToScene(event->pos());
        • アイテムのクリック位置をビュー座標に変換: QPointF viewPos = scene()->views().first()->mapFromScene(mapToScene(event->pos())); (複数のビューがある場合は、どのビューを使うか注意)
    • qDebug() を使って、各イベントで得られる event->pos(), event->scenePos(), event->screenPos() の値を確認し、期待する値が取得できているかデバッグします。
  • 原因:

    • QGraphicsView のイベントは、QGraphicsSceneMouseEvent の形でシーンに伝播します。このイベントオブジェクトには、pos() (アイテムローカル座標), scenePos() (シーン座標), screenPos() (グローバルスクリーン座標) など、複数の座標が提供されています。
    • ビューのイベントハンドラ (QGraphicsView::mousePressEvent など) の中で event->pos() を使う場合、それはビューポートのローカル座標です。これを mapToScene() でシーン座標に変換する必要があります。
    • 一方、QGraphicsItem のイベントハンドラ (QGraphicsItem::mousePressEvent など) の中で event->pos() を使う場合、それは既にアイテムのローカル座標です。これをシーン座標に変換するには QGraphicsItem::mapToScene(event->pos()) を使います。
  • エラーの症状:

    • mousePressEventmouseMoveEvent などのイベントハンドラ内で event->pos() (ビューポートローカル座標) を使って mapToScene() / mapFromScene() を行うと、期待通りに動かない。
  • 変換の可逆性を確認: point = view->mapFromScene(scenePoint); scenePointBack = view->mapToScene(point); のように、変換して逆変換を試すと、元の scenePoint に戻るはずです。戻らない場合は、何らかの問題があります。
  • シンプルな例で試す: 複雑なアプリケーションで問題が発生した場合、最小限の QGraphicsSceneQGraphicsView を作成し、変換のみをテストするシンプルなコードで現象が再現するか試します。
  • qDebug() を多用する: 座標変換のデバッグには、qDebug() を使って変換前と変換後の座標値を頻繁に出力し、想定通りの値になっているか確認するのが最も効果的です。


シーン上の特定の位置がビューのどこに表示されるかを知る

最も基本的な使用例です。シーン上の任意の点を、ビューポート上のピクセル座標に変換します。

#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QDebug>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    // シーンとビューを作成
    QGraphicsScene scene;
    scene.setSceneRect(-200, -150, 400, 300); // シーンの有効範囲を設定

    QGraphicsView view(&scene);
    view.setRenderHint(QPainter::Antialiasing); // アンチエイリアシングを有効に

    // シーンにアイテムを追加
    QGraphicsRectItem *rectItem = new QGraphicsRectItem(0, 0, 100, 50);
    rectItem->setPos(50, 20); // シーン座標 (50, 20) に配置
    rectItem->setBrush(Qt::blue);
    scene.addItem(rectItem);

    QGraphicsEllipseItem *ellipseItem = new QGraphicsEllipseItem(0, 0, 80, 80);
    ellipseItem->setPos(-100, -70); // シーン座標 (-100, -70) に配置
    ellipseItem->setBrush(Qt::red);
    scene.addItem(ellipseItem);

    // シーンの中心 (0,0) に小さな円を追加して目印にする
    QGraphicsEllipseItem *centerMark = new QGraphicsEllipseItem(-5, -5, 10, 10);
    centerMark->setBrush(Qt::green);
    scene.addItem(centerMark);

    // ----------------------------------------------------------------------
    // QGraphicsView::mapFromScene() の使用例
    // ----------------------------------------------------------------------

    // シーン座標 (0, 0) がビューのどこに表示されるか
    QPointF sceneOrigin = QPointF(0, 0);
    QPoint viewOrigin = view.mapFromScene(sceneOrigin);
    qDebug() << "Scene origin (0,0) in view coordinates:" << viewOrigin;

    // rectItem のシーン座標における中心点
    QPointF rectCenterInScene = rectItem->scenePos() + rectItem->boundingRect().center();
    QPoint rectCenterInView = view.mapFromScene(rectCenterInScene);
    qDebug() << "RectItem center in scene (" << rectCenterInScene.x() << "," << rectCenterInScene.y()
             << ") in view coordinates:" << rectCenterInView;

    // ellipseItem のシーン座標における左上角
    QPointF ellipseTopLeftInScene = ellipseItem->scenePos();
    QPoint ellipseTopLeftInView = view.mapFromScene(ellipseTopLeftInScene);
    qDebug() << "EllipseItem top-left in scene (" << ellipseTopLeftInScene.x() << "," << ellipseTopLeftInScene.y()
             << ") in view coordinates:" << ellipseTopLeftInView;

    // ----------------------------------------------------------------------

    // ウィンドウの設定
    QWidget window;
    QVBoxLayout *layout = new QVBoxLayout(&window);
    layout->addWidget(&view);

    QPushButton *zoomInButton = new QPushButton("Zoom In");
    QObject::connect(zoomInButton, &QPushButton::clicked, [&]() {
        view.scale(1.2, 1.2);
        qDebug() << "--- After Zoom In ---";
        qDebug() << "Scene origin (0,0) in view coordinates:" << view.mapFromScene(sceneOrigin);
        qDebug() << "RectItem center in scene (" << rectCenterInScene.x() << "," << rectCenterInScene.y()
                 << ") in view coordinates:" << view.mapFromScene(rectCenterInScene);
    });
    layout->addWidget(zoomInButton);

    QPushButton *panLeftButton = new QPushButton("Pan Left");
    QObject::connect(panLeftButton, &QPushButton::clicked, [&]() {
        view.translate(-50, 0); // ビューを左にパン
        qDebug() << "--- After Pan Left ---";
        qDebug() << "Scene origin (0,0) in view coordinates:" << view.mapFromScene(sceneOrigin);
        qDebug() << "RectItem center in scene (" << rectCenterInScene.x() << "," << rectCenterInScene.y()
                 << ") in view coordinates:" << view.mapFromScene(rectCenterInScene);
    });
    layout->addWidget(panLeftButton);

    window.setWindowTitle("QGraphicsView::mapFromScene() Example");
    window.resize(600, 400);
    window.show();

    return a.exec();
}

解説:

  • Zoom In ボタンと Pan Left ボタンを追加し、ビューの変換(スケールや移動)を行った後に再度 mapFromScene() を呼び出すことで、ビューの変換が結果に影響を与えることを示しています。ビューがズームされたりパンされたりすると、同じシーン座標がビューポートの異なるピクセル位置にマッピングされることがわかります。
  • 同様に、アイテムの中心点や左上角のシーン座標をビュー座標に変換し、出力します。
  • view.mapFromScene(sceneOrigin) を使用して、シーンの原点 (0,0) がビューポート上のどこにマッピングされるかをコンソールに出力します。
  • このコードでは、QGraphicsScene 上に2つのアイテム(四角と円)と、シーンの原点を示す小さな円を配置します。

マウスイベントを利用してビューポート上の位置を特定する

ユーザーがマウスをクリックしたビューポート上の位置(これは QMouseEvent::pos() で得られるビュー座標)から、対応するシーン座標や、そこにあるアイテムを特定する際に mapToScene() と組み合わせて使用されます。mapFromScene() はこの逆変換のデバッグや表示位置の調整によく使われます。

ここでは、ビュー上でのマウスイベントの位置をシーン座標に変換し、さらにそのシーン座標が特定のアイテムのローカル座標でどうなるかを調べ、最終的にそのアイテムのローカル座標をビュー座標に戻す例を示します。

#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QMouseEvent>
#include <QDebug>
#include <QVBoxLayout>
#include <QWidget>

// カスタムQGraphicsViewクラスを作成し、mousePressEventをオーバーライド
class MyGraphicsView : public QGraphicsView
{
public:
    MyGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr)
        : QGraphicsView(scene, parent)
    {
        setRenderHint(QPainter::Antialiasing);
        setMouseTracking(true); // マウスが移動したときにイベントを発生させる
    }

protected:
    void mousePressEvent(QMouseEvent *event) override
    {
        // 1. ビュー座標 (イベントの位置)
        QPoint viewPos = event->pos();
        qDebug() << "Mouse Press (View Coords):" << viewPos;

        // 2. ビュー座標をシーン座標に変換 (mapToSceneを使用)
        QPointF scenePos = mapToScene(viewPos);
        qDebug() << "Mouse Press (Scene Coords):" << scenePos;

        // 3. シーン座標にあるアイテムを取得
        QGraphicsItem *item = scene()->itemAt(scenePos, transform());
        if (item) {
            qDebug() << "Item clicked:" << item->type(); // type()はデバッグ用、通常はRTTIなどを使う

            // 4. シーン座標をアイテムのローカル座標に変換 (QGraphicsItem::mapFromSceneを使用)
            QPointF itemPos = item->mapFromScene(scenePos);
            qDebug() << "Mouse Press (Item Coords):" << itemPos;

            // 5. アイテムのローカル座標をビュー座標に変換 (QGraphicsView::mapFromScene() と QGraphicsItem::mapToScene() を組み合わせる)
            // このステップは、QGraphicsView::mapFromScene() の使用例として重要
            QPointF itemLocalPoint = QPointF(0,0); // 例えば、アイテムの原点
            QPointF itemOriginInScene = item->mapToScene(itemLocalPoint); // アイテムの原点をシーン座標に
            QPoint itemOriginInView = mapFromScene(itemOriginInScene); // シーン座標をビュー座標に
            qDebug() << "Item origin (0,0) in scene coords (" << itemOriginInScene.x() << "," << itemOriginInScene.y()
                     << ") is displayed at view coords:" << itemOriginInView;

            // 例: クリックされたアイテムの中心をビューのどこかに描画したい場合
            QPointF itemCenterInScene = item->scenePos() + item->boundingRect().center();
            QPoint itemCenterInView = mapFromScene(itemCenterInScene);
            qDebug() << "Item center in scene (" << itemCenterInScene.x() << "," << itemCenterInScene.y()
                     << ") is displayed at view coords:" << itemCenterInView;
        } else {
            qDebug() << "No item clicked at this position.";
        }

        QGraphicsView::mousePressEvent(event); // デフォルトの処理も実行
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene scene;
    scene.setSceneRect(-200, -150, 400, 300);

    MyGraphicsView view(&scene);
    view.resize(600, 400);

    QGraphicsRectItem *rectItem = new QGraphicsRectItem(0, 0, 100, 50);
    rectItem->setPos(50, 20);
    rectItem->setBrush(Qt::blue);
    scene.addItem(rectItem);

    QGraphicsEllipseItem *ellipseItem = new QGraphicsEllipseItem(0, 0, 80, 80);
    ellipseItem->setPos(-100, -70);
    ellipseItem->setBrush(Qt::red);
    scene.addItem(ellipseItem);

    // シーンの中心 (0,0) に小さな円を追加して目印にする
    QGraphicsEllipseItem *centerMark = new QGraphicsEllipseItem(-5, -5, 10, 10);
    centerMark->setBrush(Qt::green);
    scene.addItem(centerMark);

    view.setWindowTitle("QGraphicsView::mapFromScene() in MouseEvent");
    view.show();

    return a.exec();
}

解説:

  • 最後に、mapFromScene() の最も直接的な使用例として、「アイテムの原点(アイテムローカル座標の (0,0))が、シーン座標のどこにあり、それが最終的にビューポートのどこに表示されるか」 を計算しています。これは、item->mapToScene(itemLocalPoint) でアイテムローカル座標をシーン座標に変換し、その後 mapFromScene(scenePoint) でビュー座標に変換するという2段階のプロセスになります。
  • item->mapFromScene(scenePos) を使って、クリックされたシーン座標が、そのアイテム自身のローカル座標系ではどの位置になるかを調べます。
  • scene()->itemAt(scenePos, transform()) で、そのシーン座標にある最前面のアイテムを取得します。
  • mapToScene(viewPos) を使って、このビュー座標を対応するシーン座標に変換します。
  • ユーザーがビューをクリックすると、event->pos() でクリックされたビューポートのピクセル座標が得られます。
  • MyGraphicsView クラスを継承し、mousePressEvent をオーバーライドしています。

QGraphicsView はデフォルトでズームやパンの機能を提供しますが、カスタムの挙動を実装する際に mapFromScene() が役立ちます。例えば、マウスホイールでズームする際に、マウスカーソルの位置を中心にズームするような場合です。

#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QWheelEvent>
#include <QDebug>
#include <QVBoxLayout>
#include <QWidget>

class ZoomableGraphicsView : public QGraphicsView
{
public:
    ZoomableGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr)
        : QGraphicsView(scene, parent)
    {
        setRenderHint(QPainter::Antialiasing);
        setTransformationAnchor(AnchorUnderMouse); // マウスカーソルを中心にズーム
    }

protected:
    void wheelEvent(QWheelEvent *event) override
    {
        // ズームイン/アウトの係数
        qreal scaleFactor = 1.15; // 15% ずつズーム

        if (event->angleDelta().y() > 0) {
            // スクロールアップ (ズームイン)
            scale(scaleFactor, scaleFactor);
        } else {
            // スクロールダウン (ズームアウト)
            scale(1.0 / scaleFactor, 1.0 / scaleFactor);
        }

        // ここでmapFromScene()の直接的な使用は少ないが、
        // ズーム後のシーンの原点や特定の点がビューのどこにあるかをデバッグ目的で確認できる
        qDebug() << "--- After Wheel Zoom ---";
        qDebug() << "Scene origin (0,0) in view coordinates:" << mapFromScene(QPointF(0,0));

        // 例えば、マウスカーソルが指していたシーン上の点が、ズーム後も同じビュー座標に表示されるか確認
        // (AnchorUnderMouse を使っているので、通常はそうなるはず)
        QPointF mousePosInScene = mapToScene(event->pos()); // ズーム前のマウス位置をシーン座標に
        QPoint mousePosInViewAfterZoom = mapFromScene(mousePosInScene); // そのシーン座標をズーム後のビュー座標に
        qDebug() << "Mouse position (" << event->pos().x() << "," << event->pos().y()
                 << ") in view before zoom, corresponds to scene point (" << mousePosInScene.x() << "," << mousePosInScene.y()
                 << "). After zoom, this scene point is at view coords:" << mousePosInViewAfterZoom;


        event->accept(); // イベントを処理済みとしてマーク
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene scene;
    scene.setSceneRect(-200, -150, 400, 300);

    ZoomableGraphicsView view(&scene);
    view.resize(600, 400);

    QGraphicsRectItem *rectItem = new QGraphicsRectItem(0, 0, 100, 50);
    rectItem->setPos(50, 20);
    rectItem->setBrush(Qt::blue);
    scene.addItem(rectItem);

    QGraphicsEllipseItem *ellipseItem = new QGraphicsEllipseItem(0, 0, 80, 80);
    ellipseItem->setPos(-100, -70);
    ellipseItem->setBrush(Qt::red);
    scene.addItem(ellipseItem);

    QGraphicsEllipseItem *centerMark = new QGraphicsEllipseItem(-5, -5, 10, 10);
    centerMark->setBrush(Qt::green);
    scene.addItem(centerMark);

    view.setWindowTitle("Custom Zoom with mapFromScene() Debug");
    view.show();

    return a.exec();
}

解説:

  • mapFromScene() 自体が直接ズームの実装に影響するわけではありませんが、デバッグ目的でズーム後の特定のシーン座標がビューのどこに表示されるかを確認するために使用されています。これにより、ズームが意図通りに動作しているか(例えば、マウスカーソル下の点がズーム後もそのカーソル位置に近いビュー座標に留まっているか)を検証できます。
  • setTransformationAnchor(AnchorUnderMouse) を設定することで、マウスカーソルの位置を中心にズームが適用されます。
  • ZoomableGraphicsView クラスでは、wheelEvent をオーバーライドしてマウスホイールによるズームを実装しています。


以下に、mapFromScene() の代替となる、または関連する概念とプログラミング方法を説明します。

これは mapFromScene() の直接的な代替ではありませんが、特定のアイテムのローカル座標をビュー座標に変換する際に、間接的に mapFromScene() と同等の結果を得るために使用される主要な方法です。

  • QGraphicsView::mapFromScene(QPointF point): シーン座標をビュー座標に変換します。
  • QGraphicsItem::mapToScene(QPointF point): アイテムのローカル座標をシーン座標に変換します。