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