From 3b9551a8c6661585666c21b9debb341f02254c5a Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Tue, 11 Mar 2025 16:21:27 +0200 Subject: [PATCH] replace sarxos/openimaj library with openpnp-capture library --- build.gradle | 24 +- drongo | 2 +- .../sparrow/control/QRScanDialog.java | 97 ++--- .../sparrow/control/WebcamResolution.java | 54 +++ .../sparrow/control/WebcamScanDevice.java | 372 ------------------ .../sparrow/control/WebcamScanDriver.java | 45 --- .../sparrow/control/WebcamService.java | 151 ++++--- src/main/java/module-info.java | 4 +- src/main/resources/logback.xml | 4 - 9 files changed, 199 insertions(+), 554 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/control/WebcamResolution.java delete mode 100644 src/main/java/com/sparrowwallet/sparrow/control/WebcamScanDevice.java delete mode 100644 src/main/java/com/sparrowwallet/sparrow/control/WebcamScanDriver.java diff --git a/build.gradle b/build.gradle index fade4453..988a96b7 100644 --- a/build.gradle +++ b/build.gradle @@ -11,13 +11,11 @@ def osName = os.getFamilyName() if(os.macOsX) { osName = "osx" } -def targetName = "" def osArch = "x64" def releaseArch = "x86_64" if(System.getProperty("os.arch") == "aarch64") { osArch = "aarch64" releaseArch = "aarch64" - targetName = "-" + osArch } def headless = "true".equals(System.getProperty("java.awt.headless")) @@ -89,12 +87,7 @@ dependencies { implementation('com.fasterxml.jackson.core:jackson-databind:2.17.2') implementation('com.sparrowwallet:hummingbird:1.7.4') implementation('co.nstant.in:cbor:0.9') - implementation("com.nativelibs4java:bridj${targetName}:0.7-20140918-3") { - exclude group: 'com.google.android.tools', module: 'dx' - } - implementation("com.github.sarxos:webcam-capture${targetName}:0.3.13-SNAPSHOT") { - exclude group: 'com.nativelibs4java', module: 'bridj' - } + implementation('org.openpnp:openpnp-capture-java:0.0.28-2') implementation("io.matthewnelson.kotlin-components:kmp-tor:${vTor}-${vKmpTor}") { exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-common' } @@ -378,18 +371,11 @@ extraJavaModuleInfo { requires('org.slf4j') requires('com.fasterxml.jackson.databind') } - module("com.nativelibs4java:bridj${targetName}", 'com.nativelibs4java.bridj') { - exports('org.bridj') - exports('org.bridj.cpp') - requires('java.logging') - } - module("com.github.sarxos:webcam-capture${targetName}", 'com.github.sarxos.webcam.capture') { - exports('com.github.sarxos.webcam') - exports('com.github.sarxos.webcam.ds.buildin') - exports('com.github.sarxos.webcam.ds.buildin.natives') + module('org.openpnp:openpnp-capture-java', 'openpnp.capture.java') { + exports('org.openpnp.capture') + exports('org.openpnp.capture.library') requires('java.desktop') - requires('com.nativelibs4java.bridj') - requires('org.slf4j') + requires('com.sun.jna') } module('de.codecentric.centerdevice:centerdevice-nsmenufx', 'centerdevice.nsmenufx') { exports('de.codecentric.centerdevice') diff --git a/drongo b/drongo index 2468578e..e42931cd 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 2468578e723653344579aac857ee76d1a69fecde +Subproject commit e42931cd55bb99b19472499d27f13c7b5b6f6f82 diff --git a/src/main/java/com/sparrowwallet/sparrow/control/QRScanDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/QRScanDialog.java index 3af75517..15d9a05b 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/QRScanDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/QRScanDialog.java @@ -1,6 +1,6 @@ package com.sparrowwallet.sparrow.control; -import com.github.sarxos.webcam.*; +import com.google.common.base.Throwables; import com.sparrowwallet.drongo.*; import com.sparrowwallet.drongo.address.Address; import com.sparrowwallet.drongo.address.P2PKHAddress; @@ -47,6 +47,7 @@ import javafx.util.Duration; import javafx.util.StringConverter; import org.controlsfx.glyphfont.Glyph; import org.controlsfx.tools.Borders; +import org.openpnp.capture.CaptureDevice; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -80,7 +81,7 @@ public class QRScanDialog extends Dialog { private final DoubleProperty percentComplete = new SimpleDoubleProperty(0.0); - private final ObjectProperty webcamDeviceProperty = new SimpleObjectProperty<>(); + private final ObjectProperty webcamDeviceProperty = new SimpleObjectProperty<>(); public QRScanDialog() { this.urDecoder = new URDecoder(); @@ -91,7 +92,7 @@ public class QRScanDialog extends Dialog { webcamResolutionProperty.set(WebcamResolution.HD); } - this.webcamService = new WebcamService(webcamResolutionProperty.get(), null, new QRScanListener(), new ScanDelayCalculator()); + this.webcamService = new WebcamService(webcamResolutionProperty.get(), null); webcamService.setPeriod(Duration.millis(SCAN_PERIOD_MILLIS)); webcamService.setRestartOnFailure(false); WebcamView webcamView = new WebcamView(webcamService, Config.get().isMirrorCapture()); @@ -109,13 +110,13 @@ public class QRScanDialog extends Dialog { progressBar.setPadding(new Insets(0, 10, 0, 10)); progressBar.setPrefWidth(Integer.MAX_VALUE); progressBar.progressProperty().bind(percentComplete); - webcamService.openingProperty().addListener((observable, oldValue, newValue) -> { + webcamService.openingProperty().addListener((_, _, newValue) -> { if(percentComplete.get() <= 0.0) { Platform.runLater(() -> percentComplete.set(newValue ? 0.0 : -1.0)); } Platform.runLater(() -> { if(Config.get().getWebcamDevice() != null && webcamDeviceProperty.get() == null) { - for(WebcamDevice device : WebcamScanDriver.getFoundDevices()) { + for(CaptureDevice device : webcamService.getFoundDevices()) { if(device.getName().equals(Config.get().getWebcamDevice())) { webcamDeviceProperty.set(device); } @@ -123,6 +124,18 @@ public class QRScanDialog extends Dialog { } }); }); + webcamService.closedProperty().addListener((_, _, closed) -> { + if(closed && webcamResolutionProperty.get() != null) { + webcamService.setResolution(webcamResolutionProperty.get()); + webcamService.setDevice(webcamDeviceProperty.get()); + Platform.runLater(() -> { + if(!webcamService.isRunning()) { + webcamService.reset(); + webcamService.start(); + } + }); + } + }); VBox vBox = new VBox(20); vBox.getChildren().addAll(wrappedView, progressBar); @@ -131,45 +144,34 @@ public class QRScanDialog extends Dialog { webcamService.resultProperty().addListener(new QRResultListener()); webcamService.setOnFailed(failedEvent -> { - Throwable exception = failedEvent.getSource().getException(); - - Throwable nested = exception; - while(nested.getCause() != null) { - nested = nested.getCause(); - } - if(OsType.getCurrent() == OsType.WINDOWS && - nested.getMessage().startsWith("Library 'OpenIMAJGrabber' was not loaded successfully from file")) { - exception = new WebcamDependencyException("Your system is missing a dependency required for the webcam. Follow the link below for more details.\n\n[https://sparrowwallet.com/docs/faq.html#your-system-is-missing-a-dependency-for-the-webcam]", exception); - } else if(nested.getMessage().startsWith("Cannot start native grabber") && Config.get().getWebcamDevice() != null) { - exception = new WebcamOpenException("Cannot open configured webcam " + Config.get().getWebcamDevice() + ", reverting to the default webcam"); - Config.get().setWebcamDevice(null); - } - - final Throwable result = exception; - Platform.runLater(() -> setResult(new Result(result))); + Throwable exception = Throwables.getRootCause(failedEvent.getSource().getException()); + Platform.runLater(() -> setResult(new Result(exception))); }); webcamService.start(); - webcamResolutionProperty.addListener((observable, oldValue, newResolution) -> { + webcamResolutionProperty.addListener((_, _, newResolution) -> { if(newResolution != null) { setHeight(newResolution == WebcamResolution.HD ? (getHeight() - 100) : (getHeight() + 100)); EventManager.get().post(new WebcamResolutionChangedEvent(newResolution == WebcamResolution.HD)); } webcamService.cancel(); }); - webcamDeviceProperty.addListener((observable, oldValue, newValue) -> { + webcamDeviceProperty.addListener((_, _, newValue) -> { Config.get().setWebcamDevice(newValue.getName()); if(!Objects.equals(webcamService.getDevice(), newValue)) { webcamService.cancel(); } }); - setOnCloseRequest(event -> { + setOnCloseRequest(_ -> { boolean isHdCapture = (webcamResolutionProperty.get() == WebcamResolution.HD); if(Config.get().isHdCapture() != isHdCapture) { Config.get().setHdCapture(isHdCapture); } - Platform.runLater(() -> webcamResolutionProperty.set(null)); + Platform.runLater(() -> { + webcamResolutionProperty.set(null); + webcamService.close(); + }); }); final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Close", ButtonBar.ButtonData.CANCEL_CLOSE); @@ -685,37 +687,6 @@ public class QRScanDialog extends Dialog { } } - private class QRScanListener implements WebcamListener { - @Override - public void webcamOpen(WebcamEvent webcamEvent) { - - } - - @Override - public void webcamClosed(WebcamEvent webcamEvent) { - if(webcamResolutionProperty.get() != null) { - webcamService.setResolution(webcamResolutionProperty.get()); - webcamService.setDevice(webcamDeviceProperty.get()); - Platform.runLater(() -> { - if(!webcamService.isRunning()) { - webcamService.reset(); - webcamService.start(); - } - }); - } - } - - @Override - public void webcamDisposed(WebcamEvent webcamEvent) { - - } - - @Override - public void webcamImageObtained(WebcamEvent webcamEvent) { - - } - } - private class QRScanDialogPane extends DialogPane { @Override protected Node createButton(ButtonType buttonType) { @@ -735,15 +706,15 @@ public class QRScanDialog extends Dialog { button = hd; } else if(buttonType.getButtonData() == ButtonBar.ButtonData.HELP_2) { - ComboBox devicesCombo = new ComboBox<>(WebcamScanDriver.getFoundDevices()); + ComboBox devicesCombo = new ComboBox<>(webcamService.getFoundDevices()); devicesCombo.setConverter(new StringConverter<>() { @Override - public String toString(WebcamDevice device) { - return device instanceof WebcamScanDevice ? ((WebcamScanDevice)device).getDeviceName() : "Default Camera"; + public String toString(CaptureDevice device) { + return device != null && device.getName() != null ? device.getName().replaceAll(" \\(.*\\)", "") : "Default Camera"; } @Override - public WebcamDevice fromString(String string) { + public CaptureDevice fromString(String string) { throw new UnsupportedOperationException(); } }); @@ -993,10 +964,4 @@ public class QRScanDialog extends Dialog { super(message, cause); } } - - public static class ScanDelayCalculator implements WebcamUpdater.DelayCalculator { - public long calculateDelay(long snapshotDuration, double deviceFps) { - return Math.max(SCAN_PERIOD_MILLIS - snapshotDuration, 0L); - } - } } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WebcamResolution.java b/src/main/java/com/sparrowwallet/sparrow/control/WebcamResolution.java new file mode 100644 index 00000000..fa312305 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/control/WebcamResolution.java @@ -0,0 +1,54 @@ +package com.sparrowwallet.sparrow.control; + +import org.openpnp.capture.CaptureFormat; + +public enum WebcamResolution { + VGA(640, 480), + HD(1280, 720); + + private final int width; + private final int height; + + WebcamResolution(int width, int height) { + this.width = width; + this.height = height; + } + + public int getPixelsCount() { + return this.width * this.height; + } + + public int[] getAspectRatio() { + int factor = this.getCommonFactor(this.width, this.height); + int wr = this.width / factor; + int hr = this.height / factor; + return new int[] {wr, hr}; + } + + private int getCommonFactor(int width, int height) { + return height == 0 ? width : this.getCommonFactor(height, width % height); + } + + public int getWidth() { + return this.width; + } + + public int getHeight() { + return this.height; + } + + public String toString() { + int[] ratio = this.getAspectRatio(); + return super.toString() + ' ' + this.width + 'x' + this.height + " (" + ratio[0] + ':' + ratio[1] + ')'; + } + + public static WebcamResolution from(CaptureFormat captureFormat) { + for(WebcamResolution resolution : values()) { + if(captureFormat.getFormatInfo().width == resolution.width && captureFormat.getFormatInfo().height == resolution.height) { + return resolution; + } + } + + return null; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WebcamScanDevice.java b/src/main/java/com/sparrowwallet/sparrow/control/WebcamScanDevice.java deleted file mode 100644 index 5e0592b3..00000000 --- a/src/main/java/com/sparrowwallet/sparrow/control/WebcamScanDevice.java +++ /dev/null @@ -1,372 +0,0 @@ -package com.sparrowwallet.sparrow.control; - -import com.github.sarxos.webcam.*; -import com.github.sarxos.webcam.ds.buildin.natives.Device; -import com.github.sarxos.webcam.ds.buildin.natives.DeviceList; -import com.github.sarxos.webcam.ds.buildin.natives.OpenIMAJGrabber; -import org.bridj.Pointer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.awt.*; -import java.awt.color.ColorSpace; -import java.awt.image.*; -import java.nio.ByteBuffer; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -@SuppressWarnings("deprecation") -public class WebcamScanDevice implements WebcamDevice, WebcamDevice.BufferAccess, Runnable, WebcamDevice.FPSSource { - private static final Logger LOG = LoggerFactory.getLogger(WebcamScanDevice.class); - private static final int DEVICE_BUFFER_SIZE = 5; - private static final Dimension[] DIMENSIONS; - private static final int[] BAND_OFFSETS; - private static final int[] BITS; - private static final int[] OFFSET; - private static final int DATA_TYPE = 0; - private static final ColorSpace COLOR_SPACE; - public static final int SCAN_LOOP_WAIT_MILLIS = 100; - private int timeout = 5000; - private OpenIMAJGrabber grabber = null; - private Device device = null; - private Dimension size = null; - private ComponentSampleModel smodel = null; - private ColorModel cmodel = null; - private boolean failOnSizeMismatch = false; - private final AtomicBoolean disposed = new AtomicBoolean(false); - private final AtomicBoolean open = new AtomicBoolean(false); - private final AtomicBoolean fresh = new AtomicBoolean(false); - private Thread refresher = null; - private String name = null; - private String id = null; - private String fullname = null; - private long t1 = -1L; - private long t2 = -1L; - private volatile double fps = 0.0D; - - protected WebcamScanDevice(Device device) { - this.device = device; - this.name = device.getNameStr(); - this.id = device.getIdentifierStr(); - this.fullname = String.format("%s %s", this.name, this.id); - } - - public String getName() { - return this.fullname; - } - - public String getDeviceName() { - return this.name; - } - - public String getDeviceId() { - return this.id; - } - - public Device getDeviceRef() { - return this.device; - } - - public Dimension[] getResolutions() { - return DIMENSIONS; - } - - public Dimension getResolution() { - if (this.size == null) { - this.size = this.getResolutions()[0]; - } - - return this.size; - } - - public void setResolution(Dimension size) { - if (size == null) { - throw new IllegalArgumentException("Size cannot be null"); - } else if (this.open.get()) { - throw new IllegalStateException("Cannot change resolution when webcam is open, please close it first"); - } else { - this.size = size; - } - } - - public ByteBuffer getImageBytes() { - if (this.disposed.get()) { - LOG.debug("Webcam is disposed, image will be null"); - return null; - } else if (!this.open.get()) { - LOG.debug("Webcam is closed, image will be null"); - return null; - } else { - if (this.fresh.compareAndSet(false, true)) { - this.updateFrameBuffer(); - } - - LOG.trace("Webcam grabber get image pointer"); - Pointer image = this.grabber.getImage(); - this.fresh.set(false); - if (image == null) { - LOG.warn("Null array pointer found instead of image"); - return null; - } else { - int length = this.size.width * this.size.height * 3; - LOG.trace("Webcam device get buffer, read {} bytes", length); - return image.getByteBuffer((long)length); - } - } - } - - public void getImageBytes(ByteBuffer target) { - if (this.disposed.get()) { - LOG.debug("Webcam is disposed, image will be null"); - } else if (!this.open.get()) { - LOG.debug("Webcam is closed, image will be null"); - } else { - int minSize = this.size.width * this.size.height * 3; - int curSize = target.remaining(); - if (minSize > curSize) { - throw new IllegalArgumentException(String.format("Not enough remaining space in target buffer (%d necessary vs %d remaining)", minSize, curSize)); - } else { - if (this.fresh.compareAndSet(false, true)) { - this.updateFrameBuffer(); - } - - LOG.trace("Webcam grabber get image pointer"); - Pointer image = this.grabber.getImage(); - this.fresh.set(false); - if (image == null) { - LOG.warn("Null array pointer found instead of image"); - } else { - LOG.trace("Webcam device read buffer {} bytes", minSize); - image = image.validBytes((long)minSize); - image.getBytes(target); - } - } - } - } - - public BufferedImage getImage() { - ByteBuffer buffer = this.getImageBytes(); - if (buffer == null) { - LOG.error("Images bytes buffer is null!"); - return null; - } else { - byte[] bytes = new byte[this.size.width * this.size.height * 3]; - byte[][] data = new byte[][]{bytes}; - buffer.get(bytes); - DataBufferByte dbuf = new DataBufferByte(data, bytes.length, OFFSET); - WritableRaster raster = Raster.createWritableRaster(this.smodel, dbuf, (Point)null); - BufferedImage bi = new BufferedImage(this.cmodel, raster, false, (Hashtable)null); - bi.flush(); - return bi; - } - } - - public void open() { - if (!this.disposed.get()) { - LOG.debug("Opening webcam device {}", this.getName()); - if (this.size == null) { - this.size = this.getResolutions()[0]; - } - - if (this.size == null) { - throw new RuntimeException("The resolution size cannot be null"); - } else { - LOG.debug("Webcam device {} starting session, size {}", this.device.getIdentifierStr(), this.size); - this.grabber = new OpenIMAJGrabber(); - DeviceList list = (DeviceList)this.grabber.getVideoDevices().get(); - Iterator var2 = list.asArrayList().iterator(); - - while(var2.hasNext()) { - Device d = (Device)var2.next(); - d.getNameStr(); - d.getIdentifierStr(); - } - - boolean started = this.grabber.startSession(this.size.width, this.size.height, 50, Pointer.pointerTo(this.device)); - if (!started) { - throw new WebcamException("Cannot start native grabber!"); - } else { - this.grabber.setTimeout(this.timeout); - LOG.debug("Webcam device session started"); - Dimension size2 = new Dimension(this.grabber.getWidth(), this.grabber.getHeight()); - int w1 = this.size.width; - int w2 = size2.width; - int h1 = this.size.height; - int h2 = size2.height; - if (w1 != w2 || h1 != h2) { - if (this.failOnSizeMismatch) { - throw new WebcamException(String.format("Different size obtained vs requested - [%dx%d] vs [%dx%d]", w1, h1, w2, h2)); - } - - Object[] args = new Object[]{w1, h1, w2, h2, w2, h2}; - LOG.warn("Different size obtained vs requested - [{}x{}] vs [{}x{}]. Setting correct one. New size is [{}x{}]", args); - this.size = new Dimension(w2, h2); - } - - this.smodel = new ComponentSampleModel(0, this.size.width, this.size.height, 3, this.size.width * 3, BAND_OFFSETS); - this.cmodel = new ComponentColorModel(COLOR_SPACE, BITS, false, false, 1, 0); - LOG.debug("Clear memory buffer"); - this.clearMemoryBuffer(); - LOG.debug("Webcam device {} is now open", this); - this.open.set(true); - this.refresher = this.startFramesRefresher(); - } - } - } - } - - private void clearMemoryBuffer() { - for(int i = 0; i < 5; ++i) { - this.grabber.nextFrame(); - } - - } - - private Thread startFramesRefresher() { - Thread refresher = new Thread(this, String.format("frames-refresher-[%s]", this.id)); - refresher.setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance()); - refresher.setDaemon(true); - refresher.start(); - return refresher; - } - - public void close() { - if (this.open.compareAndSet(true, false)) { - LOG.debug("Closing webcam device"); - this.grabber.stopSession(); - } - } - - public void dispose() { - if (this.disposed.compareAndSet(false, true)) { - LOG.debug("Disposing webcam device {}", this.getName()); - this.close(); - } - } - - public void setFailOnSizeMismatch(boolean fail) { - this.failOnSizeMismatch = fail; - } - - public boolean isOpen() { - return this.open.get(); - } - - public int getTimeout() { - return this.timeout; - } - - public void setTimeout(int timeout) { - if (this.isOpen()) { - throw new WebcamException("Timeout must be set before webcam is open"); - } else { - this.timeout = timeout; - } - } - - private void updateFrameBuffer() { - LOG.trace("Next frame"); - if (this.t1 == -1L || this.t2 == -1L) { - this.t1 = System.currentTimeMillis(); - this.t2 = System.currentTimeMillis(); - } - - int result = (new WebcamScanDevice.NextFrameTask(this)).nextFrame(); - this.t1 = this.t2; - this.t2 = System.currentTimeMillis(); - this.fps = (4.0D * this.fps + (double)(1000L / (this.t2 - this.t1 + 1L))) / 5.0D; - if (result == -1) { - LOG.error("Timeout when requesting image!"); - } else if (result < -1) { - LOG.error("Error requesting new frame!"); - } - - } - - public void run() { - do { - try { - Thread.sleep(SCAN_LOOP_WAIT_MILLIS); - } catch(InterruptedException e) { - //ignore - } - - if (Thread.interrupted()) { - LOG.debug("Refresher has been interrupted"); - return; - } - - if (!this.open.get()) { - LOG.debug("Cancelling refresher"); - return; - } - - this.updateFrameBuffer(); - } while(this.open.get()); - - } - - public double getFPS() { - return this.fps; - } - - @Override - public boolean equals(Object o) { - if(this == o) { - return true; - } - if(o == null || getClass() != o.getClass()) { - return false; - } - WebcamScanDevice that = (WebcamScanDevice) o; - return Objects.equals(fullname, that.fullname); - } - - @Override - public int hashCode() { - return Objects.hash(fullname); - } - - static { - DIMENSIONS = new Dimension[]{WebcamResolution.QQVGA.getSize(), WebcamResolution.QVGA.getSize(), WebcamResolution.VGA.getSize()}; - BAND_OFFSETS = new int[]{0, 1, 2}; - BITS = new int[]{8, 8, 8}; - OFFSET = new int[]{0}; - COLOR_SPACE = ColorSpace.getInstance(1000); - } - - private class NextFrameTask extends WebcamTask { - private final AtomicInteger result = new AtomicInteger(0); - - public NextFrameTask(WebcamDevice device) { - super(device); - } - - public int nextFrame() { - try { - this.process(); - } catch (InterruptedException var2) { - WebcamScanDevice.LOG.debug("Image buffer request interrupted", var2); - } - - return this.result.get(); - } - - protected void handle() { - WebcamScanDevice device = (WebcamScanDevice)this.getDevice(); - if (device.isOpen()) { - try { - Thread.sleep(SCAN_LOOP_WAIT_MILLIS); - } catch(InterruptedException e) { - //ignore - } - - this.result.set(WebcamScanDevice.this.grabber.nextFrame()); - WebcamScanDevice.this.fresh.set(true); - } - } - } -} diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WebcamScanDriver.java b/src/main/java/com/sparrowwallet/sparrow/control/WebcamScanDriver.java deleted file mode 100644 index 79f01166..00000000 --- a/src/main/java/com/sparrowwallet/sparrow/control/WebcamScanDriver.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.sparrowwallet.sparrow.control; - -import com.github.sarxos.webcam.WebcamDevice; -import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDevice; -import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDriver; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; - -import java.util.ArrayList; -import java.util.List; - -public class WebcamScanDriver extends WebcamDefaultDriver { - private static final ObservableList webcamDevices = FXCollections.observableArrayList(); - private static boolean rescan; - - @Override - public List getDevices() { - if(rescan || webcamDevices.isEmpty()) { - List devices = super.getDevices(); - List scanDevices = new ArrayList<>(); - for(WebcamDevice device : devices) { - WebcamDefaultDevice defaultDevice = (WebcamDefaultDevice)device; - WebcamScanDevice scanDevice = new WebcamScanDevice(defaultDevice.getDeviceRef()); - if(scanDevices.stream().noneMatch(dev -> ((WebcamScanDevice)dev).getDeviceName().equals(scanDevice.getDeviceName()))) { - scanDevices.add(scanDevice); - } - } - - List newDevices = new ArrayList<>(scanDevices); - newDevices.removeAll(webcamDevices); - webcamDevices.addAll(newDevices); - webcamDevices.removeIf(device -> !scanDevices.contains(device)); - } - - return webcamDevices; - } - - public static ObservableList getFoundDevices() { - return webcamDevices; - } - - public static void rescan() { - rescan = true; - } -} diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WebcamService.java b/src/main/java/com/sparrowwallet/sparrow/control/WebcamService.java index 510852f4..20c6afe8 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/WebcamService.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/WebcamService.java @@ -1,6 +1,5 @@ package com.sparrowwallet.sparrow.control; -import com.github.sarxos.webcam.*; import com.google.zxing.*; import com.google.zxing.client.j2se.BufferedImageLuminanceSource; import com.google.zxing.common.HybridBinarizer; @@ -11,11 +10,18 @@ import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.concurrent.ScheduledService; import javafx.concurrent.Task; import javafx.embed.swing.SwingFXUtils; import javafx.scene.image.Image; import net.sourceforge.zbar.ZBar; +import org.openpnp.capture.CaptureDevice; +import org.openpnp.capture.CaptureFormat; +import org.openpnp.capture.CaptureStream; +import org.openpnp.capture.OpenPnpCapture; +import org.openpnp.capture.library.OpenpnpCaptureLibrary; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,38 +29,59 @@ import java.awt.*; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import java.awt.image.WritableRaster; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; public class WebcamService extends ScheduledService { private static final Logger log = LoggerFactory.getLogger(WebcamService.class); private WebcamResolution resolution; - private WebcamDevice device; - private final WebcamListener listener; - private final WebcamUpdater.DelayCalculator delayCalculator; + private CaptureDevice device; private final BooleanProperty opening = new SimpleBooleanProperty(false); + private final BooleanProperty closed = new SimpleBooleanProperty(false); private final ObjectProperty resultProperty = new SimpleObjectProperty<>(null); private static final int QR_SAMPLE_PERIOD_MILLIS = 200; - private Webcam cam; + private final OpenPnpCapture capture; + private CaptureStream stream; private long lastQrSampleTime; + private final ObservableList foundDevices = FXCollections.observableList(new ArrayList<>()); private final Reader qrReader; private final Bokmakierie bokmakierie; static { - Webcam.setDriver(new WebcamScanDriver()); + OpenpnpCaptureLibrary.INSTANCE.Cap_installCustomLogFunction((level, ptr) -> { + switch(level) { + case 0: + case 1: + case 2: + case 3: + log.error(ptr.getString(0).trim()); + break; + case 4: + case 5: + case 6: + log.info(ptr.getString(0).trim()); + break; + case 7: + log.debug(ptr.getString(0).trim()); + break; + case 8: + log.trace(ptr.getString(0).trim()); + break; + } + }); } - public WebcamService(WebcamResolution resolution, WebcamDevice device, WebcamListener listener, WebcamUpdater.DelayCalculator delayCalculator) { - this.resolution = resolution; - this.device = device; - this.listener = listener; - this.delayCalculator = delayCalculator; + public WebcamService(WebcamResolution requestedResolution, CaptureDevice requestedDevice) { + this.capture = new OpenPnpCapture(); + this.resolution = requestedResolution; + this.device = requestedDevice; this.lastQrSampleTime = System.currentTimeMillis(); this.qrReader = new QRCodeReader(); this.bokmakierie = new Bokmakierie(); @@ -62,50 +89,66 @@ public class WebcamService extends ScheduledService { @Override public Task createTask() { - return new Task() { + return new Task<>() { @Override protected Image call() throws Exception { try { - if(cam == null) { - List webcams = Webcam.getWebcams(1, TimeUnit.MINUTES); - if(webcams.isEmpty()) { - throw new UnsupportedOperationException("No camera available."); + if(stream == null) { + List devices = capture.getDevices(); + + List newDevices = new ArrayList<>(devices); + newDevices.removeAll(foundDevices); + foundDevices.addAll(newDevices); + foundDevices.removeIf(device -> !devices.contains(device)); + + if(foundDevices.isEmpty()) { + throw new UnsupportedOperationException("No cameras available"); } - cam = webcams.get(0); + CaptureDevice selectedDevice = foundDevices.getFirst(); if(device != null) { - for(Webcam webcam : webcams) { - if(webcam.getDevice().getName().equals(device.getName())) { - cam = webcam; + for(CaptureDevice webcam : foundDevices) { + if(webcam.getName().equals(device.getName())) { + selectedDevice = webcam; } } } else if(Config.get().getWebcamDevice() != null) { - for(Webcam webcam : webcams) { - if(webcam.getDevice().getName().equals(Config.get().getWebcamDevice())) { - cam = webcam; + for(CaptureDevice webcam : foundDevices) { + if(webcam.getName().equals(Config.get().getWebcamDevice())) { + selectedDevice = webcam; } } } - device = cam.getDevice(); + device = selectedDevice; - cam.setCustomViewSizes(resolution.getSize()); - cam.setViewSize(resolution.getSize()); - if(!Arrays.asList(cam.getWebcamListeners()).contains(listener)) { - cam.addWebcamListener(listener); + if(device.getFormats().isEmpty()) { + throw new UnsupportedOperationException("No resolutions supported by camera " + device.getName()); + } + + Map supportedResolutions = device.getFormats().stream() + .filter(f -> WebcamResolution.from(f) != null) + .collect(Collectors.toMap(WebcamResolution::from, Function.identity(), (u, v) -> u)); + + CaptureFormat format = supportedResolutions.get(resolution); + if(format == null) { + if(!supportedResolutions.isEmpty()) { + format = supportedResolutions.values().iterator().next(); + } else { + format = device.getFormats().getFirst(); + } + + log.warn("Could not get requested capture resolution, using " + format.getFormatInfo().width + "x" + format.getFormatInfo().height); } opening.set(true); - cam.open(true, delayCalculator); + stream = device.openStream(format); opening.set(false); + closed.set(false); } - BufferedImage originalImage = cam.getImage(); - if(originalImage == null) { - return null; - } - + BufferedImage originalImage = stream.capture(); CroppedDimension cropped = getCroppedDimension(originalImage); BufferedImage croppedImage = originalImage.getSubimage(cropped.x, cropped.y, cropped.length, cropped.length); BufferedImage framedImage = getFramedImage(originalImage, cropped); @@ -128,19 +171,24 @@ public class WebcamService extends ScheduledService { @Override public void reset() { - cam = null; + stream = null; super.reset(); } @Override public boolean cancel() { - if(cam != null && !cam.close()) { - cam.close(); + if(stream != null) { + stream.close(); + closed.set(true); } return super.cancel(); } + public void close() { + capture.close(); + } + private void readQR(BufferedImage wideImage, BufferedImage croppedImage) { Result result = readQR(wideImage); if(result == null) { @@ -235,33 +283,46 @@ public class WebcamService extends ScheduledService { } public int getCamWidth() { - return resolution.getSize().width; + return resolution.getWidth(); } public int getCamHeight() { - return resolution.getSize().height; + return resolution.getHeight(); } public void setResolution(WebcamResolution resolution) { this.resolution = resolution; } - public WebcamDevice getDevice() { + public CaptureDevice getDevice() { return device; } - public void setDevice(WebcamDevice device) { + public void setDevice(CaptureDevice device) { this.device = device; } - public boolean isOpening() { - return opening.get(); + public ObservableList getFoundDevices() { + return foundDevices; } public BooleanProperty openingProperty() { return opening; } + public BooleanProperty closedProperty() { + return closed; + } + + public static String fourCCToString(int fourCC) { + return new String(new char[] { + (char) (fourCC >> 24 & 0xFF), + (char) ((fourCC >> 16) & 0xFF), + (char) ((fourCC >> 8) & 0xFF), + (char) ((fourCC) & 0xFF) + }); + } + private static class CroppedDimension { public int x; public int y; diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index eba562f2..73d025e3 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -42,12 +42,11 @@ open module com.sparrowwallet.sparrow { requires com.h2database; requires com.sparrowwallet.hummingbird; requires org.fxmisc.flowless; - requires com.github.sarxos.webcam.capture; + requires openpnp.capture.java; requires centerdevice.nsmenufx; requires org.jcommander; requires jul.to.slf4j; requires net.sourceforge.javacsv; - requires com.nativelibs4java.bridj; requires org.reactfx.reactfx; requires dev.bwt.jni; requires io.reactivex.rxjava2; @@ -66,4 +65,5 @@ open module com.sparrowwallet.sparrow { requires com.jcraft.jzlib; requires com.sparrowwallet.tern; requires com.sparrowwallet.lark; + requires com.sun.jna; } \ No newline at end of file diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index cd590a15..5a93e64f 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,9 +1,6 @@ - - - @@ -34,7 +31,6 @@ -