11/2  Java/SWTの画像処理について
11/16  日常
11/20  SWT・追記


11/2  Java/SWTの画像処理について

はい、こんにちわ。

今日はがらりと話題を変えて私が最も得意とする(というかこれ以外はほとんど出来ない)Javaのお話をしようと思います。
JavaはJavaでも「SWT」(標準で付属してないGUIライブラリ)です。
SWTに関しては調べればざくざくと情報が出てくるのでそちらにお任せしますです。

で、さっそく本題に入りましょう。
まず、今日の話題が出ることになったきっかけがTaking a look at SWT Imagesのページなんですね。
このページにはSWTで出来る画像処理について役立つ知識が盛りだくさん! なんですが英語でわかりにく〜い(´д`)ハァ〜
まぁプログラミングをする者なら多少の英語くらい何とかせねばならんのですが、ワカランもんはワカランのじゃーっ!ヽ(`Д´)ノ

なので、そのページに書いてあることを元にして、かなり端折ったりしながら適当に翻訳&まとめたいと思います。(まだわからないところも多いのですよ)

今回使用するクラス
 ・Image …… 画像を表示する時に必要なクラス。今回はこれが無くては始まらない。
 ・ImageData …… 画像の細かいデータ(透明度や色情報など)を持っている。
 ・ImageLoader …… 今回はおまけ的扱い。画像を簡単に読み出したり保存したり出来る。また、ImageDataを使って保存も出来る。
 ・GC …… これもかなり重要。加工したImageをcanvasなどのウィジットに描きだすときに使用。
 
などのクラスを主に使います。 細かいことは追々説明するかもしれません。


お品書き

画像全体の透明度の変更
指定色のみの透明化
ピクセル単位の透明度の指定
ImageLoadeを使って加工した画像を保存
GCを使い方。あれこれ。


使用する画像
back.jpgtest.jpg(bmp,gif,pngもあり)
back_half.jpg test_half.jpg
実験した時はこの2倍の大きさでやってたんですけどね。





画像全体の透明度の変更
 画像全体の透明度を変更するには、ImageDataのalphaプロパティを変更すればオーケーです。
 ただしImageDataで透過処理をした画像はaddPaintListener()を使用しないと透過されない。    
 
<例>
 //ImageDataの生成
 ImageData imageData = new ImageData("./back.jpg");
 imageData.alpha = 64;     //0〜255の範囲
 final Image img = new Image(display,imageData);
 
 //shellの背景に描画
 shell.addPaintListener(new PaintListener(){
  public void paintControl(PaintEvent e) {
   //shellの0,0地点から描画開始
   e.gc.drawImage(img,0,0);
  }
 });
    結果


指定色のみの透明化
 透過したい色を指定。 実は指定したピクセル(の色)でも出来る。
 透過したい色を取得して、ImageDataのtransparentPixelプロパティにセットします。

 困ったことにjpgだけは圧縮の関係でRGB値が変化するのかうまく透過できない。
 bmpは無問題。png,gifは画質を高くすれば大丈夫なはずです。たぶん。  
 ちなみに、このテスト画像だけ文字が黒いコトには突っ込まないでください。    
<例>
 ImageData id2 = new ImageData("./test.png");
 //透明にする色の指定1 RGB指定(白)
 int transPixel = id2.palette.getPixel(new RGB(255,255,255));
 //透明にする色の指定2 指定したピクセルのを透過
 //int transPixel = id2.getPixel(0,0);
 //ImageDataに透明にする色を伝える
 id2.transparentPixel = transPixel;
 final Image img = new Image(display,id2);
 
 shell.addPaintListener(new PaintListener(){
  public void paintControl(PaintEvent e) {
   e.gc.drawImage(img,0,0);
  }
 });
  結果
 jpgの例
   結果

ピクセル単位の透明度の指定
 タイトルどおり、1ピクセルごとに透明度を設定します。
 これも使い方は簡単でImageDataのプロパティ[alphaData]に値をセットすれば良いのです。
 
  まず、alphaDataはバイト配列なので、表示画像のピクセル数分(縦×横)のbyte[]を用意します。
 あとはfor文でひたすら全ピクセルに透明度を指定していけばオーケーです。
 まぁ別に全ピクセルに指定する必要も無いんですが。
 使い道として徐々に透明になっていく、っていうのや、下半分だけ半透明っていうのも出来るでしょう。    
