[C# WPF] ScrollViewer内にWriteableBitmapで描画してみる

前回に引き続き、もうちょっとWriteableBitmapを使ってみます。

今回はScrollViewerで表示されている部分にだけ、WriteableBitmapで描画を行います。

スポンサーリンク

環境

  • Visual Studio 2019
  • .NET Framework 4.7.2

ScrollViewerの表示領域にだけ描画してみる

何も考えずにImageを置き換えるとこんな感じになります。

・MainWindow.xaml

<Window x:Class="Sample_WriteableBitmap.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:Sample_WriteableBitmap"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <ScrollViewer x:Name="xScrollViewer"
                  HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible"
                  ScrollChanged="ScrollViewer_ScrollChanged">
        <!-- Windowの3倍くらいのCanvasを入れておく -->
        <Canvas x:Name="xCanvas"
                Height="1800" Width="2400">
            <Image x:Name="xImage"/>
        </Canvas>
    </ScrollViewer>

</Window>

・MainWindow.xaml.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Sample_WriteableBitmap
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            DrawImage();
        }

        private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            DrawImage();
        }

        private void DrawImage()
        {
            // WritableBitmapを生成する
            // ScrollViewerの表示領域ではなくCanvas自体のサイズを指定します
            var canvasWidth = (int)xCanvas.Width;
            var canvasHeight = (int)xCanvas.Height;
            var bitmap = new WriteableBitmap(canvasWidth, canvasHeight, 96, 96, PixelFormats.Pbgra32, null);

            // 描画データを格納するバイト列を生成する
            // バイト列のサイズはScrollViewerでの表示領域の分だけ作ります
            var windowWidth = (int)Width;
            var windowHeight = (int)Height;
            var size = windowWidth * windowHeight * 4;
            var pixels = new byte[size];

            // バイト列に描画データを格納する
            for (int i = 0; i < size; i += 4)
            {
                pixels[i] = 255;        // Blue
                pixels[i + 1] = 0;      // Green
                pixels[i + 2] = 0;      // Red
                pixels[i + 3] = 255;    // Alpha
            }

            // スクロール位置に合わせてオフセット距離を算出します
            // オフセット後の描画でCanvas領域をはみ出すとクラッシュするのでリミットを掛けます
            var destinationX = (int)xScrollViewer.HorizontalOffset;
            destinationX = (destinationX < 0) ? 0 : (canvasWidth - windowWidth) < destinationX ? (canvasWidth - windowWidth) : destinationX;
            var destinationY = (int)xScrollViewer.VerticalOffset;
            destinationY = (destinationY < 0) ? 0 : (canvasHeight - windowHeight) < destinationY ? (canvasHeight - windowHeight) : destinationY;

            // バイト列 -> BitmapImage
            var stride = windowWidth * 4;    // 1行あたりのバイト数
            bitmap.WritePixels(new Int32Rect(0, 0, windowWidth, windowHeight), pixels, stride, destinationX, destinationY);

            // 転送
            xImage.Source = bitmap;
        }
    }
}

大したことをやっていないのに描画が遅い気がする。

スクロールバーを掴んで動かしているとチラつくときがあります。

WriteableBitmapは、自前でゼロから何かを描く処理には適していないっぽいですね。

画像を読み込んで編集するときには良さそうです。

おしまい。

スポンサーリンク

C#,WPFC#,WPF

Posted by peliphilo