Qt Image.sourceSize 解説:画像の一部表示と最適化のテクニック

2025-05-31

Image.sourceSize は、Qt Quick (QML) の Image 要素で使用されるプロパティの一つです。これは、画像ファイル全体の本来のサイズ(幅と高さ)を指定するために使われます。

通常、Image 要素は、source プロパティで指定された画像ファイルを読み込み、その画像の実際のサイズに基づいて表示領域を決定します。しかし、場合によっては、画像ファイル全体ではなく、その一部だけを表示したいことがあります。

そのような場合に、sourceSize プロパティを設定することで、Image 要素は、指定された sourceSize のサイズを持つ画像であるかのように扱います。ただし、実際に表示されるのは、widthheight プロパティで指定された領域に、sourceX, sourceY, sourceWidth, sourceHeight プロパティで指定された部分が拡大縮小されて描画されます。

要するに、sourceSize は、画像ファイル全体の「見かけ上のサイズ」を定義する役割を持ちます。

具体的には、以下のような場合に sourceSize が役立ちます。

  • カスタムなサイズ調整
    画像の本来のサイズとは異なる基準で、表示する領域や切り取る領域を定義したい場合。
  • パフォーマンスの向上
    大きな画像の一部分だけを繰り返し表示する場合、毎回画像全体を読み込んで処理するよりも、最初に sourceSize を設定し、必要な部分だけを sourceX, sourceY, sourceWidth, sourceHeight で指定する方が効率的な場合があります。


例えば、100x200 ピクセルの画像ファイル myimage.png があり、その左上 50x50 ピクセルの部分だけを表示したいとします。

Image {
    source: "myimage.png"
    sourceSize: Qt.size(100, 200) // 画像全体のサイズ
    sourceX: 0
    sourceY: 0
    sourceWidth: 50
    sourceHeight: 50
    width: 50
    height: 50
}

この例では、sourceSize に元の画像のサイズ (100x200) を指定し、sourceXsourceY を 0 に、sourceWidthsourceHeight を 50 に設定することで、元の画像の左上 50x50 ピクセルの部分だけが、50x50 のサイズで表示されます。

このように、Image.sourceSize は、画像の表示方法をより柔軟に制御するための重要なプロパティです。



sourceSize が実際の画像ファイルのサイズと一致しない

  • トラブルシューティング
    • 画像編集ソフトなどで、画像ファイルの実際の幅と高さを確認し、sourceSize に正確な値を設定してください。
    • 動的に画像を切り替える場合は、新しい画像のサイズに合わせて sourceSize を更新する必要があります。
    • Qt.size(width, height) を使用して、明確にサイズを指定することをお勧めします。
  • エラー
    sourceSize に設定したサイズが、source で指定した画像ファイルの実際のサイズと異なっている場合、sourceX, sourceY, sourceWidth, sourceHeight で指定した領域が正しく切り取られなかったり、拡大・縮小が不自然になったりします。

sourceX, sourceY, sourceWidth, sourceHeight の範囲が sourceSize を超えている

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

    • sourceX, sourceY, sourceWidth, sourceHeight の値が、常に sourceSize の範囲内に収まるように計算してください。
    • 特に動的にこれらの値を変更する場合は、境界チェックを実装することが重要です。例えば、以下のように範囲を制限できます。
    Image {
        source: "spritesheet.png"
        sourceSize: Qt.size(200, 100)
        property int frameX: 0
        property int frameY: 0
        property int frameWidth: 50
        property int frameHeight: 50
        sourceX: Math.min(frameX, sourceSize.width - frameWidth)
        sourceY: Math.min(frameY, sourceSize.height - frameHeight)
        sourceWidth: Math.min(frameWidth, sourceSize.width - sourceX)
        sourceHeight: Math.min(frameHeight, sourceSize.height - sourceY)
        width: frameWidth
        height: frameHeight
    }
    
  • エラー
    sourceXsourceWidth の合計、または sourceYsourceHeight の合計が、それぞれ sourceSize の幅と高さを超えている場合、画像の一部が表示されなくなったり、エラーが発生したりする可能性があります。

