Image.sourceClipRect

2025-05-31

Image.sourceClipRect の説明

Image.sourceClipRectrect 型のプロパティで、以下の4つの値を持ちます。

  • height: 切り抜く高さ
  • width: 切り抜く幅
  • y: 切り抜きを開始するY座標
  • x: 切り抜きを開始するX座標

これらの座標とサイズは、元の画像(sourceプロパティで指定された画像)のピクセル単位で指定します。

なぜ sourceClipRect を使うのか?

このプロパティを使用する主な目的は以下の通りです。

  1. リソースの節約
    • 大きな画像の一部だけを表示したい場合、sourceClipRect を使うことで、画像全体をメモリにロードしたり、レンダリングしたりする必要がなくなります。特に大きな画像ファイル(例:巨大な地図画像やスプライトシート)から一部だけを切り出して表示する場合に非常に効率的です。
    • 一部の画像フォーマット(例えば、一部のベクターグラフィックス形式)では、指定された領域だけをレンダリングすることでCPU時間を節約できる場合があります。
  2. 部分的な表示
    • 画像の一部だけを特定の状況で表示したい場合(例:進行状況バーのグラフィックの一部を段階的に表示するなど)に便利です。

使用例

以下は、QMLで sourceClipRect を使用する例です。

import QtQuick 2.15

Rectangle {
    width: 400
    height: 300
    color: "lightgray"

    Image {
        // 表示したい画像のソース
        source: "images/my_big_image.png"

        // このImage要素自体の幅と高さ
        width: 200
        height: 150

        // 元画像のサイズ(オプションだが、sourceClipRectと組み合わせて使うと分かりやすい)
        // my_big_image.pngが例えば1024x768の画像だと仮定します。
        sourceSize.width: 1024
        sourceSize.height: 768

        // 元画像の一部を切り抜いて表示する
        // (100, 100)から始まり、幅200、高さ150の領域を切り抜く
        sourceClipRect: Qt.rect(100, 100, 200, 150)

        // このImage要素を親のRectangleの中央に配置
        anchors.centerIn: parent

        // 切り抜いた画像を滑らかにスケーリングするかどうか
        smooth: true
    }
}

この例では、my_big_image.png という大きな画像の中から、(100, 100) の位置を左上隅として、幅 200、高さ 150 の四角い領域を切り抜いて表示します。切り抜かれた画像は、QMLの Image 要素の widthheight プロパティに基づいて表示サイズが調整されます。

  • sourceSize プロパティを適切に設定すると、sourceClipRect と連携して、より正確な画像の読み込みと表示が可能になります。sourceSize が設定されていない場合、Qtは画像をロードしてその実際のサイズを判別しようとします。
  • sourceClipRectundefined に設定することで、画像全体を再度ロードして表示することができます。
  • sourceClipRect は、QMLの Image 要素でのみ利用可能です。


画像が表示されない、または一部しか表示されない

よくある原因

  • 画像が完全にロードされる前に sourceClipRect が設定されている
    画像のロードは非同期で行われるため、画像が準備できる前に sourceClipRect の計算が行われると、予期しない結果になることがあります。
  • sourceClipRect と Image 要素のサイズが矛盾している
    Image 要素自体の widthheight が小さすぎて、切り抜かれた画像全体が表示しきれていない。
  • 画像のロードに失敗している
    source プロパティで指定された画像ファイルが見つからない、またはサポートされていない形式である。
  • sourceClipRect の値が不正
    x, y, width, height のいずれかの値が元の画像の範囲を超えている、または負の値になっている。

トラブルシューティング

  • Image 要素の width と height の確認
    Image 要素自体のサイズが、切り抜かれた画像を表示するのに十分な大きさがあるか確認します。必要に応じて fillMode プロパティも調整します。
  • sourceClipRect の設定タイミングの調整
    • Image.status === Image.Ready になった後に sourceClipRect を設定することで、画像が完全にロードされてからクリッピングが行われるようにします。
    Image {
        id: myImage
        source: "images/my_big_image.png"
        // sourceClipRect は初期値として設定しないか、または Image.Ready になるまで無効な値を設定する
        sourceClipRect: Qt.rect(0, 0, 0, 0) // 仮の値
    
        onStatusChanged: {
            if (myImage.status === Image.Ready) {
                // 画像がロードされた後に正しい sourceClipRect を設定
                myImage.sourceClipRect = Qt.rect(100, 100, 200, 150);
            }
        }
    }
    
  • 画像ファイルのパスと形式の確認
    source プロパティに指定されたパスが正しいか、画像ファイルがプロジェクトの適切な場所にあるか、そしてQtがサポートする画像形式(PNG, JPEGなど)であるかを確認します。QRC (Qt Resource) ファイルを使用している場合は、パスの記述に注意が必要です (qrc:/path/to/image.png)。
  • sourceClipRect の値の確認
    • console.log() を使って、sourceClipRectx, y, width, height の値が期待通りになっているか出力してみる。
    • 元の画像のサイズ (sourceSize.width, sourceSize.height) を確認し、sourceClipRect の範囲がその中に収まっているか確認する。
  • Image.status プロパティの確認
    Image 要素の status プロパティを使って、画像のロード状況を確認します。
    • Image.Null: ソースが設定されていない。
    • Image.Ready: 画像が正常にロードされた。
    • Image.Loading: 画像をロード中。
    • Image.Error: 画像のロードに失敗した。
    Image {
        id: myImage
        source: "images/my_big_image.png"
        sourceClipRect: Qt.rect(100, 100, 200, 150)
    
        onStatusChanged: {
            if (myImage.status === Image.Error) {
                console.log("画像のロードに失敗しました: " + myImage.source);
            } else if (myImage.status === Image.Ready) {
                console.log("画像が正常にロードされました。元のサイズ: " + myImage.sourceSize.width + "x" + myImage.sourceSize.height);
            }
        }
    }
    

