diff --git a/README.md b/README.md index 679cde4..f7354cf 100644 --- a/README.md +++ b/README.md @@ -61,4 +61,5 @@ This app makes usage of * NetBeans Platform: * CHDK-PTP-Java Library: -* usb4java-javax: \ No newline at end of file +* usb4java-javax: +* USB IDs: \ No newline at end of file diff --git a/eazy-bookscanner-swing/README.md b/eazy-bookscanner-swing/README.md index bf14154..37135f3 100644 --- a/eazy-bookscanner-swing/README.md +++ b/eazy-bookscanner-swing/README.md @@ -1,3 +1,11 @@ +# Eazy BookScanner + +OpenSource Book Scanner application for digitizing books with two cameras. +Usage for two digital cameras like described at . + +![Screenshot GUI Eazy BookScanner](./screenshot-20201221.jpg) + +## References * Swing: https://zetcode.com/javaswing/ * Icons: https://www.javacodegeeks.com/2020/03/javafx-tip-32-need-icons-use-ikonli.html \ No newline at end of file diff --git a/eazy-bookscanner-swing/pom.xml b/eazy-bookscanner-swing/pom.xml index c71f7af..5459d26 100644 --- a/eazy-bookscanner-swing/pom.xml +++ b/eazy-bookscanner-swing/pom.xml @@ -3,16 +3,21 @@ 4.0.0 - com.datazuul - eazy-bookscanner-parent - 0.2.1-SNAPSHOT + org.springframework.boot + spring-boot-starter-parent + 2.6.7 + Eazy BookScanner (Swing) com.datazuul.eazy.bookscanner eazy-bookscanner-swing - 1.0-SNAPSHOT + 1.0.0-SNAPSHOT + jar + 1.8 + 1.8 + 1.8 UTF-8 com.datazuul.eazy.bookscanner.App @@ -23,6 +28,11 @@ CHDK-PTP-Java 0.7.1-SNAPSHOT + + commons-cli + commons-cli + 1.5.0 + org.imgscalr imgscalr-lib @@ -38,5 +48,20 @@ ikonli-swing 12.3.1 + + org.springframework.boot + spring-boot-starter + + + + eazy-bookscanner-${project.version} + + + + org.springframework.boot + spring-boot-maven-plugin + + + \ No newline at end of file diff --git a/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/App.java b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/App.java index 7a8d097..52db74c 100644 --- a/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/App.java +++ b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/App.java @@ -1,5 +1,6 @@ package com.datazuul.eazy.bookscanner; +import com.datazuul.eazy.bookscanner.devices.CamerasProperties; import com.datazuul.eazy.bookscanner.gui.ThumbnailsAndScanPanel; import java.awt.Color; import java.awt.EventQueue; @@ -11,10 +12,22 @@ import static javax.swing.WindowConstants.EXIT_ON_CLOSE; import org.kordamp.ikonli.material2.Material2AL; import org.kordamp.ikonli.swing.FontIcon; - +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Implementing ApplicationRunner interface tells Spring Boot to automatically + * call the run method AFTER the application context has been loaded. + */ +@SpringBootApplication +@EnableConfigurationProperties(CamerasProperties.class) public class App extends JFrame { public static final int ICON_SIZE = 20; + + public static ConfigurableApplicationContext CONTEXT; public App() { initUI(); @@ -71,9 +84,17 @@ private void initUI() { } public static void main(String[] args) { + CONTEXT = new SpringApplicationBuilder(App.class).headless(false).run(args); EventQueue.invokeLater(() -> { - App ex = new App(); + App ex = CONTEXT.getBean(App.class); ex.setVisible(true); }); + + logConfig(); + } + + private static void logConfig() { + CamerasProperties props = CONTEXT.getBean(CamerasProperties.class); + System.out.println(props); } } diff --git a/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/CameraFactory.java b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/CameraFactory.java index c2591d7..7dee982 100644 --- a/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/CameraFactory.java +++ b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/CameraFactory.java @@ -1,21 +1,51 @@ package com.datazuul.eazy.bookscanner.devices; import chdk.ptp.java.ICamera; -import chdk.ptp.java.SupportedCamera; import chdk.ptp.java.connection.CameraUsbDevice; -import javax.usb.UsbDevice; +import com.datazuul.eazy.bookscanner.App; +import java.util.List; +import java.util.Objects; +import org.springframework.stereotype.Component; +@Component public class CameraFactory { public static ICamera getCamera(CameraUsbDevice cameraUsbDevice) { - SupportedCamera sc = SupportedCamera.getCamera(cameraUsbDevice.getIdVendor(), cameraUsbDevice.getIdProduct()); - if (sc != null) { - // known camera - create specified class - try { - UsbDevice usbDevice = cameraUsbDevice.getDevice(); - return sc.getClazz().getConstructor(UsbDevice.class).newInstance(usbDevice); - } catch (Exception ex) { - return null; + CamerasProperties props = App.CONTEXT.getBean(CamerasProperties.class); + + Short idVendor = cameraUsbDevice.getIdVendor(); + Short idProduct = cameraUsbDevice.getIdProduct(); + + ConfiguredCamera camera = getCamera(props, idVendor, idProduct, cameraUsbDevice); + return camera; + +// SupportedCamera sc = SupportedCamera.getCamera(idVendor, idProduct); +// if (sc != null) { +// // known camera - create specified class +// try { +// UsbDevice usbDevice = cameraUsbDevice.getDevice(); +// return sc.getClazz().getConstructor(UsbDevice.class).newInstance(usbDevice); +// } catch (Exception ex) { +// return null; +// } +// } +// return null; + } + + private static ConfiguredCamera getCamera(CamerasProperties props, Short idVendor, Short idProduct, CameraUsbDevice cameraUsbDevice) { + List vendors = props.getVendors(); + if (vendors != null) { + Vendor vendor = vendors.stream().filter(v -> Objects.equals(idVendor, v.getId())).findAny().orElse(null); + if (vendor != null) { + System.out.println("found vendor: " + vendor); + List products = vendor.getProducts(); + if (products != null) { + Product product = products.stream().filter(p -> Objects.equals(idProduct, p.getId())).findAny().orElse(null); + if (product != null) { + System.out.println("found product: " + product); + return new ConfiguredCamera(cameraUsbDevice.getDevice(), product); + } + } } } return null; diff --git a/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/CamerasProperties.java b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/CamerasProperties.java new file mode 100644 index 0000000..03204d6 --- /dev/null +++ b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/CamerasProperties.java @@ -0,0 +1,25 @@ +package com.datazuul.eazy.bookscanner.devices; + +import java.util.List; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; + +@ConfigurationProperties(prefix = "cameras") +@ConfigurationPropertiesScan +public class CamerasProperties { + + private List vendors; + + public List getVendors() { + return vendors; + } + + public void setVendors(List vendors) { + this.vendors = vendors; + } + + @Override + public String toString() { + return "CamerasProperties{" + "vendors=" + vendors + '}'; + } +} diff --git a/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/ConfiguredCamera.java b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/ConfiguredCamera.java new file mode 100644 index 0000000..17b7a3b --- /dev/null +++ b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/ConfiguredCamera.java @@ -0,0 +1,99 @@ +package com.datazuul.eazy.bookscanner.devices; + +import chdk.ptp.java.camera.FailSafeCamera; +import chdk.ptp.java.exception.GenericCameraException; +import chdk.ptp.java.exception.PTPTimeoutException; +import chdk.ptp.java.model.FocusMode; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.usb.UsbDevice; + +public class ConfiguredCamera extends FailSafeCamera { + + private Logger log = Logger.getLogger(ConfiguredCamera.class.getName()); + + private final Product product; + + public ConfiguredCamera(UsbDevice device, Product product) { + super(device); + this.product = product; + } + + @Override + public void setFocusMode(FocusMode mode) throws GenericCameraException, PTPTimeoutException { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + log.log(Level.SEVERE, e.getLocalizedMessage(), e); + throw new GenericCameraException(e.getLocalizedMessage()); + } + + // currently only supporting autofocus mode + if (FocusMode.AUTO.equals(mode)) { + List autofocus = product.getAutofocus(); + int currentFocusMode = getFocusMode().getValue(); + for (FocusModeChange focusModeChange : autofocus) { + if (currentFocusMode == focusModeChange.getCurrentMode()) { + List commands = focusModeChange.getExecute(); + if (commands != null) { + for (String command : commands) { + switch (command) { + case "LEFT": + this.executeLuaCommand("click('left');"); + break; + case "RIGHT": + this.executeLuaCommand("click('right');"); + break; + case "SET": + this.executeLuaCommand("click('set');"); + break; + default: + break; + } + if (command.startsWith("sleep")) { + long milliseconds = Long.valueOf(command.substring(5)); + try { + Thread.sleep(milliseconds); + } catch (InterruptedException ex) { + log.log(Level.SEVERE, ex.getLocalizedMessage(), ex); + throw new GenericCameraException(ex.getLocalizedMessage()); + } + } + } + } + // activate auto focus and focus + try { + this.executeLuaCommand("set_aflock(0);"); + this.executeLuaCommand("press('shoot_half');"); + Thread.sleep(800); + this.executeLuaCommand("release('shoot_half');"); + this.executeLuaCommand("set_aflock(1);"); + Thread.sleep(1000); + } catch (InterruptedException ex) { + log.log(Level.SEVERE, ex.getLocalizedMessage(), ex); + throw new GenericCameraException(ex.getLocalizedMessage()); + } + } + } + } + } + + @Override + public void setZoom(int zoomPosition) throws PTPTimeoutException, GenericCameraException { + Zoom zoom = product.getZoom(); + if (zoom != null) { + List preExecute = zoom.getPreExecute(); + for (String command : preExecute) { + switch (command) { + case "SET_AUTOFOCUS": + setFocusMode(FocusMode.AUTO); + break; + default: + break; + } + } + } + super.setZoom(zoomPosition); + } +} diff --git a/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/FocusModeChange.java b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/FocusModeChange.java new file mode 100644 index 0000000..0673461 --- /dev/null +++ b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/FocusModeChange.java @@ -0,0 +1,30 @@ +package com.datazuul.eazy.bookscanner.devices; + +import java.util.List; + +public class FocusModeChange { + + private int currentMode; + private List execute; + + public int getCurrentMode() { + return currentMode; + } + + public List getExecute() { + return execute; + } + + public void setCurrentMode(int currentMode) { + this.currentMode = currentMode; + } + + public void setExecute(List execute) { + this.execute = execute; + } + + @Override + public String toString() { + return "FocusModeChange{" + "currentMode=" + currentMode + ", execute=" + execute + '}'; + } +} diff --git a/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/Product.java b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/Product.java new file mode 100644 index 0000000..cd62353 --- /dev/null +++ b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/Product.java @@ -0,0 +1,48 @@ +package com.datazuul.eazy.bookscanner.devices; + +import java.util.List; + +public class Product { + + private List autofocus; + private Short id; + private String name; + private Zoom zoom; + + public List getAutofocus() { + return autofocus; + } + + public Short getId() { + return id; + } + + public String getName() { + return name; + } + + public Zoom getZoom() { + return zoom; + } + + public void setAutofocus(List autofocus) { + this.autofocus = autofocus; + } + + public void setId(Short id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } + + public void setZoom(Zoom zoom) { + this.zoom = zoom; + } + + @Override + public String toString() { + return "Product{" + "autofocus=" + autofocus + ", id=" + id + ", name=" + name + ", zoom=" + zoom + '}'; + } +} diff --git a/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/Vendor.java b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/Vendor.java new file mode 100644 index 0000000..c5dedbf --- /dev/null +++ b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/Vendor.java @@ -0,0 +1,39 @@ +package com.datazuul.eazy.bookscanner.devices; + +import java.util.List; + +public class Vendor { + + private Short id; + private String name; + private List products; + + public Short getId() { + return id; + } + + public String getName() { + return name; + } + + public List getProducts() { + return products; + } + + public void setId(Short id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } + + public void setProducts(List products) { + this.products = products; + } + + @Override + public String toString() { + return "Vendor{" + "id=" + id + ", name=" + name + ", products=" + products + '}'; + } +} diff --git a/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/Zoom.java b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/Zoom.java new file mode 100644 index 0000000..c8ffc8a --- /dev/null +++ b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/devices/Zoom.java @@ -0,0 +1,21 @@ +package com.datazuul.eazy.bookscanner.devices; + +import java.util.List; + +public class Zoom { + + private List preExecute; + + public List getPreExecute() { + return preExecute; + } + + public void setPreExecute(List preExecute) { + this.preExecute = preExecute; + } + + @Override + public String toString() { + return "Zoom{" + "preExecute=" + preExecute + '}'; + } +} diff --git a/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/gui/ThumbnailsAndScanPanel.form b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/gui/ThumbnailsAndScanPanel.form index edaf5a4..2b64b94 100644 --- a/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/gui/ThumbnailsAndScanPanel.form +++ b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/gui/ThumbnailsAndScanPanel.form @@ -179,6 +179,11 @@ + + + + + diff --git a/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/gui/ThumbnailsAndScanPanel.java b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/gui/ThumbnailsAndScanPanel.java index de00c9b..49d52cd 100644 --- a/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/gui/ThumbnailsAndScanPanel.java +++ b/eazy-bookscanner-swing/src/main/java/com/datazuul/eazy/bookscanner/gui/ThumbnailsAndScanPanel.java @@ -232,7 +232,6 @@ public void stateChanged(javax.swing.event.ChangeEvent evt) { scanPanels.setLayout(new javax.swing.BoxLayout(scanPanels, javax.swing.BoxLayout.X_AXIS)); - leftScanPanel.setAlignmentX(0.5F); leftScanPanel.setPreferredSize(new java.awt.Dimension(511, 800)); scanPanels.add(leftScanPanel); @@ -253,6 +252,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { add(scanPanels, java.awt.BorderLayout.CENTER); + bottomPanel.setPreferredSize(new java.awt.Dimension(59, 70)); bottomPanel.setLayout(new java.awt.BorderLayout()); shootButton.setBackground(new java.awt.Color(204, 255, 204)); diff --git a/eazy-bookscanner-swing/src/main/resources/application.yml b/eazy-bookscanner-swing/src/main/resources/application.yml new file mode 100644 index 0000000..d83c34c --- /dev/null +++ b/eazy-bookscanner-swing/src/main/resources/application.yml @@ -0,0 +1,24 @@ +cameras: + vendors: + - id: 0x04a9 + name: 'Canon, Inc.' + products: + - id: 0x322a + name: 'PowerShot A2200' + # The A2200 only has three focus modes (in this order on display): + # currentFocusMode in: "Macro" = 4 - "Normal" = 0 - "Infinity" = 3 + # FocusMode.MACRO (4), "Normal" = FocusMode.AUTO (0), "Infinity" = FocusMode.INF (3) + autofocus: + # set to "Normal" (there is no AUTO mode) and handle auto focus later during shoot + # see https://chdk.fandom.com/wiki/Script_commands#set_aflock + # macro + - current-mode: 4 + execute: [LEFT, sleep1000, RIGHT, sleep500, SET, sleep1000] + # normal + - current-mode: 0 + execute: [] + # infinity + - current-mode: 3 + execute: [LEFT, sleep1000, LEFT, sleep500, SET, sleep1000] + zoom: + pre-execute: [SET_AUTOFOCUS] diff --git a/eazy-bookscanner-swing/src/main/resources/images/logo-datazuul-bookscan-16x16.png b/eazy-bookscanner-swing/src/main/resources/images/logo-datazuul-bookscan-16x16.png new file mode 100644 index 0000000..ce310fd Binary files /dev/null and b/eazy-bookscanner-swing/src/main/resources/images/logo-datazuul-bookscan-16x16.png differ diff --git a/eazy-bookscanner-swing/src/main/resources/images/logo-datazuul-bookscan-32x32.png b/eazy-bookscanner-swing/src/main/resources/images/logo-datazuul-bookscan-32x32.png new file mode 100644 index 0000000..7281c19 Binary files /dev/null and b/eazy-bookscanner-swing/src/main/resources/images/logo-datazuul-bookscan-32x32.png differ diff --git a/eazy-bookscanner-swing/src/main/resources/images/logo-datazuul-bookscan-48x48.png b/eazy-bookscanner-swing/src/main/resources/images/logo-datazuul-bookscan-48x48.png new file mode 100644 index 0000000..c356954 Binary files /dev/null and b/eazy-bookscanner-swing/src/main/resources/images/logo-datazuul-bookscan-48x48.png differ diff --git a/eazy-bookscanner-swing/src/main/resources/images/splash-screen-bookscan-638x320.png b/eazy-bookscanner-swing/src/main/resources/images/splash-screen-bookscan-638x320.png new file mode 100644 index 0000000..ba253e2 Binary files /dev/null and b/eazy-bookscanner-swing/src/main/resources/images/splash-screen-bookscan-638x320.png differ