width や height プロパティと sourceWidth, sourceHeight の関係が意図しないものになっている

  • トラブルシューティング
    • 意図する表示サイズに合わせて widthheight を調整してください。
    • 元の画像の縦横比を維持したい場合は、width または height のどちらかを固定し、もう一方を sourceWidth / sourceHeight の比率で計算して設定すると良いでしょう。
    • fillMode プロパティを使用すると、画像の表示方法(アスペクト比を維持するか、領域に合わせて伸縮するかなど)を制御できます。
  • エラー
    widthheightImage 要素の表示領域のサイズを決定し、sourceWidthsourceHeight は切り取る画像のサイズを決定します。これらの値の組み合わせによっては、画像が歪んで表示されたり、小さすぎたり大きすぎたりすることがあります。

画像ファイルのパスが間違っている

  • エラー
    source プロパティに指定した画像ファイルのパスが間違っていると、画像が読み込まれず、sourceSize を含めた他のプロパティも正しく機能しません。

動的な sourceSize の変更に伴う問題

  • トラブルシューティング
    • sourceSize が変更されたタイミングで、sourceX, sourceY, sourceWidth, sourceHeight の値を再計算し、矛盾がないようにしてください。
  • エラー
    sourceSize を動的に変更する場合、関連する sourceX, sourceY, sourceWidth, sourceHeight の値も適切に更新しないと、表示がおかしくなることがあります。

パフォーマンスの問題

  • トラブルシューティング
    • 可能であれば、表示する画像だけを個別のファイルとして用意することを検討してください。
    • アニメーションなどで頻繁に切り替えが必要な場合は、タイマー処理などを最適化し、不必要な更新を避けるようにしてください。
  • エラー
    大きなスプライトシートの一部を頻繁に切り替えて表示する場合、sourceSizesourceX, sourceY, sourceWidth, sourceHeight の更新処理が重くなり、パフォーマンスに影響を与える可能性があります。


例1: スプライトシートから特定のアニメーションフレームを表示する

この例では、複数のアニメーションフレームが横一列に並んだスプライトシート (spritesheet.png) から、指定されたフレームを順番に表示します。

import QtQuick 2.0

Rectangle {
    width: 50
    height: 50

    Image {
        id: spriteImage
        source: "spritesheet.png" // 例: 幅 200px, 高さ 50px のスプライトシート
        sourceSize: Qt.size(200, 50)
        sourceWidth: 50
        sourceHeight: 50
        width: 50
        height: 50

        property int frameIndex: 0

        Timer {
            interval: 200 // 200ミリ秒ごとにフレームを更新
            running: true
            repeat: true
            onTriggered: {
                spriteImage.frameIndex = (spriteImage.frameIndex + 1) % 4; // 4つのフレームをループ
                spriteImage.sourceX = spriteImage.frameIndex * spriteImage.sourceWidth;
            }
        }
    }
}

解説

  • spriteImage.sourceX = spriteImage.frameIndex * spriteImage.sourceWidth; によって、frameIndex に応じて表示するフレームの X 座標を移動させます。sourceY は常に 0 です。
  • Timer は 200 ミリ秒ごとに onTriggered シグナルを発行し、frameIndex を更新します。
  • frameIndex プロパティで、現在表示するフレームのインデックスを管理します。
  • sourceWidth: 50sourceHeight: 50 で、個々のフレームのサイズを指定しています。
  • sourceSize: Qt.size(200, 50) で、画像全体のサイズを指定しています。
  • spritesheet.png は、例えば 50x50 ピクセルのフレームが横に 4 つ並んだ、200x50 ピクセルの画像だと仮定します。

例2: マウスカーソルの位置に応じて画像の一部を表示する

この例では、画像 (large_image.png) の中で、マウスカーソルの近くの 100x100 ピクセルの領域を表示します。

import QtQuick 2.0

