第03回 回転

GIMPなどの画像編集ツールには、画像を回転させる機能がある。今回は、Processingの描画機能を使った方法、出力画像のピクセルの色を自前で計算する方法の2通りを使い、元画像の一部を拡大しつつ回転するプログラムを作る。

今回のプログラムの最終的な機能

はじめに、これから作るプログラムが完成した時点の機能をざっと見ておく。


選択範囲の赤枠を表示する

マウスカーソルの位置に応じて上の図のような赤枠を表示したい。


  1. angleを求める
  2. 例えば画面の中心(width/2, height/2)と、マウスカーソルの位置(mouseX, mouseY)が図のような位置関係にあったとすると、


    この直角三角形の縦横の辺の長さはそれぞれ「mouseX-width/2」「mouseY-height/2」になる。


    Processingのatan2関数を使えば、角度「angle」は
    
    angle = atan2(縦の辺の長さ, 横の辺の長さ);
    	
    で求められる。

  3. scaleを求める
  4. 図のAが「画面の中心から右端までの距離」、Bが「選択範囲の中心から右端までの距離」にあたる。そのため、BをAで割れば「画面に対する選択範囲の大きさの比」、つまりscaleが求められる。


    Aは単純に
    
    A = width/2;
    	
    BはProcessingのdist関数を使って
    
    B = dist(width/2, height/2, mouseX, mouseY);
    	
    で求められる。あとはBをAで割るだけ。

課題 1

    ベースのプログラム
    PImage img; // 元画像の変数
    float angle; // 選択範囲の傾き
    float scale; // 画面に対する選択範囲のサイズ比
    
    void setup() {
      size(600, 400);
      textAlign(CENTER, CENTER);
      img = loadImage("1元.jpg");
      rectMode(CENTER);  // 長方形描画のときに中心で位置指定する設定に変える
      imageMode(CENTER); // 画像描画のときに中心で位置指定する設定に変える
    }
    
    void draw() {
      background(0);
      // angleを求める
      // scaleを求める
      noFill(); // 図形の中を塗りつぶさない設定にする
      stroke(255, 0, 0); // 図形の枠の色を赤にする
      strokeWeight(2);   // 図形の枠の太さを2ptにする
      translate(width/2, height/2); // 基準位置を画面中央にする
      image(img, 0, 0, width, height);// 画像を表示
      rotate(angle);                  // 座標系をangleだけ回転
      rect(0, 0, width*scale, height*scale); // 画面サイズのscale倍の長方形を描く
    }
    
    // 画面の状態を画像として出力
    void getScreenShot(){
      save("data/1選択範囲.jpg");
    }
    
    // Processingの描画機能を使って回転
    void drawToImage() {
    }
    
    // 双一次補間
    void bilinear() {
    }
    
    // cを中心として画像を1/s倍し、aだけ回転したときのベクトルfの移動先のベクトルを返す
    PVector getRotatedPosition(PVector c, PVector f, float s, float a) {
      f.sub(c);
      f.div(s);
      f.rotate(a); // fをaだけ回転させる
      f.add(c);
      return f;
    }
    
    void mousePressed() {
      getScreenShot(); // 実行画面のスクリーンショットを保存
      drawToImage();   // 描画機能で回転・拡大した画像を保存
      bilinear();      // 双一次補間で回転・拡大した画像を保存
    }
    

  1. Processingのエディタに上のサンプルプログラムのコードをコピー&ペーストする。
  2. 「img03」という名前で保存する。
  3. 適当に画像検索してサンプル用の画像(例えば建物や樹木など、上下の方向がわかりやすいもの)を用意する。
  4. 画像の横と縦のサイズが4:3になるように変更する。
  5. 画像の形式に応じて以下の変更を加える。
  6. 画像の横と縦の比が4:3になるように変更を加える。
  7. 「1元.jpg」をProcessingのウインドウにドラッグ&ドロップする。
  8. draw関数の「// angleを求める」のところに、対応するコードを入れる。
  9. draw関数の「// scaleを求める」のところに、対応するコードを入れる。
  10. ※ いずれも上の説明を参考にする。

  11. 実行してウインドウ上にマウスカーソルを乗せ、最初の図のような位置関係で赤枠が表示されることを確認する。
  12. 画面上でクリックする。
  13. ※ 画像の解像度はProcessingの実行画面のサイズ(600, 450)になる。

    この時点で存在するファイル
    ※ このあとの課題を進めるにつれて「1選択範囲.jpg」は更新される。この段階では提出用のバックアップなどはしないこと。
    ※ うまくいかないときはdraw関数の最終状態とコードを見比べる。

