AnimationClock を使ってアニメーション動画を作る(9)

トップページへ

 AnimationClock を使用したアニメーションの紹介

  1.DoubleAnimationUsingKeyFrames を使用したアニメーション(2D)
  2.Transform を使った円のアニメーション(2D)
  3.指定された経路上で円を運動させるアニメーション(2D)
  4.TileMode で描画された画像のアニメーション(2D)
  5.自公転する立方体のアニメーション(3D)
  6.立方体の表示にタイムラグを設けるアニメーション(3D)
  7.ClockGroup を使ったアニメーション−基本編(2D)
  8.ClockGroup を使ったアニメーション−応用編(2D)
  9.残像を形成するアニメーション(2D) (このページ)

なお、これらの動画は、「ソフト関連Trial−No.109」で紹介しています。

 残像を形成するアニメーション動画の作成方法(2D)

Processing言語などでは、残像を伴ったアニメーションが簡単にできます(というより、背景を更新しなければ、重ね書きが自動的に行われて、残像のあるアニメーションになります)。
一方、WPFには残像を形成する標準的なメソッドは組み込まれていない(と思います)ので、自分で残像形成の機能を作る必要があります。
今回の例は、WPFでの残像形成への1つの挑戦例で、一応残像が表示できるようになったので、その方法を紹介します。

1.作成するアニメーションの内容
DoubleAnimation を使用して 円 を左右に移動させるアニメーションに、移動する円の残像を表示する機能を 組み込んだものです。

アニメーション作成の画面は以下のようになります。


ここで使用したものと同種の手法を、3D球状オブジェクトの残像に適用した動画について、以下のページで紹介しています。
   「BallPicture」シリーズ − 「Infinity Path」
   「BallPicture」シリーズ − 「Parabolas」

2.使用した開発環境
第1例 と同じく、以下の開発環境を使用しました。
開発環境:Visual Studio 2010 Professional Edition
.NET Framework:.NET Framework 4 Client Profile
古いバージョンを使いましたが、最新の開発環境でも問題なく使用できると思います。

3.XAMLコード
XAMLコードでは、Canvas と ボタン類を配置します。
なお、ボタンのサイズ、位置、フォントなどを設定する記述は省略しています。

<Window x:Class="Example01_DoubleAnimationUKF.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Width="530" Height="360"
    WindowStartupLocation="CenterScreen" ResizeMode="NoResize">
 <Grid>
  <Grid.RowDefinitions>
   <RowDefinition Height="280"/>
   <RowDefinition Height="*"/>
  </Grid.RowDefinitions>
  <Canvas Name="cvs1" Grid.Row="0" Background="Blue" />
   <Canvas Name="cvsBlur" Width="524" Height="280" />
   <Canvas Name="cvsPic" Width="524" Height="280" />
  </Canvas>
  <StackPanel Grid.Row="1" Orientation="Horizontal" Background="LightGray">
   <Button Name="btnStart" Content="開 始" Click="btnStart_Click" />
   <Button Name="btnClear" Content="クリア" Click="btnClear_Click" />
   <Button Name="btnAvi" Content="Avi" Click="btnAvi_Click" />
  </StackPanel>
 </Grid>
</Window>

残像を表示できるようにするため、[cvs1]の中に[cvsBlur]と[cvsPic]を配置します。
[cvsPic]は 円 を配置して、アニメーションを行う Canvas、
[cvsBlur]は、残像形成に使用する Canvas、
[cvs1]は、AVIファイルを作成するためのフレーム画像をとりだす Canvas です。

