argius note

プログラミング関連

Swingで矩形選択範囲の表現

下記の投稿を見て、良いサンプルが見つからないので自分で書いてみました。




環境

あまり関係ないですが、一応。

選択範囲を色反転で表現

java.awt.GraphicsクラスにsetXORMode(Color)というメソッドがあります。


これを使って、選択範囲の描画を行います。

選択範囲をドラッグで指定して、ドラッグが終わるとそこに選択範囲の矩形が残ります。
Ctrl+クリックすると、選択範囲がすべて除去されます。

  • サンプル1:選択範囲を色反転で表現
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.*;

final class PaintPanel extends JPanel implements MouseListener, MouseMotionListener {

    Image image;
    List<Rectangle> rects;
    Rectangle draggingRect;
    volatile boolean dragging;

    PaintPanel(Image image) {
        this.image = image;
        this.rects = Collections.synchronizedList(new ArrayList<>());
        this.draggingRect = new Rectangle(0, 0);
        this.dragging = false;
        addMouseListener(this);
        addMouseMotionListener(this);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(image, 0, 0, this);
        g.setXORMode(Color.WHITE);
        if (dragging) {
            g.drawRect(draggingRect.x, draggingRect.y, draggingRect.width, draggingRect.height);
        }
        for (Rectangle r : rects) {
            g.drawRect(r.x, r.y, r.width, r.height);
        }
    }

    @Override
    public void mousePressed(MouseEvent e) {
        draggingRect.setBounds(e.getX(), e.getY(), 0, 0);
        dragging = true;
        repaint();
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        int x = (int)draggingRect.getX();
        int y = (int)draggingRect.getY();
        draggingRect.setSize(e.getX() - x, e.getY() - y);
        repaint();
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        dragging = false;
        Dimension rectSize = draggingRect.getSize();
        if (rectSize.getWidth() > 0 && rectSize.getHeight() > 0) {
            rects.add(new Rectangle(draggingRect));
        }
        draggingRect.setBounds(0, 0, 0, 0);
        repaint();
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        if (e.isControlDown()) {
            rects.clear();
        }
    }

    @Override
    public void mouseMoved(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

}

public final class App {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                Image image;
                try {
                    image = ImageIO.read(new File("./image.jpg"));
                } catch (IOException e) {
                    e.printStackTrace();
                    return;
                }
                JFrame f = new JFrame();
                f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
                f.setSize(400, 300);
                f.add(new PaintPanel(image), BorderLayout.CENTER);
                f.setLocationRelativeTo(null);
                f.setVisible(true);
            }
        });
    }

}



こんな感じになります。

  • 結果(サンプル1:選択範囲を色反転で表現)

f:id:argius:20151218134733g:plain


点滅で表現

色を周期的に変化させるクラスAutoColorChanger*1を作って、それを使って線を引きます。
ここでは、黒と白を行ったり来たりするエフェクトにしていますが、お好みで、runメソッドの実装を変えてください。
色の表現以外は、サンプル1とほぼ同じです。

  • サンプル2:選択範囲を点滅で表現
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import javax.imageio.ImageIO;
import javax.swing.*;

final class AutoColorChanger implements Runnable {

    static final int AMOUNT = 38;

    JComponent component;
    AtomicReference<Color> colorRef;
    int v;

    AutoColorChanger(JComponent component) {
        this.component = component;
        this.colorRef = new AtomicReference<Color>(Color.BLACK);
        this.v = 0;
        Executors.newSingleThreadExecutor().execute(this);
    }

    @Override
    public void run() {
        // 好きなように色を変化させるコードを書く
        boolean negate = false;
        while (true) {
            if (negate) {
                v -= AMOUNT;
                if (v < 0) {
                    v = 0;
                    negate = false;
                }
            }
            else {
                v += AMOUNT;
                if (v >= 256) {
                    v = 255;
                    negate = true;
                }
            }
            colorRef.set(new Color(v, v, v));
            // 再描画&ウェイト
            repaintAndWait();
        }
    }

    void repaintAndWait() {
        SwingUtilities.invokeLater(() -> {
            component.repaint();
        });
        try {
            Thread.sleep(120L);
        } catch (InterruptedException e) {
            throw new IllegalStateException(e);
        }
    }

    Color getColor() {
        return colorRef.get();
    }

}

final class PaintPanel extends JPanel implements MouseListener, MouseMotionListener {

    Image image;
    List<Rectangle> rects;
    Rectangle draggingRect;
    volatile boolean dragging;
    AutoColorChanger autoColorChanger;

    PaintPanel(Image image) {
        this.image = image;
        this.rects = Collections.synchronizedList(new ArrayList<>());
        this.draggingRect = new Rectangle(0, 0);
        this.dragging = false;
        this.autoColorChanger = new AutoColorChanger(this);
        addMouseListener(this);
        addMouseMotionListener(this);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(image, 0, 0, this);
        g.setColor(autoColorChanger.getColor());
        if (dragging) {
            g.drawRect(draggingRect.x, draggingRect.y, draggingRect.width, draggingRect.height);
        }
        for (Rectangle r : rects) {
            g.drawRect(r.x, r.y, r.width, r.height);
        }
    }

    @Override
    public void mousePressed(MouseEvent e) {
        draggingRect.setBounds(e.getX(), e.getY(), 0, 0);
        dragging = true;
        repaint();
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        int x = (int)draggingRect.getX();
        int y = (int)draggingRect.getY();
        draggingRect.setSize(e.getX() - x, e.getY() - y);
        repaint();
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        dragging = false;
        Dimension rectSize = draggingRect.getSize();
        if (rectSize.getWidth() > 0 && rectSize.getHeight() > 0) {
            rects.add(new Rectangle(draggingRect));
        }
        draggingRect.setBounds(0, 0, 0, 0);
        repaint();
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        if (e.isControlDown()) {
            rects.clear();
        }
    }

    @Override
    public void mouseMoved(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

}

public final class App {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                Image image;
                try {
                    image = ImageIO.read(new File("./image.jpg"));
                } catch (IOException e) {
                    e.printStackTrace();
                    return;
                }
                JFrame f = new JFrame();
                f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
                f.setSize(400, 300);
                f.add(new PaintPanel(image), BorderLayout.CENTER);
                f.setLocationRelativeTo(null);
                f.setVisible(true);
            }
        });
    }

}



追記:三角関数を使ったほうがシンプルかも。動作は少し変わりますけどね。

    @Override
    public void run() {
        // 好きなように色を変化させるコードを書く
        while (true) {
            ++v; // 値が大きくなったらリセットしたほうが良いかも
            float f = (float)((Math.sin(v / 2f) + 1) / 2);
            colorRef.set(new Color(f, f, f));
            // 再描画&ウェイト
            repaintAndWait();
        }
    }



  • 結果(サンプル2:選択範囲を点滅で表現)

f:id:argius:20151218134716g:plain



少し大がかりになってしまいましたが、画像によってはこちらのほうが見やすくなるのではと思います。


選択範囲を回る点線で表現する、というのも有りかと思いますが、さすがに大変なので今回はここまで。


ちなみに、GIF画像のキャプチャーはGifCamというソフトを使いました。便利!

via

(おわり)

*1:クラス名がイケてないですよね...