バインディングループの警告 (Binding loop detected for property "sourceClipRect")

よくある原因

  • 特に、sourceSize.widthsourceSize.height を使って sourceClipRect を計算し、その結果が再び Image 要素のプロパティに影響を与える場合に発生しやすいです。
  • sourceClipRect の値が、画像自体のロード状況やサイズに依存して計算されており、その計算結果がさらに sourceClipRect に影響を与えるような循環参照になっている場合。

トラブルシューティング

  • プロパティの初期化の順序の見直し
    sourceSize は画像のロード後に値が確定します。sourceClipRect を設定する際に sourceSize を使用する場合は、sourceSize が確定してから sourceClipRect を設定するようにします。これは、前述の「設定タイミングの調整」と同様に onStatusChanged を使うのが効果的です。

スプライトシートから画像が正しく切り抜かれない

よくある原因

  • スプライトシートのグリッドレイアウトや、各画像のパディング/マージンが考慮されていない。
  • sourceClipRectx, y, width, height が、スプライトシート内の個々の画像の正確な位置やサイズと一致していない。

トラブルシューティング

  • sourceSize の活用
    スプライトシートの実際のサイズ (sourceSize) を参照して、sourceClipRect の計算に利用すると、より汎用的なコードになります。
  • 計算の自動化
    スプライトシートがグリッド状に配置されている場合、JavaScript関数などを使って sourceClipRect の値を動的に計算することで、管理がしやすくなります。
    // 例: 4x4のスプライトシートから3行目の2列目の画像を切り抜く
    Image {
        id: spriteImage
        source: "images/my_sprite_sheet.png"
        width: 50 // 切り抜かれた画像の表示サイズ
        height: 50
    
        property int cellWidth: 100 // スプライトシート上の1つの画像の幅
        property int cellHeight: 100 // スプライトシート上の1つの画像の高さ
    
        function getClipRect(row, col) {
            return Qt.rect(col * cellWidth, row * cellHeight, cellWidth, cellHeight);
        }
    
        Component.onCompleted: {
            spriteImage.sourceClipRect = getClipRect(2, 1); // 0-indexedで3行目2列目
        }
    }
    
  • 正確な座標とサイズの確認
    画像編集ツールなどを使って、スプライトシート内の各画像のピクセル単位での正確な位置とサイズを特定します。

よくある原因

  • 非常に大きな画像から小さい領域を切り抜く場合、画像のロード自体に時間がかかることがあります。
  • sourceClipRect を頻繁に(例:アニメーションの各フレームで)変更している場合、オーバーヘッドが発生する可能性があります。

トラブルシューティング

  • スプライトシートの設計
    スプライトシートを頻繁に切り替えるアニメーションの場合、切り抜き効率を考慮した配置を検討します。
  • 画像サイズの最適化
    アプリケーションで使用する画像は、不必要に大きくしないように、適切な解像度とファイルサイズに最適化します。
  • sourceSize の利用
    source プロパティで画像パスを指定するだけでなく、sourceSize.widthsourceSize.height を設定することで、Qtは画像の実際のサイズを推測する手間を省き、ロードプロセスを最適化できます。
  • 不要な更新の回避
    sourceClipRect の値が必要な時にのみ更新されるようにロジックを見直します。


例1: 画像の一部を静的に表示する

最も基本的な使用法です。大きな画像ファイルから特定の領域を切り出して表示します。

// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: "Static Image Clip"

    Rectangle {
        width: 300
        height: 200
        color: "lightgray"
        anchors.centerIn: parent
        border.color: "black"
        border.width: 2

        Image {
            // 表示する元の画像ファイルへのパス
            // 例: "images/landscape.jpg" (幅1000px, 高さ800pxの風景写真)
            source: "images/landscape.jpg" // 適切なパスに置き換えてください

            // このImage要素自体の表示サイズ
            width: parent.width
            height: parent.height

            // 元画像から切り抜く領域をピクセル単位で指定
            // (X座標, Y座標, 幅, 高さ)
            // この例では、元画像の左上から(200, 150)の位置を起点に、幅400px、高さ300pxの領域を切り抜きます。
            sourceClipRect: Qt.rect(200, 150, 400, 300)

            // 切り抜かれた画像をスムーズにスケーリングするかどうか
            smooth: true

            // 切り抜かれた画像がImage要素の領域にどう収まるか
            // 例えば、Image要素のサイズとsourceClipRectの比率が異なる場合に重要
            // `PreserveAspectFit` はアスペクト比を維持しつつ全体を表示
            // `Stretch` はImage要素のサイズに合わせて引き伸ばす
            fillMode: Image.PreserveAspectFit
        }
    }
}

解説
sourceClipRect: Qt.rect(200, 150, 400, 300) は、landscape.jpg のピクセル座標 (200, 150) を左上隅とし、そこから右に 400 ピクセル、下に 300 ピクセルの範囲を切り抜きます。この切り抜かれた部分が、親の Rectangle の中に表示されます。

