SSブログ

JavaScriptで "Cubic Hermite Spline補間関数" を作りました。 [プログラミング]

現在も制作中の、インタラクティヴ ディジタル アート作品の為に、JavaScript三次補間関数を作成致しました。
この関数に補間対象の1次元のデータ配列と拡大倍率の値を渡すと、 " 三次Hermite スプライン補間法 " (Cubic Hermite Spline Interpolator / キュービック エルミート スプライン補間法)でデータの内挿を行い、渡された値の間を滑らかに繋ぐ新たな値を生成して、1次元の新たなデータ配列を返却します。

Cubic Hermite Spline Interpolation_SS_(2016_08_01)_1_Cropped_1 黒い背景に水色の角ばった線と橙色の滑らかな曲線が重なりあったグラフとして描かれている。左上には白く光る丸とそこから緑色の曲線が放射されている模様が描かれている。
https://c1.staticflickr.com/9/8752/28065573983_d53a4c160a_o.png
これはグラフをHTML5のCanvas要素内に描画した際のスクリーンショット画像です。
水色の角ばった線が与えたデータを表しております。
橙色の曲線が補間されたデータを表しております。
与えたデータの点を通り、滑らかな曲線を描いております。
このグラフのデータの補間による拡大倍率は12倍です。

オーヴァーシュートとアンダーシュートが出ますので、値域に関しては適切な処理が必要です。

->->
[2016年8月2日追記]
" 三次Hermite スプライン補間関数 " のオプションを追加致しました。
与えられたデータ配列の最初の要素の値と最後の要素の値が滑らかに繋がり、循環するように指定出来ます。
Cubic Hermite Spline Interpolation_SS_(2016_08_02)_1_Cropped_1 黒い背景に青緑色のぐにゃぐにゃに歪んだ滑らかな曲線の円3つ描かれている。右上の歪んだ円の中央には白く光る丸が描かれ、そこから放射状に青色と紫色のリボンの様な曲線が伸びている模様が描かれている。
https://c1.staticflickr.com/9/8654/28701035475_245787f389_o.png
このスクリーンショット画像に描かれている歪んだ円環は、乱数による一様なノイズを複数回、スケールを変えながら滑らかにデータ補間しつつ合成して行ったものを円形に繋いだ曲線です。
円の始点と終点が滑らかに繋がるようにする為に、データ補間のオプションで、ノイズの最初のデータの値と最後のデータの値が循環するように指定したものです。
<-<-
->->
[2017年5月22日追記]
データを循環させる場合の繋げ方が適切でなかったので修正致しました。
データの最初と最後が補間により滑らかに繋がります。
<-<-

私のこのプログラムは行列計算などにはしていないので処理速度は速くはない筈ですが、綺麗に補間出来ます。
また、このコードはもしかすると間違っているかもしれません。

私のブログ記事: とても美しいディジタル アートになりました。
http://crater.blog.so-net.ne.jp/2016-07-26

私のブログ記事: HTML5のCamvasと "JavaScript" で音声処理と画像描画。
http://crater.blog.so-net.ne.jp/2016-06-30

私のブログ記事: "JavaScript" で音と画像を生成するプログラムのソースコード。
http://crater.blog.so-net.ne.jp/2016-06-30-1

この関数の作成の為にWikipediaなどを参照させて頂きました。

"Wikipedia" (English)の "Cubic Hermite spline" のページのURL:
https://en.wikipedia.org/wiki/Cubic_Hermite_spline

以下に自作のソース コードを掲載致します。
自己責任の上で御自由にお使い下さいませ。
当ソース コードには間違いがあるかもしれません。
もし当ソース コードを使用した事による如何なる問題につきましても、私は責任を取る事が出来ません。
御了承下さいませ。

引数は、以下のものと致します。

dataArray:
補間対象の1次元データ配列。

dataArrayLength:
データ配列の最初の要素から数えての補間対象とするデータの要素数。
dataArray[0]からdataArray[15]までを対象とする場合、16という数値を渡す。

scaleFactor:
拡大倍率。
8倍に拡大したい場合、8という数値を渡す。

isDataLoop:
データの最初と最後を循環させる場合、trueを渡し、循環させない場合、falseを渡す。

