万華鏡風対称画像の作成


  1. 目的

    大きな模様を作成する場合、基本画像を繰り返して敷き詰めますが、画像が左右/上下対称でないと、画像が不連続になってしまいます。画像を左右対称に折り返して並べると、この不連続性を解消することができます。
     万華鏡は鏡の反射を利用して、正3角形の画像を反転しながら回転し、8角形を作成します。この6角形を平面に敷き詰めた画像が標準的な万華鏡の画像です。

  2. 左右、上下対称画像

    1. 正方形の対称画像
      3角形の対称画像は少し複雑なので、ここでは、正方形の画像を左右、上下対称に折り返した画像を生成します。元になる正方形の画像は、大きな画像化や切り出し、この切り出し位置を変更しながら対称図形を表示します。

    2. 手法
      まず、写真画像を読み込み、そのピクセル情報を配列Pixels1[]に取り出します。切り出す画像のサイズは100*100で、これを左右反転した画像を並べて200*200の画像を作成するのが目的です。Pixels1[]を左上に表示します。
       次にPixels1[]を左右反転した画像をPixcels2[]に記録します。左右反転するには、次にように左の画素と対応する右の画素を交換します。
          for (int i = 0; i < 100; i++) {
            for (int j = 0; j < 100 / 2; j++) {
              int temp = Pixels1[i*100 + j];
              Pixels2[i*100 + j] = Pixels1[i*100 + 100 - 1 - j];
              Pixels2[i*100 + 100 - 1 - j] = temp;
            }
          }
      Pixels2[]を元画像の横(下図の2)に描画します。次に、Pixels2[]を上下反転し、これを、右下(下図の3)に表示します。最後に、Pixels1[](元絵)を上下反転しPixels2[]に記録し、これを左下(下図の4)に表示します。

       

    3. アニメーション
       表示する100*100の画素を、元の画像から切り出します。切り出す先頭位置を(dispX,dispY)とし、この値を dispX += moveX で表示毎に変更してアニメーションします。
       0 < dispX < sizeX-100
      ですから、この範囲からはずれると moveX の符号を反転します。

    4. プログラムソース
      このプログラムは、林 正幸著 「Java サンプル集」 を利用していますが、この反転画像の作成手順は上記のように変更しています。

  3. プロジェクト

    1. レイアウトと実行
      移動量の増減を行うボタンを用意します。+ボタンで移動が早くなり、-ボタンで減速します。利用する画像は、"sample.jpg"ファイルです。



    2. 主要変数
      Image WorkImage1, WorkImage2; // 作業用イメージ
      Graphics WorkGraphics1, WorkGraphics2;// 作業用グラフィックス
      Image MirrorImage0; 画像ファイルを読み込む
      MirrorImage1;//反転図形を生成する
      int[ ] Pixels1 = new int[100 * 100]; // ピクセルデータを記録
      int[ ] Pixels2 = new int[100 * 100];
      int move; 移動速度

    3. クラス・メソッド
      1. クラス定義
        アプレットを継承します。また、スレッドを利用するため、Runnableを組み込みます。public class Applet1 extends Applet implements Runnable

      2. start()
        作業用の変数を初期設定します。
        MirrorImage0の画像ファイル sample.jpg を読み込みます。
        スレッドを開始します。

      3. paint()
        WorkImage2の画像を表示します。

      4. run()
        スレッドの実行メソッドです。dispXとdispYを変更しながら、
         MakeMirrorImage(dispX, dispY);
        で、対称画像を表示します。

      5. MakeMirrorImage(dispX, dispY);
        このメソッドが中心です。GetPixels(MirrorImage0, dispX, dispY, 100, 100, Pixels1);は、PixelGrabberクラスを利用して、Imageクラスの先頭(dispX, dispY)、サイズ(100,100)の画像を ピクセル配列に取り込みます。createImage()で、逆にピクセル配列からImageクラスを生成し、drawImage()で WorkGraphics1 に表示します。
         次に、Pixels1[]を左右反転した画像をPixels2[]に作成し、これを WorkGraphics1 の右上に表示します。同様に、Pixels2[]を上下反転して右下に、Pixels1[]を上下反転してPixels2[]を作成し、左下に表示します。
          public void MakeMirrorImage(int dispX, int dispY) {
        
            // 原画からピクセル情報をPixels1[]に取り込む
            GetPixels(MirrorImage0, dispX, dispY, 100, 100, Pixels1);
            MirrorImage1 = createImage(new MemoryImageSource(100, 100, Pixels1, 0, 100));
            WorkGraphics1.drawImage(MirrorImage1, 0, 0, this);
        
            // 左右対称になるようにデータを入れ替えPixels2[]に記録
            for (int i = 0; i < 100; i++) {
              for (int j = 0; j < 100 / 2; j++) {
                int temp = Pixels1[i*100 + j];
                Pixels2[i*100 + j] = Pixels1[i*100 + 100 - 1 - j];
                Pixels2[i*100 + 100 - 1 - j] = temp;
              }
            }
            MirrorImage1 = createImage(new MemoryImageSource(100, 100, Pixels2, 0, 100));
            // 作成したイメージをグラフィックスの右上に描画
            WorkGraphics1.drawImage(MirrorImage1, 100, 0, this);
        
            // 上下対称になるようにPixcel2[]データを入れ替える
            for (int i = 0; i < 100 / 2; i++) {
              for (int j = 0; j < 100; j++) {
                int temp = Pixels2[i*100 + j];
                Pixels2[i*100 + j] = Pixels2[(100 - 1 - i)*100 + j];
                Pixels2[(100 - 1 - i)*100 + j] = temp;
              }
            }
            MirrorImage1 = createImage(new MemoryImageSource(100, 100, Pixels2, 0, 100));
            // 作成したイメージを第1段階の作業グラフィックスの右下に描画
            WorkGraphics1.drawImage(MirrorImage1, 100, 100, this);
        
            // 上下対称になるようにPixels1[]データをPixels2[]入れ替える
            for (int i = 0; i < 100/2; i++) {
              for (int j = 0; j < 100 ; j++) {
                int temp = Pixels1[i*100 + j];
                Pixels2[i*100 + j] = Pixels1[(100 - 1 - i)*100 + j];
                Pixels2[(100 - 1 - i)*100 + j] = temp;
              }
            }
            MirrorImage1 = createImage(new MemoryImageSource(100, 100, Pixels2, 0, 100));
            // 作成したイメージを第1段階の作業グラフィックスの左下に描画
            WorkGraphics1.drawImage(MirrorImage1, 0, 100, this);
        
            // WorkImage1のイメージをWorkGraphics2の作業グラフィックスに描画
            WorkGraphics2.drawImage(WorkImage1, 0, 0, this);
            imgDone=true;//表示可能
          }
        }

      6. GetPixels()
        PixelGrabberクラスを利用して、Imageクラスの画像をピクセル(整数型)配列に記録します。整数は、ARGBの計4バイトで画素を記録します。Aはαチャンネルの値です。

      7. buttonInc_actionPerformed()、buttonDec_actionPerformed()
        ボタンが押されたとき、moveを増減します。


    4. ダウンロード
      このプロジェクトをダウンロードできます。次の行をクリックして、kaleido.exeファイルを適当なフォルダに保存します。
      ダウンロード開始
      このファイルは自己解凍型の圧縮ファイルです。このファイルを実行すると指定したフォルダに必要なファイルが生成されます。


    5. 拡張
      実際の万華鏡の画像は、正3角形を反転しながら回転してできる正8角形の画像が基本です。画像を切り出し、正三角形内の画素を回転させながら描画します。回転角度は60度と120度ですから、あらかじめ記録しておけば三角関数を呼び出す手間が省けます。