第02回 回転

概要

前回のプログラムに変更を加え、元画像の一部を拡大しつつ回転するプログラムを作る。

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

プログラムを実行すると実行画面上に元画像が表示され、マウスカーソルに合わせて30°傾いた赤枠 (画面サイズの1/5) が表示される。
さらに実行画面上でクリックすると、dataフォルダに4つの画像が作られる。
コンソールに「完了」が表示されてからキーボードの0~4のキーを押すとこれらの画像が画面に表示される。
(元画像は実行画面の大きさ800x600にリサイズされる)
(クリックだけですべての画像が作成されるので、キーボードを使うのは結果確認のためだけ)
キー 画像 意味 特徴
0 元jpg 元画像
1 1選択範囲.jpg Processingの実行画面のスクリーンショット 元画像にクリック時の赤枠がついたもの
2 2描画機能.jpg Processingの描画機能で選択範囲を画面のサイズに拡大した画像 なめらかに拡大される
3 3最近傍補間.jpg 最近傍補間を使って選択範囲を画面のサイズに拡大した画像 元の1ピクセルがそのまま拡大されたような粗いドットが見える
4 3双一次補間.jpg 双一次補間を使って選択範囲を画面のサイズに拡大した画像 なめらかに拡大される
完了時点でdataフォルダにあるファイル

実行例

赤枠は反時計回りに30°回転しているので、それを拡大した画像の中のものは時計回りに30°回転した状態になる。

1. 枠を回転させる

概要

前回の課題でも選択範囲に赤枠が表示されていた。
この赤枠を表示しているのはdraw関数の
    translate(cx, cy); // 原点をカーソル位置に移動
    scale(1/s);        // 拡大率の逆数のスケールをかける
    translate(-cx, -cy); // 枠内での中心位置移動
    rect(0, 0, width, height); // 元画像サイズの長方形を描く
の部分。
普通に最後のrect関数を実行するだけだと実行画面を囲む赤枠が表示されるだけだが、translate関数とscale関数を使って位置と大きさを変更することでマウスカーソルの位置に合わせて動く1/5サイズの長方形にしていた。
これにrotate関数による回転を追加すれば、斜めの赤枠を表示させられる。

課題 1

  1. Processingを起動する。
  2. 前回の完成状態のコードをコピー&ペーストする。
    次回以降もそれまでのコードを流用することがあるので、今回からはプログラムを自分でバックアップしておくこと。
  3. 「img02」という名前で保存する。
  4. 適当に画像検索して元画像 (建物や樹木などの上下の方向がわかりやすいもの) を用意する。
  5. 画像の形式に応じて以下の変更を加える。
    • JPG形式の場合→名前を元.jpgに変更
    • それ以外の場合→ペイントで開き、形式をJPGに指定して「元」という名前で保存
  6. 「元.jpg」をProcessingのウインドウにドラッグ&ドロップする。
  7. プログラム先頭部分の
    String[] fName = {"元", "選択範囲", "1描画機能", "2最近傍補間", "3双一次補間"};
    String[] fName = {"元", "1選択範囲", "2描画機能", "3最近傍補間", "3双一次補間"};
    に変更する。
    (今回は課題1で枠の回転、課題2で描画機能による拡大・回転、課題3で最近傍補間と双一次補間による拡大・回転を実装するため)

  8. プログラム先頭部分の
    float s = 5;  // 拡大の倍率
    の後に
    float a = PI/6; // 選択範囲の傾き(30°)
    を追加する。

  9. draw関数の
        translate(cx, cy); // 原点をカーソル位置に移動
    の後に
        rotate(-a);        // 座標系を-aだけ回転
    を追加する。

  10. プログラムを実行する。
    • マウスカーソルを動かすと、斜めの赤枠がカーソルにくっついて表示される。
    • クリックしてコンソールに「完了」が表示されてから0キーを押すと元画像、1キーを押すとクリックしたときの (赤枠つきの) 画面が表示される。

2. Processingの描画機能を使った拡大機能に回転を加える

概要

前回のプログラムのdrawToImage関数では、translate関数, scale関数でカーソル位置を基準とした拡大を実装していた。
これにrotate関数による回転を追加すれば、元画像を拡大しつつ回転させて表示させられる。
ただし、角度を指定できるようにするためにはこの関数に角度を指定するための引数を追加する必要がある。