// 三次Hermite スプライン補間関数を関数式により定義する。
var cubicHermiteSplineInterpolation = function( dataArray, dataArrayLength, scaleFactor, isDataLoop )
{
 var interpolatedDataArray = [];
 var startingTangent = 0;
 var endingTangent = 0;

 if( isDataLoop === true )
 // データを循環させる場合は以下の処理を行う。
 {
  // データの要素を1つ分だけ後ろへ移動する。
  var i = dataArrayLength;
  for( ; i > 0; i-- )
  {
   dataArray[i] = dataArray[i - 1];
  }

  dataArray[0] = dataArray[dataArrayLength]; // 元のデータの最後の要素の値を先頭の要素に複製する。
  dataArray[dataArrayLength + 1] = dataArray[1]; // 元のデータの最初の要素の値を最後の要素の1つ後に複製する。
  dataArray[dataArrayLength + 2] = dataArray[2]; // 元のデータの最初から2番目の要素の値を最後の要素の2つ後に複製する。
  dataArray[dataArrayLength + 3] = dataArray[3]; // 元のデータの最初から3番目の要素の値を最後の要素の3つ後に複製する。

  var j = 0;
  var k = 0;
  for( i = 1; i < dataArrayLength + 2; i++ )
  {
   // 区間の正接の計算を行う。
   startingTangent = 0.5 * (dataArray[i + 1] - dataArray[i - 1]);
   endingTangent = 0.5 * (dataArray[i + 2] - dataArray[i]);

   // 内挿を行う。
   for( j = 0; j < scaleFactor; j++, k++ )
   {
    var x = j / scaleFactor;
    interpolatedDataArray[k] =
     ((1 + 2 * x) * (1 - x) * (1 - x)) * dataArray[i]
     + (x * (1 - x) * (1 - x)) * startingTangent
     + (x * x * (3 - 2 * x)) * dataArray[i + 1]
     + (x * x * (x - 1)) * endingTangent;
   }
  }

  // 最後部の不要になった要素を削除する。
  for( i = dataArrayLength * scaleFactor; i < (dataArrayLength + 1) * scaleFactor; i++ )
  {
   interpolatedDataArray.pop();
  }

  // 返却される配列の要素数は、 'dataArrayLength * scaleFactor' となる。
 }else
 // データを循環させない場合は以下の処理を行う。
 {
  var i = 0;
  var j = 0;
  var k = 0;
  for( ; i < dataArrayLength - 1; i++ )
  {
   if( i === 0 )
   // 最初の区間の設定を行う。
   {
    startingTangent = (dataArray[i + 1] - dataArray[i]);
    endingTangent = 0.5 * (dataArray[i + 2] - dataArray[i]);
   }else if( i === dataArrayLength - 2 )
   // 最後の区間の設定を行う。
   {
    startingTangent = 0.5 * (dataArray[i + 1] - dataArray[i - 1]);
    endingTangent = (dataArray[i + 1] - dataArray[i]);
   }else
   // 最初と最後以外の区間の設定を行う。
   {
    startingTangent = 0.5 * (dataArray[i + 1] - dataArray[i - 1]);
    endingTangent = 0.5 * (dataArray[i + 2] - dataArray[i]);
   }

   // 内挿を行う。
   for( j = 0; j < scaleFactor; j++, k++ )
   {
    var x = j / scaleFactor;
    interpolatedDataArray[k] =
     ((1 + 2 * x) * (1 - x) * (1 - x)) * dataArray[i]
     + (x * (1 - x) * (1 - x)) * startingTangent
     + (x * x * (3 - 2 * x)) * dataArray[i + 1]
     + (x * x * (x - 1)) * endingTangent;
   }
  }
  interpolatedDataArray[(dataArrayLength - 1) * scaleFactor] = dataArray[dataArrayLength - 1]; // 最後の要素の値を代入する。

  // 返却される配列の要素数は、 '(dataArrayLength - 1) * scaleFactor + 1' となる。
 }
 return( interpolatedDataArray ); // 補間された新たな1次元のデータ配列を返却する。
};

コメント(0)  トラックバック(0) 

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

※ブログオーナーが承認したコメントのみ表示されます。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。