今日のお絵描き

バイキュービック法によるリサンプリングを実装した。

バイキュービック法っていうと画像の拡大縮小回転を連想されることが多いけど、それには限らないよね。与えられた画像の非整数座標の値をサンプリングする方法なので、いろんな変換をして遊べる。

これで作品作りの幅が広がるなー。左下に出ている模様はピクセルのサイズに起因するものだと思うので1ピクセルが1/16mmくらいで印刷したときには気にならない予定。ただこのサイズで6秒掛かるのがなぁ。A3だとこの100倍のサイズなので10分かかってしまうなぁ。もう少し高速化の余地がないか調べてみる必要がありそうだ。

ソースコード、貼った瞬間ミスに気付いて(const &にし忘れ) 6.49sec -> 1.94sec。まだまだ馬鹿なミスがありそう。

double get_pixel(const GrayCanvas& src, int x, int y){
  // if (x, y) out of canvas, return nearest value
  if(x < 0) x = 0;
  if(y < 0) y = 0;
  if(x >= src.width) x = src.width - 1;
  if(y >= src.height) y = src.height - 1;
  return src.image[y][x];
} 

void _bicubic(double t, vector<double> &ks){
  double t2 = t * t, t3 = t2 * t;
  ks[0] = 0 + -1 * t +  2 * t2 + -1 * t3;
  ks[1] = 2 +  0 * t + -5 * t2 +  3 * t3;
  ks[2] = 0 +  1 * t +  4 * t2 + -3 * t3;
  ks[3] = 0 +  0 * t + -1 * t2 +  1 * t3;
}

double _dot_product(const vector<double>& xs, const vector<double>& ys){
  double result = 0.0;
  for(size_t i=0; i<xs.size(); i++){
    result += xs[i] * ys[i];
  }
  return result;
}

double get_bicubic(const GrayCanvas& src, double x, double y){
  double t = x - floor(x);
  vector<double> ks(4);
  _bicubic(t, ks);
  vector<double> xs(4), ys(4);
  for(size_t i=0; i<4; i++){
    for(size_t j=0; j<4; j++){
      xs[j] = get_pixel(src, floor(x) - 1 + j, floor(y) - 1 + i);
    }
    ys[i] = 0.5 * _dot_product(ks, xs); 
  }
  t = y - floor(y);
  _bicubic(t, ks);
  return 0.5 * _dot_product(ks, ys);
}

ピクセルごとにget_bicubicが叩かれるのでget_bicubicの中でvector作るのはやめた方が良さそうだなぁ。まあ後でプロファイラーを掛ける。

実装にかかった時間: 1時間、やる気を出すまでにかかった時間7日(苦笑


行き帰りの電車でプロファイラ掛けたりとかした。100倍のサイズ(A3に360DPI)で3m49.047s。しかしボトルネックバイキュービックでのリサンプリングじゃなかった。

gaussはガウシアン分布を自前で計算して加算合成しているコードで、まあそりゃ重いよね。まあこれに関してはFFTを使って高速化することにしよう。

今日のお絵描き画像