課題 2

  1. プログラムの下の方にあるmousePressed関数の
      drawToImage(s, 2);     // 描画機能でs倍に拡大した画像をimg[2]に保存
      drawToImage(s, a, 2);     // 描画機能でs倍に拡大、aだけ回転した画像をimg[2]に保存
    に変更する。
    (まだdrawToImage関数に変更を加えていないので、この時点ではエラーになる)

  2. drawToImage関数の
    // Processingの描画機能を使って拡大(課題1)
    void drawToImage(float s, int n) {
    // Processingの描画機能を使って拡大(課題2)
    void drawToImage(float s, float a, int n) {
    に変更する。
    (コメント文の修正も忘れずに)
    (これでmousePressed関数の方のエラーが解決する)

  3. drawToImage関数の
      pg.translate(cx, cy);    // (pg)原点を(cx, cy)だけ平行移動
    の後に
      pg.rotate(a);            // (pg)aだけ回転
    を追加する。

  4. プログラムを実行し、適当な場所が選択されている状態でクリックする。
    • コンソールに「完了」が表示されてから2キーを押すと赤枠部分が拡大された画像が表示される。

3. 自前の拡大機能に回転を加える

概要

前回の最近傍補間、双一次補間による拡大では、座標変換を行うgetScaledPosition関数をnearestNeighbor関数とbilinear関数から呼び出して使っていた。
getScaledPosition関数では、座標変換ではなくPVector型の「ベクトル変数」を使った処理を行っている。

元のコード
  f.sub(c); // fからcを引く
  f.div(s); // fを1/s倍する
  f.add(c); // fにcを加える
のコメント文にある通り、「sub」が引き算、「div」が割り算、「add」が足し算にあたる。

このgetScaledPosition関数に回転の処理を追加したい。
ベクトル f を 回転させる書式は
f.rotate(角度);
となる。関数名は rotate だが、座標系を回転させる関数とは全く別のもの。

角度を指定できるようにするにはgetScaledPosition、nearestNeighbor、bilinearの3つの関数で「float a」を受け取れるようにする必要がある。

課題 3

  1. nearestNeighbor関数の
    // 最近傍補間(課題2)
    void nearestNeighbor(float s, int n) {
    // 最近傍補間(課題3)
    void nearestNeighbor(float s, float a, int n) {
    に変更する。
    (コメント文の修正も忘れずに)

  2. bilinear関数の
    void bilinear(float s, int n) {
    void bilinear(float s, float a, int n) {
    に変更する。

  3. getScaledPosition関数の
    // cを中心として画像を1/s倍したときのベクトルfの移動先のベクトルを返す(課題2, 3で使用)
    PVector getScaledPosition(PVector c, PVector f, float s) {
    // cを中心として画像を1/s倍して-a回転させたときのベクトルfの移動先のベクトルを返す(課題3で使用)
    PVector getScaledPosition(PVector c, PVector f, float s, float a) {
    に変更する。
    (コメント文の修正も忘れずに)

  4. getScaledPosition関数の
      f.div(s); // fを1/s倍する
    の後に
      f.rotate(-a); // fを-a回転させる
    を追加する。

  5. mousePressed関数の
      nearestNeighbor(s, 3); // 最近傍補間でs倍に拡大した画像をimg[3]に保存
      bilinear(s, 4);        // 双一次補間でs倍に拡大した画像をimg[4]に保存
      nearestNeighbor(s, a, 3); // 最近傍補間でs倍に拡大、aだけ回転した画像をimg[3]に保存
      bilinear(s, a, 4);        // 双一次補間でs倍に拡大、aだけ回転した画像をimg[4]に保存
    に変更する。
    (コメント文の修正も忘れずに)

  6. nearestNeighbor関数、bilinear関数の
          PVector pos = getScaledPosition(new PVector(cx-0.5, cy-0.5), new PVector(i, j), s);
          PVector pos = getScaledPosition(new PVector(cx-0.5, cy-0.5), new PVector(i, j), s, a);
    に変更する (どちらの関数にもこのコードがあるので、両方変更する)。

  7. プログラムを実行し、適当な場所が選択されていて赤枠が画面に収まっている状態でクリックする。
    • コンソールに「完了」が表示されてから3キー, 4キーを押すと最近傍補間、双一次補間で赤枠部分が拡大された画像が表示される。
    • 赤枠が画面からはみ出した状態でクリックするとエラーになってしまう。エラーが出ないようにするにはnearestNeighbor関数、bilinear関数に例外処理を追加すればよいが、具体的な方法はここでは省略する。

提出

締切後提出用フォーム
※ 点数はつきますが、欠席だった場合は出席にはなりません。
※ コードはTeamsのClass Notebookに保存してください。
戻る