View Javadoc
1   package edu.jiangxin.apktoolbox.convert.color;
2   
3   import edu.jiangxin.apktoolbox.swing.extend.EasyPanel;
4   import edu.jiangxin.apktoolbox.utils.Constants;
5   import org.apache.commons.lang3.StringUtils;
6   
7   import java.awt.*;
8   import java.awt.event.ActionEvent;
9   import java.awt.geom.Line2D;
10  import java.awt.geom.Rectangle2D;
11  
12  import javax.swing.*;
13  
14  import java.util.LinkedList;
15  import java.util.Timer;
16  import java.util.TimerTask;
17  
18  import java.awt.datatransfer.Clipboard;
19  import java.awt.datatransfer.Transferable;
20  import java.awt.datatransfer.StringSelection;
21  
22  public class ColorPickerPanel extends EasyPanel {
23  
24      enum ColorMode {
25          RGB, HTML, HEX, HSB
26      }
27  
28      private static final long serialVersionUID = 1L;
29  
30      private static final int TOP_PADDING = 20;
31  
32      private static final int BOTTOM_PADDING = 20;
33  
34      private static final int LEFT_PADDING = 10;
35  
36      private static final int RIGHT_PADDING = 10;
37  
38      private static final int TEXT_AREA_WIDTH = 200;
39  
40      private static final int DEFAULT_HEIGHT_SMALL = 20;
41  
42      private static final int DEFAULT_HEIGHT_BIG = 100;
43  
44      private static final int DEFAULT_WIDTH = 100;
45  
46      private static final int WIDTH = LEFT_PADDING + DEFAULT_WIDTH * 3 + Constants.DEFAULT_X_BORDER * 3 + TEXT_AREA_WIDTH + RIGHT_PADDING;
47  
48      private static final int HEIGHT = TOP_PADDING + DEFAULT_HEIGHT_SMALL * 3 + DEFAULT_HEIGHT_BIG + Constants.DEFAULT_Y_BORDER * 2 + BOTTOM_PADDING;
49  
50      private JPanel colorPanel;
51      private JLabel coordinateLabel;
52      private JLabel colorLabel;
53  
54      private Robot robot;
55      private Point mousePoint; // 光标点
56      private Image areaImage; // 待放大的图片
57  
58      private int zoomFactor = 2;
59  
60      private Point prevPoint = null; // 上一次的光标位置
61  
62      private Color currentColor; // 当前的颜色,按键记录才记录163, 184, 204
63  
64      private static ColorMode currentColorMode = ColorMode.RGB; // 当前颜色模式
65  
66      private Line2D crossHorizontal; // 交叉线
67      private Line2D crossVertical;
68  
69      private int colorRecordMax = 5; // 记录的color record个数
70      private LinkedList<Color> colorQueue = new LinkedList<>();
71  
72      private JTextField colorCopyTextField;
73  
74      // 边框
75      private final Rectangle2D colorRect = new Rectangle2D.Double();
76      private final Rectangle2D zoomRect = new Rectangle2D.Double();
77      private final Rectangle2D recordRect = new Rectangle2D.Double();
78      // 颜色记录JLabel数组
79      private JLabel colorRecordValue[] = new JLabel[colorRecordMax];
80      // hsb 数组
81      float[] hsbArr;
82  
83      private boolean isLocked = false;
84  
85      @Override
86      public void initUI() {
87          setLayout(null);
88  
89          initFirstColumn();
90  
91          initSecondColumn();
92  
93          initThirdColumn();
94  
95          initFourthColumn();
96  
97          final InputMap inputMap = colorPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
98          inputMap.put(KeyStroke.getKeyStroke("alt C"), "record.color");
99          inputMap.put(KeyStroke.getKeyStroke("alt L"), "lock.position");
100         inputMap.put(KeyStroke.getKeyStroke("alt U"), "unlock.position");
101 
102         final Action recordColorAction = new RecordColorAction(); // '记录'动作
103         final Action lockPositionAction = new LockPositionAction();
104         final Action unlockPositionAction = new UnlockPositionAction();
105 
106         final ActionMap actionMap = colorPanel.getActionMap();
107         actionMap.put("record.color", recordColorAction);
108         actionMap.put("lock.position", lockPositionAction);
109         actionMap.put("unlock.position", unlockPositionAction);
110 
111         mouseListener();
112     }
113 
114     private void initFirstColumn() {
115         // 左侧的颜色框和显示的坐标,颜色值
116         // 添加 panel 组件用来做颜色框
117         colorPanel = new JPanel();
118         colorPanel.setBounds(LEFT_PADDING, TOP_PADDING, DEFAULT_WIDTH, DEFAULT_HEIGHT_BIG);
119         add(colorPanel);
120 
121         coordinateLabel = new JLabel();
122         coordinateLabel.setBounds(LEFT_PADDING, TOP_PADDING + DEFAULT_HEIGHT_BIG + Constants.DEFAULT_Y_BORDER, DEFAULT_WIDTH, DEFAULT_HEIGHT_SMALL);
123         add(coordinateLabel);
124 
125         colorLabel = new JLabel();
126         colorLabel.setBounds(LEFT_PADDING, TOP_PADDING + DEFAULT_HEIGHT_BIG + DEFAULT_HEIGHT_SMALL + Constants.DEFAULT_Y_BORDER * 2, DEFAULT_WIDTH, DEFAULT_HEIGHT_SMALL);
127         add(colorLabel);
128 
129         JComboBox<String> colorModeCombo = new JComboBox<>();
130         for (ColorMode t : ColorMode.values()) {
131             colorModeCombo.addItem(t.toString());
132         }
133 
134         colorModeCombo.addActionListener(event -> {
135             currentColorMode = ColorMode.valueOf((String) colorModeCombo.getSelectedItem());
136         });
137         colorModeCombo.setBounds(LEFT_PADDING, TOP_PADDING + DEFAULT_HEIGHT_BIG + DEFAULT_HEIGHT_SMALL * 2 + Constants.DEFAULT_Y_BORDER * 3, DEFAULT_WIDTH, DEFAULT_HEIGHT_SMALL);
138         add(colorModeCombo);
139     }
140 
141     private void initSecondColumn() {
142         crossHorizontal = new Line2D.Double(
143                 (double) LEFT_PADDING + DEFAULT_WIDTH + Constants.DEFAULT_X_BORDER,
144                 (double) TOP_PADDING + DEFAULT_HEIGHT_BIG / 2.0,
145                 (double) LEFT_PADDING + DEFAULT_WIDTH * 2 + Constants.DEFAULT_X_BORDER,
146                 (double) TOP_PADDING + DEFAULT_HEIGHT_BIG / 2.0);
147         crossVertical = new Line2D.Double(
148                 (double) LEFT_PADDING + DEFAULT_WIDTH + Constants.DEFAULT_X_BORDER + DEFAULT_WIDTH / 2.0,
149                 TOP_PADDING,
150                 (double) LEFT_PADDING + DEFAULT_WIDTH + Constants.DEFAULT_X_BORDER + DEFAULT_WIDTH / 2.0,
151                 (double) TOP_PADDING + DEFAULT_HEIGHT_BIG);
152 
153         colorCopyTextField = new JTextField();
154         colorCopyTextField.setEditable(false);
155         colorCopyTextField.setBounds(LEFT_PADDING + DEFAULT_WIDTH + Constants.DEFAULT_X_BORDER, TOP_PADDING + DEFAULT_HEIGHT_BIG + Constants.DEFAULT_Y_BORDER, DEFAULT_WIDTH, DEFAULT_HEIGHT_SMALL);
156         add(colorCopyTextField);
157 
158         JButton colorCopyButton = new JButton("Copy");
159         colorCopyButton.addActionListener(event -> {
160             Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
161             Transferable trans = new StringSelection(getColorText(currentColor));
162             clipboard.setContents(trans, null);
163         });
164         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);
165         add(colorCopyButton);
166     }
167 
168     private void initThirdColumn() {
169         for (int i = 0; i < colorRecordMax; i++) {
170             colorRecordValue[i] = new JLabel();
171             colorRecordValue[i].setOpaque(true); // 背景不透明
172             colorRecordValue[i].setBounds(LEFT_PADDING + DEFAULT_WIDTH * 2 + Constants.DEFAULT_X_BORDER * 2,
173                     TOP_PADDING + i * DEFAULT_HEIGHT_SMALL, DEFAULT_WIDTH, DEFAULT_HEIGHT_SMALL);
174             add(colorRecordValue[i]);
175         }
176         JComboBox<String> magnificationModeCombo = new JComboBox<>();
177         magnificationModeCombo.addItem("Zoom: 100%");
178         magnificationModeCombo.addItem("Zoom: 200%");
179         magnificationModeCombo.addItem("Zoom: 300%");
180         magnificationModeCombo.addItem("Zoom: 400%");
181         magnificationModeCombo.addItem("Zoom: 500%");
182         magnificationModeCombo.addItem("Zoom: 1200%");
183         magnificationModeCombo.addItem("Zoom: 2800%");
184         magnificationModeCombo.setSelectedItem("Zoom: 200%");
185         magnificationModeCombo.addActionListener(event -> {
186             String selectedItem = (String) magnificationModeCombo.getSelectedItem();
187             String tmp = StringUtils.substringBetween(selectedItem, "Zoom: ", "%");
188             zoomFactor = Integer.valueOf(tmp) / 100;
189             logger.info("zoomFactor: " + zoomFactor);
190         });
191         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);
192         add(magnificationModeCombo);
193     }
194 
195     private void initFourthColumn() {
196         JTextArea textArea = new JTextArea();
197         textArea.setBounds(LEFT_PADDING + DEFAULT_WIDTH * 3 + Constants.DEFAULT_X_BORDER * 3, TOP_PADDING, TEXT_AREA_WIDTH, DEFAULT_HEIGHT_BIG);
198         textArea.append("记录颜色: ALT+C" + System.getProperty("line.separator"));
199         textArea.append("锁定区域: ALT+L" + System.getProperty("line.separator"));
200         textArea.append("取消锁定区域: ALT+U" + System.getProperty("line.separator"));
201         add(textArea);
202     }
203 
204     class RecordColorAction extends AbstractAction {
205         private static final long serialVersionUID = 1L;
206 
207         @Override
208         public void actionPerformed(final ActionEvent event) {
209             if (robot == null) {
210                 logger.error("robot is null");
211                 return;
212             }
213             Color color = robot.getPixelColor(mousePoint.x, mousePoint.y);
214             currentColor = color;
215 
216             // 头进尾出,就可以满足绘制到顶部的记录是最新的
217             colorQueue.offerFirst(color);
218             if (colorQueue.size() > colorRecordMax) {
219                 colorQueue.pollLast(); // 删除尾
220             }
221 
222             // 显示颜色值到文本框
223             colorCopyTextField.setText(getColorText(color));
224             repaint();
225         }
226     }
227 
228     class LockPositionAction extends AbstractAction {
229         private static final long serialVersionUID = 1L;
230 
231         @Override
232         public void actionPerformed(final ActionEvent event) {
233             isLocked = true;
234         }
235     }
236 
237     class UnlockPositionAction extends AbstractAction {
238         private static final long serialVersionUID = 1L;
239 
240         @Override
241         public void actionPerformed(final ActionEvent event) {
242             isLocked = false;
243         }
244     }
245 
246     /**
247      * 重置窗口大小
248      */
249     public Dimension getPreferredSize() {
250         return new Dimension(WIDTH, HEIGHT);
251     }
252 
253     private void mouseListener() {
254         try {
255             robot = new Robot();
256         } catch (final AWTException e) {
257             logger.error("Create Rebot instance failed");
258         }
259 
260         final Timer timer = new Timer();
261         timer.schedule(new TimerTask() {
262             @Override
263             public void run() {
264                 mouseAction();
265             }
266         }, 100, 100);
267     }
268 
269     /**
270      * 鼠标事件
271      * 获取位置和颜色,并显示位置和颜色信息
272      */
273     private void mouseAction() {
274         // 获取光标位置之后,与上次比对,避免重复运行
275         PointerInfo pointerInfo = MouseInfo.getPointerInfo();
276         if (pointerInfo == null) {
277             logger.warn("pointerInfo is null");
278             return;
279         }
280         mousePoint = pointerInfo.getLocation();
281 
282         if (mousePoint.equals(prevPoint)) {
283             return;
284         } else {
285             prevPoint = mousePoint;
286         }
287         if (robot == null) {
288             logger.error("robot is null");
289             return;
290         }
291         final Color pixel = robot.getPixelColor(mousePoint.x, mousePoint.y);
292         colorPanel.setBackground(pixel);
293 
294         coordinateLabel.setText(String.format("[%d, %d]", mousePoint.x, mousePoint.y));
295         colorLabel.setText(getColorText(pixel));
296 
297         if (!isLocked) {
298             getMouseArea();
299         }
300     }
301 
302     private String getColorText(final Color color) {
303         if (color == null) {
304             return "";
305         }
306         String s = "";
307         switch (currentColorMode) {
308             case RGB:
309                 s = String.format("%d, %d, %d", color.getRed(), color.getGreen(), color.getBlue());
310                 break;
311             case HTML:
312                 s = String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue());
313                 break;
314             case HEX:
315                 s = String.format("0x%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue());
316                 break;
317             case HSB:
318                 hsbArr = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
319                 s = String.format("%3.0f%% %3.0f%% %3.0f%%", hsbArr[0] * 100, hsbArr[1] * 100, hsbArr[2] * 100);
320                 break;
321             default:
322                 break;
323         }
324         return s;
325 
326     }
327 
328     protected void getMouseArea() {
329 
330         final int x = mousePoint.x;
331         final int y = mousePoint.y;
332 
333         int length = 100 / zoomFactor;
334         final Rectangle r = new Rectangle(x - length / 2, y - length / 2, length, length);
335         if (robot != null) {
336             areaImage = robot.createScreenCapture(r);
337         }
338         repaint(); // 重绘,调用 paintComponent
339     }
340 
341     /**
342      * 绘制界面
343      */
344     public void paintComponent(final Graphics g) {
345         // 父类的paitComponent需要绘制其他默认的组件,比如左侧颜色框panel以及坐标和颜色值label。如果不执行,则绘制的区域会重叠
346         super.paintComponent(g);
347 
348         // 中间放大镜
349         final Graphics2D g2 = (Graphics2D) g;
350         //g2.drawImage(areaImage,10,300,null); // 原大小
351         g2.drawImage(areaImage, LEFT_PADDING + DEFAULT_WIDTH + Constants.DEFAULT_X_BORDER, TOP_PADDING, DEFAULT_WIDTH, DEFAULT_HEIGHT_BIG, null);
352         // 放大镜的十字线
353         g2.setPaint(Color.RED);
354         g2.draw(crossHorizontal);
355         g2.draw(crossVertical);
356 
357         // 右侧颜色历史记录
358         paintColorRecord(g2);
359 
360         // 绘制各组件的边框
361         paintBorder(g2);
362 
363         // 颜色值重新赋值,因为可能有模式的改变
364         colorCopyTextField.setText(getColorText(currentColor));
365 
366     }
367 
368     private void paintColorRecord(final Graphics2D g2) {
369         int i = 0;
370 
371         for (Color c : colorQueue) {
372             Color penC = new Color(255 - c.getRed(), 255 - c.getGreen(), 255 - c.getBlue()); // 反色
373             // 字体颜色设置
374             colorRecordValue[i].setForeground(penC);
375             colorRecordValue[i].setText(getColorText(c));
376 
377             colorRecordValue[i].setBackground(c);
378 
379             if (++i > colorRecordMax)
380                 break;
381         }
382     }
383 
384     private void paintBorder(Graphics2D g2) {
385         g2.setPaint(Color.BLACK);
386         colorRect.setFrameFromDiagonal(
387                 (double) LEFT_PADDING - 1,
388                 (double) TOP_PADDING - 1,
389                 (double) LEFT_PADDING + DEFAULT_WIDTH,
390                 (double) TOP_PADDING + DEFAULT_HEIGHT_BIG);
391         zoomRect.setFrameFromDiagonal(
392                 (double) LEFT_PADDING + DEFAULT_WIDTH + Constants.DEFAULT_X_BORDER - 1,
393                 (double) TOP_PADDING - 1,
394                 (double) LEFT_PADDING + DEFAULT_WIDTH * 2 + Constants.DEFAULT_X_BORDER,
395                 (double) TOP_PADDING + DEFAULT_HEIGHT_BIG);
396         recordRect.setFrameFromDiagonal(
397                 (double) LEFT_PADDING + DEFAULT_WIDTH * 2 + Constants.DEFAULT_X_BORDER * 2 - 1,
398                 (double) TOP_PADDING - 1,
399                 (double) LEFT_PADDING + DEFAULT_WIDTH * 3 + Constants.DEFAULT_X_BORDER * 2,
400                 (double) TOP_PADDING + DEFAULT_HEIGHT_BIG);
401         g2.draw(colorRect);
402         g2.draw(zoomRect);
403         g2.draw(recordRect);
404     }
405 }