Processingの描画機能を使う

課題 2

  1. 第02回の課題の最終状態のプログラムのdrawToImage関数の中のコードを、今のプログラムのdrawToImage関数の中にコピー&ペーストする。
  2. ※ 前回とは違い、今回のdrawToImage関数には引数「s」がないので、そのままではエラーになる。
    ※ USBメモリなどで保存していなかった場合は、送信済みメールからコードを回収する。
    ※ 前回の分ができていない場合は、まずそちらの課題を先に完成させる。

  3. drawToImage関数から以下の行を削除する。
  4.   float cx = mouseX*w/width;  // 画像サイズに換算した拡大基準の横位置
      float cy = mouseY*h/height; // 画像サイズに換算した拡大基準の縦位置
    
      pg.image(img, -cx*(s-1), -cy*(s-1), w*s, h*s);
    

  5. drawToImage関数の最後の行のファイル名のところを「1描画機能.jpg」から「2描画機能.jpg」に変更する。
  6. ※ この時点でdrawToImage関数は図のようになっているはず。


  7. 「pg.beginDraw();」と「pg.endDraw();」の間に以下のコメント文、コードを追加する。
  8.   pg.background(0);     // (pg)背景を黒で塗りつぶす
      pg.imageMode(CENTER); // (pg)画像描画のときに中心で位置指定する設定に変える
      // (pg)画像の中央の位置まで平行移動
      // (pg)-angleだけ回転
      // (pg)(1/scale)だけ倍率をかける
      pg.image(img, 0, 0);  // (pg)画像imgを元の大きさで表示
    

  9. コメント文のみになっている3行の左側に、対応するコードを記述する。
  10. 実行して適当な場所が選択されている状態でクリックする。
  11. ※ 「1選択範囲.jpg」の赤枠の部分が「2描画機能.jpg」の中身になるはず。
    ※ 枠が右に傾いていれば、「2描画機能.jpg」の中のモノは左に傾く。

    この時点で存在するファイル
    ※ うまくいかないときはdrawToImage関数の最終状態とコードを見比べる。

双一次補間

課題 3

  1. 第02回の課題の最終状態のプログラムのbilinear関数の中のコードを、今のプログラムのbilinear関数の中にコピー&ペーストする。
  2. ※ この関数にも引数「s」がないので、そのままではエラーになる。

  3. bilinear関数の中の3, 4行目を以下のように書き換える。
  4.   float cx = mouseX*w/width;  // 画像サイズに換算した拡大基準の横位置
      float cy = mouseY*h/height; // 画像サイズに換算した拡大基準の縦位置
    
      float cx = w/2; // 画像の中心の横位置
      float cy = h/2; // 画像の中心の縦位置
    
    ※ (cx, cy)は回転・拡大の基準点。前回はマウスカーソルの位置によって変わる点だったが、今回はつねに画像の中心。

  5. bilinear関数の中の「PVector ij」を求める部分を以下のように書き換える。
  6.       PVector ij = getScaledPosition(new PVector(cx, cy), new PVector(i, j), s);
    
          PVector ij = getRotatedPosition(new PVector(cx, cy), new PVector(i, j), 1/scale, angle);
    
    ※ 前回はgetScaledPositionで「拡大前の対応点」を求めたが、今回はgetRotatedPositionで「拡大+回転前の対応点」を求める。
      (どちらも自前の関数)

  7. bilinear関数の「int j_ = int(ij.y);」の下に、以下のコードを追加する。
  8.       // (i_, j_)とその右、下、右下のピクセルのいずれかが画像の範囲外なら黒にする
          if (i_<0 || i_>=w-1 || j_<0 || j_>=h-1){
            outputImg.pixels[i+j*w] = color(0);
            continue;
          }
    	
    ※ わざわざこんなことをするのは、例えば赤枠が図のように元画像からはみ出していると、そこに対応する元画像の座標が許容範囲外になってしまうため。
      (その点の色を取り出そうとしても、「そんなピクセルは存在しない」のでエラーになる)。

  9. 実行して適当な場所が選択されている状態でクリックする。
  10. ※ 「2描画機能.jpg」と「3双一次補間.jpg」はほぼ同じになるはず。


    この時点で存在するファイル → 提出ファイル
    ※ うまくいかないときはbilinear関数の最終状態とコードを見比べる。

提出

小テスト予告



戻る inserted by FC2 system