mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2025-03-17 13:22:40 +01:00
replace sarxos/openimaj library with openpnp-capture library
This commit is contained in:
parent
289a4453a4
commit
3b9551a8c6
24
build.gradle
24
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')
|
||||
|
2
drongo
2
drongo
@ -1 +1 @@
|
||||
Subproject commit 2468578e723653344579aac857ee76d1a69fecde
|
||||
Subproject commit e42931cd55bb99b19472499d27f13c7b5b6f6f82
|
@ -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<QRScanDialog.Result> {
|
||||
|
||||
private final DoubleProperty percentComplete = new SimpleDoubleProperty(0.0);
|
||||
|
||||
private final ObjectProperty<WebcamDevice> webcamDeviceProperty = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<CaptureDevice> webcamDeviceProperty = new SimpleObjectProperty<>();
|
||||
|
||||
public QRScanDialog() {
|
||||
this.urDecoder = new URDecoder();
|
||||
@ -91,7 +92,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||
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<QRScanDialog.Result> {
|
||||
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<QRScanDialog.Result> {
|
||||
}
|
||||
});
|
||||
});
|
||||
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<QRScanDialog.Result> {
|
||||
|
||||
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<QRScanDialog.Result> {
|
||||
}
|
||||
}
|
||||
|
||||
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<QRScanDialog.Result> {
|
||||
|
||||
button = hd;
|
||||
} else if(buttonType.getButtonData() == ButtonBar.ButtonData.HELP_2) {
|
||||
ComboBox<WebcamDevice> devicesCombo = new ComboBox<>(WebcamScanDriver.getFoundDevices());
|
||||
ComboBox<CaptureDevice> 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<QRScanDialog.Result> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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<Byte> 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<Byte> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<WebcamDevice> webcamDevices = FXCollections.observableArrayList();
|
||||
private static boolean rescan;
|
||||
|
||||
@Override
|
||||
public List<WebcamDevice> getDevices() {
|
||||
if(rescan || webcamDevices.isEmpty()) {
|
||||
List<WebcamDevice> devices = super.getDevices();
|
||||
List<WebcamDevice> 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<WebcamDevice> newDevices = new ArrayList<>(scanDevices);
|
||||
newDevices.removeAll(webcamDevices);
|
||||
webcamDevices.addAll(newDevices);
|
||||
webcamDevices.removeIf(device -> !scanDevices.contains(device));
|
||||
}
|
||||
|
||||
return webcamDevices;
|
||||
}
|
||||
|
||||
public static ObservableList<WebcamDevice> getFoundDevices() {
|
||||
return webcamDevices;
|
||||
}
|
||||
|
||||
public static void rescan() {
|
||||
rescan = true;
|
||||
}
|
||||
}
|
@ -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<Image> {
|
||||
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<Result> 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<CaptureDevice> 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<Image> {
|
||||
|
||||
@Override
|
||||
public Task<Image> createTask() {
|
||||
return new Task<Image>() {
|
||||
return new Task<>() {
|
||||
@Override
|
||||
protected Image call() throws Exception {
|
||||
try {
|
||||
if(cam == null) {
|
||||
List<Webcam> webcams = Webcam.getWebcams(1, TimeUnit.MINUTES);
|
||||
if(webcams.isEmpty()) {
|
||||
throw new UnsupportedOperationException("No camera available.");
|
||||
if(stream == null) {
|
||||
List<CaptureDevice> devices = capture.getDevices();
|
||||
|
||||
List<CaptureDevice> 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<WebcamResolution, CaptureFormat> 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<Image> {
|
||||
|
||||
@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<Image> {
|
||||
}
|
||||
|
||||
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<CaptureDevice> 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;
|
||||
|
@ -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;
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
<configuration>
|
||||
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
|
||||
|
||||
<logger name="com.github.sarxos.webcam.Webcam" level="OFF"/>
|
||||
<logger name="com.github.sarxos.webcam.ds.cgt.WebcamOpenTask" level="OFF"/>
|
||||
<logger name="com.github.sarxos.webcam.ds.cgt.WebcamCloseTask" level="OFF"/>
|
||||
<logger name="javafx.css" level="ERROR"/>
|
||||
<logger name="javafx.scene.focus" level="INFO"/>
|
||||
<logger name="sun.net.www.protocol.http.HttpURLConnection" level="INFO" />
|
||||
@ -34,7 +31,6 @@
|
||||
<logger name="org.eclipse.jetty.http.HttpParser" level="OFF" />
|
||||
<logger name="org.eclipse.jetty.util.log" level="OFF" />
|
||||
<logger name="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler" level="OFF" />
|
||||
<logger name="org.bitcoinj.crypto.MnemonicCode" level="OFF" />
|
||||
<logger name="org.springframework.core.KotlinDetector" level="OFF" />
|
||||
<logger name="org.springframework.http.converter.json.Jackson2ObjectMapperBuilder" level="OFF" />
|
||||
<logger name="org.springframework.web.HttpLogging" level="OFF" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user