Java tutorial
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package jp.co.heppokoact.autocapture; import java.awt.AWTException; import java.awt.MouseInfo; import java.awt.Point; import java.awt.Robot; import java.awt.event.InputEvent; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.text.DecimalFormat; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.ResourceBundle; import javafx.animation.Animation.Status; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.beans.binding.Bindings; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.concurrent.Service; import javafx.concurrent.Task; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.geometry.Rectangle2D; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.image.Image; import javafx.scene.input.KeyCode; import javafx.scene.input.MouseEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundImage; import javafx.scene.layout.BackgroundPosition; import javafx.scene.layout.BackgroundRepeat; import javafx.scene.layout.BackgroundSize; import javafx.scene.layout.Pane; import javafx.scene.media.AudioClip; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.stage.DirectoryChooser; import javafx.stage.Modality; import javafx.stage.Screen; import javafx.stage.Stage; import javafx.stage.StageStyle; import javafx.stage.Window; import javafx.util.Duration; import javax.imageio.ImageIO; import jp.co.heppokoact.util.ImageUtil; import org.apache.commons.io.FileUtils; import org.controlsfx.dialog.Dialogs; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ? * * @author M.Yoshida */ public class FXMLDocumentController implements Initializable { private static final Logger LOGGER = LoggerFactory.getLogger(FXMLDocumentController.class); private static final File CONFIG_FILE = new File("config.xml"); private static final double CAPTURE_INTERVAL = 1000.0; private Stage stage; @FXML private AnchorPane anchorPane; @FXML private Label areaStartXLabel; @FXML private Label areaStartYLabel; @FXML private Label areaEndXLabel; @FXML private Label areaEndYLabel; @FXML private Label nextPointXLabel; @FXML private Label nextPointYLabel; @FXML private Label prevPointXLabel; @FXML private Label prevPointYLabel; @FXML private Label saveDirectoryLabel; @FXML private Button areaButton; @FXML private Button pointButton; @FXML private Button saveDirectoryButton; @FXML private Button startButton; @FXML private Button stopButton; private IntegerProperty areaStartX = new SimpleIntegerProperty(0); private IntegerProperty areaStartY = new SimpleIntegerProperty(0); private IntegerProperty areaEndX = new SimpleIntegerProperty(0); private IntegerProperty areaEndY = new SimpleIntegerProperty(0); private IntegerProperty nextPointX = new SimpleIntegerProperty(0); private IntegerProperty nextPointY = new SimpleIntegerProperty(0); private IntegerProperty prevPointX = new SimpleIntegerProperty(0); private IntegerProperty prevPointY = new SimpleIntegerProperty(0); private ObjectProperty<File> saveDirectory = new SimpleObjectProperty<>(); private int dragStartX; private int dragStartY; private Rectangle areaRect; /** */ private Properties prop; /** ?? */ private CaptureService captureService; /** ??? */ private Timeline captureTimeline; /** ?? */ private Robot robot; /** */ private AudioClip clip; @Override public void initialize(URL url, ResourceBundle rb) { // ??? prop = new Properties(); if (CONFIG_FILE.exists()) { try (InputStream in = new FileInputStream(CONFIG_FILE)) { prop.loadFromXML(in); } catch (IOException e) { throw new RuntimeException("????????", e); } } // ???????? String saveDirectoryPath = prop.getProperty("saveDirectoryPath"); if (saveDirectoryPath != null) { File tempSaveDirectory = new File(saveDirectoryPath); if (tempSaveDirectory.exists()) { saveDirectory.set(tempSaveDirectory); } } // ??? saveDirectoryLabel.textProperty().bind(Bindings.createStringBinding(() -> { File sd = saveDirectory.get(); return (sd == null) ? "" : sd.getName(); }, saveDirectory)); areaStartXLabel.textProperty().bind(Bindings.convert(areaStartX)); areaStartYLabel.textProperty().bind(Bindings.convert(areaStartY)); areaEndXLabel.textProperty().bind(Bindings.convert(areaEndX)); areaEndYLabel.textProperty().bind(Bindings.convert(areaEndY)); nextPointXLabel.textProperty().bind(Bindings.convert(nextPointX)); nextPointYLabel.textProperty().bind(Bindings.convert(nextPointY)); prevPointXLabel.textProperty().bind(Bindings.convert(prevPointX)); prevPointYLabel.textProperty().bind(Bindings.convert(prevPointY)); // ??? stopButton.setDisable(true); // ???? captureService = new CaptureService(); captureTimeline = new Timeline(new KeyFrame(new Duration(CAPTURE_INTERVAL), e -> { captureService.restart(); })); captureTimeline.setCycleCount(Timeline.INDEFINITE); try { robot = new Robot(); } catch (AWTException e) { throw new RuntimeException("???????", e); } // ?? clip = new AudioClip(ClassLoader.getSystemResource("ayashi.wav").toString()); } @FXML private void areaButtonClicked(ActionEvent event) throws IOException { System.out.println("areaButtonClicked"); // ?? Stage transparentStage = createTransparentStage(); // ????? Scene scene = transparentStage.getScene(); scene.setOnMousePressed(e -> { // dragStartX = (int) e.getScreenX(); dragStartY = (int) e.getScreenY(); // ?? areaRect = new Rectangle(e.getX(), e.getY(), 0, 0); areaRect.setStroke(Color.RED); areaRect.setStrokeWidth(1.0); areaRect.setFill(Color.TRANSPARENT); ((Pane) scene.getRoot()).getChildren().add(areaRect); }); // ????? scene.setOnMouseDragged(e -> { // ?????? areaRect.setWidth(e.getScreenX() - dragStartX); areaRect.setHeight(e.getScreenY() - dragStartY); }); // ????? scene.setOnMouseReleased(e -> { // ? areaStartX.set(dragStartX); areaStartY.set(dragStartY); areaEndX.set((int) e.getScreenX()); areaEndY.set((int) e.getScreenY()); // ?? transparentStage.close(); }); // ?????? transparentStage.setOnCloseRequest(e -> { // ????? dragStartX = 0; dragStartY = 0; areaRect = null; }); transparentStage.show(); } @FXML private void pointButtonClicked(ActionEvent event) throws IOException { System.out.println("pointButtonClicked"); // ?? Stage transparentStage = createTransparentStage(); // ???? Scene scene = transparentStage.getScene(); // EventHandler<? super MouseEvent> setPrevPoint = e -> { // ???? prevPointX.set((int) e.getScreenX()); prevPointY.set((int) e.getScreenY()); transparentStage.close(); }; // ? EventHandler<MouseEvent> setNextPoint = e -> { // ???? nextPointX.set((int) e.getScreenX()); nextPointY.set((int) e.getScreenY()); scene.setOnMouseClicked(setPrevPoint); }; scene.setOnMouseClicked(setNextPoint); transparentStage.show(); } @FXML private void saveDirectoryButtonClicked(ActionEvent event) throws IOException { System.out.println("saveDirectoryButtonClicked"); // ??? DirectoryChooser dc = new DirectoryChooser(); dc.setTitle("??"); File sd = null; Window window = anchorPane.getScene().getWindow(); try { dc.setInitialDirectory(saveDirectory.get()); sd = dc.showDialog(window); } catch (IllegalArgumentException e) { dc.setInitialDirectory(null); sd = dc.showDialog(window); } saveDirectory.set(sd); // ????? if (sd != null) { prop.setProperty("saveDirectoryPath", sd.getAbsolutePath()); try (OutputStream out = new FileOutputStream(CONFIG_FILE)) { prop.storeToXML(out, "AutoCapture"); } } } @FXML private void startButtonClicked(ActionEvent event) { System.out.println("startButtonClicked"); if (!validateStartButtonClicked()) { return; } // ?????? areaButton.setDisable(true); pointButton.setDisable(true); saveDirectoryButton.setDisable(true); startButton.setDisable(true); stopButton.setDisable(false); // ? captureService.init(); captureTimeline.play(); } /** * ???????????????? * ????????? * * @return ???true */ private boolean validateStartButtonClicked() { if (calcAreaHeight() == 0 || calcAreaWidth() == 0) { Dialogs.create()// .owner(stage)// .title("ERROR")// .masthead(null)// .message("???????????")// .showError(); return false; } if (saveDirectory.get() == null) { Dialogs.create()// .owner(stage)// .title("ERROR")// .masthead(null)// .message("???????")// .showError(); return false; } return true; } @FXML private void stopButtonClicked(ActionEvent event) { System.out.println("stopButtonClicked"); stopCapture(); } /** * ????? */ private void stopCapture() { // ? captureTimeline.stop(); captureService.cancel(); // ?????? areaButton.setDisable(false); pointButton.setDisable(false); saveDirectoryButton.setDisable(false); startButton.setDisable(false); stopButton.setDisable(true); clip.play(); } /** * ??????????? * ???????? * ???ESC???????? * * @return ??????? * @throws IOException ????? */ private Stage createTransparentStage() throws IOException { // ?????????? Stage transparentStage = new Stage(StageStyle.TRANSPARENT); transparentStage.initOwner(anchorPane.getScene().getWindow()); transparentStage.initModality(Modality.APPLICATION_MODAL); transparentStage.setResizable(false); Rectangle2D rect = Screen.getPrimary().getVisualBounds(); transparentStage.setWidth(rect.getWidth()); transparentStage.setHeight(rect.getHeight()); // ??? java.awt.Rectangle awtRect = new java.awt.Rectangle((int) rect.getWidth(), (int) rect.getHeight()); BufferedImage captureImage = robot.createScreenCapture(awtRect); // ?????? ByteArrayInputStream in = ImageUtil.convToInputStream(captureImage); BackgroundImage bgImage = new BackgroundImage(new Image(in), BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, BackgroundSize.DEFAULT); Pane pane = new Pane(); pane.setBackground(new Background(bgImage)); pane.setStyle("-fx-border-color: rgba(255, 255, 0, 0.5); -fx-border-style: solid; -fx-border-width: 15;"); // ???ESC????? Scene scene = new Scene(pane); transparentStage.setScene(scene); scene.setOnKeyPressed(e -> { if (e.getCode() == KeyCode.ESCAPE) { transparentStage.close(); } }); return transparentStage; } /** * ?? * * @author M.Yoshida */ class CaptureService extends Service<Void> { /** ? */ private java.awt.Rectangle captureRect; /** ?? */ private int currentPageNumber; /** ????????? */ private int youngestPageNumber; /** ?? */ private Map<Integer, Page> pages = new HashMap<>(); /** ??? */ private Direction prevDirection1; /** ??? */ private Direction prevDirection2; /** ?? */ private BufferedImage prevImage; /** ????????? */ private boolean isSameImage1; /** ?????????? */ private boolean isSameImage2; /** ????? */ private boolean completed; /** * ???? */ public void init() { captureRect = new java.awt.Rectangle(areaStartX.get(), areaStartY.get(), calcAreaWidth(), calcAreaHeight()); youngestPageNumber = 1; currentPageNumber = 1; pages = new HashMap<>(); prevDirection1 = null; prevDirection2 = null; prevImage = null; isSameImage1 = false; isSameImage2 = false; completed = false; } @Override protected Task<Void> createTask() { return new Task<Void>() { @Override protected Void call() throws Exception { // ???????? Page currentPage = pages.get(currentPageNumber); if (currentPage == null) { currentPage = new Page(currentPageNumber); pages.put(currentPageNumber, currentPage); } // ? BufferedImage image = capture(); isSameImage2 = isSameImage1; isSameImage1 = ImageUtil.equals(prevImage, image); prevImage = image; // if (isCompleted()) { completed = true; return null; } // ???????????? if (!currentPage.isFixed()) { currentPage.submitImage(image); // ??? if (currentPage.isFixed()) { savePage(currentPage); updateYoungestPageNumber(); } } // ??? Direction direction = decideDirection(); prevDirection2 = prevDirection1; prevDirection1 = direction; direction.turnPage(); return null; } }; } @Override protected void ready() { captureTimeline.pause(); System.out.printf("RADY -> cur:%d, you:%d, dir:(%s,%s), img:(%b,%b)%n", currentPageNumber, youngestPageNumber, prevDirection1, prevDirection2, isSameImage1, isSameImage2); } @Override protected void succeeded() { System.out.printf("SUCC -> cur:%d, you:%d, dir:(%s,%s), img:(%b,%b)%n", currentPageNumber, youngestPageNumber, prevDirection1, prevDirection2, isSameImage1, isSameImage2); if (completed) { stopCapture(); } else if (captureTimeline.getStatus() == Status.PAUSED) { captureTimeline.play(); } } @Override protected void failed() { System.out.printf("FAIL -> cur:%d, you:%d, dir:(%s,%s), img:(%b,%b)%n", currentPageNumber, youngestPageNumber, prevDirection1, prevDirection2, isSameImage1, isSameImage2); stopCapture(); LOGGER.error("?", getException()); Dialogs.create()// .title("FATAL")// .masthead("?")// .showException(getException()); } /** * ?????? * * @return ??? */ private BufferedImage capture() { return robot.createScreenCapture(captureRect); } /** * ??? * * ????????? * * <ul> * <li>?1,2???????????????FIX??? * <ol> * <li>??????????????</li> * <li>??????????????</li> * </ol> * </li> * <li>?1,2????????FIX?????? * <ol> * <li>??????????????</li> * <li>??????????</li> * </ol> * </li> * </ul> * * @return ????true */ private boolean isCompleted() { if (prevDirection2 == FORWARD && isSameImage2) { if (prevDirection1 == FORWARD && isSameImage1) { return true; } else if (prevDirection1 == BACKWARD && !isSameImage1) { return true; } } return false; } /** * ??? * * @param page ?? * @throws IOException ?????? */ private void savePage(Page page) throws IOException { String stringSeq = new DecimalFormat("00000").format(page.getPageNumber()); File captureFile = FileUtils.getFile(saveDirectory.get(), stringSeq + ".bmp"); System.out.println("Save " + captureFile.getPath()); ImageIO.write(page.getImage(), "bmp", captureFile); } /** * ?????????? */ private void updateYoungestPageNumber() { Page page = pages.get(youngestPageNumber); while (page != null && page.isFixed()) { youngestPageNumber++; page = pages.get(youngestPageNumber); } } /** * ??????????? * * @return ???true */ private Direction decideDirection() { if (currentPageNumber <= youngestPageNumber) { return FORWARD; } else { return BACKWARD; } } /** * ??? * * @param x X * @param y Y */ private void clickPoint(IntegerProperty x, IntegerProperty y) { Point mousePoint = MouseInfo.getPointerInfo().getLocation(); robot.mouseMove(x.get(), y.get()); robot.mousePress(InputEvent.BUTTON1_MASK); robot.mouseRelease(InputEvent.BUTTON1_MASK); robot.mouseMove(mousePoint.x, mousePoint.y); } /** * ?????? * ?Enum????enum?static??????????????? */ private abstract class Direction { abstract void turnPage(); } /** ??? */ private final Direction FORWARD = new Direction() { @Override void turnPage() { clickPoint(nextPointX, nextPointY); currentPageNumber++; } @Override public String toString() { return "FORWARD"; } }; /** ??? */ private final Direction BACKWARD = new Direction() { @Override void turnPage() { clickPoint(prevPointX, prevPointY); currentPageNumber--; } @Override public String toString() { return "BACKWARD"; } }; } private int calcAreaWidth() { return areaEndX.get() - areaStartX.get(); } private int calcAreaHeight() { return areaEndY.get() - areaStartY.get(); } public void setStage(Stage stage) { this.stage = stage; } }