<例>
 ImageData id3 = new ImageData("./back.jpg");
 byte[] alphaData = new byte[id3.width*id3.height];
 for(int y=0; y<id3.height; y++){
  byte[] alphaRow = new byte[id3.width];
  for(int x=0; x<id3.width; x++){
   //透明度の計算。透明〜不透明へ。1行ごとに濃くしてゆく。
   alphaRow[x] = (byte) ((255 * y) / id3.height);
  }
  //alphaRow[]の中身(ピクセル横一列分)を、alphaData(変更後配列)へコピー。
  System.arraycopy(alphaRow,0,alphaData,y*id3.width,id3.width);
 }
 id3.alphaData = alphaData;
 final Image img = new Image(display,id3);
 shell.addPaintListener(new PaintListener(){
  public void paintControl(PaintEvent e) {
   e.gc.drawImage(img,0,0);
  }
 });
  結果


ImageLoadeを使って加工した画像を保存
 ImageLoaderクラスを使って、ウィジットに描画された画像を保存します。
 GCなどで動的に描かれた画像を保存するのに使える。。。 ってそのまんまやん。
 でもいまいち理解していない部分があるので、話半分に聞いておいてください。    
<例>
 //背景の画像
 final Image img = new Image(display,"./resource/1101/back.jpg");
 //合成する画像
 ImageData id = new ImageData("./resource/1101/test.png");
 //左上のピクセルの色を透明に。
 id.transparentPixel = id.getPixel(0,0);
 final Image img2 = new Image(display,id);
 shell.addPaintListener(new PaintListener(){
  public void paintControl(PaintEvent e) {
  //描き込み
   e.gc.drawImage(img,0,0);
   e.gc.drawImage(img2,100,150);
  }
 });
 //ダブルクリックしたら保存するように。
 shell.addMouseListener(new MouseAdapter(){
  public void mouseDoubleClick(MouseEvent e){
   gc = new GC(shell);  //shellのGCを取得
   //640x480の大きさの新規Imageクラスを生成
   Image img3 = new Image(display,shell.getSize().x,shell.getSize().y);
   //img3に、GC(shell)の0,0から640,480の領域をコピーする
   gc.copyArea(img3,0,0);
   ImageLoader il = new ImageLoader();
   //保存したい画像を保持したImageDataを渡す。
   il.data = new ImageData[]{ img3.getImageData() };
   il.save("./output1.bmp",SWT.IMAGE_BMP);
  }
 });
  結果

 保存される画像の大きさは、新規に作ったImage3の大きさです。
 
 実は気をつけることがひとつ。
shellにタイトルバーがついてると、GCの0,0座標はウィンドウの左上、タイトルバーの直下になるのです。
そのぶん画面領域が減るので、このまま保存を実行するとタイトルバーの分だけ下部に空きが出来てしまいます。
これを回避するにはshellにタイトルバーを表示させないか、タイトルバーの正確な大きさを調べておくかですね。
 ちなみに、横幅もちょび〜っとだけ、狭くなってます。

 保存形式はSWT.IMAGE_BMPのほかにも、IMAGE_JPG、IMAGE_GIF、IMAGE_PNG、IMAGE_TIFFがありました。
ただ、試してないので使えるかどうかはわかりません。
どっかでTIFFあたりにバグがあるとか無いとか聞いたような……?


GCを使い方。あれこれ
 GC。グラフィックコンテキスト。仮想画面?
 デスクトップではない別の領域があって、そこに線を書いたり円を書くなどいろいろでき、 その仮想画面を実画面に転送することによって描画とする。
 
 ゲームとか作るなら必須だねッ!
 
 大まかにできることの箇条書き。      
 ・点
 ・線
 ・弧
 ・長方形
 ・楕円
 ・多角形
 ・多角線
 
 
 gc.jpg  (点は見えないので省略)
 


これまでに描いた透過処理ももちろんGC無くしては語りえないので
GCは大事ダヨ!


終わりに。

いや……長かったですよ……。意外に。
もともと、ノベルゲームが作りたくてJavaの画像処理を調べていたけど
ようやく、出口の入り口が見え始めたかもしれません。 遥か遠くに。

もう正直疲れたヨ( -Д-)〜3
また明日から目標目指してがんばろー


追伸:
使用済みオブジェクトのdispose()は忘れないでください。

11/16  日常

会社での日課。




自習。




……エート、モウ半年ハ、過ギマシタヨ?

