WPF学習中「2枚の画像を左右に並べて表示 – その2」

C# コンピュータ
C#

Windowサイズに合わせて画像が拡大縮小して画像全体を表示させたいのですが、ImageコントロールのStretchプロパティの動作が今一つ理解できず、いろいろ試行錯誤してみました。

プロジェクトの作成

mkdir プロジェクト名
cd プロジェクト名
dotnet new wpf
code .

ソースコード

ファイル名:MainWindow.xaml

<Window x:Class="Sample55CopyPixels2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Sample55CopyPixels2"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <StackPanel>
            <Canvas>
                <Image
                    Name="Image1"
                    Width="{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType=StackPanel}}"
                    Height="{Binding ActualHeight, RelativeSource={RelativeSource FindAncestor, AncestorType=StackPanel}}"
                    Stretch="Uniform">
                </Image>
            </Canvas>
        </StackPanel>
    </Grid>
</Window>

ファイル名:MainWindow.xaml.cs

using System.IO;
using System.Windows;
using System.Windows.Media.Imaging;

namespace Sample55CopyPixels2;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    /// <summary>
    /// ビットマップソースの読み込み
    /// </summary>
    /// <param name="fs">ファイルストリーム</param>
    /// <returns>BitmapImageオブジェクト</returns>
    public static async Task<BitmapImage> LoadBitmapImageAsync(Stream fs)
    {
        using var ms = new MemoryStream();

        await fs.CopyToAsync(ms);

        BitmapImage bi = await Task.Run(()=>
        {
            BitmapImage bi = new();
            ms.Seek(0, SeekOrigin.Begin);

            bi.BeginInit();
            bi.CacheOption = BitmapCacheOption.OnLoad;
            bi.StreamSource = ms;
            bi.EndInit();
            bi.Freeze();

            return bi;
        });

        return bi;
    }
    /// <summary>
    /// ビットマップソースの読み込み
    /// </summary>
    /// <param name="file">画像ファイルのパス</param>
    /// <returns>BitmapImageオブジェクト</returns>
    public static async Task<BitmapImage> LoadBitmapImageAsync(string file)
    {
        using var fs = new FileStream(file, FileMode.Open, FileAccess.Read);
        return await LoadBitmapImageAsync(fs);
    }
    /// <summary>
    /// 2枚のBitmapImageを左右に連結
    /// </summary>
    /// <param name="lb"></param>
    /// <param name="rb"></param>
    /// <returns>WriteableBitmapオブジェクト</returns>
    public static async Task<WriteableBitmap> JoinBitmapImageAsync(BitmapImage lb, BitmapImage rb)
    {
        int lb_stride = (lb.PixelWidth * lb.Format.BitsPerPixel + 7) / 8;
        byte[] lb_datas = new byte[lb_stride * lb.PixelHeight];
        
        int rb_stride = (rb.PixelWidth * rb.Format.BitsPerPixel + 7) / 8;
        byte[] rb_datas = new byte[rb_stride * rb.PixelHeight];

        Task lcopy = Task.Run(()=>
        {
            // 左側画像をbyte配列にコピー
            lb.CopyPixels(
                new Int32Rect(0, 0, lb.PixelWidth, lb.PixelHeight), lb_datas, lb_stride, 0);
        });

        Task rcopy = Task.Run(()=>
        {
            // 右側画像をbyte配列へコピー
            rb.CopyPixels(
                new Int32Rect(0, 0, rb.PixelWidth, rb.PixelHeight), rb_datas, rb_stride, 0);
        });

        await lcopy;
        await rcopy;

        WriteableBitmap wb = await Task.Run(()=>
        {
            // 表示画像の作成
            int height = (lb.PixelHeight > rb.PixelHeight) ? lb.PixelHeight : rb.PixelHeight;
            int width = lb.PixelWidth + rb.PixelWidth;
            WriteableBitmap wb = new (width, height, lb.DpiX, lb.DpiY, lb.Format, null);

            // 左右のbyte配列をWriteableBitmapへコピー
            wb.WritePixels(
                new Int32Rect(0, 0, lb.PixelWidth, lb.PixelHeight), lb_datas, lb_stride, 0);
            wb.WritePixels(
                new Int32Rect(lb.PixelWidth, 0, rb.PixelWidth, rb.PixelHeight), rb_datas, rb_stride, 0);

            wb.Freeze();
            return wb;
        });


        // 画像をコピー
        return wb;
    }

    async void init()
    {
        // 左側画像の読み込み
        string leftPath = @"F:\csharp\dotnet8\wpf\Sample55CopyPixels2\sample1.png";
        var lb = await LoadBitmapImageAsync(leftPath);

        // 右側画像の読み込み
        string rightPath = @"F:\csharp\dotnet8\wpf\Sample55CopyPixels2\sample2.png";
        var rb = await LoadBitmapImageAsync(rightPath);

        // 左右の画像を結合        
        WriteableBitmap wb = await JoinBitmapImageAsync(lb, rb);

        // イメージコントロールのソースにビットマップイメージを割り当て
        Image1.Source = wb;
    }
    /// <summary>
    /// コンストラクタ
    /// </summary>
    public MainWindow()
    {
        InitializeComponent();

        init();
    }
}

感想

成功すると画像が表示されます。

MicrosoftのサイトでImage.Stretchプロパティの規定値のUniformの説明を見ると

割り当てられた広さに収まるようにコンテンツのサイズを変更しますが、元の縦横比が維持されます。

との記述があります。”割り当てられた広さ”をImageコントロールのサイズだと当たりをつけて考えてみます。
前回は画像を16:9になるように加工していましたが、今回は単純に2枚並べる加工にしています。

縦横比を維持しながら画像全体が表示されるようになり、望み通りの動作をするようになりました。

コメント