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

トップページへ

 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」で紹介しています。

 ClockGroup を使ったアニメーション−応用編(2D)

1.作成するアニメーション動画の内容
このアニメーション動画は以下のように構成されています。
 (1) 1枚の画像を 16 分割して、16 個の画像要素(以下、ピースと略)を作成し、Canvas 上に配置する。
 (2) 16 個のピースを1個づつ、1秒間隔で Canvas 上の所定位置まで回転させながら移動して、元の画像を復元させる。
ピースの回転と移動は、回転と移動の Transform を生成して、これに DoubleAnimation を適用して行います。
この Transform へのDoubleAnimation の適用は、ClockGroup を介して行います。

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


作者の以下の公開動画、JigsawAnime シリーズには、このアニメーション手法が応用されています。
JigsawAnime(BallPicture) JigsawAnime(Fractal 1) JigsawAnime(Fractal 2)
JigsawAnime(Fractal Ball) JigsawAnime(JellyFish) JigsawAnime(CubeMapping)
JigsawAnime(Sliced Balls) JigsawAnime(CubeMapping 2)

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

3.XAMLコード
XAMLコードは、Canvas のサイズと Background の値を除いて、第1例 と同じなので、省略します。

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

    private const int DST_X = 183;       // 配置先のLeft(DestinationLeft)
    private const int NUM = 4;         // 縦横各方向のピースの数
    private const int SIZE = 70;        // 1個のピースの縦横サイズ
    private const int INI_X = -100;       // 初期配置のLeft
    private const int INI_Y = 105;       // 初期配置のTop
    private const int TIME = 1;
    private const int TOTAL = NUM * NUM;    // 全ピースの数
    private readonly int[] ORDER =       // ピースの移動順番(10番ピースから開始)
        { 10, 0, 13, 2, 15, 5, 8, 7, 1, 14, 4, 11, 9, 3, 12, 6 };
    private ClockGroup _cgRot, _cgTrX, _cgTrY;
    private Piece[] _piece = new Piece[TOTAL]; // Pieceオブジェクトの配列

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

    // [開始]ボタンクリックイベント
    private void btnStart_Click(object sender, RoutedEventArgs e)
    {
      _cgRot.Controller.Begin();
      _cgTrX.Controller.Begin();
      _cgTrY.Controller.Begin();
    }

    // [停止]ボタンクリックイベント
    private void btnStop_Click(object sender, RoutedEventArgs e)
    {
      _cgRot.Controller.Stop();
      _cgTrX.Controller.Stop();
      _cgTrY.Controller.Stop();
    }


    // 切り取り画像のBitmapを返すメソッド
    private CroppedBitmap GetCrpBmp(BitmapFrame vframe,
              int left, int top, int width, int height)
    {
      var cbmp = new CroppedBitmap();
      cbmp.BeginInit();
      cbmp.Source = vframe;
      cbmp.SourceRect = new Int32Rect(left, top, width, height);
      cbmp.EndInit();

      return cbmp;
    }

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

    // ピースのアニメーションを生成するメソッド
    private void CreateAnimation()
    {
      // ParallelTimeline生成
      var ptRot = new ParallelTimeline();
      var ptTrX = new ParallelTimeline();
      var ptTrY = new ParallelTimeline();

      // Transform(回転、移動)の配列生成
      RotateTransform[] rottran = new RotateTransform[TOTAL];
      TranslateTransform[] tranX = new TranslateTransform[TOTAL];
      TranslateTransform[] tranY = new TranslateTransform[TOTAL];

      var tsDur = new TimeSpan(0, 0, TIME);  // アニメーションのTimeSpan

      // EasingFunction設定
      var easing = new CircleEase();
      easing.EasingMode = EasingMode.EaseOut;

      // ピースのアニメーションとClockGroup生成
      for (int i = 0; i < TOTAL; i++)
      {
        var tsBegin = new TimeSpan(0, 0, i * TIME + 1);

        // Transform(回転、移動)設定
        rottran[i] = new RotateTransform();
        rottran[i].CenterX = SIZE / 2;
        rottran[i].CenterY = SIZE / 2;
        tranX[i] = new TranslateTransform();
        tranY[i] = new TranslateTransform();
        var groupT = new TransformGroup();
        groupT.Children.Add(rottran[i]);
        groupT.Children.Add(tranX[i]);
        groupT.Children.Add(tranY[i]);
        _piece[i].RenderTransform = groupT;

        // ピース回転のアニメーション
        var daRot = new DoubleAnimation(360, new Duration(tsDur));
        daRot.BeginTime = tsBegin;
        daRot.EasingFunction = easing;
        ptRot.Children.Add(daRot);

        // ピース横方向移動のアニメーション
        var daX = new DoubleAnimation(_piece[i].DestX, new Duration(tsDur));
        daX.BeginTime = tsBegin;
        daX.EasingFunction = easing;
        ptTrX.Children.Add(daX);

        // ピース縦方向移動のアニメーション
        var daY = new DoubleAnimation(_piece[i].DestY, new Duration(tsDur));
        daY.BeginTime = tsBegin;
        daY.EasingFunction = easing;
        ptTrY.Children.Add(daY);
      }

      // ClockGroup生成
      _cgRot = ptRot.CreateClock();  // 回転のClockGroup
      _cgTrX = ptTrX.CreateClock();  // 横方向移動のClockGroup
      _cgTrY = ptTrY.CreateClock();  // 縦方向移動のClockGroup

      // Transform(回転、移動)へClockGroupの子要素適用
      for (int i2 = 0; i2 < TOTAL; i2++)
      {
        rottran[i2].ApplyAnimationClock(RotateTransform.AngleProperty,
                        (AnimationClock)_cgRot.Children[i2]);
        tranX[i2].ApplyAnimationClock(TranslateTransform.XProperty,
                       (AnimationClock)_cgTrX.Children[i2]);
        tranY[i2].ApplyAnimationClock(TranslateTransform.YProperty,
                       (AnimationClock)_cgTrY.Children[i2]);
      }

      _cgRot.Controller.Stop();
      _cgTrX.Controller.Stop();
      _cgTrY.Controller.Stop();
    }

    // ピースを生成するメソッド
    private void CreatePieces()
    {
      var frame = BitmapFrame.Create(new Uri("Pic1.JPG", UriKind.Relative));

      for (int i = 0; i < TOTAL; i++)
      {
        _piece[i] = new Piece(); // Pieceクラス(最下行で定義)のインスタンス生成

        Canvas.SetLeft(_piece[i], INI_X);
        Canvas.SetTop(_piece[i], INI_Y);
        var x = ORDER[i] / NUM * SIZE;
        var y = ORDER[i] % NUM * SIZE;
        _piece[i].DestX = DST_X + x;
        _piece[i].DestY = y - INI_Y;
        _piece[i].Source = GetCrpBmp(frame, x, y, SIZE, SIZE);
        cvs1.Children.Add(_piece[i]);
      }
    }

    // ピースとそのアニメーションの設定
    private void InitializeWindow()
    {
      CreatePieces();
      CreateAnimation();
    }

    // Aviファイル作成
    private void SaveAsAvi(string filepath)
    {
      var fps = 48;                  // フレームレート
      var totalFrames = fps * TIME * (TOTAL + 2);   // 全フレーム数
      var secs = Enumerable.Range(0, totalFrames).Select(t => (((double)t) / fps));
      var aviManager = new AviFile.AviManager(filepath, false);
      AviFile.VideoStream aviStream = null;
      var tempFile = "frame.png";

      foreach (var sec in secs)
      {
        _cgRot.Controller.SeekAlignedToLastTick(TimeSpan.FromSeconds(sec),
                            TimeSeekOrigin.BeginTime);
        _cgTrX.Controller.SeekAlignedToLastTick(TimeSpan.FromSeconds(sec),
                            TimeSeekOrigin.BeginTime);
        _cgTrY.Controller.SeekAlignedToLastTick(TimeSpan.FromSeconds(sec),
                            TimeSeekOrigin.BeginTime);
        cvs1.UpdateLayout();
        cvs1.SaveImage(tempFile);      // cvs1の画像をファイルに保存する
        var bmp = new System.Drawing.Bitmap(tempFile);
        if (aviStream == null)
          aviStream = aviManager.AddVideoStream(true, fps, bmp);
        else
          aviStream.AddFrame(bmp);
        bmp.Dispose();
      }
      aviManager.Close();
      
      _cgRot.Controller.Stop();
      _cgTrX.Controller.Stop();
      _cgTrY.Controller.Stop();
      
      System.IO.File.Delete(tempFile);
    }
    
    // Pieceクラスの定義−Imageクラスに DestX, DestY プロパティを追加したもの
    private class Piece : Image
    {
      public int DestX { get; set; }  // ピース着地点のX座標
      public int DestY { get; set; }  // ピース着地点のY座標
    }
  }

Piece クラス(private クラス)の定義は、コードの最後部に記載してあります。
ピース(Piece クラスのインスタンス)は、[CreatePieces]メソッドで生成されます。
ピースの各アニメーション、各ClockGroup は[CreateAnimation]メソッドで生成されます。
アニメーションの開始は、[開始]ボタンクリックイベント内の、各 ClockGroup の Begin メソッドで行います。
アニメーションの停止は、[停止]ボタンクリックイベント内の、各 ClockGroup の Stop メソッドで行います。
Aviファイル作成のためにフレーム画像を取り出す操作は、[SaveAsAvi]メソッド内の、各 ClockGroup の SeekAlignedToLastTick メソッドで行います。
[SaveAsAvi]メソッドの中間付近にある cvs1 の SaveImage 拡張メソッドについては、第1例 の記載を参照してください。
[CreateAnimation]メソッドで使用した EasingFunction は、ピースを減速して着地させるために使用しています。

 ダウンロード

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

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

 公開・更新履歴

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

 ご質問・ご意見・ご感想

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

トップページへ