/* * Copyright (c) 2017 - sikulix.com - MIT license */ package com.sikulix.tool; import com.sikulix.api.*; import com.sikulix.api.Window; import com.sikulix.core.Content; import com.sikulix.core.Finder; import com.sikulix.core.SX; import com.sikulix.core.SXLog; import com.sikulix.devices.hotkey.HotkeyCallback; import com.sikulix.devices.hotkey.HotkeyEvent; import com.sikulix.devices.hotkey.HotkeyManager; import com.sikulix.util.FileChooser; import com.vladsch.flexmark.ast.Node; import com.vladsch.flexmark.html.HtmlRenderer; import com.vladsch.flexmark.parser.Parser; import com.vladsch.flexmark.util.options.MutableDataSet; import org.opencv.core.*; import org.opencv.core.Point; import org.opencv.imgproc.Imgproc; import javax.swing.*; import javax.swing.border.Border; import javax.swing.border.CompoundBorder; import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; import java.io.File; import java.util.ArrayList; import java.util.List; import static java.awt.event.KeyEvent.VK_ESCAPE; public class Tool { //<editor-fold desc="housekeeping"> private static final SXLog log = SX.getSXLog("SX.Tool"); //<editor-fold desc="fields"> JFrame intro = new JFrame(); private Picture base = null; private Element rect = null; boolean isImage = false; private List<Element> lastRects = new ArrayList<>(); private int maxRevert = 10; private Picture shot; private Mat mShot = null; private Mat mShotSaved = null; int shotDisplayedW = 0; int shotDisplayedH = 0; boolean dirty = false; private JFrame box = null; private Container pBox = null; private JLabel content = new JLabel() { @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g.create(); if (getActiveSide() == ALL) { drawSelection(g2d); } else { drawCrossHair(g2d); } } }; private JLabel status = new JLabel(); private int statusFontHeight = 16; private int statusLineWidht = 2; private int statusHeight = statusFontHeight + 4 + statusLineWidht; private int zoomStep = 10; private float wFactor = 0; private float hFactor = 0; final private int TOP = 0; final private int LEFT = 1; final private int BOTTOM = 2; final private int RIGHT = 3; final private int ALL = 4; final private int NONE = 5; final private int COUNTERCLOCKWISE = 6; final private int CLOCKWISE = 7; final private int OPPOSITE = 8; private int activeSide = NONE; private int activeSideSaved = TOP; private String[] activeSides = new String[]{"TOP", "LEFT", "BOTTOM", "RIGHT", "ALL", "NONE"}; private boolean running = false; private int borderThickness = 5; private int minWidthHeight = 5; private int whichSideMargin = 10; MouseEvent lastDrag = null; MouseEvent dragStart = null; MouseEvent dragCurrent = null; int dragButton = 1; public void setDragButton(int dragButton) { this.dragButton = dragButton; } String bundlePath = ""; String appName = ""; Window toolApp = null; int scrW = 0; int scrH = 0; int scrID = 0; public boolean isRunning() { return running; } //</editor-fold> public Tool() { //TODO self focus init //toolApp = new Window(); initBundlePath(); intro = new JFrame(); intro.addKeyListener(new KeyAdapter() { @Override public void keyTyped(KeyEvent e) { boolean shouldQuit = false; super.keyTyped(e); String sKey = "" + e.getKeyChar(); if (e.getKeyChar() == VK_ESCAPE) { shouldQuit = true; } else { if (!"pocarbihq".contains(sKey)) { log.trace("keyTyped: %s", sKey); return; } if ("p".equals("" + e.getKeyChar())) { log.trace("action: bundlePath"); actionBundlePath(intro); } else if ("o".equals("" + e.getKeyChar())) { log.trace("action: open"); actionOpen(intro); } else if ("c".equals("" + e.getKeyChar())) { log.trace("action: capture"); intro.setVisible(false); SX.pause(0.5); actionCapture(); } else if ("a".equals("" + e.getKeyChar())) { log.trace("action: focus"); actionFocus(intro); } else if ("b".equals("" + e.getKeyChar())) { if (Do.popAsk("now going to background:" + "\n- use ctrl-alt-2 to capture" + "\n- use ctrl-alt-1 for back to foreground", "SikulixTool::ToBackground", intro)) { actionToBackground(); } } else if ("h".equals("" + e.getKeyChar())) { log.trace("action: get help"); showInfo("toolIntroHelp.md", intro); } else if ("i".equals("" + e.getKeyChar())) { log.trace("action: get status"); showInfo(prepareInfo("toolIntroStatus", "bundle", bundlePath, "app", appName), intro); } else if ("r".equals("" + e.getKeyChar())) { log.trace("action: reset tool"); bundlePath = ""; SX.setOption("Tool.bundlePath", ""); initBundlePath(); appName = ""; } else if ("q".equals("" + e.getKeyChar())) { shouldQuit = true; } } if (shouldQuit) { log.trace("action: quit requested"); actionQuit(); } } }); intro.setUndecorated(true); intro.setAlwaysOnTop(true); intro.setResizable(false); if (!log.isGlobalLevel(log.TRACE)) { intro.setAlwaysOnTop(true); } Container introPane = intro.getContentPane(); introPane.setLayout(new BoxLayout(introPane, BoxLayout.Y_AXIS)); introPane.setBackground(Color.white); String aText = Content.extractResourceToString("/gui", "toolIntro.html"); JLabel introText = new JLabel(aText); introText.setFont(new Font(Font.MONOSPACED, Font.BOLD, 16)); introText.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createLineBorder(Color.magenta, 2), BorderFactory.createEmptyBorder(0, 10, 10, 10))); introPane.add(introText); intro.pack(); Dimension introSize = intro.getPreferredSize(); Element centered = new Element(introSize).getCentered(Do.onMain()); intro.setLocation(centered.x, centered.y); initBox(); intro.setVisible(true); } private String prepareInfo(String name, String... options) { String content = ""; if (SX.isSet(name)) { if (!name.endsWith(".md")) { name += ".md"; } String prefix = "/gui"; content = Content.extractResourceToString(prefix, name); if (SX.isNotSet(content)) { log.error("prepareInfo: no resource: %s/%s", prefix, name); content = ""; } } if (!content.isEmpty() && options.length > 1) { for (int i = 0; i < options.length; i = i + 2) { String key = options[i]; String val = options[i + 1]; val = val.isEmpty() ? "no value set" : val; log.trace("prepareInfo: %s = %s", key, val); if (val.contains("\\")) { val = val.replaceAll("\\\\", "/"); } content = content.replaceAll(String.format("`\\[%s\\]`", key), val); } } return "#" + content; } private void showInfo(String name, JFrame parent) { if (SX.isNotSet(name)) { return; } boolean isMarkUp = false; String aText = ""; if (!name.startsWith("#")) { if (name.endsWith(".md")) { isMarkUp = true; } else if (!name.endsWith(".html")) { name += ".html"; } String prefix = "/gui"; aText = Content.extractResourceToString(prefix, name); if (SX.isNotSet(aText)) { log.error("showInfo: no resource: %s/%s", prefix, name); return; } } else { isMarkUp = true; aText = name.substring(1); if (aText.isEmpty()) { return; } } String header = ""; Element onMain = Do.onMain(); int fHeight = (int) (onMain.h * 0.8); int fWidth = 600; if (isMarkUp) { MutableDataSet options = new MutableDataSet(); Parser parser = Parser.builder(options).build(); options.set(HtmlRenderer.SOFT_BREAK, "<br>\n"); HtmlRenderer renderer = HtmlRenderer.builder(options).build(); Node document = parser.parse(aText); int count = 4; int idx = 0; int max = 0; int maxmax = 90; int last = 0; while ((idx = aText.indexOf("\n", idx)) > -1) { int line = idx - last; if (line > maxmax) { count += ((line + 90) / 90) - 1; max = maxmax; } else if (line > max) { max = line; } idx++; count++; last = idx; } fHeight = Math.min(fHeight, count * 22); fWidth = Math.max(fWidth, max * 10); fWidth = Math.min(fWidth, 800); header = "<html><body style=\"font-family: %s; font-size:large;\""; aText = (renderer.render(document)).replaceAll("/<br>\\n", "<br>") + "<br>" + "<hr>type ESC to close"; } JFrame frame = new JFrame(); frame.setUndecorated(true); frame.setAlwaysOnTop(true); // Container introPane = frame.getContentPane(); // introPane.setLayout(new BoxLayout(introPane, BoxLayout.Y_AXIS)); // introPane.setBackground(Color.white); // JLabel text = new JLabel(aText); // text.setFont(new Font(Font.DIALOG, Font.PLAIN, 14)); // text.setBorder(BorderFactory.createCompoundBorder( // BorderFactory.createLineBorder(Color.magenta, 2), // BorderFactory.createEmptyBorder(0, 10, 10, 10))); // introPane.add(text); JTextPane jtp = new JTextPane(); jtp.setContentType("text/html"); jtp.setFont(new Font(Font.DIALOG, Font.PLAIN, 14)); if (!header.isEmpty()) { aText = String.format(header, jtp.getFont().getFamily()) + aText; } jtp.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createLineBorder(Color.magenta, 2), BorderFactory.createEmptyBorder(0, 10, 10, 10))); jtp.setText(aText); jtp.setEditable(false); jtp.setCaretPosition(0); JScrollPane jsp = new JScrollPane(jtp); jsp.setPreferredSize(new Dimension(fWidth, fHeight)); jtp.addKeyListener(new KeyAdapter() { @Override public void keyTyped(KeyEvent e) { if (e.getKeyChar() == VK_ESCAPE) { frame.dispose(); if (SX.isNotNull(parent)) { parent.setVisible(true); } } } }); frame.setLayout(new BorderLayout()); frame.add(jsp, BorderLayout.CENTER); frame.pack(); Dimension size = frame.getPreferredSize(); Element centered = new Element(size).getCentered(Do.onMain()); frame.setLocation(centered.x, centered.y); if (SX.isNotNull(parent)) { parent.setVisible(false); } frame.setVisible(true); } public Tool(boolean internalUse) { initBundlePath(); initBox(); internal = true; } private void initBundlePath() { if (bundlePath.isEmpty()) { bundlePath = SX.getOption("Tool.bundlePath"); if (bundlePath.isEmpty()) { bundlePath = Content.asFolder(SX.getSXSTORE(), "Images").getAbsolutePath(); } } log.trace("initBundlePath: %s", bundlePath); } private boolean internal = false; public static Picture capture() { Tool tool = new Tool(true); tool.actionCapture(); while (tool.isRunning()) { SX.pause(1); } return tool.shot; } public static Picture open(Object... args) { if (args.length > 0) { Tool tool = new Tool(true); tool.actionOpen(null, args[0]); if (tool.isValid()) { tool.show(); while (tool.isRunning()) { SX.pause(1); } return tool.shot; } } return new Picture(); } private boolean isValid() { return SX.isNotNull(base); } private void show() { //isImage = false; activeSide = ALL; updateStatus(); resetBox(); } private void initBox() { box = new JFrame(); box.setUndecorated(true); box.setResizable(false); box.setAlwaysOnTop(!log.isGlobalLevel(SXLog.TRACE)); //<editor-fold desc="*** Key Mouse handler"> box.addKeyListener(new KeyAdapter() { @Override public void keyTyped(KeyEvent e) { super.keyTyped(e); myKeyTyped(e); } @Override public void keyReleased(KeyEvent e) { super.keyReleased(e); myKeyReleased(e); } }); box.addMouseListener(new MouseAdapter() { @Override public void mouseReleased(MouseEvent e) { if (getActiveSide() == ALL && SX.isNotNull(dragStart)) { Element start = getPos(dragStart); Element end = getPos(dragCurrent); int w = Math.abs(end.x - start.x); int h = Math.abs(end.y - start.y); if (e.getButton() > 1) { start.at(start.x - w / 2, start.y - h / 2); end.at(start.x + w, start.y + h); } if (w > minWidthHeight && h > minWidthHeight) { pushRect(); int rx = rect.x; int ry = rect.y; rect.change(new Element(rx + Math.min(start.x, end.x), ry + Math.min(start.y, end.y), w, h)); } dragStart = null; dragCurrent = null; content.repaint(); if (masking) { mask = new Element(rect); rect = lastRects.remove(0); mask.x -= rect.x; mask.y -= rect.y; eventState(MASKDRAWN, 0, 0); } else { resizeToFrame(); dirty = true; } } lastDrag = null; dragButton = 1; } @Override public void mouseClicked(MouseEvent e) { super.mouseClicked(e); myMouseClicked(e); } @Override public void mouseEntered(MouseEvent e) { super.mouseEntered(e); myMouseEntered(e); } @Override public void mouseExited(MouseEvent e) { super.mouseExited(e); myMouseExited(e); } }); box.addMouseMotionListener(new MouseMotionAdapter() { @Override public void mouseDragged(MouseEvent e) { super.mouseDragged(e); setDragButton(e.getButton()); myMouseMoved(e); myMouseDragged(e); } @Override public void mouseMoved(MouseEvent e) { super.mouseMoved(e); myMouseMoved(e); } }); box.addMouseWheelListener(new MouseWheelListener() { @Override public void mouseWheelMoved(MouseWheelEvent e) { if (getActiveSide() != ALL) { if (e.getWheelRotation() < 0) { actionZoomIn(); } else { actionZoomOut(); } } } }); //</editor-fold> pBox = box.getContentPane(); pBox.setLayout(new BoxLayout(pBox, BoxLayout.Y_AXIS)); pBox.setBackground(Color.white); status.setOpaque(true); status.setBackground(Color.white); status.setFont(new Font(Font.MONOSPACED, Font.PLAIN, statusFontHeight)); status.setText(" , "); status.setBorder(BorderFactory.createMatteBorder(0, statusLineWidht, statusLineWidht, statusLineWidht, Color.black)); pBox.add(content); pBox.add(status); running = true; new Thread(new EventHandler()).start(); scrW = Do.onMain().w; scrH = Do.onMain().h; } private Element mousePos = new Element(); private String statusAction = ""; private void updateStatus() { updateStatus(null); } private void updateStatus(Element elem) { if (!SX.isNull(elem)) { mousePos = elem; } status.setText(String.format("%s%04d, %04d [%04d, %04d %04dx%04d] %s %s", dirty ? "*" : "", rect.x + mousePos.x, rect.y + mousePos.y, rect.x, rect.y, rect.w, rect.h, base.getName(), statusAction)); } //</editor-fold> //<editor-fold desc="mouse click/move/drag"> private void myMouseClicked(MouseEvent e) { String doubleClick = e.getClickCount() > 1 ? "Double" : ""; String rightClick = e.getButton() != MouseEvent.BUTTON1 ? "Right" : ""; log.trace("mouse%s%sClicked: %d,%d", rightClick, doubleClick, e.getX(), e.getY()); if (SX.isSet(doubleClick)) { if (SX.isNotSet(rightClick)) { eventState(CLICKDOUBLE, e.getX(), e.getY()); } } else if (SX.isSet(rightClick)) { eventState(CLICKRIGHT, e.getX(), e.getY()); } else { eventState(CLICKLEFT, e.getX(), e.getY()); } } int exited = -1; private void myMouseMoved(MouseEvent e) { Element elem = getPos(new Element(e.getX(), e.getY())); updateStatus(elem); } private void myMouseDragged(MouseEvent e) { if (getActiveSide() == ALL) { if (SX.isNull(lastDrag)) { dragStart = e; } dragCurrent = e; content.repaint(); } else { dragSelection(e); } lastDrag = e; } private void dragSelection(MouseEvent e) { int step = 1; if (SX.isNotNull(lastDrag) && exited < 0) { if (activeSide == NONE) { if (rect.w > halfCenteredWidth && rect.h > halfCenteredWidth) { step = 10; } Element newRect = null; if (e.getY() > lastDrag.getY()) { if (e.getX() > lastDrag.getX()) { newRect = new Element(rect.x - step, rect.y - step, rect.w, rect.h); } else if (e.getX() > lastDrag.getX()) { newRect = new Element(rect.x + step, rect.y - step, rect.w, rect.h); } else { newRect = new Element(rect.x, rect.y - step, rect.w, rect.h); } } else if (e.getY() < lastDrag.getY()) { if (e.getX() > lastDrag.getX()) { newRect = new Element(rect.x - step, rect.y + step, rect.w, rect.h); } else if (e.getX() > lastDrag.getX()) { newRect = new Element(rect.x + step, rect.y + step, rect.w, rect.h); } else { newRect = new Element(rect.x, rect.y + step, rect.w, rect.h); } } else { if (e.getX() > lastDrag.getX()) { newRect = new Element(rect.x - step, rect.y, rect.w, rect.h); } else { newRect = new Element(rect.x + step, rect.y, rect.w, rect.h); } } if (SX.isNotNull(newRect)) { log.info("action: move %s", getActiveSideText()); int newRectW = newRect.w; int newRectH = newRect.h; newRect.intersect(base); if (newRect.w == newRectW && newRect.h == newRectH) { rect.change(newRect); resizeToFrame(); } } } } } //</editor-fold> //<editor-fold desc="mouse enter/exit"> private void myMouseEntered(MouseEvent e) { // if (!isDragging() && activeSide != NONE && activeSide != ALL) { // int side = whichSide(e.getX() - borderThickness, e.getY() - borderThickness); // if (side == exited) { // log.trace("mouseEntered: %s", activeSides[side]); // content.setBorder(coloredSide(side)); // box.repaint(); // } // } exited = -1; } private void myMouseExited(MouseEvent e) { if (!isDragging()) { exited = whichSide(e.getX() - borderThickness, e.getY() - borderThickness); log.trace("mouseExited: %s ", activeSides[exited]); } else { actionZoomOut(); } } private int whichSide(int x, int y) { if (isMiddle(box.getWidth(), x) && isLow(box.getHeight(), y)) return TOP; if (isMiddle(box.getHeight(), y) && isLow(box.getWidth(), x)) return LEFT; if (isMiddle(box.getWidth(), x) && isHigh(box.getHeight(), y)) return BOTTOM; if (isMiddle(box.getHeight(), y) && isHigh(box.getWidth(), x)) return RIGHT; return TOP; } private boolean isLow(int range, int point) { return point < range / whichSideMargin; } private boolean isMiddle(int range, int point) { int lower = range / whichSideMargin; int upper = range - lower; return point > lower && point < upper; } private boolean isHigh(int range, int point) { return point > range - range / whichSideMargin; } //</editor-fold> //<editor-fold desc="eventHandler"> private static int CLICKLEFT = 0; private static int CLICKRIGHT = 1; private static int CLICKDOUBLE = 2; private static int MASKDRAWN = 3; private static int EVENTNOTSET = -1; int clickType = EVENTNOTSET; int clickedX = 0; int clickedY = 0; synchronized int[] eventState(int... args) { if (args.length == 0) { return new int[]{clickType, clickedX, clickedY}; } clickType = (int) args[0]; clickedX = (int) args[1]; clickedY = (int) args[2]; return null; } protected class EventHandler implements Runnable { @Override public void run() { Element element = null; log.trace("ClickHandler: started"); boolean shouldReset = false; while (isRunning()) { int[] event = eventState(); element = new Element(event[1], event[2]); if (event[0] == CLICKLEFT) { SX.pause(0.3); event = eventState(); element = new Element(event[1], event[2]); if (event[0] == CLICKLEFT) { if (evaluatingSegments) { log.trace("event: crop segment at (%d,%d)", element.x, element.y); } else { log.trace("event: crop to (%d,%d) from %s", element.x, element.y, getActiveSideText()); } crop(getPos(element)); shouldReset = true; } else if (event[0] == CLICKDOUBLE) { if (!evaluatingSegments) { log.trace("event: center at (%d,%d) %s", element.x, element.y, rect); center(element); } shouldReset = true; } } else if (event[0] == CLICKRIGHT) { log.trace("event: menu at (%d,%d)", element.x, element.y); shouldReset = true; } else if (event[0] == MASKDRAWN) { log.trace("event: mask %s", mask); actionMasking(); shouldReset = true; } if (shouldReset) { eventState(EVENTNOTSET, 0, 0); shouldReset = false; } } log.trace("ClickHandler: ended"); } } //</editor-fold> //<editor-fold desc="key typed"> private void myKeyTyped(KeyEvent e) { boolean shouldQuit = false; String allowed = " +-facrpostmnihqz"; // off e x if (e.CHAR_UNDEFINED != e.getKeyChar()) { if (e.getKeyChar() == VK_ESCAPE) { log.trace("action: quit request"); shouldQuit = true; } else { String sKey = "" + e.getKeyChar(); if (!allowed.contains(sKey)) { log.trace("keyTyped: %s", sKey); return; } if (!evaluatingSegments) { if (" ".equals("" + e.getKeyChar())) { if (masking) { maskingToggle = maskingToggle ? false : true; actionMasking(); } else { if (activeSide == NONE) { activeSide = activeSideSaved; } else { activeSideSaved = activeSide; activeSide = NONE; } content.setBorder(coloredSide(activeSide)); box.pack(); } } else if ("x".equals("" + e.getKeyChar())) { // X Masking (off) if (masking) { actionMaskingStop(); } else { actionMasking(); } } else if ("+".equals("" + e.getKeyChar())) { // + ZoomIn actionZoomIn(); log.trace("action: zoom-in to %s", rect); } else if ("-".equals("" + e.getKeyChar())) { // - ZoomOut actionZoomOut(); log.trace("action: zoom-out to %s", rect); } else if ("r".equals("" + e.getKeyChar())) { // r Reset (hidden) log.trace("action: reset"); actionReset(); } else if ("p".equals("" + e.getKeyChar())) { // p new bundlepath log.trace("action: bundlePath"); actionBundlePath(box); } else if ("o".equals("" + e.getKeyChar())) { // o open a picture file log.trace("action: open"); actionOpen(box); } else if ("s".equals("" + e.getKeyChar())) { // s save a picture file log.trace("action: save"); actionSave(); } else if ("c".equals("" + e.getKeyChar())) { // c new capture log.trace("action: capture"); actionCapture(); } else if ("f".equals("" + e.getKeyChar())) { // f find on screen log.trace("action: find: %s", rect); actionFind(); } else if ("a".equals("" + e.getKeyChar())) { // a findAll on screen log.trace("action: findAll: %s", rect); actionFindAll(); } else if ("t".equals("" + e.getKeyChar())) { // t set target log.trace("action: set target"); actionTarget(); } else if ("m".equals("" + e.getKeyChar())) { // m set similarity log.trace("action: set similarity"); actionSimilar(); } else if ("n".equals("" + e.getKeyChar())) { // n set name log.trace("action: change name"); actionName(); } else if ("i".equals("" + e.getKeyChar())) { // i get info log.trace("action: show info"); actionInfo(); } else if ("h".equals("" + e.getKeyChar())) { // h show help log.trace("action: show help"); actionHelp(); } else if ("z".equals("" + e.getKeyChar())) { // z revert last action log.trace("action: revert"); actionRevert(); } } else { if ("+".equals("" + e.getKeyChar()) && segmentState == segmentStateAll) { mShotSaved = mShot.clone(); mShot = actionSegmentsExpand(mShot); resizeToFrame(new Picture(mShot)); } else if ("-".equals("" + e.getKeyChar()) && segmentState == segmentStateAll) { if (SX.isNotNull(mShotSaved)) { mShot = mShotSaved.clone(); mShotSaved = null; resizeToFrame(new Picture(mShot)); } } else if (" ".equals("" + e.getKeyChar())) { if (segmentState == segmentStateAll) { crop(null); } else if (segmentState == segmentStateExternal && segments.size() == 1) { crop(new Element(segments.get(0).x + 1, segments.get(0).y + 1)); } } } if ("e".equals("" + e.getKeyChar())) { log.trace("action: segments"); actionSegments(); } else if ("q".equals("" + e.getKeyChar())) { log.trace("action: quit request"); shouldQuit = true; } } } if (shouldQuit) { if (evaluatingSegments) { evaluatingSegments = false; resizeToFrame(); } else { if (internal) { actionQuit(); } else { if (dirty) { if (!Do.popAsk("discard not saved image?", "SikuliX Tool::SaveImage", box)) { actionSave(); } dirty = false; } if (!inBackground) { if (SX.isNull(shot)) { box.setVisible(false); intro.setVisible(true); } else { resetBox(); shot = null; } } else { if (!Do.popAsk("still want to run in background?" + "\n- use ctrl-alt-2 to capture" + "\n- use ctrl-alt-1 for back to foreground", "SikulixTool::ToBackground", intro)) { inBackground = false; intro.setVisible(true); } } } } } } //</editor-fold> //<editor-fold desc="actions"> private boolean masking = false; private boolean maskingToggle = false; private Element mask; private boolean innerMask = true; private Mat mMask; private Picture shotMasked = null; private void actionMasking() { if (masking) { if (SX.isNotNull(mask)) { mMask = new Mat(shot.getContent().size(), CvType.CV_8UC1); if (!maskingToggle) { mMask.setTo(new Scalar(0)); Imgproc.fillPoly(mMask, elementToPoints(mask), new Scalar(255)); innerMask = true; } else { mMask.setTo(new Scalar(255)); Imgproc.fillPoly(mMask, elementToPoints(mask), new Scalar(0)); innerMask = false; } List<Mat> mBGRA = new ArrayList<>(); Core.split(shot.getContent(), mBGRA); mBGRA.add(mMask); Mat mResult = new Mat(mMask.size(), CvType.CV_8UC4); Core.merge(mBGRA, mResult); shotMasked = new Picture(mResult); resizeToFrame(shotMasked); } } else { masking = true; mask = null; activeSide = ALL; statusAction = "<-masking->"; updateStatus(); resizeToFrame(); log.trace("action: masking: started"); return; } } private void actionMaskingStop() { if (SX.isNotNull(mask)) { dirty = true; shotMasked.setName(""); imageToSave = shotMasked; actionSave(); } mask = null; mMask = null; masking = false; statusAction = ""; updateStatus(); resizeToFrame(); log.trace("action: masking: stopped"); } private List<MatOfPoint> elementToPoints(Element element) { List<Point> points = new ArrayList<>(); points.add(new Point(element.x, element.y)); points.add(new Point(element.x + element.w, element.y)); points.add(new Point(element.x + element.w, element.y + element.h)); points.add(new Point(element.x, element.y + element.h)); MatOfPoint mPoints = new MatOfPoint(); mPoints.fromList(points); List<MatOfPoint> finalPoints = new ArrayList<MatOfPoint>(); finalPoints.add(mPoints); return finalPoints; } String hotKeyCapture = ""; String hotKeyCaptureFinal = ""; String hotKeyCaptureDefault = "ctrl alt 2"; HotkeyCallback hotkeyCaptureListener = new HotkeyCallback() { @Override public void hotkeyPressed(HotkeyEvent e) { if (inBackground) { log.trace("Hotkey: %s (action capture)", e); actionCapture(); } } }; String hotkeyForeground = ""; String hotkeyForegroundFinal = ""; String hotkeyForegroundDefault = "ctrl alt 1"; HotkeyCallback hotkeyForegroundListener = new HotkeyCallback() { @Override public void hotkeyPressed(HotkeyEvent e) { if (inBackground) { log.trace("Hotkey: %s (back to foreground)", e); inBackground = false; intro.setVisible(true); } } }; private boolean inBackground = false; private HotkeyManager hotkeyManager = null; private void actionToBackground() { if (!inBackground && SX.isNotSet(hotkeyForegroundFinal)) { hotkeyManager = HotkeyManager.get(); hotkeyForeground = hotkeyForegroundDefault; hotkeyForegroundFinal = hotkeyManager.addHotkey(hotkeyForegroundListener, hotkeyForeground); if (SX.isSet(hotkeyForegroundFinal)) { hotKeyCapture = hotKeyCaptureDefault; hotKeyCaptureFinal = hotkeyManager.addHotkey(hotkeyCaptureListener, hotKeyCapture); if (SX.isSet(hotKeyCaptureFinal)) { inBackground = true; intro.setVisible(false); } } } else { inBackground = true; intro.setVisible(false); } } private void actionZoomOut() { int stepX, stepY; stepX = stepY = 1; if (rect.w > halfCenteredWidth && rect.h > halfCenteredWidth) { stepX = Math.max(1, rect.w / 10); stepY = Math.max(1, rect.h / 10); } rect.x -= stepX; rect.w += 2 * stepX; rect.y -= stepY; rect.h += 2 * stepY; dirty = true; resizeToFrame(); } private void actionZoomIn() { int stepX, stepY; stepX = stepY = 1; if (rect.w > halfCenteredWidth && rect.h > halfCenteredWidth) { stepX = Math.max(1, rect.w / 10); stepY = Math.max(1, rect.h / 10); } rect.x += stepX; rect.w -= 2 * stepX; rect.y += stepY; rect.h -= 2 * stepY; dirty = true; resizeToFrame(); } private void actionCapture() { boolean shouldSelfFocus = false; if (!appName.isEmpty()) { actionFocus(null, appName); shouldSelfFocus = true; } base = new Element(scrID).capture(); isImage = false; activeSide = ALL; resetBox(); dirty = true; if (shouldSelfFocus) { //TODO focus self //actionFocus(null, "java"); //toolApp.getAppName()); } } private boolean evaluatingSegments = false; private List<Element> segments = new ArrayList<>(); private List<MatOfPoint> contours = new ArrayList<>(); int segmentState = 0; int segmentStateDetail = 0; int segmentStateExternal = 1; int segmentStateAll = 2; private void actionSegments() { if (evaluatingSegments) { segmentState++; if (segmentState > segmentStateAll) { segments.clear(); } } else { activeSide = ALL; evaluatingSegments = true; segmentState = segmentStateDetail; contours = new ArrayList<>(); } if (segmentState == segmentStateDetail) { segments = Finder.getElements(shot); } else if (segmentState == segmentStateExternal) { segments = Finder.getElements(shot, true); } else if (segmentState == segmentStateAll) { if (segments.size() > 1) { int x = 99999; int y = 99999; int w = 0; int h = 0; for (Element segment : segments) { if (segment.x < x) { x = segment.x; } if (segment.y < y) { y = segment.y; } if (segment.x + segment.w > x + w) { w = segment.x + segment.w - x; } if (segment.y + segment.h > y + h) { h = segment.y + segment.h - y; } } segments.clear(); segments.add(new Element(x, y, w, h)); } else { contours = Finder.getElement(shot); mShot = shot.getContent().clone(); Imgproc.fillPoly(mShot, contours, oColorBlack); Imgproc.dilate(mShot, mShot, Element.getNewMat()); contours = Finder.getElement(new Picture(mShot)); resizeToFrame(new Picture(mShot)); return; } } if (segments.size() > 0) { Story segmented = new Story(shot, true); for (Element rect : segments) { segmented.add(Symbol.rectangle(rect.w, rect.h).setColor(Color.red), new Element(rect.x, rect.y)); } Picture picture = segmented.get(); resizeToFrame(picture); } else { activeSide = ALL; evaluatingSegments = false; resizeToFrame(); } } private Scalar oColorBlack = new Scalar(0, 0, 0); private Scalar oColorWhite = new Scalar(255, 255, 255); private Mat actionSegmentsExpand(Mat mShot) { Picture toShow = new Picture(mShot); contours = Finder.getElement(toShow); mShot = toShow.getContent(); Imgproc.fillPoly(mShot, contours, oColorBlack); contours = Finder.getElement(new Picture(mShot)); return mShot; } private void actionReset() { resetBox(); } private void resetBox() { box.setVisible(false); rect = new Element(this.base); dirty = false; resizeToFrame(); box.setVisible(true); } private void actionFind() { Runnable find = new Runnable() { @Override public void run() { box.setVisible(false); Picture where = base; Element what = base.getSub(rect); if (isImage) { SX.pause(0.3); where = new Element(scrID).capture(); } Do.find(what, where); where.showMatch(); box.setVisible(true); } }; new Thread(find).start(); } private void actionFindAll() { Runnable find = new Runnable() { @Override public void run() { box.setVisible(false); Picture where = base; Element what = base.getSub(rect); if (isImage) { SX.pause(0.3); where = new Element(scrID).capture(); } Do.findAll(what, where); where.showMatches(); box.setVisible(true); } }; new Thread(find).start(); } private void actionQuit() { if (!internal) { if (dirty) { log.error("image not saved yet"); } log.trace("waiting for subs to end"); SX.setOption("Tool.bundlePath", bundlePath); SX.saveOptions(); } if (SX.isNotNull(hotkeyManager)) { hotkeyManager.stop(); } if (SX.isNotNull(box)) { box.dispose(); } if (SX.isNotNull(intro)) { intro.dispose(); SX.pause(1); } running = false; } private boolean actionBundlePath(JFrame frame) { String result = selectPath("Select BundlePath", frame); boolean success = false; if (SX.isSet(result)) { bundlePath = result; success = true; log.trace("new bundlePath: %s", result); } return success; } private String selectPath(String title, JFrame frame) { String path = null; if (bundlePath.isEmpty()) { } File fPath = FileChooser.folder(frame, title); if (SX.isNotNull(fPath)) { path = fPath.getAbsolutePath(); } return path; } private void actionFocus(JFrame frame) { actionFocus(frame, null); } private void actionFocus(JFrame frame, String app) { boolean shouldrun = false; String focused = ""; if (SX.isNotNull(frame)) { frame.setVisible(false); } if (SX.isNull(app)) { String msg = "select an application to focus"; if (!appName.isEmpty()) { msg = "select an application to focus\nor click Cancel to reset"; } List<String> apps = Window.getApps(); app = Do.popSelect("select app", apps.toArray(new String[0])); if (SX.isNotNull(app)) { shouldrun = true; } else { appName = ""; } } else if (!app.isEmpty()) { shouldrun = true; } if (shouldrun) { if (app.isEmpty() && !appName.isEmpty()) { app = appName; } if (!app.isEmpty()) { focused = new Window(app).toFront(); if (!focused.isEmpty()) { appName = focused; } } } log.trace("actionFocus: %s (%s)", (!focused.isEmpty() ? "success" : "no success"), appName); if (SX.isNotNull(frame)) { if (!shouldrun) { SX.pause(1); } frame.setVisible(true); } } private void actionOpen(JFrame frame) { actionOpen(frame, null); } private void actionOpen(JFrame frame, Object image) { boolean frameGiven = true; activeSide = ALL; if (SX.isNull(frame)) { frameGiven = false; frame = Do.getFrame(); } else { frame.setVisible(false); } File fImage; if (SX.isNull(image)) { fImage = FileChooser.loadImage(frame, new File(bundlePath)); if (SX.isNotNull(fImage)) { String result = fImage.getAbsolutePath(); log.trace("new image: %s", result); base = new Picture(result); if (base.isValid()) { rect = new Element(base); isImage = true; resetBox(); if (!bundlePath.equals(fImage.getParent()) && Do.popAsk(fImage.getParent() + "\nUse this folder as bundlepath?", "SikuliX Tool::BundlePath", box)) { bundlePath = fImage.getParent(); } updateStatus(); return; } } } else { if (image instanceof String) { fImage = new File(bundlePath, (String) image); log.trace("open given image: %s", fImage.getAbsolutePath()); base = new Picture(fImage.getAbsolutePath()); } else if (image instanceof Picture) { base = (Picture) image; } if (base.isValid()) { rect = new Element(base); isImage = true; resetBox(); updateStatus(); return; } } if (frameGiven) { frame.setVisible(true); } } private Picture imageToSave = null; private void actionSave() { String savedName = base.getName(); if (SX.isNull(imageToSave)) { imageToSave = shot; shot.setName(""); } boolean shouldSelectPath = false; String savePath = bundlePath; if (SX.isNotSet(savePath)) { shouldSelectPath = true; } else { if (!Do.popAsk(savePath + "\nSave to this folder (bundle path)?", "SikuliX Tool::ImageSave", box)) { shouldSelectPath = true; } } if (shouldSelectPath) { savePath = selectPath("Select a Directory/Folder", box); if (SX.isNotSet(savePath)) { return; } } if (!imageToSave.hasName()) { actionName(); if (base.hasName()) { imageToSave.setName(base.getName()); base.setName(savedName); } else { return; } } if (Content.existsFile(savePath, Content.asImageFilename(imageToSave.getName()))) { if (!Do.popAsk(new File(savePath, imageToSave.getName()).getAbsolutePath() + "\nOverwrite image file?", "SikuliX Tool::ImageSave", box)) { return; } } String imageName = imageToSave.getName(); String imageSaved = imageToSave.save(imageName, savePath); if (SX.isSet(imageSaved)) { if (!bundlePath.equals(savePath) && Do.popAsk(savePath + "\nUse this folder as bundlepath?", "SikuliX Tool::BundlePath", box)) { bundlePath = savePath; } dirty = false; imageToSave = null; updateStatus(); } } private void actionTarget() { dirty = true; } private void actionSimilar() { dirty = true; } private void actionName() { String newName = Do.input("Change image name", "SikuliX Tool::ImageName", (base.hasName() ? base.getName() : ""), box); if (SX.isSet(newName) && !newName.equals(base.getName())) { base.setName(newName); dirty = true; updateStatus(); } } private void actionInfo() { Do.popup(imgInfo(), "PictureTool::Information", box); } private void actionHelp() { } private void actionRevert() { if (lastRects.size() > 0) { log.trace("action: revert crop from %s to %s", rect, lastRects.get(0)); rect = lastRects.remove(0); } masking = false; statusAction = ""; updateStatus(); resizeToFrame(); } //</editor-fold> //<editor-fold desc="key released (special keys)"> private void myKeyReleased(KeyEvent e) { int code = e.getKeyCode(); if (e.CHAR_UNDEFINED != e.getKeyChar()) { } else { String cName = keyReleasedHandler(code, 1); log.trace("keyReleased: %s (%d) %s %s %s", e.getKeyText(code), code, cName, activeSides[activeSide], activeSides[activeSideSaved]); } } private String keyReleasedHandler(int code, int step) { String cName = ""; Element newRect = null; switch (code) { case KeyEvent.VK_DOWN: cName = "#DOWN"; if (activeSide == TOP) { newRect = new Element(rect.x, rect.y + step, rect.w, rect.h - step); } else if (activeSide == BOTTOM) { newRect = new Element(rect.x, rect.y, rect.w, rect.h + step); } else if (activeSide == LEFT || activeSide == RIGHT) { newRect = new Element(rect.x, rect.y - step, rect.w, rect.h); } else if (activeSide == ALL) { newRect = new Element(rect.x + step, rect.y + step, rect.w - 2 * step, rect.h - 2 * step); } else if (activeSide == NONE) { newRect = new Element(rect.x, rect.y - step, rect.w, rect.h); } break; case KeyEvent.VK_UP: cName = "#UP"; if (activeSide == TOP) { newRect = new Element(rect.x, rect.y - step, rect.w, rect.h + step); } else if (activeSide == BOTTOM) { newRect = new Element(rect.x, rect.y, rect.w, rect.h - step); } else if (activeSide == LEFT || activeSide == RIGHT) { newRect = new Element(rect.x, rect.y + step, rect.w, rect.h); } else if (activeSide == ALL) { newRect = new Element(rect.x - step, rect.y - step, rect.w + 2 * step, rect.h + 2 * step); } else if (activeSide == NONE) { newRect = new Element(rect.x, rect.y + step, rect.w, rect.h); } break; case KeyEvent.VK_LEFT: cName = "#LEFT"; if (activeSide == LEFT) { newRect = new Element(rect.x - step, rect.y, rect.w + step, rect.h); } else if (activeSide == RIGHT) { newRect = new Element(rect.x, rect.y, rect.w - step, rect.h); } else if (activeSide == TOP || activeSide == BOTTOM) { newRect = new Element(rect.x - step, rect.y, rect.w, rect.h); } else if (activeSide == ALL) { newRect = new Element(rect.x - step, rect.y - step, rect.w + 2 * step, rect.h + 2 * step); } else if (activeSide == NONE) { newRect = new Element(rect.x + step, rect.y, rect.w, rect.h); } break; case KeyEvent.VK_RIGHT: cName = "#RIGHT"; if (activeSide == LEFT) { newRect = new Element(rect.x + step, rect.y, rect.w - step, rect.h); } else if (activeSide == RIGHT) { newRect = new Element(rect.x, rect.y, rect.w + step, rect.h); } else if (activeSide == TOP || activeSide == BOTTOM) { newRect = new Element(rect.x + step, rect.y, rect.w, rect.h); } else if (activeSide == ALL) { newRect = new Element(rect.x + step, rect.y + step, rect.w - 2 * step, rect.h - 2 * step); } else if (activeSide == NONE) { newRect = new Element(rect.x - step, rect.y, rect.w, rect.h); } break; case KeyEvent.VK_SHIFT: cName = "#SHIFT"; content.setBorder(coloredSide(CLOCKWISE)); box.pack(); break; case KeyEvent.VK_CONTROL: cName = "#CTRL"; content.setBorder(coloredSide(COUNTERCLOCKWISE)); box.pack(); break; case KeyEvent.VK_ALT: cName = "#ALT"; content.setBorder(coloredSide(OPPOSITE)); box.pack(); break; case KeyEvent.VK_META: if (activeSide == ALL) { activeSide = activeSideSaved; } else { activeSideSaved = activeSide; activeSide = ALL; } cName = "#META"; content.setBorder(coloredSide(activeSide)); box.pack(); break; } if (!SX.isNull(newRect)) { log.info("action: crop %s from %s to %s", cName, getActiveSideText(), newRect); rect.change(newRect); resizeToFrame(); } return cName; } //</editor-fold> //<editor-fold desc="coloredSide"> private Border coloredSide(int side) { return coloredSide(side, Color.RED, Color.GREEN); } private Border coloredSide(int side, Color outerCol, Color innerCol) { Border inner = null; Border outer = null; if (side == NONE) { activeSide = NONE; return BorderFactory.createLineBorder(outerCol, borderThickness); } else if (side == ALL) { activeSide = ALL; return BorderFactory.createLineBorder(innerCol, borderThickness); } else if (side == COUNTERCLOCKWISE) { activeSide = ++activeSide > RIGHT ? TOP : activeSide; } else if (side == CLOCKWISE) { activeSide = --activeSide < TOP ? RIGHT : activeSide; } else if (side == OPPOSITE) { activeSide = activeSide + 2; if (activeSide > RIGHT) { activeSide -= 4; } } else { activeSide = side; } switch (activeSide) { case TOP: inner = BorderFactory.createMatteBorder(borderThickness, 0, 0, 0, innerCol); outer = BorderFactory.createMatteBorder(0, borderThickness, borderThickness, borderThickness, outerCol); break; case LEFT: inner = BorderFactory.createMatteBorder(0, borderThickness, 0, 0, innerCol); outer = BorderFactory.createMatteBorder(borderThickness, 0, borderThickness, borderThickness, outerCol); break; case BOTTOM: inner = BorderFactory.createMatteBorder(0, 0, borderThickness, 0, innerCol); outer = BorderFactory.createMatteBorder(borderThickness, borderThickness, 0, borderThickness, outerCol); break; case RIGHT: inner = BorderFactory.createMatteBorder(0, 0, 0, borderThickness, innerCol); outer = BorderFactory.createMatteBorder(borderThickness, borderThickness, borderThickness, 0, outerCol); break; case ALL: return BorderFactory.createMatteBorder(borderThickness, borderThickness, borderThickness, borderThickness, innerCol); } return new CompoundBorder(outer, inner); } private String getActiveSideText() { return activeSides[activeSide]; } private int getActiveSide() { return activeSide; } //</editor-fold> //<editor-fold desc="handle selection"> private void pushRect() { lastRects.add(0, new Element(rect)); if (lastRects.size() > maxRevert) { lastRects.remove(maxRevert); } } Element getPos(Element elem) { return new Element((int) ((elem.x - borderThickness) / resizeFactor), (int) ((elem.y - borderThickness) / resizeFactor)); } Element getPos(MouseEvent evt) { return new Element((int) ((evt.getX() - borderThickness) / resizeFactor), (int) ((evt.getY() - borderThickness) / resizeFactor)); } int halfCenteredWidth = 150; void crop(Element clicked) { pushRect(); if (evaluatingSegments) { if (contours.size() > 0) { log.trace("clicked: %s erase around", clicked); // Element segment = Finder.contoursToRectangle(contours).getAll(0); // rect.x += segment.x - 1; // rect.y += segment.y - 1; // rect.w = segment.w + 3; // rect.h = segment.h + 3; Mat mShot = Finder.drawContoursInImage(contours, shot.getContent()); resizeToFrame(new Picture(mShot)); return; } else { for (Element segment : segments) { if (segment.contains(clicked)) { log.trace("clicked: %s in segment: %s", clicked, segment); rect.x += segment.x + 2; rect.y += segment.y + 2; rect.w = segment.w - 3; rect.h = segment.h - 3; break; } } } evaluatingSegments = false; } else { clicked = getPos(clicked); int x = clicked.x; int y = clicked.y; if (TOP == activeSide) { rect.h -= y; rect.y += y; } else if (BOTTOM == activeSide) { rect.h -= rect.h - y; } else if (LEFT == activeSide) { rect.w -= x; rect.x += x; } else if (RIGHT == activeSide) { rect.w -= rect.w - x; } } exited = -1; dirty = true; resizeToFrame(); } void center(Element clicked) { pushRect(); clicked = getPos(clicked); int x = rect.x + clicked.x; int y = rect.y + clicked.y; if (activeSide > RIGHT) { rect.x = Math.max(0, x - halfCenteredWidth * base.w / base.h); rect.y = Math.max(0, y - halfCenteredWidth); rect.w = 2 * Math.min(halfCenteredWidth * base.w / base.h, Math.min(x, base.w - x)); rect.h = 2 * Math.min(halfCenteredWidth, Math.min(y, base.h - y)); } else { rect.x = x - rect.w / 2; rect.y = y - rect.y / 2; } exited = -1; dirty = true; resizeToFrame(); } private void checkSelection() { rect.intersect(base); Element minimum = rect.getCenter().grow(); if (rect.w < minWidthHeight || rect.h < minWidthHeight) { rect.change(minimum); } } double resizeFactor = 10; private void resizeToFrame() { checkSelection(); shot = base.getSub(rect); resizeToFrame(shot); } private void resizeToFrame(Picture shot) { if (activeSide > RIGHT) { double wFactor = shot.w / (scrW * 0.85f); double hFactor = shot.h / (scrH * 0.85f); resizeFactor = 1 / Math.max(wFactor, hFactor); resizeFactor = Math.min(resizeFactor, 10); } BufferedImage img = getResizedAsBufferedImage(shot, resizeFactor); shotDisplayedW = img.getWidth(); shotDisplayedH = img.getHeight(); Dimension dim = new Dimension(img.getWidth() + 2 * borderThickness, img.getHeight() + 2 * borderThickness + statusHeight); content.setIcon(new ImageIcon(img)); content.setBorder(coloredSide(activeSide)); pBox.setPreferredSize(dim); box.pack(); box.setLocation((int) ((scrW - dim.getWidth()) / 2), (int) (scrH - dim.getHeight()) / 2); updateStatus(); } private BufferedImage getResizedAsBufferedImage(Picture img, double factor) { BufferedImage bImg = null; Mat resizedMat = img.getResizedMat(factor); bImg = Picture.getBufferedImage(resizedMat); return bImg; } private void drawSelection(Graphics2D g2d) { if (SX.isNotNull(dragStart)) { g2d.setStroke(new BasicStroke(2)); g2d.setColor(Color.blue); int x, y, x2, y2, w, h; int crossVx1, crossVy1, crossVx2, crossVy2, crossHx1, crossHy1, crossHx2, crossHy2; if (dragButton > 1) { int xD = (dragCurrent.getX() - dragStart.getX()) / 2; int yD = (dragCurrent.getY() - dragStart.getY()) / 2; x = dragStart.getX() - xD; y = dragStart.getY() - yD; x2 = dragStart.getX() + xD; y2 = dragStart.getY() + yD; } else { x = dragStart.getX(); y = dragStart.getY(); x2 = dragCurrent.getX(); y2 = dragCurrent.getY(); } w = x2 - x; h = y2 - y; g2d.drawLine(x, y, x + w, y); g2d.drawLine(x + w, y, x + w, y + h); g2d.drawLine(x + w, y + h, x, y + h); g2d.drawLine(x, y + h, x, y); crossVx1 = x + w / 2; crossVy1 = y; crossVx2 = crossVx1; crossVy2 = y + h; crossHx1 = x; crossHy1 = y + h / 2; crossHx2 = x + w; crossHy2 = crossHy1; g2d.setStroke(new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{3, 2}, 0)); g2d.drawLine(crossVx1, crossVy1, crossVx2, crossVy2); g2d.drawLine(crossHx1, crossHy1, crossHx2, crossHy2); } } private void drawCrossHair(Graphics2D g2d) { g2d.setColor(Color.red); g2d.setStroke(new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{3, 2}, 0)); g2d.drawLine(shotDisplayedW / 2 + borderThickness, borderThickness, shotDisplayedW / 2 + borderThickness, shotDisplayedH); g2d.drawLine(borderThickness, shotDisplayedH / 2 + borderThickness, shotDisplayedW, shotDisplayedH / 2 + borderThickness); } private boolean isDragging() { return SX.isNotNull(dragStart); } //</editor-fold> //<editor-fold desc="image attributes"> private File imgPath = null; public File getImgPath() { File path = imgPath; if (SX.isNull(imgPath)) { path = new File(SX.getSXSTORE()); } return path; } // private String imgName = ""; private String getImageName() { if (base.hasName()) { return base.getName(); } return ""; } private File getImageFile() { String name = getImageName(); return new File(getImgPath(), name + ".png"); } private String imgInfo() { return String.format("ImagePath: %s\n" + "ImageName: %s\n" + "Selection: %s", getImgPath(), getImageFile().getName(), rect); } //</editor-fold> }