Rectangle {
    width: 200
    height: 200

    MouseArea {
        anchors.fill: parent
        hoverEnabled: true
        property real mouseXPos: 0
        property real mouseYPos: 0
        onPositionChanged: {
            mouseXPos = mouseX
            mouseYPos = mouseY
        }

        Image {
            id: zoomView
            source: "large_image.png" // 例: 大きな画像ファイル
            sourceSize: Qt.size(sourceWidth, sourceHeight) // 画像のロード後に実際のサイズを設定
            sourceX: Math.max(0, mouseXPos - 50)
            sourceY: Math.max(0, mouseYPos - 50)
            sourceWidth: 100
            sourceHeight: 100
            width: 100
            height: 100
            anchors.centerIn: parent

            Component.onCompleted: {
                // 画像がロードされた後に実際のサイズを取得して sourceSize に設定
                zoomView.sourceSize = Qt.size(zoomView.implicitWidth, zoomView.implicitHeight)
            }
        }
    }
}

解説

  • Component.onCompleted ブロック内で、画像がロードされた後に implicitWidthimplicitHeight を使用して実際の画像サイズを取得し、sourceSize に設定しています。これは、sourceSize を最初に具体的な値で初期化する必要がない場合に便利です。
  • sourceWidthsourceHeight は常に 100 に固定されています。
  • zoomViewsourceXsourceY は、マウスカーソルの位置を中心とした 100x100 ピクセルの領域の左上の座標を計算しています。Math.max(0, ...) は、画像の外に切り取り範囲が出ないようにするための処理です。
  • MouseArea はマウスの動きを追跡し、onPositionChanged シグナルでマウスの座標 (mouseX, mouseY) を更新します。
  • large_image.png は、表示するよりも大きな画像ファイルだと仮定します。

例3: 画像の一部分を異なるサイズで表示する

この例では、元の画像 (original_image.png) の一部を切り取り、それを異なる widthheight で表示します。

import QtQuick 2.0

Row {
    spacing: 10

    Image {
        source: "original_image.png" // 例: 200x150 ピクセルの画像
        sourceSize: Qt.size(200, 150)
        sourceX: 50
        sourceY: 25
        sourceWidth: 100
        sourceHeight: 100
        width: 50
        height: 50
        border.color: "red"
        border.width: 2
    }

    Image {
        source: "original_image.png"
        sourceSize: Qt.size(200, 150)
        sourceX: 50
        sourceY: 25
        sourceWidth: 100
        sourceHeight: 100
        width: 150
        height: 150
        border.color: "blue"
        border.width: 2
    }
}
  • border プロパティは、それぞれの Image 要素の境界線を表示するために使用しています。
  • 2 番目の Image 要素は width: 150, height: 150 なので、切り取られた 100x100 の領域が 150x150 に拡大されて表示されます。
  • 最初の Image 要素は width: 50, height: 50 なので、切り取られた 100x100 の領域が 50x50 に縮小されて表示されます。
  • sourceX: 50, sourceY: 25, sourceWidth: 100, sourceHeight: 100 によって、元の画像の (50, 25) の位置から 100x100 ピクセルの領域を切り取っています。
  • 両方の Image 要素は同じ画像ソースと sourceSize を持っています。
  • original_image.png は、例えば 200x150 ピクセルの画像だと仮定します。


裁剪 (クリッピング) を使用する (Clip プロパティと Rectangle を組み合わせる)

Image 要素自体は直接的なクリッピング機能を持っていませんが、clip プロパティを true に設定し、その Image 要素を Rectangle などの別の要素で囲み、Rectangle のサイズを調整することで、間接的に画像の表示範囲を制御できます。

import QtQuick 2.0

Rectangle {
    width: 100
    height: 100
    clip: true // この Rectangle の範囲外の描画をクリップする

    Image {
        id: clippedImage
        source: "large_image.png"
        // sourceSize はここでは画像全体のサイズを指定することが多い
        sourceSize: Qt.size(implicitWidth, implicitHeight)
        // 表示位置を調整して見せたい部分を表示する
        x: -50
        y: -25
    }
}

解説

  • 欠点
    Image 要素自体のサイズや位置を管理する必要がある。拡大・縮小は Image 要素の scale プロパティなどで行う必要がある。
  • 利点
    直感的に表示領域を定義できる。
  • sourceSize は通常、元の画像全体のサイズを設定しますが、この方法では sourceX, sourceY, sourceWidth, sourceHeight は直接使用しません。
  • Image 要素の xy プロパティを調整することで、Rectangle の表示領域内に見せたい画像の部分を移動させます。
  • Rectangleclip プロパティを true に設定することで、この Rectangle の境界線を超えて描画される内容は切り取られます。

