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