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