アニメーションGIFと普通のGIFを合成するコード

SWTを使う場合、アニメーションGIFを描画するにはちょっとばかしコードがいります。アニメーションGIFのデータは、複数のフレームを一つのファイルに同梱しています。1枚の画像当たり描画時間を指定することでアニメーションしたように見せかけます。*1画像データを読み込むImageLoader*2クラスは複数のImageData*3クラスのインスタンスを返します。しかし、SWTが用意している画像クラスImage*4は複数のImageDataを持てません。なので描画処理をするGC*5をうまく使ってアニメーションGIFを描画する必要があるのです。
と、ここまではSWTのSnipetsがあるのですが、ふと思い立って普通のGIF上に透過処理を施したアニメーションGIFを合成して表示したいと思いました。以下がそのコードです。例によってJUnit4でラーニングとして実装しています。

  @Test
  public void learningOverlayIcon() throws Exception{
    final Shell shell = new Shell(display);
    shell.setSize(300, 300);
    shell.open();
    final GC shellGC = new GC(shell);

    final String animationGifPath = "アニメーションGIFのファイルパス";
    final String basePath = "ベース画像のファイルパス";
    ImageLoader loader = new ImageLoader();
    final ImageData[] data = loader.load(animationGifPath);
    final CompositeImageDescriptor icon = new CompositeImageDescriptor(){
      private int imageDataIndex = 0;
      
      @Override
      protected void drawCompositeImage(int width, int height) {
        drawImage(new ImageData(basePath),0,0);
        ImageData imageData = data[imageDataIndex];
        // 画像を繰り返し表示させるために最後の要素だったら0に戻している
        imageDataIndex = imageDataIndex == data.length - 1 ? 0 : imageDataIndex + 1;
        drawImage(imageData, 0, 0);
         int ms = imageData.delayTime;
         try {
          // msは1/100秒なので、ミリ秒にするために10倍にしている。
          Thread.sleep(ms * 10);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }

      @Override
      protected Point getSize() {
        return new Point(16,16);
      }
    };
    Runnable runner = new Runnable() {
      public void run() {
        while(true){
          Image image = icon.createImage();
          shellGC.drawImage(image, 0, 0);
          image.dispose();
        }
      }
    };
    Thread thread = new Thread(runner,"animation");
    thread.setDaemon(true);
    thread.start();
    display.timerExec(3000, new Runnable() {
      public void run() {
        shell.close();
      }
    });
    while (!shell.isDisposed()) {
      if (!display.readAndDispatch())
        display.sleep();
    }
    display.dispose();  
  }

CompositeImageDescriptor*6クラスは二つの画像を合成してくれるディスクリプタですが、ImageDataで取得しつつ画像を合成することで、アニメーションを実現しています。*7
とまぁ思い立って書いてみたものの、実際にこのコードを使って画面を作ってみようと思うともう一歩やらんといかん事があるんです。Widgetの中で指定できる画像はImageクラスです。そこから合成したアニメーションGIFを描画するにはちょちょっといじらないといけないみたいです。それはきっと明日のネタの予定。おやすみなさい。

*1:ちなみに描画時間の単位は1/100秒

*2:org.eclipse.swt.graphics.ImageLoader

*3:org.eclipse.graphics.ImageData

*4:org.eclipse.swt.graphics.Image

*5:org.eclipse.swt.graphics.GC(Graphic Contextの方)

*6:org.eclipse.jface.resource.CompositeImageDescriptor

*7:この例はちょっと良くないカモしれません。毎回画像を合成せずにキャッシュさせた方が良さそうですね。