1 package com.android.hierarchyviewer.ui;
2 
3 import com.android.ddmlib.IDevice;
4 import com.android.ddmlib.RawImage;
5 import com.android.hierarchyviewer.scene.ViewNode;
6 import com.android.hierarchyviewer.ui.util.IconLoader;
7 import com.android.hierarchyviewer.ui.util.PngFileFilter;
8 import com.android.hierarchyviewer.util.WorkerThread;
9 
10 import java.awt.AlphaComposite;
11 import java.awt.BorderLayout;
12 import java.awt.Color;
13 import java.awt.Dimension;
14 import java.awt.FlowLayout;
15 import java.awt.Graphics;
16 import java.awt.Graphics2D;
17 import java.awt.GridBagConstraints;
18 import java.awt.GridBagLayout;
19 import java.awt.Insets;
20 import java.awt.Point;
21 import java.awt.Rectangle;
22 import java.awt.RenderingHints;
23 import java.awt.event.ActionEvent;
24 import java.awt.event.ActionListener;
25 import java.awt.event.MouseAdapter;
26 import java.awt.event.MouseEvent;
27 import java.awt.event.MouseMotionAdapter;
28 import java.awt.event.MouseWheelEvent;
29 import java.awt.event.MouseWheelListener;
30 import java.awt.image.BufferedImage;
31 import java.io.File;
32 import java.io.IOException;
33 import java.util.concurrent.ExecutionException;
34 
35 import javax.imageio.ImageIO;
36 import javax.swing.BorderFactory;
37 import javax.swing.Box;
38 import javax.swing.JButton;
39 import javax.swing.JCheckBox;
40 import javax.swing.JComponent;
41 import javax.swing.JFileChooser;
42 import javax.swing.JLabel;
43 import javax.swing.JPanel;
44 import javax.swing.JScrollPane;
45 import javax.swing.JSlider;
46 import javax.swing.SwingUtilities;
47 import javax.swing.SwingWorker;
48 import javax.swing.Timer;
49 import javax.swing.event.ChangeEvent;
50 import javax.swing.event.ChangeListener;
51 
52 class ScreenViewer extends JPanel implements ActionListener {
53     private final Workspace workspace;
54     private final IDevice device;
55 
56     private GetScreenshotTask task;
57     private BufferedImage image;
58     private int[] scanline;
59     private volatile boolean isLoading;
60 
61     private BufferedImage overlay;
62     private AlphaComposite overlayAlpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f);
63 
64     private ScreenViewer.LoupeStatus status;
65     private ScreenViewer.LoupeViewer loupe;
66     private ScreenViewer.Crosshair crosshair;
67 
68     private int zoom = 8;
69     private int y = 0;
70 
71     private Timer timer;
72     private ViewNode node;
73 
74     private JSlider zoomSlider;
75 
ScreenViewer(Workspace workspace, IDevice device, int spacing)76     ScreenViewer(Workspace workspace, IDevice device, int spacing) {
77         setLayout(new GridBagLayout());
78         setOpaque(false);
79 
80         this.workspace = workspace;
81         this.device = device;
82 
83         timer = new Timer(5000, this);
84         timer.setInitialDelay(0);
85         timer.setRepeats(true);
86 
87         JPanel panel = buildViewerAndControls();
88         add(panel, new GridBagConstraints(0, 0, 1, 1, 0.3f, 1.0f,
89                 GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH,
90                 new Insets(0, 0, 0, 0), 0, 0));
91 
92         JPanel loupePanel = buildLoupePanel(spacing);
93         add(loupePanel, new GridBagConstraints(1, 0, 1, 1, 0.7f, 1.0f,
94                 GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH,
95                 new Insets(0, 0, 0, 0), 0, 0));
96 
97         SwingUtilities.invokeLater(new Runnable() {
98             public void run() {
99                 timer.start();
100             }
101         });
102     }
103 
buildLoupePanel(int spacing)104     private JPanel buildLoupePanel(int spacing) {
105         loupe = new LoupeViewer();
106         loupe.addMouseWheelListener(new WheelZoomListener());
107         CrosshairPanel crosshairPanel = new CrosshairPanel(loupe);
108 
109         JPanel loupePanel = new JPanel(new BorderLayout());
110         loupePanel.add(crosshairPanel);
111         status = new LoupeStatus();
112         loupePanel.add(status, BorderLayout.SOUTH);
113 
114         loupePanel.setBorder(BorderFactory.createEmptyBorder(0, spacing, 0, 0));
115         return loupePanel;
116     }
117 
118     private class WheelZoomListener implements MouseWheelListener {
mouseWheelMoved(MouseWheelEvent e)119         public void mouseWheelMoved(MouseWheelEvent e) {
120             if (zoomSlider != null) {
121                 int val = zoomSlider.getValue();
122                 val -= e.getWheelRotation() * 2;
123                 zoomSlider.setValue(val);
124             }
125         }
126     }
127 
buildViewerAndControls()128     private JPanel buildViewerAndControls() {
129         JPanel panel = new JPanel(new GridBagLayout());
130         crosshair = new Crosshair(new ScreenshotViewer());
131         crosshair.addMouseWheelListener(new WheelZoomListener());
132         JScrollPane scroller = new JScrollPane(crosshair);
133         scroller.setPreferredSize(new Dimension(320, 480));
134         scroller.setBorder(null);
135         panel.add(scroller,
136                 new GridBagConstraints(0, y++, 2, 1, 1.0f, 1.0f,
137                     GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH,
138                     new Insets(0, 0, 0, 0), 0, 0));
139         buildSlider(panel, "Overlay:", "0%", "100%", 0, 100, 30, 1).addChangeListener(
140                 new ChangeListener() {
141                     public void stateChanged(ChangeEvent event) {
142                         float opacity = ((JSlider) event.getSource()).getValue() / 100.0f;
143                         overlayAlpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity);
144                         repaint();
145                     }
146         });
147         buildOverlayExtraControls(panel);
148         buildSlider(panel, "Refresh Rate:", "1s", "40s", 1, 40, 5, 1).addChangeListener(
149                 new ChangeListener() {
150                     public void stateChanged(ChangeEvent event) {
151                         int rate = ((JSlider) event.getSource()).getValue() * 1000;
152                         timer.setDelay(rate);
153                         timer.setInitialDelay(0);
154                         timer.restart();
155                     }
156         });
157         zoomSlider = buildSlider(panel, "Zoom:", "2x", "24x", 2, 24, 8, 2);
158         zoomSlider.addChangeListener(
159                 new ChangeListener() {
160                     public void stateChanged(ChangeEvent event) {
161                         zoom = ((JSlider) event.getSource()).getValue();
162                         loupe.clearGrid = true;
163                         loupe.moveToPoint(crosshair.crosshair.x, crosshair.crosshair.y);
164                         repaint();
165                     }
166         });
167         panel.add(Box.createVerticalGlue(),
168                 new GridBagConstraints(0, y++, 2, 1, 1.0f, 1.0f,
169                     GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE,
170                     new Insets(0, 0, 0, 0), 0, 0));
171         return panel;
172     }
173 
buildOverlayExtraControls(JPanel panel)174     private void buildOverlayExtraControls(JPanel panel) {
175         JPanel extras = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
176 
177         JButton loadOverlay = new JButton("Load...");
178         loadOverlay.addActionListener(new ActionListener() {
179             public void actionPerformed(ActionEvent event) {
180                 SwingWorker<?, ?> worker = openOverlay();
181                 if (worker != null) {
182                     worker.execute();
183                 }
184             }
185         });
186         extras.add(loadOverlay);
187 
188         JCheckBox showInLoupe = new JCheckBox("Show in Loupe");
189         showInLoupe.setSelected(false);
190         showInLoupe.addActionListener(new ActionListener() {
191             public void actionPerformed(ActionEvent event) {
192                 loupe.showOverlay = ((JCheckBox) event.getSource()).isSelected();
193                 loupe.repaint();
194             }
195         });
196         extras.add(showInLoupe);
197 
198         panel.add(extras, new GridBagConstraints(1, y++, 1, 1, 1.0f, 0.0f,
199                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
200                         new Insets(0, 0, 0, 0), 0, 0));
201     }
202 
openOverlay()203     public SwingWorker<?, ?> openOverlay() {
204         JFileChooser chooser = new JFileChooser();
205         chooser.setFileFilter(new PngFileFilter());
206         int choice = chooser.showOpenDialog(this);
207         if (choice == JFileChooser.APPROVE_OPTION) {
208             return new OpenOverlayTask(chooser.getSelectedFile());
209         } else {
210             return null;
211         }
212     }
213 
buildSlider(JPanel panel, String title, String minName, String maxName, int min, int max, int value, int tick)214     private JSlider buildSlider(JPanel panel, String title, String minName, String maxName,
215             int min, int max, int value, int tick) {
216         panel.add(new JLabel(title), new GridBagConstraints(0, y, 1, 1, 1.0f, 0.0f,
217                     GridBagConstraints.LINE_END, GridBagConstraints.NONE,
218                     new Insets(0, 0, 0, 6), 0, 0));
219         JPanel sliderPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
220         sliderPanel.add(new JLabel(minName));
221         JSlider slider = new JSlider(min, max, value);
222         slider.setMinorTickSpacing(tick);
223         slider.setMajorTickSpacing(tick);
224         slider.setSnapToTicks(true);
225         sliderPanel.add(slider);
226         sliderPanel.add(new JLabel(maxName));
227         panel.add(sliderPanel, new GridBagConstraints(1, y++, 1, 1, 1.0f, 0.0f,
228                     GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE,
229                         new Insets(0, 0, 0, 0), 0, 0));
230         return slider;
231     }
232 
stop()233     void stop() {
234         timer.stop();
235     }
236 
start()237     void start() {
238         timer.start();
239     }
240 
select(ViewNode node)241     void select(ViewNode node) {
242         this.node = node;
243         repaint();
244     }
245 
246     class LoupeViewer extends JComponent {
247         private final Color lineColor = new Color(1.0f, 1.0f, 1.0f, 0.3f);
248 
249         private int width;
250         private int height;
251         private BufferedImage grid;
252         private int left;
253         private int top;
254         public boolean clearGrid;
255 
256         private final Rectangle clip = new Rectangle();
257         private boolean showOverlay = false;
258 
LoupeViewer()259         LoupeViewer() {
260             addMouseListener(new MouseAdapter() {
261                 @Override
262                 public void mousePressed(MouseEvent event) {
263                     moveToPoint(event);
264                 }
265             });
266             addMouseMotionListener(new MouseMotionAdapter() {
267                 @Override
268                 public void mouseDragged(MouseEvent event) {
269                     moveToPoint(event);
270                 }
271             });
272         }
273 
274         @Override
paintComponent(Graphics g)275         protected void paintComponent(Graphics g) {
276             if (isLoading) {
277                 return;
278             }
279 
280             g.translate(-left, -top);
281 
282             if (image != null) {
283                 Graphics2D g2 = (Graphics2D) g.create();
284                 g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
285                         RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
286                 g2.scale(zoom, zoom);
287                 g2.drawImage(image, 0, 0, null);
288                 if (overlay != null && showOverlay) {
289                     g2.setComposite(overlayAlpha);
290                     g2.drawImage(overlay, 0, image.getHeight() - overlay.getHeight(), null);
291                 }
292                 g2.dispose();
293             }
294 
295             int width = getWidth();
296             int height = getHeight();
297 
298             Graphics2D g2 = null;
299             if (width != this.width || height != this.height) {
300                 this.width = width;
301                 this.height = height;
302 
303                 grid = new BufferedImage(width + zoom + 1, height + zoom + 1,
304                         BufferedImage.TYPE_INT_ARGB);
305                 clearGrid = true;
306                 g2 = grid.createGraphics();
307             } else if (clearGrid) {
308                 g2 = grid.createGraphics();
309                 g2.setComposite(AlphaComposite.Clear);
310                 g2.fillRect(0, 0, grid.getWidth(), grid.getHeight());
311                 g2.setComposite(AlphaComposite.SrcOver);
312             }
313 
314             if (clearGrid) {
315                 clearGrid = false;
316 
317                 g2.setColor(lineColor);
318                 width += zoom;
319                 height += zoom;
320 
321                 for (int x = zoom; x <= width; x += zoom) {
322                     g2.drawLine(x, 0, x, height);
323                 }
324 
325                 for (int y = 0; y <= height; y += zoom) {
326                     g2.drawLine(0, y, width, y);
327                 }
328 
329                 g2.dispose();
330             }
331 
332             if (image != null) {
333                 g.getClipBounds(clip);
334                 g.clipRect(0, 0, image.getWidth() * zoom + 1, image.getHeight() * zoom + 1);
335                 g.drawImage(grid, clip.x - clip.x % zoom, clip.y - clip.y % zoom, null);
336             }
337 
338             g.translate(left, top);
339         }
340 
moveToPoint(MouseEvent event)341         void moveToPoint(MouseEvent event) {
342             int x = Math.max(0, Math.min((event.getX() + left) / zoom, image.getWidth() - 1));
343             int y = Math.max(0, Math.min((event.getY() + top) / zoom, image.getHeight() - 1));
344             moveToPoint(x, y);
345             crosshair.moveToPoint(x, y);
346         }
347 
moveToPoint(int x, int y)348         void moveToPoint(int x, int y) {
349             left = x * zoom - width / 2 + zoom / 2;
350             top = y * zoom - height / 2 + zoom / 2;
351             repaint();
352         }
353     }
354 
355     class LoupeStatus extends JPanel {
356         private JLabel xLabel;
357         private JLabel yLabel;
358         private JLabel rLabel;
359         private JLabel gLabel;
360         private JLabel bLabel;
361         private JLabel hLabel;
362         private ScreenViewer.LoupeStatus.ColoredSquare square;
363         private Color color;
364 
LoupeStatus()365         LoupeStatus() {
366             setOpaque(true);
367             setLayout(new GridBagLayout());
368             setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
369 
370             square = new ColoredSquare();
371             add(square, new GridBagConstraints(0, 0, 1, 2, 0.0f, 0.0f,
372                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
373                     new Insets(0, 0, 0, 12), 0, 0 ));
374 
375             JLabel label;
376 
377             add(label = new JLabel("#ffffff"), new GridBagConstraints(0, 2, 1, 1, 0.0f, 0.0f,
378                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
379                     new Insets(0, 0, 0, 12), 0, 0 ));
380             label.setForeground(Color.WHITE);
381             hLabel = label;
382 
383             add(label = new JLabel("R:"), new GridBagConstraints(1, 0, 1, 1, 0.0f, 0.0f,
384                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
385                     new Insets(0, 6, 0, 6), 0, 0 ));
386             label.setForeground(Color.WHITE);
387             add(label = new JLabel("255"), new GridBagConstraints(2, 0, 1, 1, 0.0f, 0.0f,
388                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
389                     new Insets(0, 0, 0, 12), 0, 0 ));
390             label.setForeground(Color.WHITE);
391             rLabel = label;
392 
393             add(label = new JLabel("G:"), new GridBagConstraints(1, 1, 1, 1, 0.0f, 0.0f,
394                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
395                     new Insets(0, 6, 0, 6), 0, 0 ));
396             label.setForeground(Color.WHITE);
397             add(label = new JLabel("255"), new GridBagConstraints(2, 1, 1, 1, 0.0f, 0.0f,
398                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
399                     new Insets(0, 0, 0, 12), 0, 0 ));
400             label.setForeground(Color.WHITE);
401             gLabel = label;
402 
403             add(label = new JLabel("B:"), new GridBagConstraints(1, 2, 1, 1, 0.0f, 0.0f,
404                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
405                     new Insets(0, 6, 0, 6), 0, 0 ));
406             label.setForeground(Color.WHITE);
407             add(label = new JLabel("255"), new GridBagConstraints(2, 2, 1, 1, 0.0f, 0.0f,
408                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
409                     new Insets(0, 0, 0, 12), 0, 0 ));
410             label.setForeground(Color.WHITE);
411             bLabel = label;
412 
413             add(label = new JLabel("X:"), new GridBagConstraints(3, 0, 1, 1, 0.0f, 0.0f,
414                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
415                     new Insets(0, 6, 0, 6), 0, 0 ));
416             label.setForeground(Color.WHITE);
417             add(label = new JLabel("0 px"), new GridBagConstraints(4, 0, 1, 1, 0.0f, 0.0f,
418                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
419                     new Insets(0, 0, 0, 12), 0, 0 ));
420             label.setForeground(Color.WHITE);
421             xLabel = label;
422 
423             add(label = new JLabel("Y:"), new GridBagConstraints(3, 1, 1, 1, 0.0f, 0.0f,
424                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
425                     new Insets(0, 6, 0, 6), 0, 0 ));
426             label.setForeground(Color.WHITE);
427             add(label = new JLabel("0 px"), new GridBagConstraints(4, 1, 1, 1, 0.0f, 0.0f,
428                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
429                     new Insets(0, 0, 0, 12), 0, 0 ));
430             label.setForeground(Color.WHITE);
431             yLabel = label;
432 
433             add(Box.createHorizontalGlue(), new GridBagConstraints(5, 0, 1, 1, 1.0f, 0.0f,
434                     GridBagConstraints.LINE_START, GridBagConstraints.BOTH,
435                     new Insets(0, 0, 0, 0), 0, 0 ));
436         }
437 
438         @Override
paintComponent(Graphics g)439         protected void paintComponent(Graphics g) {
440             g.setColor(Color.BLACK);
441             g.fillRect(0, 0, getWidth(), getHeight());
442         }
443 
showPixel(int x, int y)444         void showPixel(int x, int y) {
445             xLabel.setText(x + " px");
446             yLabel.setText(y + " px");
447 
448             int pixel = image.getRGB(x, y);
449             color = new Color(pixel);
450             hLabel.setText("#" + Integer.toHexString(pixel));
451             rLabel.setText(String.valueOf((pixel >> 16) & 0xff));
452             gLabel.setText(String.valueOf((pixel >>  8) & 0xff));
453             bLabel.setText(String.valueOf((pixel      ) & 0xff));
454 
455             square.repaint();
456         }
457 
458         private class ColoredSquare extends JComponent {
459             @Override
getPreferredSize()460             public Dimension getPreferredSize() {
461                 Dimension d = super.getPreferredSize();
462                 d.width = 60;
463                 d.height = 30;
464                 return d;
465             }
466 
467             @Override
paintComponent(Graphics g)468             protected void paintComponent(Graphics g) {
469                 g.setColor(color);
470                 g.fillRect(0, 0, getWidth(), getHeight());
471 
472                 g.setColor(Color.WHITE);
473                 g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
474             }
475         }
476     }
477 
478     class Crosshair extends JPanel {
479         // magenta = 0xff5efe
480         private final Color crosshairColor = new Color(0x00ffff);
481         Point crosshair = new Point();
482         private int width;
483         private int height;
484         private final ScreenshotViewer screenshotViewer;
485 
Crosshair(ScreenshotViewer screenshotViewer)486         Crosshair(ScreenshotViewer screenshotViewer) {
487             this.screenshotViewer = screenshotViewer;
488             setOpaque(true);
489             setLayout(new BorderLayout());
490             add(screenshotViewer);
491             addMouseListener(new MouseAdapter() {
492                 @Override
493                 public void mousePressed(MouseEvent event) {
494                     moveToPoint(event);
495                 }
496             });
497             addMouseMotionListener(new MouseMotionAdapter() {
498                 @Override
499                 public void mouseDragged(MouseEvent event) {
500                     moveToPoint(event);
501                 }
502             });
503         }
504 
moveToPoint(int x, int y)505         void moveToPoint(int x, int y) {
506             crosshair.x = x;
507             crosshair.y = y;
508             status.showPixel(crosshair.x, crosshair.y);
509             repaint();
510         }
511 
moveToPoint(MouseEvent event)512         private void moveToPoint(MouseEvent event) {
513             crosshair.x = Math.max(0, Math.min(image.getWidth() - 1, event.getX()));
514             crosshair.y = Math.max(0, Math.min(image.getHeight() - 1, event.getY()));
515             loupe.moveToPoint(crosshair.x, crosshair.y);
516             status.showPixel(crosshair.x, crosshair.y);
517 
518             repaint();
519         }
520 
521         @Override
getPreferredSize()522         public Dimension getPreferredSize() {
523             return screenshotViewer.getPreferredSize();
524         }
525 
526         @Override
getMaximumSize()527         public Dimension getMaximumSize() {
528             return screenshotViewer.getPreferredSize();
529         }
530 
531         @Override
paint(Graphics g)532         public void paint(Graphics g) {
533             super.paint(g);
534 
535             if (crosshair == null || width != getWidth() || height != getHeight()) {
536                 width = getWidth();
537                 height = getHeight();
538                 crosshair = new Point(width / 2, height / 2);
539             }
540 
541             g.setColor(crosshairColor);
542 
543             g.drawLine(crosshair.x, 0, crosshair.x, height);
544             g.drawLine(0, crosshair.y, width, crosshair.y);
545         }
546 
547         @Override
paintComponent(Graphics g)548         protected void paintComponent(Graphics g) {
549             super.paintComponent(g);
550             g.setColor(Color.BLACK);
551             g.fillRect(0, 0, getWidth(), getHeight());
552         }
553     }
554 
555     class ScreenshotViewer extends JComponent {
556         private final Color boundsColor = new Color(0xff5efe);
557 
ScreenshotViewer()558         ScreenshotViewer() {
559             setOpaque(true);
560         }
561 
562         @Override
paintComponent(Graphics g)563         protected void paintComponent(Graphics g) {
564             g.setColor(Color.BLACK);
565             g.fillRect(0, 0, getWidth(), getHeight());
566 
567             if (isLoading) {
568                 return;
569             }
570 
571             if (image != null) {
572                 g.drawImage(image, 0, 0, null);
573                 if (overlay != null) {
574                     Graphics2D g2 = (Graphics2D) g.create();
575                     g2.setComposite(overlayAlpha);
576                     g2.drawImage(overlay, 0, image.getHeight() - overlay.getHeight(), null);
577                 }
578             }
579 
580             if (node != null) {
581                 Graphics s = g.create();
582                 s.setColor(boundsColor);
583                 ViewNode p = node.parent;
584                 while (p != null) {
585                     s.translate(p.left - p.scrollX, p.top - p.scrollY);
586                     p = p.parent;
587                 }
588                 s.drawRect(node.left, node.top, node.width - 1, node.height - 1);
589                 s.translate(node.left, node.top);
590 
591                 s.setXORMode(Color.WHITE);
592                 if ((node.paddingBottom | node.paddingLeft |
593                         node.paddingTop | node.paddingRight) != 0) {
594                     s.setColor(Color.BLACK);
595                     s.drawRect(node.paddingLeft, node.paddingTop,
596                             node.width - node.paddingRight - node.paddingLeft - 1,
597                             node.height - node.paddingBottom - node.paddingTop - 1);
598                 }
599                 if (node.hasMargins && (node.marginLeft | node.marginBottom |
600                         node.marginRight | node.marginRight) != 0) {
601                     s.setColor(Color.BLACK);
602                     s.drawRect(-node.marginLeft, -node.marginTop,
603                             node.marginLeft + node.width + node.marginRight - 1,
604                             node.marginTop + node.height + node.marginBottom - 1);
605                 }
606 
607                 s.dispose();
608             }
609         }
610 
611         @Override
getPreferredSize()612         public Dimension getPreferredSize() {
613             if (image == null) {
614                 return new Dimension(320, 480);
615             }
616             return new Dimension(image.getWidth(), image.getHeight());
617         }
618     }
619 
620     private class CrosshairPanel extends JPanel {
621         private final Color crosshairColor = new Color(0xff5efe);
622         private final Insets insets = new Insets(0, 0, 0, 0);
623 
CrosshairPanel(LoupeViewer loupe)624         CrosshairPanel(LoupeViewer loupe) {
625             setLayout(new BorderLayout());
626             add(loupe);
627         }
628 
629         @Override
paint(Graphics g)630         public void paint(Graphics g) {
631             super.paint(g);
632 
633             g.setColor(crosshairColor);
634 
635             int width = getWidth();
636             int height = getHeight();
637 
638             getInsets(insets);
639 
640             int x = (width - insets.left - insets.right) / 2;
641             int y = (height - insets.top - insets.bottom) / 2;
642 
643             g.drawLine(insets.left + x, insets.top, insets.left + x, height - insets.bottom);
644             g.drawLine(insets.left, insets.top + y, width - insets.right, insets.top + y);
645         }
646 
647         @Override
paintComponent(Graphics g)648         protected void paintComponent(Graphics g) {
649             g.setColor(Color.BLACK);
650             Insets insets = getInsets();
651             g.fillRect(insets.left, insets.top, getWidth() - insets.left - insets.right,
652                     getHeight() - insets.top - insets.bottom);
653         }
654     }
655 
actionPerformed(ActionEvent event)656     public void actionPerformed(ActionEvent event) {
657         if (task != null && !task.isDone()) {
658             return;
659         }
660         task = new GetScreenshotTask();
661         task.execute();
662     }
663 
664     private class GetScreenshotTask extends SwingWorker<Boolean, Void> {
GetScreenshotTask()665         private GetScreenshotTask() {
666             workspace.beginTask();
667         }
668 
669         @Override
670         @WorkerThread
doInBackground()671         protected Boolean doInBackground() throws Exception {
672             RawImage rawImage;
673             try {
674                 rawImage = device.getScreenshot();
675             } catch (IOException ioe) {
676                 return false;
677             }
678 
679             boolean resize = false;
680             isLoading = true;
681             try {
682                 if (rawImage != null) {
683                     if (image == null || rawImage.width != image.getWidth() ||
684                             rawImage.height != image.getHeight()) {
685                         image = new BufferedImage(rawImage.width, rawImage.height,
686                                 BufferedImage.TYPE_INT_ARGB);
687                         scanline = new int[rawImage.width];
688                         resize = true;
689                     }
690 
691                     switch (rawImage.bpp) {
692                         case 16:
693                             rawImage16toARGB(rawImage);
694                             break;
695                         case 32:
696                             rawImage32toARGB(rawImage);
697                             break;
698                     }
699                 }
700             } finally {
701                 isLoading = false;
702             }
703 
704             return resize;
705         }
706 
getMask(int length)707         private int getMask(int length) {
708             int res = 0;
709             for (int i = 0 ; i < length ; i++) {
710                 res = (res << 1) + 1;
711             }
712 
713             return res;
714         }
715 
rawImage32toARGB(RawImage rawImage)716         private void rawImage32toARGB(RawImage rawImage) {
717             byte[] buffer = rawImage.data;
718             int index = 0;
719 
720             final int redOffset = rawImage.red_offset;
721             final int redLength = rawImage.red_length;
722             final int redMask = getMask(redLength);
723             final int greenOffset = rawImage.green_offset;
724             final int greenLength = rawImage.green_length;
725             final int greenMask = getMask(greenLength);
726             final int blueOffset = rawImage.blue_offset;
727             final int blueLength = rawImage.blue_length;
728             final int blueMask = getMask(blueLength);
729             final int alphaLength = rawImage.alpha_length;
730             final int alphaOffset = rawImage.alpha_offset;
731             final int alphaMask = getMask(alphaLength);
732 
733             for (int y = 0 ; y < rawImage.height ; y++) {
734                 for (int x = 0 ; x < rawImage.width ; x++) {
735                     int value = buffer[index++] & 0x00FF;
736                     value |= (buffer[index++] & 0x00FF) << 8;
737                     value |= (buffer[index++] & 0x00FF) << 16;
738                     value |= (buffer[index++] & 0x00FF) << 24;
739 
740                     int r = ((value >>> redOffset) & redMask) << (8 - redLength);
741                     int g = ((value >>> greenOffset) & greenMask) << (8 - greenLength);
742                     int b = ((value >>> blueOffset) & blueMask) << (8 - blueLength);
743                     int a = 0xFF;
744 
745                     if (alphaLength != 0) {
746                         a = ((value >>> alphaOffset) & alphaMask) << (8 - alphaLength);
747                     }
748 
749                     scanline[x] = a << 24 | r << 16 | g << 8 | b;
750                 }
751 
752                 image.setRGB(0, y, rawImage.width, 1, scanline,
753                         0, rawImage.width);
754             }
755         }
756 
rawImage16toARGB(RawImage rawImage)757         private void rawImage16toARGB(RawImage rawImage) {
758             byte[] buffer = rawImage.data;
759             int index = 0;
760 
761             for (int y = 0 ; y < rawImage.height ; y++) {
762                 for (int x = 0 ; x < rawImage.width ; x++) {
763                     int value = buffer[index++] & 0x00FF;
764                     value |= (buffer[index++] << 8) & 0x0FF00;
765 
766                     int r = ((value >> 11) & 0x01F) << 3;
767                     int g = ((value >> 5) & 0x03F) << 2;
768                     int b = ((value     ) & 0x01F) << 3;
769 
770                     scanline[x] = 0xFF << 24 | r << 16 | g << 8 | b;
771                 }
772 
773                 image.setRGB(0, y, rawImage.width, 1, scanline,
774                         0, rawImage.width);
775             }
776         }
777 
778         @Override
done()779         protected void done() {
780             workspace.endTask();
781             try {
782                 if (get()) {
783                     validate();
784                     crosshair.crosshair = new Point(image.getWidth() / 2,
785                             image.getHeight() / 2);
786                     status.showPixel(image.getWidth() / 2, image.getHeight() / 2);
787                     loupe.moveToPoint(image.getWidth() / 2, image.getHeight() / 2);
788                 }
789             } catch (InterruptedException e) {
790                 e.printStackTrace();
791             } catch (ExecutionException e) {
792                 e.printStackTrace();
793             }
794             repaint();
795         }
796     }
797 
798     private class OpenOverlayTask extends SwingWorker<BufferedImage, Void> {
799         private File file;
800 
OpenOverlayTask(File file)801         private OpenOverlayTask(File file) {
802             this.file = file;
803             workspace.beginTask();
804         }
805 
806         @Override
807         @WorkerThread
doInBackground()808         protected BufferedImage doInBackground() {
809             try {
810                 return IconLoader.toCompatibleImage(ImageIO.read(file));
811             } catch (IOException ex) {
812                 ex.printStackTrace();
813             }
814             return null;
815         }
816 
817         @Override
done()818         protected void done() {
819             try {
820                 overlay = get();
821                 repaint();
822             } catch (InterruptedException e) {
823                 e.printStackTrace();
824             } catch (ExecutionException e) {
825                 e.printStackTrace();
826             } finally {
827                 workspace.endTask();
828             }
829         }
830     }
831 }
832