例2: スプライトシートからアニメーションする

ゲームやUIでよく使われるスプライトシートから、画像を切り替えてアニメーションさせます。

準備
以下のようなスプライトシート画像を用意してください(例: images/sprite_sheet.png)。これは、複数のフレーム(例えば4つのフレーム)が横一列に並んでいるものとします。各フレームの幅は100px、高さは100pxと仮定します。

+-----+-----+-----+-----+
| F1  | F2  | F3  | F4  |
+-----+-----+-----+-----+
// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: "Sprite Sheet Animation"

    Rectangle {
        width: 100 // スプライトの1フレームの幅
        height: 100 // スプライトの1フレームの高さ
        color: "transparent" // 背景は透明に
        anchors.centerIn: parent

        Image {
            id: characterSprite
            source: "images/sprite_sheet.png" // スプライトシート画像
            width: parent.width
            height: parent.height

            // スプライトシートの1フレームの幅と高さ
            property int frameWidth: 100
            property int frameHeight: 100
            // 現在表示しているフレームのインデックス (0から始まる)
            property int currentFrame: 0
            // 総フレーム数
            property int totalFrames: 4 // 例: 4フレームのアニメーション

            // sourceClipRect を currentFrame に応じて動的に変更
            sourceClipRect: Qt.rect(currentFrame * frameWidth, 0, frameWidth, frameHeight)

            // アニメーションタイマー
            Timer {
                interval: 150 // 150msごとにフレームを切り替える
                running: true
                repeat: true
                onTriggered: {
                    characterSprite.currentFrame = (characterSprite.currentFrame + 1) % characterSprite.totalFrames;
                }
            }
        }
    }
}

解説
currentFrame プロパティが Timer によって定期的に更新されます。それに合わせて sourceClipRectx 座標が currentFrame * frameWidth に計算され、適切なフレームがスプライトシートから切り抜かれ、アニメーションのように表示されます。

地図アプリなどで、画像を拡大・縮小しながら表示領域を移動させる(パン)ような効果を実現します。

// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 800
    height: 600
    visible: true
    title: "Image Zoom and Pan"

    Rectangle {
        width: parent.width
        height: parent.height
        color: "darkgray"

        Image {
            id: zoomableImage
            source: "images/large_map.jpg" // 非常に大きな地図画像など
            width: parent.width
            height: parent.height
            fillMode: Image.PreserveAspectFit

            // 表示する元の画像の中心座標 (ピクセル単位)
            property real centerX: sourceSize.width / 2
            property real centerY: sourceSize.height / 2
            // 表示する範囲の「ズームレベル」(小さいほど拡大)
            property real zoomLevel: 1.0 // 1.0で元画像全体、0.5で2倍ズーム

            // sourceClipRect の計算
            // 表示する領域の幅と高さ
            property real clipWidth: sourceSize.width * zoomLevel
            property real clipHeight: sourceSize.height * zoomLevel

            // 切り抜く領域の左上隅のX, Y座標
            // centerX, centerYを中心にclipWidth, clipHeightの領域を切り抜く
            property real clipX: Math.max(0, Math.min(sourceSize.width - clipWidth, centerX - clipWidth / 2))
            property real clipY: Math.max(0, Math.min(sourceSize.height - clipHeight, centerY - clipHeight / 2))

            sourceClipRect: Qt.rect(clipX, clipY, clipWidth, clipHeight)

            // マウスドラッグで画像をパンする
            MouseArea {
                anchors.fill: parent
                property real lastX: 0
                property real lastY: 0
                onPressed: (mouse) => {
                    lastX = mouse.x;
                    lastY = mouse.y;
                }
                onPositionChanged: (mouse) => {
                    if (mouse.pressedButtons & Qt.LeftButton) {
                        // マウスの移動量に応じて centerX, centerY を更新
                        // zoomLevel で調整することで、ズームレベルに応じたパンの速度にする
                        zoomableImage.centerX -= (mouse.x - lastX) * zoomableImage.zoomLevel;
                        zoomableImage.centerY -= (mouse.y - lastY) * zoomableImage.zoomLevel;

                        // 座標が画像範囲外に出ないように制限
                        zoomableImage.centerX = Math.max(zoomableImage.clipWidth / 2, Math.min(zoomableImage.sourceSize.width - zoomableImage.clipWidth / 2, zoomableImage.centerX));
                        zoomableImage.centerY = Math.max(zoomableImage.clipHeight / 2, Math.min(zoomableImage.sourceSize.height - zoomableImage.clipHeight / 2, zoomableImage.centerY));

                        lastX = mouse.x;
                        lastY = mouse.y;
                    }
                }
                // マウスホイールでズーム
                onWheel: (wheel) => {
                    if (wheel.angleDelta.y > 0) { // スクロールアップ (ズームイン)
                        zoomableImage.zoomLevel *= 0.9;
                    } else { // スクロールダウン (ズームアウト)
                        zoomableImage.zoomLevel /= 0.9;
                    }
                    // ズームレベルの制限
                    zoomableImage.zoomLevel = Math.max(0.1, Math.min(1.0, zoomableImage.zoomLevel));
                }
            }

            // 画像がロードされたら初期設定を行う
            onStatusChanged: {
                if (status === Image.Ready) {
                    // sourceSize が確定したら centerX/Y を初期化
                    zoomableImage.centerX = sourceSize.width / 2;
                    zoomableImage.centerY = sourceSize.height / 2;
                }
            }
        }
    }
}

解説
この例では、centerX, centerY, zoomLevel の3つのプロパティを操作することで、sourceClipRect を動的に変更し、画像のズームとパンを実現しています。

  • onStatusChangedImage.Ready になったことを確認してから centerXcenterY を初期化しているのは、sourceSize が画像ロード後に確定するためです。
  • Math.maxMath.min を使って、切り抜き範囲が元の画像の境界をはみ出さないように制限しています。
  • centerXcenterY を変更すると、切り抜かれる領域の中心が移動し、パン操作が行われます。
  • zoomLevel を小さくすると、clipWidthclipHeight が小さくなり、より小さい領域が切り抜かれるため、拡大して見えます。


Image 要素自体の width/height と fillMode を組み合わせる

sourceClipRect は、元の画像から特定のピクセル範囲を切り抜くのに対し、この方法は、Image 要素自身のサイズを調整し、その中に画像をどのようにフィットさせるかを制御します。これはクリッピングというよりは、表示領域とスケーリングの調整です。

特徴

  • 制限
    sourceClipRect のように、元の画像の一部を「ピクセル単位で切り抜く」機能はありません。画像全体がロードされ、その後表示領域に合わせてスケーリングされます。
  • スケーリング
    fillMode プロパティ(Image.PreserveAspectFitImage.PreserveAspectCropImage.Stretch など)を使って、画像が指定された領域にどのように収まるかを細かく制御できます。
  • シンプルさ
    コードが非常にシンプルで直感的です。

使用例

Image {
    source: "images/my_big_image.png"
    width: 200 // 表示したい幅
    height: 150 // 表示したい高さ
    fillMode: Image.PreserveAspectCrop // アスペクト比を維持して、Image要素全体を埋めるように拡大/縮小し、はみ出た部分は切り取る
}

sourceClipRect との比較

  • sourceClipRect はスプライトシートから特定のフレームを抽出するのに適していますが、この方法は通常、画像全体を扱います。
  • sourceClipRect は元の画像データを「どの範囲から使うか」を指定しますが、この方法は「どの範囲に表示するか」を指定し、その範囲で画像をスケーリングします。

Rectangle の clip プロパティと Image の位置調整

これは、sourceClipRect がない場合に、QMLの一般的なクリッピング機能を使って同様の効果を実現する方法です。親の Rectangle でクリッピング領域を定義し、その中に Image 要素を配置し、Image の位置をずらして表示したい部分を Rectangle のクリッピング領域内に持ってきます。

特徴

  • パフォーマンス
    sourceClipRect が画像データレベルで最適化されるのに対し、この方法はレンダリングレベルでのクリッピングになるため、非常に大きな画像や多数の要素に対して適用するとパフォーマンスに影響を与える可能性があります。
  • 視覚的理解
    クリッピング領域と画像の位置関係が視覚的に分かりやすい場合があります。
  • 汎用性
    Image 要素だけでなく、任意のQML要素に対してクリッピングを適用できます。

使用例

Rectangle {
    width: 200
    height: 150
    border.color: "blue"
    border.width: 2
    clip: true // ここが重要!このRectangleの境界からはみ出た内容を切り抜く

    Image {
        source: "images/my_big_image.png" // 例: 元画像が800x600px
        // 元画像の左上(100, 50)の位置を、親Rectangleの左上に表示したい場合
        // Image要素自体を負の座標に移動させる
        x: -100
        y: -50

        // Image要素のサイズは元画像と同じかそれ以上にする
        width: 800
        height: 600

        // fillMode は通常 Stretch にするか、設定しない(元サイズで表示)
        fillMode: Image.Stretch
    }
}

sourceClipRect との比較

  • スプライトシートのように頻繁に切り抜く部分が変わる場合は、sourceClipRect の方がより効率的です。
  • Rectangleclip は、画像全体をロードしてレンダリングし、そのレンダリング結果を後からクリッピングします。そのため、リソースの節約という点では sourceClipRect に劣る可能性があります。
  • sourceClipRect は画像データソースを切り抜くため、必要な部分だけをメモリにロードしたり、GPUに転送したりする最適化が行われる可能性があります。

ShaderEffect を利用する

より複雑なクリッピングや画像処理(例えば、非矩形のクリッピングやエフェクトとの組み合わせ)が必要な場合、ShaderEffect を使ってOpenGL ES/GLSLシェーダーで画像をレンダリングする方法があります。

特徴

  • 複雑性
    シェーダープログラミングの知識が必要となり、学習コストが高いです。
  • GPUアクセラレーション
    処理はGPU上で行われるため、適切に記述すれば高いパフォーマンスが期待できます。
  • 高い柔軟性
    シェーダーコードでピクセル単位の処理を記述できるため、矩形クリッピング以外のあらゆる種類のクリッピングやエフェクトが可能です。

使用例 (概念)

// MyImageClipper.qml
import QtQuick 2.0
import QtQuick.Effects 1.0 // ShaderEffectItem のために必要

