reduce java-based cpu usage when scanning

This commit is contained in:
Craig Raw 2021-04-05 11:29:13 +02:00
parent e524396aaf
commit d635815607
3 changed files with 81 additions and 29 deletions

View file

@ -3,6 +3,7 @@ package com.sparrowwallet.sparrow.control;
import com.github.sarxos.webcam.WebcamEvent;
import com.github.sarxos.webcam.WebcamListener;
import com.github.sarxos.webcam.WebcamResolution;
import com.github.sarxos.webcam.WebcamUpdater;
import com.sparrowwallet.drongo.ExtendedKey;
import com.sparrowwallet.drongo.KeyDerivation;
import com.sparrowwallet.drongo.OutputDescriptor;
@ -39,6 +40,7 @@ import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.util.Duration;
import org.controlsfx.glyphfont.Glyph;
import org.controlsfx.tools.Borders;
import org.slf4j.Logger;
@ -67,6 +69,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
private static final Pattern PART_PATTERN = Pattern.compile("p(\\d+)of(\\d+) (.+)");
private static final int SCAN_PERIOD_MILLIS = 100;
private final ObjectProperty<WebcamResolution> webcamResolutionProperty = new SimpleObjectProperty<>(WebcamResolution.VGA);
private final DoubleProperty percentComplete = new SimpleDoubleProperty(0.0);
@ -79,7 +82,9 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
webcamResolutionProperty.set(WebcamResolution.HD);
}
this.webcamService = new WebcamService(webcamResolutionProperty.get(), new QRScanListener());
this.webcamService = new WebcamService(webcamResolutionProperty.get(), new QRScanListener(), new ScanDelayCalculator());
webcamService.setPeriod(Duration.millis(SCAN_PERIOD_MILLIS));
webcamService.setRestartOnFailure(false);
WebcamView webcamView = new WebcamView(webcamService);
final DialogPane dialogPane = new QRScanDialogPane();
@ -703,4 +708,10 @@ 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);
}
}
}

View file

@ -3,6 +3,7 @@ package com.sparrowwallet.sparrow.control;
import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamListener;
import com.github.sarxos.webcam.WebcamResolution;
import com.github.sarxos.webcam.WebcamUpdater;
import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
@ -10,7 +11,7 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.concurrent.Service;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image;
@ -19,16 +20,24 @@ import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
public class WebcamService extends Service<Image> {
public class WebcamService extends ScheduledService<Image> {
private WebcamResolution resolution;
private final WebcamListener listener;
private BooleanProperty opening = new SimpleBooleanProperty(false);
private final WebcamUpdater.DelayCalculator delayCalculator;
private final BooleanProperty opening = new SimpleBooleanProperty(false);
private final ObjectProperty<Result> resultProperty = new SimpleObjectProperty<>(null);
public WebcamService(WebcamResolution resolution, WebcamListener listener) {
private static final int QR_SAMPLE_PERIOD_MILLIS = 400;
private Webcam cam;
private long lastQrSampleTime;
public WebcamService(WebcamResolution resolution, WebcamListener listener, WebcamUpdater.DelayCalculator delayCalculator) {
this.resolution = resolution;
this.listener = listener;
this.delayCalculator = delayCalculator;
this.lastQrSampleTime = System.currentTimeMillis();
}
@Override
@ -36,35 +45,52 @@ public class WebcamService extends Service<Image> {
return new Task<Image>() {
@Override
protected Image call() throws Exception {
Webcam cam = Webcam.getWebcams(1, TimeUnit.MINUTES).get(0);
try {
cam.setCustomViewSizes(resolution.getSize());
cam.setViewSize(resolution.getSize());
if(!Arrays.asList(cam.getWebcamListeners()).contains(listener)) {
cam.addWebcamListener(listener);
if(cam == null) {
cam = Webcam.getWebcams(1, TimeUnit.MINUTES).get(0);
cam.setCustomViewSizes(resolution.getSize());
cam.setViewSize(resolution.getSize());
if(!Arrays.asList(cam.getWebcamListeners()).contains(listener)) {
cam.addWebcamListener(listener);
}
opening.set(true);
cam.open(true, delayCalculator);
opening.set(false);
}
opening.set(true);
cam.open();
opening.set(false);
while(!isCancelled()) {
if(cam.isImageNew()) {
BufferedImage bimg = cam.getImage();
updateValue(SwingFXUtils.toFXImage(bimg, null));
readQR(bimg);
}
BufferedImage bimg = cam.getImage();
Image image = SwingFXUtils.toFXImage(bimg, null);
updateValue(image);
if(System.currentTimeMillis() > (lastQrSampleTime + QR_SAMPLE_PERIOD_MILLIS)) {
readQR(bimg);
lastQrSampleTime = System.currentTimeMillis();
}
return getValue();
return image;
} finally {
opening.set(false);
if(!cam.close()) {
cam.close();
}
}
}
};
}
@Override
public void reset() {
cam = null;
super.reset();
}
@Override
public boolean cancel() {
if(cam != null && !cam.close()) {
cam.close();
}
return super.cancel();
}
private void readQR(BufferedImage bufferedImage) {
LuminanceSource source = new BufferedImageLuminanceSource(bufferedImage);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));

View file

@ -1,7 +1,10 @@
package com.sparrowwallet.sparrow.control;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Region;
import org.slf4j.Logger;
@ -14,7 +17,9 @@ public class WebcamView {
private final WebcamService service;
private final Region view;
private final Label statusPlaceholder ;
private final Label statusPlaceholder;
private final ObjectProperty<Image> imageProperty = new SimpleObjectProperty<>(null);
public WebcamView(WebcamService service) {
this.service = service ;
@ -23,22 +28,32 @@ public class WebcamView {
// make the cam behave like a mirror:
imageView.setScaleX(-1);
service.valueProperty().addListener((observable, oldValue, newValue) -> {
if(newValue != null) {
imageProperty.set(newValue);
}
});
this.statusPlaceholder = new Label();
this.view = new Region() {
{
service.stateProperty().addListener((obs, oldState, newState) -> {
switch (newState) {
case READY:
statusPlaceholder.setText("Initializing");
getChildren().setAll(statusPlaceholder);
if(imageProperty.get() == null) {
statusPlaceholder.setText("Initializing");
getChildren().setAll(statusPlaceholder);
}
break ;
case SCHEDULED:
statusPlaceholder.setText("Waiting");
getChildren().setAll(statusPlaceholder);
if(imageProperty.get() == null) {
statusPlaceholder.setText("Waiting");
getChildren().setAll(statusPlaceholder);
}
break ;
case RUNNING:
imageView.imageProperty().unbind();
imageView.imageProperty().bind(service.valueProperty());
imageView.imageProperty().bind(imageProperty);
getChildren().setAll(imageView);
break ;
case CANCELLED: