ColorPickerPanel.java

package edu.jiangxin.apktoolbox.convert.color;

import edu.jiangxin.apktoolbox.swing.extend.EasyPanel;
import edu.jiangxin.apktoolbox.utils.Constants;
import org.apache.commons.lang3.StringUtils;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;

import javax.swing.*;

import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;

import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.StringSelection;

public class ColorPickerPanel extends EasyPanel {

    enum ColorMode {
        RGB, HTML, HEX, HSB
    }

    private static final long serialVersionUID = 1L;

    private static final int TOP_PADDING = 20;

    private static final int BOTTOM_PADDING = 20;

    private static final int LEFT_PADDING = 10;

    private static final int RIGHT_PADDING = 10;

    private static final int TEXT_AREA_WIDTH = 200;

    private static final int DEFAULT_HEIGHT_SMALL = 20;

    private static final int DEFAULT_HEIGHT_BIG = 100;

    private static final int DEFAULT_WIDTH = 100;

    private static final int WIDTH = LEFT_PADDING + DEFAULT_WIDTH * 3 + Constants.DEFAULT_X_BORDER * 3 + TEXT_AREA_WIDTH + RIGHT_PADDING;

    private static final int HEIGHT = TOP_PADDING + DEFAULT_HEIGHT_SMALL * 3 + DEFAULT_HEIGHT_BIG + Constants.DEFAULT_Y_BORDER * 2 + BOTTOM_PADDING;

    private JPanel colorPanel;
    private JLabel coordinateLabel;
    private JLabel colorLabel;

    private Robot robot;
    private Point mousePoint; // 光标点
    private Image areaImage; // 待放大的图片

    private int zoomFactor = 2;

    private Point prevPoint = null; // 上一次的光标位置

    private Color currentColor; // 当前的颜色,按键记录才记录163, 184, 204

    private static ColorMode currentColorMode = ColorMode.RGB; // 当前颜色模式

    private Line2D crossHorizontal; // 交叉线
    private Line2D crossVertical;

    private int colorRecordMax = 5; // 记录的color record个数
    private LinkedList<Color> colorQueue = new LinkedList<>();

    private JTextField colorCopyTextField;

    // 边框
    private final Rectangle2D colorRect = new Rectangle2D.Double();
    private final Rectangle2D zoomRect = new Rectangle2D.Double();
    private final Rectangle2D recordRect = new Rectangle2D.Double();
    // 颜色记录JLabel数组
    private JLabel colorRecordValue[] = new JLabel[colorRecordMax];
    // hsb 数组
    float[] hsbArr;

    private boolean isLocked = false;

    @Override
    public void initUI() {
        setLayout(null);

        initFirstColumn();

        initSecondColumn();

        initThirdColumn();

        initFourthColumn();

        final InputMap inputMap = colorPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
        inputMap.put(KeyStroke.getKeyStroke("alt C"), "record.color");
        inputMap.put(KeyStroke.getKeyStroke("alt L"), "lock.position");
        inputMap.put(KeyStroke.getKeyStroke("alt U"), "unlock.position");

        final Action recordColorAction = new RecordColorAction(); // '记录'动作
        final Action lockPositionAction = new LockPositionAction();
        final Action unlockPositionAction = new UnlockPositionAction();

        final ActionMap actionMap = colorPanel.getActionMap();
        actionMap.put("record.color", recordColorAction);
        actionMap.put("lock.position", lockPositionAction);
        actionMap.put("unlock.position", unlockPositionAction);

        mouseListener();
    }

    private void initFirstColumn() {
        // 左侧的颜色框和显示的坐标,颜色值
        // 添加 panel 组件用来做颜色框
        colorPanel = new JPanel();
        colorPanel.setBounds(LEFT_PADDING, TOP_PADDING, DEFAULT_WIDTH, DEFAULT_HEIGHT_BIG);
        add(colorPanel);

        coordinateLabel = new JLabel();
        coordinateLabel.setBounds(LEFT_PADDING, TOP_PADDING + DEFAULT_HEIGHT_BIG + Constants.DEFAULT_Y_BORDER, DEFAULT_WIDTH, DEFAULT_HEIGHT_SMALL);
        add(coordinateLabel);

        colorLabel = new JLabel();
        colorLabel.setBounds(LEFT_PADDING, TOP_PADDING + DEFAULT_HEIGHT_BIG + DEFAULT_HEIGHT_SMALL + Constants.DEFAULT_Y_BORDER * 2, DEFAULT_WIDTH, DEFAULT_HEIGHT_SMALL);
        add(colorLabel);

        JComboBox<String> colorModeCombo = new JComboBox<>();
        for (ColorMode t : ColorMode.values()) {
            colorModeCombo.addItem(t.toString());
        }

        colorModeCombo.addActionListener(event -> {
            currentColorMode = ColorMode.valueOf((String) colorModeCombo.getSelectedItem());
        });
        colorModeCombo.setBounds(LEFT_PADDING, TOP_PADDING + DEFAULT_HEIGHT_BIG + DEFAULT_HEIGHT_SMALL * 2 + Constants.DEFAULT_Y_BORDER * 3, DEFAULT_WIDTH, DEFAULT_HEIGHT_SMALL);
        add(colorModeCombo);
    }

    private void initSecondColumn() {
        crossHorizontal = new Line2D.Double(
                (double) LEFT_PADDING + DEFAULT_WIDTH + Constants.DEFAULT_X_BORDER,
                (double) TOP_PADDING + DEFAULT_HEIGHT_BIG / 2.0,
                (double) LEFT_PADDING + DEFAULT_WIDTH * 2 + Constants.DEFAULT_X_BORDER,
                (double) TOP_PADDING + DEFAULT_HEIGHT_BIG / 2.0);
        crossVertical = new Line2D.Double(
                (double) LEFT_PADDING + DEFAULT_WIDTH + Constants.DEFAULT_X_BORDER + DEFAULT_WIDTH / 2.0,
                TOP_PADDING,
                (double) LEFT_PADDING + DEFAULT_WIDTH + Constants.DEFAULT_X_BORDER + DEFAULT_WIDTH / 2.0,
                (double) TOP_PADDING + DEFAULT_HEIGHT_BIG);

        colorCopyTextField = new JTextField();
        colorCopyTextField.setEditable(false);
        colorCopyTextField.setBounds(LEFT_PADDING + DEFAULT_WIDTH + Constants.DEFAULT_X_BORDER, TOP_PADDING + DEFAULT_HEIGHT_BIG + Constants.DEFAULT_Y_BORDER, DEFAULT_WIDTH, DEFAULT_HEIGHT_SMALL);
        add(colorCopyTextField);

        JButton colorCopyButton = new JButton("Copy");
        colorCopyButton.addActionListener(event -> {
            Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
            Transferable trans = new StringSelection(getColorText(currentColor));
            clipboard.setContents(trans, null);
        });
        colorCopyButton.setBounds(LEFT_PADDING + DEFAULT_WIDTH + Constants.DEFAULT_X_BORDER, TOP_PADDING + DEFAULT_HEIGHT_BIG + Constants.DEFAULT_Y_BORDER * 2 + DEFAULT_HEIGHT_SMALL, DEFAULT_WIDTH, DEFAULT_HEIGHT_SMALL);
        add(colorCopyButton);
    }

    private void initThirdColumn() {
        for (int i = 0; i < colorRecordMax; i++) {
            colorRecordValue[i] = new JLabel();
            colorRecordValue[i].setOpaque(true); // 背景不透明
            colorRecordValue[i].setBounds(LEFT_PADDING + DEFAULT_WIDTH * 2 + Constants.DEFAULT_X_BORDER * 2,
                    TOP_PADDING + i * DEFAULT_HEIGHT_SMALL, DEFAULT_WIDTH, DEFAULT_HEIGHT_SMALL);
            add(colorRecordValue[i]);
        }
        JComboBox<String> magnificationModeCombo = new JComboBox<>();
        magnificationModeCombo.addItem("Zoom: 100%");
        magnificationModeCombo.addItem("Zoom: 200%");
        magnificationModeCombo.addItem("Zoom: 300%");
        magnificationModeCombo.addItem("Zoom: 400%");
        magnificationModeCombo.addItem("Zoom: 500%");
        magnificationModeCombo.addItem("Zoom: 1200%");
        magnificationModeCombo.addItem("Zoom: 2800%");
        magnificationModeCombo.setSelectedItem("Zoom: 200%");
        magnificationModeCombo.addActionListener(event -> {
            String selectedItem = (String) magnificationModeCombo.getSelectedItem();
            String tmp = StringUtils.substringBetween(selectedItem, "Zoom: ", "%");
            zoomFactor = Integer.valueOf(tmp) / 100;
            logger.info("zoomFactor: " + zoomFactor);
        });
        magnificationModeCombo.setBounds(LEFT_PADDING + Constants.DEFAULT_X_BORDER * 2 + DEFAULT_WIDTH * 2, TOP_PADDING + DEFAULT_HEIGHT_BIG + Constants.DEFAULT_Y_BORDER, DEFAULT_WIDTH, DEFAULT_HEIGHT_SMALL);
        add(magnificationModeCombo);
    }

    private void initFourthColumn() {
        JTextArea textArea = new JTextArea();
        textArea.setBounds(LEFT_PADDING + DEFAULT_WIDTH * 3 + Constants.DEFAULT_X_BORDER * 3, TOP_PADDING, TEXT_AREA_WIDTH, DEFAULT_HEIGHT_BIG);
        textArea.append("记录颜色: ALT+C" + System.getProperty("line.separator"));
        textArea.append("锁定区域: ALT+L" + System.getProperty("line.separator"));
        textArea.append("取消锁定区域: ALT+U" + System.getProperty("line.separator"));
        add(textArea);
    }

    class RecordColorAction extends AbstractAction {
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(final ActionEvent event) {
            if (robot == null) {
                logger.error("robot is null");
                return;
            }
            Color color = robot.getPixelColor(mousePoint.x, mousePoint.y);
            currentColor = color;

            // 头进尾出,就可以满足绘制到顶部的记录是最新的
            colorQueue.offerFirst(color);
            if (colorQueue.size() > colorRecordMax) {
                colorQueue.pollLast(); // 删除尾
            }

            // 显示颜色值到文本框
            colorCopyTextField.setText(getColorText(color));
            repaint();
        }
    }

    class LockPositionAction extends AbstractAction {
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(final ActionEvent event) {
            isLocked = true;
        }
    }

    class UnlockPositionAction extends AbstractAction {
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(final ActionEvent event) {
            isLocked = false;
        }
    }

    /**
     * 重置窗口大小
     */
    public Dimension getPreferredSize() {
        return new Dimension(WIDTH, HEIGHT);
    }

    private void mouseListener() {
        try {
            robot = new Robot();
        } catch (final AWTException e) {
            logger.error("Create Rebot instance failed");
        }

        final Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                mouseAction();
            }
        }, 100, 100);
    }

    /**
     * 鼠标事件
     * 获取位置和颜色,并显示位置和颜色信息
     */
    private void mouseAction() {
        // 获取光标位置之后,与上次比对,避免重复运行
        PointerInfo pointerInfo = MouseInfo.getPointerInfo();
        if (pointerInfo == null) {
            logger.warn("pointerInfo is null");
            return;
        }
        mousePoint = pointerInfo.getLocation();

        if (mousePoint.equals(prevPoint)) {
            return;
        } else {
            prevPoint = mousePoint;
        }
        if (robot == null) {
            logger.error("robot is null");
            return;
        }
        final Color pixel = robot.getPixelColor(mousePoint.x, mousePoint.y);
        colorPanel.setBackground(pixel);

        coordinateLabel.setText(String.format("[%d, %d]", mousePoint.x, mousePoint.y));
        colorLabel.setText(getColorText(pixel));

        if (!isLocked) {
            getMouseArea();
        }
    }

    private String getColorText(final Color color) {
        if (color == null) {
            return "";
        }
        String s = "";
        switch (currentColorMode) {
            case RGB:
                s = String.format("%d, %d, %d", color.getRed(), color.getGreen(), color.getBlue());
                break;
            case HTML:
                s = String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue());
                break;
            case HEX:
                s = String.format("0x%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue());
                break;
            case HSB:
                hsbArr = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
                s = String.format("%3.0f%% %3.0f%% %3.0f%%", hsbArr[0] * 100, hsbArr[1] * 100, hsbArr[2] * 100);
                break;
            default:
                break;
        }
        return s;

    }

    protected void getMouseArea() {

        final int x = mousePoint.x;
        final int y = mousePoint.y;

        int length = 100 / zoomFactor;
        final Rectangle r = new Rectangle(x - length / 2, y - length / 2, length, length);
        if (robot != null) {
            areaImage = robot.createScreenCapture(r);
        }
        repaint(); // 重绘,调用 paintComponent
    }

    /**
     * 绘制界面
     */
    public void paintComponent(final Graphics g) {
        // 父类的paitComponent需要绘制其他默认的组件,比如左侧颜色框panel以及坐标和颜色值label。如果不执行,则绘制的区域会重叠
        super.paintComponent(g);

        // 中间放大镜
        final Graphics2D g2 = (Graphics2D) g;
        //g2.drawImage(areaImage,10,300,null); // 原大小
        g2.drawImage(areaImage, LEFT_PADDING + DEFAULT_WIDTH + Constants.DEFAULT_X_BORDER, TOP_PADDING, DEFAULT_WIDTH, DEFAULT_HEIGHT_BIG, null);
        // 放大镜的十字线
        g2.setPaint(Color.RED);
        g2.draw(crossHorizontal);
        g2.draw(crossVertical);

        // 右侧颜色历史记录
        paintColorRecord(g2);

        // 绘制各组件的边框
        paintBorder(g2);

        // 颜色值重新赋值,因为可能有模式的改变
        colorCopyTextField.setText(getColorText(currentColor));

    }

    private void paintColorRecord(final Graphics2D g2) {
        int i = 0;

        for (Color c : colorQueue) {
            Color penC = new Color(255 - c.getRed(), 255 - c.getGreen(), 255 - c.getBlue()); // 反色
            // 字体颜色设置
            colorRecordValue[i].setForeground(penC);
            colorRecordValue[i].setText(getColorText(c));

            colorRecordValue[i].setBackground(c);

            if (++i > colorRecordMax)
                break;
        }
    }

    private void paintBorder(Graphics2D g2) {
        g2.setPaint(Color.BLACK);
        colorRect.setFrameFromDiagonal(
                (double) LEFT_PADDING - 1,
                (double) TOP_PADDING - 1,
                (double) LEFT_PADDING + DEFAULT_WIDTH,
                (double) TOP_PADDING + DEFAULT_HEIGHT_BIG);
        zoomRect.setFrameFromDiagonal(
                (double) LEFT_PADDING + DEFAULT_WIDTH + Constants.DEFAULT_X_BORDER - 1,
                (double) TOP_PADDING - 1,
                (double) LEFT_PADDING + DEFAULT_WIDTH * 2 + Constants.DEFAULT_X_BORDER,
                (double) TOP_PADDING + DEFAULT_HEIGHT_BIG);
        recordRect.setFrameFromDiagonal(
                (double) LEFT_PADDING + DEFAULT_WIDTH * 2 + Constants.DEFAULT_X_BORDER * 2 - 1,
                (double) TOP_PADDING - 1,
                (double) LEFT_PADDING + DEFAULT_WIDTH * 3 + Constants.DEFAULT_X_BORDER * 2,
                (double) TOP_PADDING + DEFAULT_HEIGHT_BIG);
        g2.draw(colorRect);
        g2.draw(zoomRect);
        g2.draw(recordRect);
    }
}