ShaderEffect {
    width: 200
    height: 150
    property variant sourceImage: Image { source: "images/my_big_image.png" }
    // シェーダーに渡す uniform 変数
    property real clipX: 100.0
    property real clipY: 50.0
    property real clipWidth: 200.0
    property real clipHeight: 150.0

    // fragmentShader の中でクリッピングロジックを記述
    fragmentShader: "
        varying highp vec2 qt_TexCoord0;
        uniform highp sampler2D sourceImage;
        uniform highp float clipX;
        uniform highp float clipY;
        uniform highp float clipWidth;
        uniform highp float clipHeight;
        uniform highp float sourceWidth; // sourceImageの実際の幅
        uniform highp float sourceHeight; // sourceImageの実際の高さ

        void main() {
            // 現在のピクセルのテクスチャ座標 (0.0 - 1.0)
            highp vec2 texCoord = qt_TexCoord0;

            // 元画像のピクセル座標に変換
            highp vec2 imageCoord = texCoord * vec2(sourceWidth, sourceHeight);

            // クリッピング領域内にあるかチェック
            if (imageCoord.x >= clipX && imageCoord.x < (clipX + clipWidth) &&
                imageCoord.y >= clipY && imageCoord.y < (clipY + clipHeight))
            {
                // クリッピング領域内の場合は元の色をサンプリング
                gl_FragColor = texture2D(sourceImage, texCoord);
            } else {
                // クリッピング領域外の場合は透明にする
                gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
            }
        }
    "
    // sourceImage の sourceSize を利用してシェーダーに渡す
    onSourceImageChanged: {
        sourceWidth = sourceImage.sourceSize.width;
        sourceHeight = sourceImage.sourceSize.height;
    }
}

sourceClipRect との比較

  • しかし、単に矩形を切り抜くだけなら、sourceClipRect が圧倒的にシンプルでパフォーマンスも最適化されています。ShaderEffect はオーバーヘッドが大きくなる可能性があります。
  • ShaderEffect は、sourceClipRect が提供する矩形クリッピング以上の複雑なニーズに対応できます。

C++ でカスタム QQuickPaintedItem または QQuickItem を作成する

QMLの既存の要素では要件を満たせない場合、C++でカスタムのQMLアイテムを作成し、QQuickPaintedItem を継承して paint() メソッド内で直接 QPainter を使って描画したり、QQuickItem を継承してQSG (Qt Scene Graph) を直接操作したりする方法があります。

特徴

  • 複雑性
    C++とQtの描画APIに関する深い知識が必要です。QMLとC++間の連携も考慮する必要があります。
  • パフォーマンス最適化
    非常に特殊な描画要件がある場合、QSGを直接操作することで最高のパフォーマンスを引き出せる可能性があります。
  • 最高の柔軟性
    QMLでは不可能な低レベルの描画制御が可能です。

使用例 (概念)

// myclippedimageitem.h
#include <QQuickPaintedItem>
#include <QImage>

class MyClippedImageItem : public QQuickPaintedItem
{
    Q_OBJECT
    Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged)
    Q_PROPERTY(QRect sourceClipRect READ sourceClipRect WRITE setSourceClipRect NOTIFY sourceClipRectChanged)

public:
    MyClippedImageItem(QQuickItem *parent = nullptr);

    void paint(QPainter *painter) override;

    QString source() const { return m_source; }
    void setSource(const QString &source);

    QRect sourceClipRect() const { return m_sourceClipRect; }
    void setSourceClipRect(const QRect &rect);

signals:
    void sourceChanged();
    void sourceClipRectChanged();

private:
    QString m_source;
    QRect m_sourceClipRect;
    QImage m_image; // ロードされた画像
};

// myclippedimageitem.cpp
#include "myclippedimageitem.h"
#include <QPainter>
#include <QQuickWindow>

MyClippedImageItem::MyClippedImageItem(QQuickItem *parent)
    : QQuickPaintedItem(parent)
{
    // setAntialiasing(true); // 必要に応じてアンチエイリアシングを有効にする
}

void MyClippedImageItem::setSource(const QString &source)
{
    if (m_source == source)
        return;

    m_source = source;
    // 画像を非同期でロードすることも検討
    m_image.load(m_source); // またはQImageReaderなどを使用
    emit sourceChanged();
    update(); // 描画を更新
}

void MyClippedImageItem::setSourceClipRect(const QRect &rect)
{
    if (m_sourceClipRect == rect)
        return;

    m_sourceClipRect = rect;
    emit sourceClipRectChanged();
    update(); // 描画を更新
}

void MyClippedImageItem::paint(QPainter *painter)
{
    if (m_image.isNull() || m_sourceClipRect.isEmpty())
        return;

    // 元画像のm_sourceClipRect部分を、このアイテムの(0,0)に描画
    painter->drawImage(boundingRect(), m_image, m_sourceClipRect);
}

そしてQMLから利用:

// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import MyCustomItems 1.0 // C++で登録したモジュール名

Window {
    width: 640
    height: 480
    visible: true
    title: "Custom Clipped Image"

    MyClippedImageItem {
        width: 300
        height: 200
        source: "images/my_big_image.png"
        sourceClipRect: Qt.rect(200, 150, 400, 300)
        anchors.centerIn: parent
    }
}

sourceClipRect との比較

  • Qtのネイティブな Image.sourceClipRect は、ほとんどのユースケースでC++でのカスタム実装よりも優れたパフォーマンスと開発効率を提供します。
  • QQuickItem を使って直接QSGを操作する方法は最もパフォーマンスが高いですが、学習曲線が非常に急です。
  • QQuickPaintedItem は柔軟性を提供しますが、Qt Scene Graphの最適化を享受できないため、パフォーマンスが劣る場合があります。

