ToyGraphics クラスを使うと2次元グラフィックスを扱うプログラムを簡単に描画できます。 ここでは、その使用方法について説明します。
ToyGraphics.java |
/* * Copyright (c) 2020 Yoshihisa Nitta * Released under the MIT license * http://opensource.org/licenses/mit-license.php */ import java.util.*; import java.awt.geom.*; import java.awt.*; import java.awt.image.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; public class ToyGraphics extends JComponent { int width, height; BufferedImage image; protected Graphics2D g2d; public boolean isMousePressed = false; public void initImage(int w,int h) { width = w; height = h; image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB); g2d = image.createGraphics(); setFont(); clear(); } public void initFrame() { JFrame frame = new JFrame("Graphic Window"); Container c = frame.getContentPane(); Dimension size = getPreferredSize(); int w = size.width; int h = size.height; c.setSize(w,h); c.add(this,BorderLayout.CENTER); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); } public Dimension getPreferredSize() { return new Dimension(image.getWidth(),image.getHeight()); } public ToyGraphics() { this(1280,720); } public ToyGraphics(int w,int h) { initImage(w,h); initFrame(); } public void setFont() { setFont("Serif",24); } public void setFont(String name,int size) { g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Font font = new Font(name,Font.PLAIN,size); g2d.setFont(font); } public void setColor(Color col) { g2d.setColor(col); } public void drawText(String txt,int x,int y) { g2d.drawString(txt,x,y); } public void paint(Graphics g) { g.drawImage(image,0,0,this); } public static int rgb2pixel(int r,int g,int b) throws IllegalArgumentException { return rgb2pixel(255,r,g,b); } public static int rgb2pixel(int a,int r,int g,int b) throws IllegalArgumentException { if (a<0 || a>255 || r<0 || r>255 || g<0 || g>255 || b<0 || b>255) throw new IllegalArgumentException("bad color:("+a+","+r+","+g+","+b+")"); return (a<<24) | (r << 16) | (g << 8) | (b); } public void drawRGB(int x,int y,int r,int g,int b) { setRGB(x,y,r,g,b); repaint(x,y,1,1); } public void drawRGB(int x,int y,int pxl) { setRGB(x,y,pxl); repaint(x,y,1,1); } public void setRGB(int x,int y,int r,int g,int b) { setRGB(x,y,rgb2pixel(r,g,b)); } public void setRGB(int x,int y,int pxl) { if (x < 0 || x>width || y<0 || y>height) return; image.setRGB(x,y,pxl); } public void clear() { for (int y=0; y<height; y++) { for (int x=0; x<width; x++) { setRGB(x,y,0,0,0); } } } private void fillRandomColor(Random random) { for (int y=0; y<height; y++) { for (int x=0; x<width; x++) { int pxl = random.nextInt() | 0xff000000; setRGB(x,y,pxl); } } } } |
Sample1.java |
import java.awt.*; import java.awt.geom.*; public class Sample1 { public void run() { ToyGraphics tg = new ToyGraphics(640, 360); // 640x360のサイズのウィンドウを生成する tg.g2d.setPaint(Color.black); tg.g2d.fill(new Rectangle2D.Double(0, 0, tg.width, tg.height)); // ウィンドウ全体を黒で塗りつぶす tg.g2d.setStroke(new BasicStroke(3.0f)); // 描画する線の太さを3.0fにする。 tg.g2d.setPaint(Color.white); tg.g2d.draw(new Line2D.Double(20,20,100,80)); // 白色の線分を描画する tg.g2d.setPaint(Color.cyan); tg.g2d.draw(new Rectangle2D.Double(50,50,200,100)); // 水色の長方形(枠)を描画する tg.g2d.setPaint(Color.blue); tg.g2d.fill(new Rectangle2D.Double(100,100,300,100)); // 青色の長方形(塗りつぶし)を描画する tg.g2d.setStroke(new BasicStroke(3.0f)); // 枠線の太さを5.0fに変更する tg.g2d.setPaint(Color.red); tg.g2d.draw(new Arc2D.Double(150,150,200,100,0,360,Arc2D.OPEN)); // 赤色の楕円(枠)を描画する tg.g2d.setPaint(Color.yellow); tg.g2d.fill(new Arc2D.Double(200,220,300,100,0,360,Arc2D.OPEN)); // 黄色の楕円(塗りつぶし)を描画する tg.g2d.setPaint(Color.green); tg.g2d.drawString("Hello World", 150, 80); // 緑の文字を描画する tg.repaint(0,0,tg.width,tg.height); // 範囲を指定して再描画する (これが無いと変更が反映されない) } public static void main(String[] args) { Sample1 app = new Sample1(); app.run(); } } |
ToyGraphicsのコンストラクタにウィンドウのサイズを与えます。
tg.g2d
に Graphics2D クラスのオブジェクトが設定されています。
Sample2.java |
import java.awt.*; import java.awt.geom.*; public class Sample2 extends ToyGraphics { public Sample2() { super(640, 360); } public void run() { g2d.setPaint(Color.black); g2d.fill(new Rectangle2D.Double(0, 0, width, height)); // ウィンドウ全体を黒で塗りつぶす g2d.setStroke(new BasicStroke(3.0f)); // 描画する線の太さを3.0fにする。 g2d.setPaint(Color.white); g2d.draw(new Line2D.Double(20,20,100,80)); // 白色の線分を描画する g2d.setPaint(Color.cyan); g2d.draw(new Rectangle2D.Double(50,50,200,100)); // 水色の長方形(枠)を描画する g2d.setPaint(Color.blue); g2d.fill(new Rectangle2D.Double(100,100,300,100)); // 青色の長方形(塗りつぶし)を描画する g2d.setStroke(new BasicStroke(3.0f)); // 枠線の太さを5.0fに変更する g2d.setPaint(Color.red); g2d.draw(new Arc2D.Double(150,150,200,100,0,360,Arc2D.OPEN)); // 赤色の楕円(枠)を描画する g2d.setPaint(Color.yellow); g2d.fill(new Arc2D.Double(200,220,300,100,0,360,Arc2D.OPEN)); // 黄色の楕円(塗りつぶし)を描画する g2d.setPaint(Color.green); g2d.drawString("Hello World", 150, 80); // 緑の文字を描画する repaint(0,0,width,height); // 範囲を指定して再描画する (これが無いと変更が反映されない) } public static void main(String[] args) { Sample2 app = new Sample2(); app.run(); } } |
ToyGraphics クラスをextends してクラスを定義するように変更した例です。
Sample3.java |
import java.awt.*; import java.awt.geom.*; public class Sample3 extends ToyGraphics { public Sample3() { super(640, 360); } public void run() { g2d.setPaint(Color.black); g2d.fill(new Rectangle2D.Double(0, 0, width, height)); // ウィンドウ全体を黒で塗りつぶす drawRect(50,50,200,100,255,0,0); drawRect(100,80,200,100,0,255,255); } void drawRect(int x,int y,int w,int h,int r,int g,int b) { for (int line=y; line < y+h; line++) { for (int col=x; col < x+w; col++) { setRGB(col,line,r,g,b); } } repaint(x,y,w,h); } public static void main(String[] args) { Sample3 app = new Sample3(); app.run(); } } |
特定のピクセルの色を設定する例です。
Sample4.java |
import java.awt.*; import java.awt.geom.*; import java.awt.event.*; public class Sample4 extends ToyGraphics implements MouseListener { public Sample4() { super(640,480); } public void run() { addMouseListener(this); // ウィンドウに対するMouseイベントを自分で受け取る g2d.setPaint(Color.white); g2d.fill(new Rectangle2D.Double(0, 0, width, height)); // ウィンドウ全体を黒で塗りつぶす repaint(0,0,width,height); // 範囲を指定して再描画する (これが無いと変更が反映されない) } public void mouseClicked(MouseEvent e) { System.err.println("mouseClicked"); } public void mousePressed(MouseEvent e) { int mx = e.getX(), my = e.getY(); System.err.println("mousePressed " + mx + " " + my); int w=10, h=10, x=mx-w/2, y=my-h/2; g2d.setPaint(Color.blue); g2d.fill(new Rectangle2D.Double(x,y,w,h)); // (x,y)の回りを10x10の青色の四角で塗りつぶす repaint(x,y,w,h); } public void mouseReleased(MouseEvent e) { int mx = e.getX(), my = e.getY(); System.err.println("mouseReleased " + mx + " " + my); int w=10, h=10, x=mx-w/2, y=my-h/2; g2d.setPaint(Color.red); g2d.fill(new Rectangle2D.Double(x,y,w,h)); // (x,y)の回りを10x10の赤色の四角で塗りつぶす repaint(x,y,w,h); } public void mouseEntered(MouseEvent e) { System.err.println("mouseEntered"); } public void mouseExited(MouseEvent e) { System.err.println("mouseExited"); } public static void main(String[] args) { Sample4 app = new Sample4(); app.run(); } } |
Sample4.log |
% java Sample3 mouseEntered mousePressed 104 83 mouseReleased 192 170 mousePressed 276 201 mouseReleased 236 295 mousePressed 394 395 mouseReleased 397 227 |
描画するウィンドウに対するマウス操作のイベントを受け取るクラスは、 ToyGraphics の(正確にはその祖先の java.awt.Component の) subclassである必要があります。
生成したウィンドウへのマウス操作のイベントを受け取るためには、 MouseLisenter インターフェイスを implements して addListener(MouseListener) を呼び出す必要があります。
WinDraw.java |
import java.util.*; import java.awt.*; import java.awt.geom.*; import java.awt.event.*; public class WinDraw extends ToyGraphics implements MouseListener { static final int RECTANGLE = 1; static final int CIRCLE = 2; boolean isDragging = false; int sx, sy, ex, ey; // Mouse pressed (sx,sy), released (ex,ey) Color strokeColor = Color.red; int shapeType = RECTANGLE; int[][] menuArea = { {0,0,100,30}, {0,30,100,30}, {0,60,100,30}, {0,90,100,30}, {0,120,100,30}, }; String[] menuString = { "Redraw", "Red", "Blue", "Rectangle", "Circle" }; ArrayList<Shape> shapes; public WinDraw() { this(1280,720); } public WinDraw(int w,int h) { shapes = new ArrayList<Shape>(); addMouseListener(this); } public void run() { redraw(); } boolean inArea(int mx,int my,int x,int y,int w,int h) { return (mx >= x && mx < x+w && my >= y && my < y+h); } boolean inArea(int mx,int my,int[] r) { int x=r[0], y=r[1], w=r[2], h=r[3]; return (mx >= x && mx < x+w && my >= y && my < y+h); } boolean checkMenu(int sx,int sy) { for (int i = 0; i<menuArea.length; i++) { int[] r= menuArea[i]; if (inArea(sx,sy,r)) { System.err.println("menu "+i); switch (i) { case 0: redraw(); break; case 1: strokeColor = Color.red; break; case 2: strokeColor = Color.blue; break; case 3: shapeType = RECTANGLE; break; case 4: shapeType = CIRCLE; break; default: System.err.println("no menu"); System.exit(-1);break; } return true; } } return false; } void redraw() { g2d.setPaint(Color.black); g2d.fill(new Rectangle2D.Double(0,0,width,height)); drawMenu(); for (Shape shape: shapes) { shape.draw(g2d); } repaint(0,0,width,height); } void drawMenu() { for (int i=0; i<menuArea.length; i++) { int[] r = menuArea[i]; Rectangle2D shape = new Rectangle2D.Double(r[0],r[1],r[2],r[3]); g2d.setPaint(Color.white); g2d.fill(shape); g2d.setPaint(Color.black); g2d.draw(shape); g2d.drawString(menuString[i], r[0]+5, r[1]+20); repaint(r[0],r[1],r[2],r[3]); } } public void mouseClicked(MouseEvent e) {} public void mousePressed(MouseEvent e) { sx = e.getX(); sy = e.getY(); System.err.println("Pressed " + sx + " " + sy); isDragging = !checkMenu(sx,sy); } public void mouseReleased(MouseEvent e) { ex = e.getX(); ey = e.getY(); System.err.println("Released " + ex + " " + ey); if (! isDragging) return; // select menu int x = Math.min(sx,ex), y = Math.min(sy,ey), w = Math.abs(ex-sx), h = Math.abs(ey-sy); Shape shape; if (shapeType == RECTANGLE) { shape = new Rect(x,y,w,h,3,strokeColor,null); } else { // (shapeType == CIRCLE) shape = new Circle(x,y,w,h,3,strokeColor,null); } shape.draw(g2d); repaint(x,y,w,h); shapes.add(shape); } public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} public static void main(String[] args) { WinDraw wd = new WinDraw(); wd.run(); } } |
Shape.java |
import java.awt.*; public abstract class Shape { int x, y; Color strokeColor, fillColor; abstract void draw(Graphics2D g2d); } |
Rect.java |
import java.awt.*; import java.awt.geom.*; public class Rect extends Shape { int w, h, lineWidth; public Rect(int x,int y,int w,int h,int lineWidth,Color strokeColor,Color fillColor) { this.x = x; this.y = y; this.w = w; this.h = h; this.lineWidth = lineWidth; this.strokeColor = strokeColor; this.fillColor = fillColor; } void draw(Graphics2D g2d) { Rectangle2D shape = new Rectangle2D.Double(x,y,w,h); if (fillColor != null) { g2d.setPaint(fillColor); g2d.fill(shape); } if (strokeColor != null) { g2d.setStroke(new BasicStroke((float) lineWidth)); g2d.setPaint(strokeColor); g2d.draw(shape); } } } |
Circle.java |
import java.awt.*; import java.awt.geom.*; public class Circle extends Shape { int w, h, lineWidth; public Circle(int x,int y,int w,int h,int lineWidth,Color strokeColor,Color fillColor) { this.x = x; this.y = y; this.w = w; this.h = h; this.lineWidth = lineWidth; this.strokeColor = strokeColor; this.fillColor = fillColor; } void draw(Graphics2D g2d) { Arc2D shape = new Arc2D.Double(x,y,w,h,0,360,Arc2D.OPEN); if (fillColor != null) { g2d.setPaint(fillColor); g2d.fill(shape); } if (strokeColor != null) { g2d.setStroke(new BasicStroke((float) lineWidth)); g2d.setPaint(strokeColor); g2d.draw(shape); } } } |
WinDraw.log |
% javac WinDraw.java % java WinDraw Pressed 11 46 menu 1 Released 11 46 Pressed 65 103 menu 3 Released 65 103 Pressed 251 163 Released 430 315 Pressed 54 131 menu 4 Released 56 132 Pressed 683 272 Released 967 479 Pressed 30 74 menu 2 Released 31 74 Pressed 372 120 Released 683 382 Pressed 73 109 menu 3 Released 73 109 Pressed 135 273 Released 609 582 |
敢えて、ToyGraphicsの生成する1つのウィンドウだけで作ってみた「お絵描きツール」です。 ウィンドウの中に領域を定義してメニューとして何か表示して、それをクリックすることで状態を変化させます。 メニュー以外のウィンド領域はマウスボタンの「押す」「放す」イベントにより図形を配置します。
もっとオブジェクト指向らしいプログラムの書き方はありえますが、 ここでは「敢えて」目先の単純さを優先したプログラム例を示していると理解して下さい。 改善策はいろいろあります。特に「メニュー」回りの扱いは大いに改善の余地があることでしょう。