Javaでできた拡大鏡アプリ

なんとなく作った拡大鏡アプリです。
最初は、マウスカーソルがある場所のピクセルの色を抽出して16進数表示を出すだけだったんですけど、
マウスカーソルの付近がよく見えるように拡大表示する機能をつけたらそっちがメインになってしまいました。

アプリとしての機能よりも、300行程度のソースにいろんなクラスが使ってあって、サンプルコードとして面白いかと思うので公開します。

スクリーンショット


起動方法

ダウンロード

上のリンクからダウンロードして適当なディレクトリに保存。ダブルクリックで起動。
または、こちらのリンクをクリック => Java Web Startで起動
J2SE 5.0以上が必要です。

Javaで作ってありますが、Windows XPでしか動作確認してません。
MaxやLinuxでも動くかもしれないですが不明です。;p
もし動くようでしたら、こちらの日記にコメントください。

使い方

ソース

ソースは以下のとおり。jarファイルにも入ってます。
HTMLの色づけは pastebin.comを使いました。
  1. package sawat.loupe;
  2. import java.awt.*;
  3. import java.awt.datatransfer.*;
  4. import java.awt.event.*;
  5. import java.awt.image.BufferedImage;
  6. import java.util.*;
  7.  
  8. import javax.swing.*;
  9. import javax.swing.border.Border;
  10.  
  11. public class Loupe extends JFrame {
  12.    private static final int SLIDER_ACCURACY = 5;
  13.    private static final int INITIAL_SCALE = 3;
  14.    private static final int MAX_SCALE = 6;
  15.    private static final int MIN_SCALE = 0;
  16.  
  17.    public static void main(String[] args) throws AWTException {
  18.  
  19.       Loupe f = new Loupe();
  20.       f.setAlwaysOnTop(true);
  21.       f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  22.       f.pack();
  23.       f.setLocationRelativeTo(null);
  24.       f.setVisible(true);
  25.    }
  26.  
  27.    private boolean captureEnable = true;
  28.    private final Robot robot;
  29.    private final JLabel captureLabel;
  30.    private final JSlider slider;
  31.    private final JTextField text;
  32.    private GridMode gridMode = GridMode.GUIDE_LINE;
  33.  
  34.    public Loupe() throws AWTException {
  35.       super("Loupe");
  36.  
  37.       this.robot = new Robot();
  38.       robot.setAutoDelay(0);
  39.  
  40.       this.captureLabel = new JLabel();
  41.       captureLabel.setBorder(BorderFactory.createEtchedBorder());
  42.       getContentPane().add(captureLabel, BorderLayout.CENTER);
  43.       captureLabel.setPreferredSize(new Dimension(200, 200));
  44.  
  45.       text = new JTextField();
  46.       text.setText("Press the space key");
  47.  
  48.       getContentPane().add(text, BorderLayout.SOUTH);
  49.  
  50.       slider = new JSlider(SwingConstants.VERTICAL,
  51.             MIN_SCALE * SLIDER_ACCURACY,
  52.             MAX_SCALE * SLIDER_ACCURACY,
  53.             INITIAL_SCALE * SLIDER_ACCURACY);
  54.       getContentPane().add(slider, BorderLayout.EAST);
  55.  
  56.       initSliderLabels();
  57.       initEventListener();
  58.       initTimer();
  59.    }
  60.  
  61.    private void initSliderLabels() {
  62.       Hashtable<Integer, JLabel> labels = new Hashtable<Integer, JLabel>();
  63.       for (int i = MIN_SCALE, n = MAX_SCALE; i <= n; i++) {
  64.          labels.put(i * SLIDER_ACCURACY,
  65.                new JLabel("×" + (1 << (i))));
  66. //               new JLabel(String.format("× %.1f", Math.pow(2, i)))); // if(MIN_SCALE < 0)
  67.       }
  68.       slider.setLabelTable(labels);
  69.       slider.setPaintLabels(true);
  70.    }
  71.  
  72.    private void appendHex(StringBuffer buffer, int i) {
  73.       if (i < 0x10)
  74.          buffer.append('0');
  75.       buffer.append(Integer.toHexString(i));
  76.    }
  77.  
  78.    protected void capture() {
  79.       Point location = MouseInfo.getPointerInfo().getLocation();
  80.  
  81.       double scale = Math
  82.             .pow(2, (double) slider.getValue() / SLIDER_ACCURACY);
  83.       int width = (int) (captureLabel.getWidth() / scale);
  84.       int height = (int) (captureLabel.getHeight() / scale);
  85.  
  86.       if (width <= 0 || height <= 0) {
  87.          return;
  88.       }
  89.  
  90.       BufferedImage image = robot.createScreenCapture(new Rectangle(
  91.             location.x - width / 2, location.y - width / 2, width, height));
  92.  
  93.       captureLabel.setIcon(new ExpandImageIcon(image, scale, gridMode));
  94.    }
  95.  
  96.    private String toHexString(Color color) {
  97.       StringBuffer buffer = new StringBuffer(7);
  98.       buffer.append('#');
  99.       appendHex(buffer, color.getRed());
  100.       appendHex(buffer, color.getGreen());
  101.       appendHex(buffer, color.getBlue());
  102.  
  103.       return buffer.toString();
  104.    }
  105.  
  106.    protected void copyToClipbord() {
  107.       text.setText("Copty to Clipboard!");
  108.  
  109.       EventQueue.invokeLater(new Runnable() {
  110.          public void run() {
  111.             Clipboard clipboard = Toolkit.getDefaultToolkit()
  112.                   .getSystemClipboard();
  113.             Border border = captureLabel.getBorder();
  114.  
  115.             Insets insets = border.getBorderInsets(captureLabel);
  116.  
  117.             int w = captureLabel.getWidth() - insets.left - insets.right;
  118.             int h = captureLabel.getHeight() - insets.top - insets.bottom;
  119.  
  120.             BufferedImage bi = new BufferedImage(w, h,
  121.                   BufferedImage.TYPE_INT_RGB);
  122.             Graphics graphics = bi.getGraphics();
  123.             try {
  124.                graphics.translate(-insets.left, -insets.top);
  125.                captureLabel.paint(graphics);
  126.             } finally {
  127.                graphics.dispose();
  128.             }
  129.  
  130.             ImageAndTextSelection selection = new ImageAndTextSelection(bi,
  131.                   pickUp());
  132.             clipboard.setContents(selection, selection);
  133.          }
  134.       });
  135.    }
  136.  
  137.    private void initEventListener() {
  138.       Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
  139.          public void eventDispatched(AWTEvent event) {
  140.             if (event.getID() == KeyEvent.KEY_PRESSED) {
  141.                KeyEvent ke = (KeyEvent) event;
  142.                switch (ke.getKeyChar()) {
  143.                case 'l': toggleCaptureEnable(); break;
  144.                case 'c': copyToClipbord(); break;
  145.                case 'g': changeGridMode(); break;
  146.                case 'f': capture(); break;
  147.                default: pickUp();
  148.                }
  149.             }
  150.             else if(event.getID() == MouseWheelEvent.MOUSE_WHEEL) {
  151.                 MouseWheelEvent mwe = (MouseWheelEvent) event;
  152.                 slider.setValue(slider.getValue() +
  153.                         mwe.getWheelRotation());
  154.             }
  155.          }
  156.  
  157.       }, AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK);
  158.    }
  159.  
  160.    private void initTimer() {
  161.       Timer timer = new Timer(true);
  162.       timer.schedule(new TimerTask() {
  163.          @Override
  164.          public void run() {
  165.             if (captureEnable)
  166.                capture();
  167.          }
  168.       }, 500, 100);
  169.    }
  170.  
  171.    public String pickUp() {
  172.       Point location = MouseInfo.getPointerInfo().getLocation();
  173.  
  174.       Color color = robot.getPixelColor(location.x, location.y);
  175.  
  176.       String hexColor = toHexString(color);
  177.       text.setText(hexColor);
  178.       Border border = BorderFactory
  179.             .createCompoundBorder(BorderFactory.createEtchedBorder(),
  180.                   BorderFactory.createLineBorder(color, 3));
  181.  
  182.       text.setBorder(border);
  183.  
  184.       return hexColor;
  185.    }
  186.  
  187.    protected void toggleCaptureEnable() {
  188.       captureEnable = !captureEnable;
  189.  
  190.       text.setText((this.captureEnable ? "The screen capture was begun."
  191.             : "The screen capture was locked."));
  192.    }
  193.  
  194.    protected void changeGridMode() {
  195.       GridMode[] modes = GridMode.values();
  196.       gridMode = modes[(gridMode.ordinal()+1)%modes.length];
  197.  
  198.       text.setText("GridMode is chaged.");
  199.    }
  200.  
  201.    enum GridMode {   GUIDE_LINE, GRID, NONE };
  202.    
  203.    static class ExpandImageIcon extends ImageIcon {
  204.       private static final Color guidLineColor = new Color(0, 0, 255, 128);
  205.       private static final Color gridColor = new Color(128, 128, 128, 128);
  206.       private final double scale;
  207.       private final GridMode gridMode;
  208.  
  209.       public ExpandImageIcon(Image image, double scale, GridMode gridMode) {
  210.          super(image);
  211.          this.scale = scale;
  212.          this.gridMode = gridMode;
  213.       }
  214.  
  215.       @Override
  216.       public int getIconHeight() {
  217.          return (int) (super.getIconHeight() * scale);
  218.       }
  219.  
  220.       @Override
  221.       public int getIconWidth() {
  222.          return (int) (super.getIconWidth() * scale);
  223.       }
  224.  
  225.       private void paintGuideLine(Graphics g, int x, int y, Image image) {
  226.          g.setColor(guidLineColor);
  227.          int w = image.getWidth(getImageObserver());
  228.          int h = image.getHeight(getImageObserver());
  229.          if (w < 0 || h < 0) {
  230.             return;
  231.          }
  232.          int xc = x + (int) (scale * (w / 2));
  233.          int yc = y + (int) (scale * (h / 2));
  234.          
  235.          g.drawLine(xc, y, xc, y + getIconHeight());
  236.          g.drawLine(x, yc, x + getIconWidth(), yc);
  237.          
  238.          int xcPlus1 = xc + (int) scale;
  239.          int ycPlus1 = yc + (int) scale;
  240.          g.drawLine(xcPlus1, y, xcPlus1, y + getIconHeight());
  241.          g.drawLine(x, ycPlus1, x + getIconWidth(), ycPlus1);
  242.       }
  243.  
  244.       @Override
  245.       public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
  246.          Image image = getImage();
  247.          g.drawImage(image, x, y, getIconWidth(), getIconHeight(), c);
  248.          switch (gridMode) {
  249.          case GUIDE_LINE : paintGuideLine(g, x, y, image); break;
  250.          case GRID : paintGridLine(g, x, y); break;
  251.          case NONE: break;
  252.          default : throw new AssertionError();
  253.          }
  254.       }
  255.  
  256.       private void paintGridLine(Graphics g, int x, int y) {
  257.          g.setColor(gridColor);
  258.          for (int xi = 0; xi * scale < x + getIconWidth(); xi++) {
  259.             g.drawLine(x + (int)(xi*scale), y,
  260.                      x + (int)(xi*scale), y + getIconHeight());
  261.          }
  262.          for (int yi = 0; yi * scale < y + getIconHeight(); yi++) {
  263.             g.drawLine(x, y + (int)(yi*scale),
  264.                      x + getIconWidth(), y + (int)(yi*scale));
  265.          }
  266.       }
  267.    }
  268.  
  269.    static class ImageAndTextSelection implements Transferable, ClipboardOwner {
  270.       private String hexColor;
  271.       private Image image;
  272.  
  273.       public ImageAndTextSelection(Image image, String text) {
  274.          this.image = image;
  275.          this.hexColor = text;
  276.       }
  277.  
  278.       public Object getTransferData(DataFlavor flavor)
  279.             throws UnsupportedFlavorException {
  280.          if (DataFlavor.imageFlavor.equals(flavor)) {
  281.             return image;
  282.          } else if (DataFlavor.stringFlavor.equals(flavor)) {
  283.             return hexColor;
  284.          }
  285.          throw new UnsupportedFlavorException(flavor);
  286.       }
  287.  
  288.       public DataFlavor[] getTransferDataFlavors() {
  289.          return new DataFlavor[] { DataFlavor.imageFlavor,
  290.                DataFlavor.stringFlavor };
  291.       }
  292.  
  293.       public boolean isDataFlavorSupported(DataFlavor flavor) {
  294.          return DataFlavor.imageFlavor.equals(flavor)
  295.                || DataFlavor.stringFlavor.equals(flavor);
  296.       }
  297.  
  298.       public void lostOwnership(Clipboard clipboard, Transferable contents) {
  299.          image = null;
  300.       }
  301.    }
  302. }
topへ