多くの場合、Image.sourceClipRect は画像の部分的な表示において最も効率的で推奨される方法です。これは、Qtが内部でこの機能を最適化しているためです。

代替手段を検討するのは、以下のような特殊なケースに限られます。

  • 単に画像を拡大縮小して表示領域にフィットさせたいだけで、特定のピクセル範囲を切り抜く必要がない場合 (fillMode)。
  • QMLの Image 要素の機能だけでは実現できない、非常に低レベルな描画制御が必要な場合 (QQuickPaintedItem / QQuickItem)。
  • クリッピングと同時に、高度な視覚効果を適用したい (ShaderEffect)。
  • 矩形以外の複雑な形状で画像をクリッピングしたい (ShaderEffect)。

基本的には、まず Image.sourceClipRect の使用を検討し、それが要件を満たせない場合にのみ、これらの代替手段を検討することをお勧めします。 Qt の Image.sourceClipRect は、画像の一部を効率的に表示するための非常に便利なプロパティですが、代替手段もいくつか存在します。これらの代替手段は、特定のユースケースやパフォーマンス要件に応じて選択されます。

Item::clip プロパティと Image::fillMode を組み合わせる

これは最も直接的な代替手段の一つです。Image 要素を親の Item (例: RectangleItem そのもの)で囲み、その親の clip プロパティを true に設定します。そして、Image 要素自体のサイズや fillMode を調整して、親のクリッピング領域に収まるようにします。

特徴

  • パフォーマンス
    sourceClipRect ほどではないかもしれませんが、GPUによる高速なクリッピングが可能です。ただし、元画像全体がロードされるため、メモリ使用量は sourceClipRect より多くなる可能性があります。
  • 柔軟性
    親の Item の形状(radius を使って角丸にするなど)に合わせてクリッピングできます。
  • シンプル
    実装が簡単です。

コード例

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: "Clipping with Item::clip"

    // 画像をクリップする親のRectangle
    Rectangle {
        width: 200
        height: 150
        color: "lightgray"
        anchors.centerIn: parent
        border.color: "black"
        border.width: 2

        // このRectangleの境界で子要素をクリップする
        clip: true

        Image {
            source: "images/landscape.jpg" // 元の画像
            // Image要素のサイズを親より大きく設定し、ズームインしたように見せる
            width: 400
            height: 300

            // Image要素の中心を親のRectangleの中心に合わせる
            // これにより、Imageの特定の部分が親のクリッピング領域に表示される
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.verticalCenter: parent.verticalCenter

            // Image要素の原点を調整して、表示したい部分を中央に持ってくる
            // (x, y) が Image 要素の左上隅のオフセットになる
            // 例:元の画像の (200, 150) の位置を Image 要素の左上隅に合わせる場合
            x: -200 // landscape.jpg の x=200 の位置を Image の x=0 にする
            y: -150 // landscape.jpg の y=150 の位置を Image の y=0 にする

            // fillMode は Image の width/height に合わせて画像をどうフィットさせるかを指定
            // ここでは Image のサイズが固定なのであまり影響しないが、`PreserveAspectCrop` なども考慮できる
            fillMode: Image.PreserveAspectFit
            smooth: true
        }
    }
}

sourceClipRect との比較
sourceClipRect画像のどの部分をロードして表示するかを画像データレベルで指定するのに対し、Item::clip既にロードされた画像を、親要素の描画領域で切り取るという考え方です。そのため、メモリ使用量や初期ロード時間において、sourceClipRect の方が有利な場合があります(特に大きな画像の場合)。

QtGraphicalEffects.OpacityMask を使用する

QtGraphicalEffects モジュールにある OpacityMask を使うと、任意の形状で画像をクリッピングできます。これは四角形以外の形状(円形、星形など)で画像をクリッピングしたい場合に非常に強力です。

特徴

  • パフォーマンス
    シェーダーベースでGPUを活用するため、高速な処理が可能です。ただし、追加のグラフィックスエフェクトのオーバーヘッドが発生します。
  • 柔軟性
    マスクとなる Item は、RectangleImagePath など、QMLで描画可能な任意の要素を使用できます。
  • 任意の形状のクリッピング
    四角形に限定されず、マスクとなる Item の形状に合わせてクリッピングできます。

コード例 (円形クリッピング)

// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtGraphicalEffects 1.15 // QtGraphicalEffects モジュールをインポート

Window {
    width: 640
    height: 480
    visible: true
    title: "Opacity Mask Clipping"

    // 画像を含むItem
    Item {
        id: imageContainer
        width: 300
        height: 300
        anchors.centerIn: parent

        Image {
            id: myImage
            source: "images/landscape.jpg"
            anchors.fill: parent // 親のImageContainerに合わせて表示
            fillMode: Image.PreserveAspectCrop // 画像をはみ出す場合は切り抜き
            smooth: true
        }

        // OpacityMask を適用
        layer.enabled: true // OpacityMask を適用するためにレイヤーを有効にする
        layer.effect: OpacityMask {
            // マスクとして使用するItem
            // ここでは円形のRectangleをマスクとして使用
            maskSource: Rectangle {
                // maskSourceは自身のサイズを持つ必要があるため、親にフィットさせる
                width: imageContainer.width
                height: imageContainer.height
                radius: Math.min(width, height) / 2 // 円形にする
                color: "white" // マスクの色は関係ない(不透明度が使われる)
            }
        }
    }
}