ShaderEffect を使用して画像を加工する

より高度な方法として、ShaderEffect を使用して画像の一部を切り取ったり、透明にしたりすることができます。GLSL (OpenGL Shading Language) でシェーダープログラムを記述する必要があるため、やや複雑になります。

import QtQuick 2.0
import QtQuick.Effects 1.0 // ShaderEffect を使用するためにインポート

Rectangle {
    width: 100
    height: 100

    ShaderEffect {
        anchors.fill: parent
        source: "large_image.png"
        property real clipX: 50 / source.width
        property real clipY: 25 / source.height
        property real clipWidth: 100 / source.width
        property real clipHeight: 100 / source.height

        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() {
                highp vec2 texCoord = qt_TexCoord0;
                if (texCoord.x >= clipX && texCoord.x < clipX + clipWidth &&
                    texCoord.y >= clipY && texCoord.y < clipY + clipHeight) {
                    gl_FragColor = texture2D(source, texCoord);
                } else {
                    gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); // 透明にする
                }
            }
        "
    }
}

解説

  • 欠点
    GLSL の知識が必要。パフォーマンスに注意が必要な場合がある。
  • 利点
    非常に柔軟な画像処理が可能。複雑な切り抜きや効果も実現できる。
  • texture2D(source, texCoord) で元のピクセルの色を取得し、指定された範囲内であればその色を、範囲外であれば透明な色 (vec4(0.0, 0.0, 0.0, 0.0)) を出力します。
  • clipX, clipY, clipWidth, clipHeight プロパティは、切り取りたい領域を 0.0 から 1.0 の範囲で指定します。
  • fragmentShader に記述された GLSL コードが、各ピクセルに対して実行されます。
  • ShaderEffect は、GPU を利用して画像に様々な視覚効果を適用できます。

複数の小さな Image 要素を組み合わせて表示する

スプライトシートのように、複数の独立した画像が一つにまとまっている場合に、それらを個別の Image 要素として読み込み、位置を調整して表示する方法です。

import QtQuick 2.0

Row {
    spacing: 0

    Image { source: "sprite_part1.png" }
    Image { source: "sprite_part2.png" }
    Image { source: "sprite_part3.png" }
    // ...
}

解説

  • 欠点
    事前に画像を分割する手間がかかる。多数の Image 要素を使用するとパフォーマンスに影響が出る可能性がある。
  • 利点
    シンプルで理解しやすい。個々のパーツに対して個別のプロパティ(アニメーションなど)を適用しやすい。
  • 複数の Image 要素を RowColumnGrid などのレイアウト要素を使って配置します。
  • 元の大きな画像を、表示したい個々の部分ごとに事前に切り分けておく必要があります。

Canvas 要素を使用して描画する

Canvas 要素は、JavaScript を使用してカスタムな描画を行うことができます。画像の一部を描画することも可能です。

import QtQuick 2.0

Canvas {
    width: 100
    height: 100

    onPaint: {
        var ctx = getContext("2d");
        var img = new Image();
        img.onload = function() {
            ctx.drawImage(img, 50, 25, 100, 100, 0, 0, width, height);
        }
        img.src = "large_image.png";
    }
}
  • 欠点
    JavaScript の知識が必要。QML の宣言的な記述とは異なるため、やや手続き的なコードになる。
  • 利点
    ピクセルレベルでの操作が可能。複雑な描画処理も実現できる。
  • drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) は、元の画像の (sx, sy) から (sWidth, sHeight) の領域を、Canvas の (dx, dy) に (dWidth, dHeight) のサイズで描画します。
  • Image オブジェクトを作成し、onload イベントハンドラーで画像のロード完了後に drawImage メソッドを使用して画像の一部を描画します。
  • Canvas 要素内で JavaScript を実行し、2D グラフィックコンテキストを取得します。