4.C#コード

  // usingによって名前空間を参照するコードの部分は省略
  
  public partial class MainWindow : Window
  {
    // コンストラクター
    public MainWindow()
    {
      InitializeComponent();
      InitializeWindow();
    }

    private const double TIME = 3;      // アニメーションの時間(片道)
    private AnimationClock _clock;

    // [Avi]ボタンクリックイベント
    private void btnAvi_Click(object sender, RoutedEventArgs e)
    {
      var filepath = GetAviFilePath();
      if (0 < filepath.Length)
      {
        StartAnimation(filepath);
      }
    }

    // [クリア]ボタンクリックイベント
    private void btnClear_Click(object sender, RoutedEventArgs e)
    {
      cvsBlur.Background = new SolidColorBrush(Colors.Transparent);
    }

    // [開始]ボタンクリックイベント
    private void btnStart_Click(object sender, RoutedEventArgs e)
    {
      StartAnimation(String.Empty);
    }

    // 不透明度が設定されたImageBrushを返すメソッド
    private ImageBrush GetFadeImageBrush(string filename, double opacity)
    {
      var bmpImg = new BitmapImage();
      using (var stream = System.IO.File.OpenRead(filename))
      {
        bmpImg.BeginInit();
        bmpImg.CacheOption = BitmapCacheOption.OnLoad;
        bmpImg.StreamSource = stream;
        bmpImg.EndInit();
        stream.Close();
      }
      var ibrush = new ImageBrush(bmpImg);
      ibrush.Opacity = opacity;

      return ibrush;
    }

    // Aviファイル保存パス取得
    private string GetAviFilePath()
    {
      // 省略
    }

    // DoEvents処理を行うメソッド
    // 出典:https://www.ipentec.com/document/document.aspx?
    //        page=csharp-wpf-implement-application-doevents
    private void DoEvents()
    {
      var frame = new DispatcherFrame();
      var callback = new DispatcherOperationCallback(obj =>
      {
        ((DispatcherFrame)obj).Continue = false;
        return null;
      });
      Dispatcher.CurrentDispatcher.BeginInvoke(
            DispatcherPriority.Background, callback, frame);
      Dispatcher.PushFrame(frame);
    }

    // 円の配置とアニメーションの適用
    private void InitializeWindow()
    {
      const double DIAMETER = 60;
      
      // 円の生成と配置
      var circle = new Ellipse();
      circle.Width = DIAMETER;
      circle.Height = DIAMETER;
      Canvas.SetLeft(circle, 0);
      Canvas.SetTop(circle, 100);
      var gscollect = new GradientStopCollection();
      gscollect.Add(new GradientStop(Colors.Yellow, 0.5));
      gscollect.Add(new GradientStop(Colors.Red, 0.5));
      circle.Fill = new LinearGradientBrush(gscollect);
      cvsPic.Children.Add(circle);      // 円をcvsPicに配置する

      // アニメーション生成と円への適用
      var animation = new DoubleAnimation(455, new Duration(TimeSpan.FromSeconds(TIME)));
      animation.AutoReverse = true;
      animation.RepeatBehavior = RepeatBehavior.Forever;
      _clock = animation.CreateClock();
      circle.ApplyAnimationClock(Canvas.LeftProperty, _clock);
      _clock.Controller.Stop();
    }

    // アニメーションのメソッド
    private void StartAnimation(string filepath)
    {
      const int NUM_BLUR = 12;    // 残像表示間隔を決める数値、小さいほど密に表示される
      const double OPACITY = 0.9;   // 不透明度、0〜1の範囲で設定
      
      var isAvi = (0 < filepath.Length);    // AVIファイル作成時に true
      var fps = 48;               // フレームレート
      var totalFrames = (int)(fps * TIME * 2); // 全フレーム数
      var secs = Enumerable.Range(0, totalFrames).Select(t => (((double)t) / fps));
      AviFile.VideoStream aviStream = null;
      AviFile.AviManager aviManager = null;
      if (isAvi)
        aviManager = new AviFile.AviManager(filepath, false);
      var tempFile = "frame.png";
      
      var count = 0;              // 残像形成工程で使用するカウンター

      foreach (var sec in secs)
      {
        count += 1;
        _clock.Controller.SeekAlignedToLastTick(TimeSpan.FromSeconds(sec),
                            TimeSeekOrigin.BeginTime);
        cvs1.UpdateLayout();
        cvs1.SaveImage(tempFile);       // cvs1の画像をファイルに保存する
        
        if (isAvi)
        {
          var bmp = new System.Drawing.Bitmap(tempFile);
          if (aviStream == null)
            aviStream = aviManager.AddVideoStream(true, fps, bmp);
          else
            aviStream.AddFrame(bmp);
          bmp.Dispose();
        }
        
        // 以下の行で残像を形成する
        if (count % NUM_BLUR == 0)
        {
          cvsBlur.Background = GetFadeImageBrush(tempFile, OPACITY);
          DoEvents();
        }
      }
      
      if (isAvi)
      {
        aviManager.Close();
        aviStream = null;
        aviManager = null;
      }
      
      _clock.Controller.Stop();
      System.IO.File.Delete(tempFile);
      
      MessageBox.Show("OK");
    }
  }

このプロジェクトのアニメーションは、[InitializeWindow]メソッド内で定義される単純な DoubleAnimation で、 これから AnimationClock を生成します。
[開始]ボタンのクリックで、[StartAnimation]メソッドが実行され、 AnimationClock の SeekAlignedToLastTick メソッドでアニメーションが行われます。
残像の形成は、[StartAnimation]メソッド内の以下のコードで行います。
  // 以下の行で残像を形成する
  if (count % NUM_BLUR == 0)
  {
    cvsBlur.Background = GetFadeImageBrush(tempFile, OPACITY);
    DoEvents();
  }

[StartAnimation]メソッドの中間付近にある cvs1 の SaveImage 拡張メソッドについては、第1例 の記載を参照してください。

 ダウンロード

ここで紹介したアニメーションを作成するプロジェクトファイルは、以下のリンクからダウンロードすることができます。
「Example09_Blur」プロジェクトファイルのダウンロード
なお、このプロジェクトでは、AVIファイル化用クラスライブラリとして、Code Project の「 A Simple C# Wrapper for the AviFile Library 」からダウンロードして入手した[AviFile.dll]を使用しています。

ダウンロードしたファイルの利用は、全て利用される方の責任で行っていただきます。
作者は十分な注意を払って本ファイルを作成していますが、もし万一、本ファイルの内容に誤りがあっても、 またその利用によって使用パソコンに問題が発生しても、作者は一切責任を負いません。

 公開・更新履歴

 2017/09/01 ページを公開しました。

 ご質問・ご意見・ご感想

ご質問、ご意見、ご感想等の連絡は、 こちらへ

トップページへ