解説
imageContainerOpacityMask を適用し、maskSource に円形の Rectangle を指定することで、myImage が円形にクリッピングされて表示されます。layer.enabled: true は、効果を適用するために描画レイヤーを有効にするために必要です。

ShaderEffect を使用する (より高度なクリッピング/画像処理)

ShaderEffect を使用すると、OpenGL ES Shading Language (GLSL) を直接記述して、カスタムの画像処理やクリッピングを行うことができます。これは最も柔軟な方法ですが、GLSLの知識が必要です。

特徴

  • 学習コスト
    GLSLの知識が必須であり、デバッグが難しい場合があります。
  • 高性能
    GPU上で直接処理されるため、複雑な効果も高速に実現できます。
  • 究極の柔軟性
    ピクセルレベルで画像の描画を完全に制御できます。

コード例 (シンプルな矩形クリッピングの概念)

// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15 // スライダーのために必要

Window {
    width: 640
    height: 480
    visible: true
    title: "Shader Effect Clipping"

    Image {
        id: sourceImage
        source: "images/landscape.jpg"
        visible: false // 直接表示しない(ShaderEffectSourceで参照するため)
    }

    ShaderEffect {
        width: 400
        height: 300
        anchors.centerIn: parent

        // シェーダーの入力として元画像を指定
        property variant source: ShaderEffectSource {
            sourceItem: sourceImage // Image要素をソースとして使う
            hideSource: true // 元のImageは非表示にする
        }

        // クリッピング領域のパラメータ (0.0から1.0の正規化された値)
        property real clipX: 0.2
        property real clipY: 0.2
        property real clipWidth: 0.6
        property real clipHeight: 0.6

        // フラグメントシェーダー (GLSL)
        // このシェーダーは、指定された矩形領域内にあるピクセルのみを描画する
        fragmentShader: "
            varying highp vec2 qt_TexCoord0; // テクスチャ座標
            uniform sampler2D source;       // 元画像テクスチャ
            uniform highp float clipX;
            uniform highp float clipY;
            uniform highp float clipWidth;
            uniform highp float clipHeight;

            void main() {
                // qt_TexCoord0 は0.0から1.0の範囲
                // クリッピング領域の左上隅と右下隅を計算
                highp float minX = clipX;
                highp float minY = clipY;
                highp float maxX = clipX + clipWidth;
                highp float maxY = clipY + clipHeight;

                // 現在のピクセルがクリッピング領域内にあるかチェック
                if (qt_TexCoord0.x >= minX && qt_TexCoord0.x <= maxX &&
                    qt_TexCoord0.y >= minY && qt_TexCoord0.y <= maxY)
                {
                    // 領域内であれば、元の画像の色をそのまま出力
                    gl_FragColor = texture2D(source, qt_TexCoord0);
                } else {
                    // 領域外であれば、透明な色を出力(クリップされる)
                    gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
                }
            }
        "
    }

    // スライダーでクリッピング領域を調整するUI (デバッグ用)
    Column {
        anchors.bottom: parent.bottom
        anchors.horizontalCenter: parent.horizontalCenter
        spacing: 10
        width: parent.width - 40
        Slider {
            from: 0.0; to: 1.0; stepSize: 0.01
            value: shaderEffect.clipX
            onValueChanged: shaderEffect.clipX = value
            Text { text: "clipX: " + shaderEffect.clipX.toFixed(2); anchors.left: parent.right; anchors.verticalCenter: parent.verticalCenter; x: 10 }
        }
        Slider {
            from: 0.0; to: 1.0; stepSize: 0.01
            value: shaderEffect.clipY
            onValueChanged: shaderEffect.clipY = value
            Text { text: "clipY: " + shaderEffect.clipY.toFixed(2); anchors.left: parent.right; anchors.verticalCenter: parent.verticalCenter; x: 10 }
        }
        Slider {
            from: 0.0; to: 1.0; stepSize: 0.01
            value: shaderEffect.clipWidth
            onValueChanged: shaderEffect.clipWidth = value
            Text { text: "clipWidth: " + shaderEffect.clipWidth.toFixed(2); anchors.left: parent.right; anchors.verticalCenter: parent.verticalCenter; x: 10 }
        }
        Slider {
            from: 0.0; to: 1.0; stepSize: 0.01
            value: shaderEffect.clipHeight
            onValueChanged: shaderEffect.clipHeight = value
            Text { text: "clipHeight: " + shaderEffect.clipHeight.toFixed(2); anchors.left: parent.right; anchors.verticalCenter: parent.verticalCenter; x: 10 }
        }
    }
}

解説
ShaderEffect は、source プロパティで元の画像を受け取ります。フラグメントシェーダー内で qt_TexCoord0 (0.0~1.0の範囲のテクスチャ座標) を使用して、現在のピクセルが定義されたクリッピング矩形内にあるかどうかを判断します。領域内であれば元の画像の色を、そうでなければ透明な色を出力することでクリッピングを実現します。

QQuickPaintedItem をC++で実装し、カスタム描画を行う

