◆PROCESSING 逆引きリファレンス
カテゴリー:制御系
非矩形ウィンドウを表示するには
【解説】
PROCESSINGにかぎらず、多くの画面を扱うプログラムでは「ウィンドウといえば四角(矩形)」が定番です。
しかし世の中には、矩形ではないウィンドウを持つプログラムもあります。
例えばPCで利用する音楽プレーヤーソフトでは、スキンやフェースと呼ばれる画像情報を読み込むことで、音楽プレーヤーの画面をキャラクターの形に変更する事が可能です。
(画像URL:偽抹茶堂 様、真咲さん )
上記の画像は、音楽プレーヤーソフトAIMP 用のスキンで、偽抹茶堂 様が公開されているものです。元画像はPixivのイラスト作家である真咲さん のものです。
ウィンドウの下に、サクラエディタの画面が透けて見えているのがわかると思います。
こんなウィンドウが作りたい!(笑)・・・というわけで、非矩形のウィンドウをPROCESSINGで作成する方法を紹介したいと思います。
Javaで非矩形ウィンドウを作成する方法については、下記サイト様などが参考となります。
- エマノンの雑記帳 様
- TACの雑記 様
- Schlechte Welt 様
- つらねの日記 様
【注意】
PROCESSINGの標準命令では非矩形ウィンドウは作成できないため、Javaの命令を駆使する事になります。詳細は下記サンプルプログラムを参照してください。
「ここまでするなら、素直にJavaでコーディングすれば?」という声が聞こえてきそうですね(汗)。
PROCESSINGの実行結果Windowを非矩形化するため、PROCESSINGがもつ PSurfaceオブジェクトからJavaのWindowフレーム(Frame)オブジェクトを取り出して利用しています。
また非矩形Windowを実現するためには、Frameに「この部分は絵がある部分(不透明)で、ここは絵が無い部分(透明)なんだよ」という情報を教えてあげる必要があります。
そのため、表示したい画像の画素を1つ1つ調べて、不透明な画素からShapeと呼ばれるマスク用画像を作成しています。
(画像URL:©GMO Internet, Inc. 様:美雲あんず:画像の再利用は禁止です)
注意点は、FrameのremoveNotifyを行う位置です。setup()関数のなるべく早い段階で、removeNotifyを行って下さい。
(原因は不明なのですが:汗)Shapeの作成を行った後でremoveNotifyを行うと
「java.lang.IllegalStateException: Buffers have not been createdjava.lang.IllegalStateException: Buffers have not been created at sun.awt.windows.WComponentPeer.getBackBuffer(WComponentPeer.java:1018) at java.awt.Component$FlipBufferStrategy.getBackBuffer(Component.java:4065)」
のような例外が発生します。
下記サンプルプログラムでは、マウスのドラッグ&ドロップで、実行結果ウィンドウを移動する処理が加えてあります。
またマウスが右クリックされたら、APを終了するための問い合わせを行うダイアログBOXを表示しています。
本当はダイアログBOXではなくポップアップウィンドウが使いたかったのですが、(これも原因は不明なのですが)コメント化してあるポップアップメニューは、期待通り動作しませんでした。
ポップアップ表示はするのですが、非矩形ウィンドウの端で開くと、ポップアップメニューの文字が欠けて表示されてしまいます・・・。
やむなくダイアログBOXを使っている次第です(汗)。解決方法がわかる方が見えましたら、教えて頂けると幸いです(m(_._)m)。
【関連記事】
サンプルプログラム
非矩形ウィンドウを表示する例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
/** * 非矩形Window表示サンプル * @author MSLABO * @version 1.0 */ import java.awt.Shape; import javax.swing.ImageIcon; import java.awt.Image; import java.awt.geom.GeneralPath; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.Rectangle; import java.awt.Frame; import java.awt.Point; import java.awt.PointerInfo; import java.awt.MouseInfo; import javax.swing.JOptionPane; import processing.awt.PSurfaceAWT; import javax.swing.JPopupMenu; import javax.swing.JMenuItem; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; /** * フレーム用Shape管理クラス */ class FrameShapeMng{ Shape shape; //非矩形領域 ImageIcon icon; //アイコン画像 PImage pimg; //PROCESSING用画像 /** * コンストラクタ * @param fileName:画像ファイルパス */ FrameShapeMng( String fileName ){ //画像ファイルをアイコン画像化 icon = new ImageIcon( fileName ); //アイコン画像からShapeを得る shape = getImageShape( icon ); //アイコン画像からPROCESSING用画像を得る Image img = icon.getImage(); pimg = new PImage( img ); } /** * アイコン画像の不透明部分を Shapeにする関数 * @param icon:アイコン画像 * @return Shape:作成した Shapeオブジェクト */ Shape getImageShape(ImageIcon icon){ //Shape領域作成 GeneralPath shape = new GeneralPath(); //アイコン画像と同じ大きさのARGBイメージバッファ生成 final BufferedImage bi = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB); //イメージバッファにアイコン画像を描画する icon.paintIcon(null, bi.createGraphics(), 0, 0); ColorModel cm = bi.getColorModel(); //イメージバッファの不透明画素をShape化する for(int y = 0; y < bi.getHeight(); y++){ for(int x = 0; x < bi.getWidth(); x++){ // 不透明ならShapeに追加する if(cm.getAlpha(bi.getRGB(x, y)) > 0){ int start = x; //追加開始横位置 int notTransparentWidth = 0; //追加する画素数 //横1ラインの中で、連続している不透明画素を数える while((x < bi.getWidth()) && (cm.getAlpha(bi.getRGB(x++, y)) > 0)){ notTransparentWidth++; } //不透明画素部分をマスクとして、Shapeに追加する shape.append(new Rectangle(start,y,notTransparentWidth,1),true); } } } return( shape ); } } int imgIndex; //表示画像位置 ArrayList<FrameShapeMng> myShape ; //Shape管理クラスのList PImage dispImg; //PROCESSING用表示画像 JPopupMenu popup; //ポップアップメニュー Point startPt; //マウスドラッグ開始座標 /** * PROCESSING 初期処理関数 */ void setup() { //画面のチラつきを防止するため、直ちに実行結果Windowを //画面外に押しやり、非表示にする surface.setLocation( -300, -300 ); surface.setVisible( false ); //PSurfaceが持つFrameを得る Frame frame = getFrame(); //Frameを画面から切り離す(なるべく早い段階で行う事) frame.removeNotify(); //タイトルバーを削除する frame.setUndecorated( true ); //Frameを画面に接続する(Frameの変更終了) frame.addNotify(); //実行結果Windowのサイズ変更を許可する surface.setResizable( true ); //Shape管理クラスを、画像分だけ生成する myShape = new ArrayList<FrameShapeMng>(); myShape.add( new FrameShapeMng( dataPath("") + "\\" + "anzu1.png" ) ); myShape.add( new FrameShapeMng( dataPath("") + "\\" + "anzu2.png" ) ); myShape.add( new FrameShapeMng( dataPath("") + "\\" + "anzu3.png" ) ); //最初のクラスからアイコンとShapeを取り出し、実行結果Windowのサイズ //を調整し、実行結果WindowにShapeをセットする imgIndex = 0; FrameShapeMng work = myShape.get( imgIndex ); surface.setSize( work.icon.getIconWidth(), work.icon.getIconHeight()); frame.setShape( work.shape ); //AP終了のためのポップアップメニューを作成する //PopupMenu pAct = new PopupMenu(); //popup = new JPopupMenu(); //JMenuItem exitMenuItem = new JMenuItem("終了"); //exitMenuItem.addActionListener( pAct ); //popup.add( exitMenuItem ); //適当な位置に実行結果Windowを表示する surface.setVisible(true); surface.setLocation( 100,100 ); //PROCESSING用の表示画像を取得しておく dispImg = work.pimg; } /** * ポップアップメニュー選択イベント受け取り用クラス */ //class PopupMenu implements ActionListener { // @Override // public void actionPerformed( ActionEvent e ) { // String command = e.getActionCommand(); // if (command.equals("終了")) { // //終了メニューが選択されたのでAP終了 // exit(); // } // } //} /** * PSurface(実行結果Window)が持つFrameを得る * @return Frame:Frameオブジェクト */ Frame getFrame() { PSurfaceAWT.SmoothCanvas canvas; canvas = (PSurfaceAWT.SmoothCanvas)getSurface().getNative(); return( (Frame)canvas.getFrame() ); } /** * PROCESSING マウス押下イベント * @param e:PROCESSINGマウスイベント */ void mousePressed( MouseEvent e ){ //マウスが押下されたスクリーン座標を記録する PointerInfo pi = MouseInfo.getPointerInfo(); startPt = pi.getLocation(); } /** * PROCESSING マウスドラッグイベント * @param e:PROCESSINGマウスイベント */ void mouseDragged( MouseEvent e ){ //マウスが移動したスクリーン座標を取得する PointerInfo pi = MouseInfo.getPointerInfo(); Point nowPt = pi.getLocation(); //どこ程度動いたかを計算し、画面を移動する int sabunX = nowPt.x - startPt.x ; int sabunY = nowPt.y - startPt.y ; int x = getFrame().getX() + sabunX; int y = getFrame().getY() + sabunY; surface.setLocation( x, y ); //次の移動のために、現在のマウスカーソル位置を更新 startPt = nowPt; } /** * PROCESSING マウスクリックイベント * @param e:PROCESSINGマウスイベント */ void mouseClicked( MouseEvent e ){ Frame frame = getFrame(); //ダブルクリックされたら、Shapeを切り替える if( e.getCount() > 1 ){ imgIndex++; if( imgIndex >= myShape.size() ){ imgIndex = 0; } //新しいShapeを実行結果Windowにセットする FrameShapeMng work = myShape.get( imgIndex ); surface.setSize(work.icon.getIconWidth(), work.icon.getIconHeight()); frame.setShape( work.shape ); //PROCESSING用の表示画像を取得しておく dispImg = work.pimg; return; } //右クリックされたら、ダイアログBOXを開いて //終了するか問い合わせる if( e.getButton() == RIGHT ){ //ポップアップメニューを表示する //popup.show( getFrame().getComponent(0), ex, ey ); //popup.repaint(); int ans = JOptionPane.showConfirmDialog(getFrame().getComponent(0), "終了しますか?", "問い合わせ", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if( ans == JOptionPane.YES_OPTION ){ //YESなのでAP終了 exit(); } } } /** * PROCESSING 描画処理関数 */ void draw() { background(0); image( dispImg, 0, 0 ); } |
画像には、©GMO Internet, Inc. 様が公開されている、公式マスコットキャラクタの「美雲あんず」を利用させて頂いております。
画像の再利用は禁止されています。ご注意ください。詳しくは©GMO Internet, Inc. 様のサイトを参照して下さい。
画像をダブルクリックすると、あんずちゃんの絵が切り替わります。右クリックするとダイアログBOXが表示されます。「はい」を選択すると、画面を終了する事ができます。
<出力サンプル>
(画像URL:©GMO Internet, Inc. 様:美雲あんず:画像の再利用は禁止です)
作っておいて言うのも何ですが・・・・オタク街道まっしぐらです(笑)。やってしまいました(汗)。
また、上記プログラムでShapeを作成する処理は、エマノンの雑記帳 様に掲載されていたロジックを流用しています。ありがとうございました。
非矩形ウィンドウを表示する例(Java版):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
package SamplePkg; import java.awt.Container; import java.awt.Graphics; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.Shape; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.GeneralPath; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.util.ArrayList; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; /** * 非矩形ウィンドウ生成サンプル</br> * @author MSLABO * @version 1.0 */ public class SanpmeClass { /** * フレーム用Shape管理クラス */ static class FrameShapeMng{ Shape shape; //非矩形領域 ImageIcon icon; //アイコン画像 Image img; //表示用画像 /** * コンストラクタ * @param fileName:画像ファイルパス */ FrameShapeMng( String fileName ){ //画像ファイルをアイコン画像化 icon = new ImageIcon( fileName ); //アイコン画像からShapeを得る shape = getImageShape( icon ); //アイコン画像から表示用画像を得る img = icon.getImage(); } /** * アイコン画像の不透明部分を Shapeにする関数</br> * @param icon:アイコン画像 * @return Shape:作成した Shapeオブジェクト */ Shape getImageShape(ImageIcon icon){ //Shape領域作成 GeneralPath shape = new GeneralPath(); //アイコン画像と同じ大きさのARGBイメージバッファ生成 final BufferedImage bi = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB); //イメージバッファにアイコン画像を描画する icon.paintIcon(null, bi.createGraphics(), 0, 0); ColorModel cm = bi.getColorModel(); //イメージバッファの不透明画素をShape化する for(int y = 0; y < bi.getHeight(); y++){ for(int x = 0; x < bi.getWidth(); x++){ // 不透明ならShapeに追加する if(cm.getAlpha(bi.getRGB(x, y)) > 0){ int start = x; //追加開始横位置 int notTransparentWidth = 0; //追加する画素数 //横1ラインの中で、連続している不透明画素を数える while((x < bi.getWidth()) && (cm.getAlpha(bi.getRGB(x++, y)) > 0)){ notTransparentWidth++; } //不透明画素部分をマスクとして、Shapeに追加する shape.append(new Rectangle(start, y, notTransparentWidth, 1), true); } } } return( shape ); } } /** * パネル再描画用クラス</br> * Frame描画時に、パネルに張り付けた画像を再表示させる</br> */ public static class myPanel extends JPanel { private Image image = null; public void setImage(Image image) { this.image = image; } @Override public void paintComponent(Graphics g) { if( image == null ) return; g.drawImage(image, 0, 0, this); } } /** * マウス関連イベント処理用クラス</br> */ public static class DragWindowListener extends MouseAdapter { private MouseEvent start; private Point loc; private JFrame window; /** * コンストラクタ</br> * @param frame:表示用Window */ public DragWindowListener(JFrame frame){ super(); window = frame; } /** * マウス押下イベント</br> */ @Override public void mousePressed(MouseEvent me) { //ダブルクリックされたら、画像を切り替える if(me.getClickCount() >= 2){ imgIndex++; if( imgIndex >= myShape.size() ){ imgIndex = 0; } //新しいShapeをWindowにセットする FrameShapeMng work = myShape.get( imgIndex ); frame.setSize(work.icon.getIconWidth(), work.icon.getIconHeight()); frame.setShape( work.shape ); //画像をパネルにセットしてJFrameに描画する myPanel myPanel = new myPanel(); try { myPanel.setImage(work.img); } catch( Exception e ) { e.printStackTrace(); } Container contentPane = frame.getContentPane(); contentPane.remove(0); contentPane.add( myPanel ); return; } //マウスドラッグ開始位置を覚える start = me; //右クリックなら終了問い合わせを行う if( me.getButton() == MouseEvent.BUTTON3 ){ //showPopup( me ); int ans = JOptionPane.showConfirmDialog(frame.getComponent(0), "終了しますか?", "問い合わせ", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if( ans == JOptionPane.YES_OPTION ){ //YESなのでAP終了 System.exit(0); } } } /** * マウスドラッグ開始イベント</br> */ @Override public void mouseDragged(MouseEvent me) { //移動した座標を得る loc = window.getLocation(loc); //移動量をもとに、Windowを動かす int x = loc.x- start.getX() + me.getX(); int y = loc.y - start.getY() + me.getY(); window.setLocation(x, y); } } static ArrayList<FrameShapeMng> myShape; static int imgIndex = 0; static JFrame frame = null; /** * メイン処理</br> * @param args:なし */ public static void main(String[] args) { // JFrameを生成する frame = new JFrame(); //×ボタンの挙動を Frameに合わせる frame.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE); //マウス関連イベントリスナーを定義する DragWindowListener dl = new DragWindowListener(frame); frame.addMouseListener(dl); frame.addMouseMotionListener(dl); //JFrameを画面から切り離して、タイトルバーを非表示にする frame.removeNotify(); frame.setUndecorated(true); //JFrameを画面に再接続 frame.addNotify(); //Shape管理クラスを生成する myShape = new ArrayList<FrameShapeMng>(); myShape.add( new FrameShapeMng("C:\\pleiades\\workspace\\SampleProject2\\anzu1.png") ); myShape.add( new FrameShapeMng("C:\\pleiades\\workspace\\SampleProject2\\anzu2.png") ); myShape.add( new FrameShapeMng("C:\\pleiades\\workspace\\SampleProject2\\anzu3.png") ); //最初の画像をWindowにセットする imgIndex = 0; FrameShapeMng work = myShape.get( imgIndex ); frame.setSize( work.icon.getIconWidth(), work.icon.getIconHeight()); frame.setShape( work.shape ); //画像をパネルにセットしてJFrameに描画する myPanel myPanel = new myPanel(); try { myPanel.setImage(work.img); } catch( Exception e ) { e.printStackTrace(); } Container contentPane = frame.getContentPane(); contentPane.add( myPanel ); //JFrameを適当な位置に表示する frame.setLocation( 300,300 ); frame.setVisible(true); } } |
参考までに、Java版のサンプルも掲載しておきます。
PROCESSING版とほとんど一緒ですが、JFrameを生成している所が異なります。
またJFrameに画像を描画するのに、JPanelを利用しています。
本ページで利用しているアイコン画像は、下記サイト様より拝借しております。各画像の著作権は、それぞれのサイト様および作者にあります。