まぁ、好き放題自習させてもらってるから先日のような結果が出せるんですけどね……
ある日突然クビになったりしてw アハハッwwww (←笑い事じゃない?


いやー、Cが難しいなぁー。
いまだにCを勉強する意味はあるのか疑問ですよー。C++で十分じゃないのかなぁ?
でもまぁC++を勉強しようと思ったらCが必要になるから結局おなじかぁ。。。

がんばろー
11/20  SWT・追記

11/2に書いたSWTに関する話題の追記です。

指定色を透過した上で、さらに別の色を半透過したい場合。
描画に適したウィジット
最後に。

使用する画像
back1120.png(ただの灰色画像)Frame.png
back1120_half.jpg Frame_half.jpg
例によって1/2倍です。



指定色を透過した上で、さらに別の色を半透過したい場合。
 前提
 ・ImageData.transparentPixelプロパティとalphaプロパティは両立しないらしい。
 ・ImageData.transparentPixelは一色しか指定できない。
 ・ImageData.alphaプロパティは強制で画像全体(全色)が透過されてしまう。

 前提をご覧のように前回の方法では二色以上の透過が出来ませんでした。
 しかしそれを実現する方法がないわけではもちろん、ないです。
 それを実現する方法は簡単で、
 前回のピクセル単位の透明度の指定をちょこっと応用すれば実現できます。
 
 今回は灰色の画像に四角い枠の画像を合成します。
 枠画像の0,0ピクセルを完全に透過し、赤茶色の部分を半透過してみます。
 以下ソース      
 //背景
 img = new Image(display,"./resource/back1120.png");
 //透過&半透明処理したい画像
 ImageData idata = new ImageData("./resource/Frame.png");

 //透過色の指定(0,0座標の色)
 int AlphaColor = idata.getPixel(0,0);
 //半透過色の指定(赤茶)
 int HalfAlphaColor = idata.palette.getPixel(new RGB(128,0,0));
 //半透過率(0〜255)
 byte HalfAlpha = 64;		

 //alphadata[](透過)の計算
 int width = idata.width;
 int height = idata.height;
 byte[] alphaData = new byte[height * width];

 for(int y=0;y<height;y++){
  byte[] alphaRow = new byte[width];
  for(int x=0;x<width;x++){
   //x,yピクセルが透過色ならば、透明にする。
   if(idata.getPixel(x,y) == AlphaColor)
    alphaRow[x] = 0;
   //半透過に指定した色ならば、指定した半透過率に。
   else if(idata.getPixel(x,y) == HalfAlphaColor)
    alphaRow[x] = HalfAlpha;
   //それ以外の色は透過しない
   else
    alphaRow[x] = (byte)255;
  }
  System.arraycopy(alphaRow,0,alphaData,y*width,width);
 }

 //もしtransparentPixelプロパティを有効にしている場合は無効にする。
 idata.transparentPixel = -1;
 //アルファデータの代入
 idata.alphaData = alphaData;

 img2 = new Image(Display.getDefault(),idata);

 gc = new GC(img);

 //Frame.pngをimgの0,380の位置に描画
 gc.drawImage(img2,0,0,
   img2.getBounds().width,img2.getBounds().height,
   0,380,img2.getBounds().width,img2.getBounds().height
 );
 //描画処理の終了。
 gc.dispose();
 
 shell.addPaintListener(new PaintListener(){
  public void paintControl(PaintEvent e) {
   e.gc.drawImage(img,0,0);
  }
 });
 
   こんな感じですネ。( ´ー`)ノ
 もちろん半透過色を複数指定すれば複数の色がそれぞれの透過度で透過できます。
 

描画に適したウィジット
 前回の日記では特に何も考えずShellに直接描画してましたが、
 Shellに描画してると困ったことになったりしますので、実際に使用するときにはcanvasに描画するようにしましょう。
 
 例えば短い間隔で何度もredraw()を繰り返すと、再描画した際にずいぶんとちらつきます。
 
 canvasでもデフォルトのだとちらつきますので、回避するにはコンストラクタにSWT.NO_BACKGROUNDを指定すればおkです。
 

 最後に。
 めんどくさくて原因を追求しなかったんですが、
 以前SWT.IMAGE_COPYを使用してイメージのコピーを作った時、
 なぜか上下左右が反転したものが出来たんですよ。
 いま再現しようとしても出来ないのがさらに謎なんですが、まぁその時は回避策として
 Image copyimg = new Image(Display.getDefault(),(ImageData)img.getImageData.clone());
 で新しいImageを作りました。
 何か折に参考になれば幸いでス。