QMLの機能だけでは不十分な場合や、非常に複雑な画像処理が必要な場合は、C++で QQuickPaintedItem を継承したカスタムQMLアイテムを作成し、paint() メソッド内で QPainter を使用して画像を直接描画する方法があります。

特徴

  • パフォーマンス
    QPainter の描画はCPUで行われるため、GPUベースのソリューション(sourceClipRectShaderEffect)に比べてパフォーマンスが劣る場合があります。ただし、必要に応じてOpenGLと連携することも可能です。
  • 複雑な処理
    ピクセル単位での複雑な画像操作や、独自アルゴリズムの実装が可能です。
  • C++の全機能にアクセス可能
    Qtの豊富な画像処理機能(QImage, QPainter など)を最大限に活用できます。

コード例 (概念のみ、C++部分の詳細は省略)

mycustomimageitem.h

#ifndef MYCUSTOMIMAGEITEM_H
#define MYCUSTOMIMAGEITEM_H

#include <QQuickPaintedItem>
#include <QImage>
#include <QRect>

class MyCustomImageItem : public QQuickPaintedItem
{
    Q_OBJECT
    Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged)
    Q_PROPERTY(QRect sourceClipRect READ sourceClipRect WRITE setSourceClipRect NOTIFY sourceClipRectChanged)

public:
    MyCustomImageItem(QQuickItem *parent = nullptr);

    void paint(QPainter *painter) override;

    QString source() const { return m_source; }
    void setSource(const QString &source);

    QRect sourceClipRect() const { return m_sourceClipRect; }
    void setSourceClipRect(const QRect &clipRect);

signals:
    void sourceChanged();
    void sourceClipRectChanged();

private:
    QString m_source;
    QImage m_image; // ロードした画像
    QRect m_sourceClipRect; // 切り抜き領域
};

#endif // MYCUSTOMIMAGEITEM_H

mycustomimageitem.cpp (抜粋)

#include "mycustomimageitem.h"
#include <QPainter>
#include <QDebug>

MyCustomImageItem::MyCustomImageItem(QQuickItem *parent)
    : QQuickPaintedItem(parent)
{
    // 初期値
    m_sourceClipRect = QRect();
}

void MyCustomImageItem::setSource(const QString &source)
{
    if (m_source != source) {
        m_source = source;
        m_image.load(source); // 画像をロード
        update(); // 再描画を要求
        emit sourceChanged();
    }
}

void MyCustomImageItem::setSourceClipRect(const QRect &clipRect)
{
    if (m_sourceClipRect != clipRect) {
        m_sourceClipRect = clipRect;
        update(); // 再描画を要求
        emit sourceClipRectChanged();
    }
}

void MyCustomImageItem::paint(QPainter *painter)
{
    if (m_image.isNull()) {
        return;
    }

    if (m_sourceClipRect.isValid() && m_sourceClipRect.intersects(m_image.rect())) {
        // sourceClipRect が有効で、画像領域と交差する場合
        QRect finalClipRect = m_sourceClipRect.intersected(m_image.rect());
        painter->drawImage(boundingRect(), m_image, finalClipRect);
    } else {
        // sourceClipRect が無効な場合や交差しない場合は全体を描画
        painter->drawImage(boundingRect(), m_image);
    }
}

main.qml

// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import MyCustomItems 1.0 // C++で登録したモジュールをインポート

Window {
    width: 640
    height: 480
    visible: true
    title: "Custom Image Item"

    MyCustomImageItem {
        width: 300
        height: 200
        anchors.centerIn: parent
        source: "images/landscape.jpg" // 元の画像
        sourceClipRect: Qt.rect(200, 150, 400, 300) // 切り抜き領域
    }
}

解説
C++のクラスで sourcesourceClipRect をプロパティとして公開し、paint() メソッド内で QPainter::drawImage(const QRectF &targetRect, const QImage &image, const QRectF &sourceRect) を使用して、画像から指定された領域を切り抜いて描画します。これにより、QMLからは通常の Image 要素のように扱えます。

  • QQuickPaintedItem (C++)

    • QMLだけでは実現できないような複雑なC++での画像処理(例: リアルタイムな画像フィルタリング、独自の描画アルゴリズム)を行いたい場合。
    • QMLとC++の連携が必要な場合。
    • パフォーマンスはCPU処理が主になるため、注意が必要です。
  • ShaderEffect

    • 非常に高度な画像処理や、ピクセル単位でのカスタムクリッピングロジックが必要な場合。
    • GLSLの知識が必須で、学習コストが高いですが、最高のパフォーマンスと柔軟性を提供します。
  • QtGraphicalEffects.OpacityMask

    • 矩形以外の形状(円、多角形、カスタムパスなど)で画像をクリッピングしたい場合。
    • マスクとなる Item の形状をQMLで定義できるため、比較的簡単に任意の形状のクリッピングが可能です。
  • Item::clip と Image::fillMode

    • Image 要素の矩形描画領域全体をクリップするだけでよい場合。
    • 親の Itemradius を設定して角丸クリッピングを実現したい場合。
    • 複雑な形状のクリッピングはできません。
  • Image.sourceClipRect (デフォルト推奨)

    • 最もシンプルで、指定された矩形領域のクリッピングに最適です。
    • パフォーマンスも考慮されており、画像ローダーが最適化されたクリッピングをサポートしている場合があります。
    • ほとんどのユースケースでこれがベストな選択肢です。