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;
63
64 private static ColorMode currentColorMode = ColorMode.RGB;
65
66 private Line2D crossHorizontal;
67 private Line2D crossVertical;
68
69 private int colorRecordMax = 5;
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
79 private JLabel colorRecordValue[] = new JLabel[colorRecordMax];
80
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
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();
339 }
340
341
342
343
344 public void paintComponent(final Graphics g) {
345
346 super.paintComponent(g);
347
348
349 final Graphics2D g2 = (Graphics2D) g;
350
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 }