2020/06/20 Updated by

Java: ToyGraphics


[Up] Japanese English

Javaで2次元グラフィックスを簡単に扱う方法

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);
	    }
	}
    }
}

簡単な例(1)




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 クラスのオブジェクトが設定されています。


  • 簡単な例(2)



  • 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 してクラスを定義するように変更した例です。


  • 簡単な例(3)



  • 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();
        }
    }
    

    特定のピクセルの色を設定する例です。


    マウス入力を扱う例(1)




    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) を呼び出す必要があります。


    マウス入力を扱う例(2)




    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つのウィンドウだけで作ってみた「お絵描きツール」です。 ウィンドウの中に領域を定義してメニューとして何か表示して、それをクリックすることで状態を変化させます。 メニュー以外のウィンド領域はマウスボタンの「押す」「放す」イベントにより図形を配置します。

    もっとオブジェクト指向らしいプログラムの書き方はありえますが、 ここでは「敢えて」目先の単純さを優先したプログラム例を示していると理解して下さい。 改善策はいろいろあります。特に「メニュー」回りの扱いは大いに改善の余地があることでしょう。