commit 24/03/2025

This commit is contained in:
rdkartono
2025-03-24 15:42:39 +07:00
parent 58194d8979
commit fbb68b4da7
14 changed files with 1216 additions and 580 deletions

View File

@@ -14,9 +14,8 @@ public class AudioPlayer {
public void WaitUntilFinished(){
while(currentFileHandle!=0){
try {
Thread.sleep(100);
Thread.sleep(10);
} catch (InterruptedException ignored) {
}
}
}
@@ -102,9 +101,8 @@ public class AudioPlayer {
* Play Audio File
* @param filename File to be played
* @param playbackstatus PlaybackStatus callback
* @return true if success, false if failed
*/
public boolean PlayFile(final String filename, final PlaybackStatus playbackstatus){
public void PlayFile(final String filename, final PlaybackStatus playbackstatus){
if (inited && filename!=null && !filename.isEmpty()){
int filehandle = bass.BASS_StreamCreateFile(false, filename, 0, 0, 0);
if (filehandle!=0){
@@ -136,11 +134,18 @@ public class AudioPlayer {
currentFile = "";
currentFileHandle = 0;
}).start();
return true;
} else Logger.error("AudioPlayer PlayFile failed, BASS_ChannelStart failed, error code: "+bass.BASS_ErrorGetCode());
} else Logger.error("AudioPlayer PlayFile failed, BASS_StreamCreateFile failed, error code: "+bass.BASS_ErrorGetCode());
} else Logger.info("AudioPlayer PlayFile failed, AudioPlayer is not initialized");
return false;
} else {
Logger.error("AudioPlayer PlayFile failed, BASS_ChannelStart failed, error code: "+bass.BASS_ErrorGetCode());
if (playbackstatus!=null) playbackstatus.onPlaybackFailure(filename);
}
} else {
Logger.error("AudioPlayer PlayFile failed, BASS_StreamCreateFile failed, error code: "+bass.BASS_ErrorGetCode());
if (playbackstatus!=null) playbackstatus.onPlaybackFailure(filename);
}
} else {
Logger.info("AudioPlayer PlayFile failed, AudioPlayer is not initialized");
if (playbackstatus!=null) playbackstatus.onPlaybackFailure(filename);
}
}
}

View File

@@ -4,11 +4,12 @@ public interface LiveCamEvent {
void onDetectedQRCode(String qrCode);
void onFrontalFaceDetector(boolean hasface, int width, int height);
void onProfileFaceDetector(boolean hasface, int width, int height);
void onEyeDetector(boolean hasEye, int width, int height);
void onEyeDetector(boolean hasEye);
void onLeftEarDetector(boolean hasLeftEar, int width, int height);
void onRightEarDetector(boolean hasRightEar, int width, int height);
void onLeftEyeDetector(boolean hasLeftEye, int width, int height);
void onRightEyeDetector(boolean hasRightEye, int width, int height);
void onLog(String log);
void onBlink(int counter);
void onStartCapturing();
}

View File

@@ -45,6 +45,11 @@ public class ConfigFile {
private @Getter int cascadeMinSize;
private @Getter int cascadeMaxSize;
private @Getter boolean MirrorCamera = false;
private @Getter boolean FlipCamera = false;
private @Getter double SharpnessThreshold;
private boolean needsave = false;
public ConfigFile(){
@@ -53,6 +58,27 @@ public class ConfigFile {
Load();
}
public void setMirrorCamera(boolean value){
if (MirrorCamera != value){
MirrorCamera = value;
needsave = true;
}
}
public void setFlipCamera(boolean value){
if (FlipCamera != value){
FlipCamera = value;
needsave = true;
}
}
public void setSharpnessThreshold(double value){
if (SharpnessThreshold != value){
SharpnessThreshold = value;
needsave = true;
}
}
public void setCascadeScaleFactor(double value){
if (cascadeScaleFactor != value){
cascadeScaleFactor = value;
@@ -554,6 +580,10 @@ public class ConfigFile {
if (prop.getProperty("FTPPass") == null) allcorrect = false;
if (prop.getProperty("FTPPath") == null) allcorrect = false;
if (prop.getProperty("PhotoDirectory") == null) allcorrect = false;
if (prop.getProperty("MirrorCamera") == null) allcorrect = false;
if (prop.getProperty("FlipCamera") == null) allcorrect = false;
if (prop.getProperty("SharpnessThreshold") == null) allcorrect = false;
if (prop.getProperty(CameraConfigEnum.CameraConfigLeft90.toString()) == null) allcorrect = false;
if (prop.getProperty(CameraConfigEnum.CameraConfigLeft45.toString()) == null) allcorrect = false;
@@ -601,6 +631,11 @@ public class ConfigFile {
Detectors.setFaceMinSize(cascadeMinSize);
Detectors.setScaleFactor(cascadeScaleFactor);
MirrorCamera = toBoolean(prop.getProperty("MirrorCamera"));
FlipCamera = toBoolean(prop.getProperty("FlipCamera"));
SharpnessThreshold = toDouble(prop.getProperty("SharpnessThreshold"));
Logger.info("Config Loaded");
MakeDirectories();
return;
@@ -653,6 +688,9 @@ public class ConfigFile {
Detectors.setFaceMaxSize(cascadeMaxSize);
Detectors.setFaceMinSize(cascadeMinSize);
Detectors.setScaleFactor(cascadeScaleFactor);
MirrorCamera = false;
FlipCamera = false;
SharpnessThreshold = 75.0;
Logger.info("Default Config Created");
needsave = true;
Save();
@@ -709,6 +747,10 @@ public class ConfigFile {
prop.setProperty("cascadeMinSize", String.valueOf(cascadeMinSize));
prop.setProperty("cascadeMaxSize", String.valueOf(cascadeMaxSize));
prop.setProperty("MirrorCamera", String.valueOf(MirrorCamera));
prop.setProperty("FlipCamera", String.valueOf(FlipCamera));
prop.setProperty("SharpnessThreshold", String.valueOf(SharpnessThreshold));
try{
prop.store(new FileOutputStream(Path.of(currentDirectory, "config.properties").toString()), null);
Logger.info("Config Saved");

View File

@@ -15,6 +15,7 @@ import org.bytedeco.opencv.opencv_core.Mat;
import org.bytedeco.opencv.opencv_core.Rect;
import org.bytedeco.opencv.opencv_core.Size;
import org.bytedeco.opencv.opencv_core.UMat;
import org.opencv.core.MatOfDouble;
import org.tinylog.Logger;
import java.awt.image.BufferedImage;
@@ -31,6 +32,7 @@ import java.util.ArrayList;
import java.util.List;
import static org.bytedeco.opencv.global.opencv_core.CV_64F;
import static org.bytedeco.opencv.global.opencv_core.CV_64FC3;
@SuppressWarnings("unused")
public class SomeCodes {
@@ -229,6 +231,10 @@ public class SomeCodes {
}
}
public static boolean toBoolean(String x){
return x!=null && x.equalsIgnoreCase("true");
}
/**
* Check if string is valid IPV4 address
* @param ipaddress IPV4 address
@@ -620,30 +626,48 @@ public class SomeCodes {
if (values!=null && values.length>0){
double lowest = values[0];
for(double x : values){
if (x<lowest){
lowest = x;
if (x>=0){
if (x<lowest){
lowest = x;
}
}
}
return lowest;
}
return 0;
}
public static boolean ValidDouble(String x){
try{
double xx = Double.parseDouble(x);
return true;
} catch (Exception ignored){
}
return false;
}
public static double CalculateSharpness(UMat mat){
if (mat!=null && !mat.empty()){
UMat gray = new UMat();
opencv_imgproc.cvtColor(mat, gray, opencv_imgproc.COLOR_BGR2GRAY);
opencv_imgproc.equalizeHist(gray, gray);
UMat laplacian = new UMat();
opencv_imgproc.Laplacian(gray, laplacian, CV_64F);
UMat mean = new UMat(1,1, CV_64F);
UMat stddev = new UMat(1,1, CV_64F);
opencv_core.meanStdDev(laplacian, mean, stddev);
Mat _std = new Mat();
stddev.copyTo(_std);
return _std.ptr(0).getDouble() * _std.ptr(0).getDouble();
double value = _std.createIndexer().getDouble(0,0);
return Math.pow(value,2);
}
return 0;
}

View File

@@ -102,6 +102,17 @@ public class Cameradetail {
@FXML
private Slider exposureSlider;
@FXML
private Label face_indicator;
@FXML
private Label eye_indicator;
@FXML
private Label sharpness_indicator;
private Double sharpness_value = 0.0;
private @Getter final UMat BestMat = new UMat();
private @Getter final UMat LiveMat = new UMat();
private @Getter final UMat ReducedMat = new UMat();
@@ -805,12 +816,7 @@ public class Cameradetail {
System.out.println("Camera "+cameratitle+" started");
Capturing.set(true);
// just information
String ss = String.format("Camera Started with resolution %dx%d@%d", BestSize.width(), BestSize.height(),LiveFPS);
Platform.runLater(()->setCameraStatus(ss));
raise_log(ss);
if (event!=null) event.onStartCapturing();
Task<Image> task = new Task<>() {
@SuppressWarnings("BusyWait")
@@ -826,7 +832,11 @@ public class Cameradetail {
TimerTask fpsTask = new TimerTask() {
@Override
public void run() {
LiveFPS = fps.getAndSet(0);
int fpsval = fps.getAndSet(0);
if (fpsval!=LiveFPS){
LiveFPS = fpsval;
if (event!=null) event.onStartCapturing();
}
}
};
@@ -884,15 +894,29 @@ public class Cameradetail {
fps.incrementAndGet();
UMat originalmat = new UMat();
mat.copyTo(originalmat); // copy to BestMat for using OpenCL
// revisi 18/03/2025
UMat flippedmat = new UMat();
opencv_core.flip(originalmat, flippedmat, 1); // flip horizontal
opencv_core.rotate(flippedmat, BestMat, opencv_core.ROTATE_90_COUNTERCLOCKWISE);
mat.copyTo(originalmat); // copy to originalmat for using OpenCL
if (config.isMirrorCamera()){
// revisi 18/03/2025
UMat flippedmat = new UMat();
opencv_core.flip(originalmat, flippedmat, 0); // flip vertical
flippedmat.copyTo(originalmat);
}
if (config.isFlipCamera()){
// revisi 18/03/2025
UMat flippedmat = new UMat();
opencv_core.flip(originalmat, flippedmat, 1); // flip horizontal
flippedmat.copyTo(originalmat);
}
// rotate 90 degree counter clockwise karena kamera potrait
opencv_core.rotate(originalmat, originalmat, opencv_core.ROTATE_90_COUNTERCLOCKWISE);
IsGrabbingLiveView.set(false);
if (!BestMat.empty()) {
opencv_imgproc.resize(BestMat, LiveMat, LiveSize); // resize to LiveSize
if (!originalmat.empty()) {
opencv_imgproc.resize(originalmat, LiveMat, LiveSize); // resize to LiveSize
UMat graymat = new UMat(); // use OpenCL for grayscale
opencv_imgproc.cvtColor(LiveMat,graymat, COLOR_BGR2GRAY); // convert to grayscale
if (use_qr){
@@ -950,6 +974,14 @@ public class Cameradetail {
no_face_counter = 0;
if (event!=null) event.onFrontalFaceDetector(true, _face_width, _face_height);
face_indicator.setVisible(true);
double sharpness = CalculateSharpness(originalmat);
if (sharpness>=config.getSharpnessThreshold()){
sharpness_value = sharpness;
originalmat.copyTo(BestMat);
Platform.runLater(()->sharpness_indicator.setText(String.format("%.2f", sharpness_value)));
}
if (theface.getFace()!=null){
LiveMatROI = new Rect(theface.getFace().x(), theface.getFace().y(), theface.getFace().width(), theface.getFace().height());
@@ -963,12 +995,18 @@ public class Cameradetail {
open_eye_counter++;
continue;
}
System.out.println("Valid Open Eyes");
//System.out.println("Valid Open Eyes");
if (eye_state==0){
if (eye_state!=1){
// transisi dari tutup mata ke buka mata
System.out.println("Transition from close to open eyes");
eye_state = 1;
if (event!=null) event.onEyeDetector(true);
eye_indicator.setVisible(true);
long now = System.currentTimeMillis();
if (waiting_for_second_blink){
long diff = now - last_blink;
@@ -984,7 +1022,6 @@ public class Cameradetail {
}
last_blink = now;
}
eye_state = 1;
} else {
// ada muka, tidak ada mata
// transisi dari buka mata ke tutup mata
@@ -993,15 +1030,26 @@ public class Cameradetail {
close_eye_counter++;
continue;
}
System.out.println("Valid Closed Eyes");
//System.out.println("Valid Closed Eyes");
if (eye_state!=0){
System.out.println("Transition from open to close eyes");
eye_state = 0;
if (event!=null) event.onEyeDetector(false);
eye_indicator.setVisible(false);
}
eye_state = 0;
}
} else if (have_left_45_face ){
no_face_counter = 0;
if (event!=null) event.onProfileFaceDetector(true, _face_width, _face_height);
face_indicator.setVisible(true);
double sharpness = CalculateSharpness(originalmat);
if (sharpness>=config.getSharpnessThreshold()){
sharpness_value = sharpness;
originalmat.copyTo(BestMat);
Platform.runLater(()->sharpness_indicator.setText(String.format("%.2f", sharpness_value)));
}
} else {
// no face detected, but let's not cancel the previous state immediately
@@ -1021,6 +1069,8 @@ public class Cameradetail {
if (event!=null) {
event.onFrontalFaceDetector(false, _face_width, _face_height);
event.onProfileFaceDetector(false, _face_width, _face_height);
face_indicator.setVisible(false);
eye_indicator.setVisible(false);
}
} else no_face_counter++;

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,11 @@ package id.co.gtc.erhacam;
import FTP.FTPCheck;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.stage.DirectoryChooser;
@@ -13,6 +16,7 @@ import org.bytedeco.javacv.VideoInputFrameGrabber;
import org.tinylog.Logger;
import java.io.File;
import java.util.Objects;
import static Config.SomeCodes.*;
@@ -39,9 +43,18 @@ public class SettingView {
@FXML
private TextField FTPPath;
@FXML
private TextField Sharpness;
@FXML
private TextField PhotoDirectoryPath;
@FXML
private CheckBox MirrorCamera;
@FXML
private CheckBox FlipCamera;
final FileChooser jfc = new FileChooser();
@@ -68,8 +81,20 @@ public class SettingView {
PhotoDirectoryPath.setText(path);
}
@FXML
private void SharpnessApply(){
String str = Sharpness.getText();
if (ValidDouble(str)){
config.setSharpnessThreshold(Double.parseDouble(str));
config.Save();
} else {
ShowAlert(Alert.AlertType.ERROR, "Sharpness Setting Error", "Sharpness Setting Error", "Sharpness Setting must be a number");
}
}
@FXML
private void CascadeSettingApply(){
String minsize = cascadeMinSize.getText();
String scalefactor = cascadeScaleFactor.getText();
String maxsize = cascadeMaxSize.getText();
@@ -104,6 +129,7 @@ public class SettingView {
} else show_cascade_alert("Max Size must not empty");
} else show_cascade_alert("Min Size must not empty");
}
private void show_cascade_alert(String content){
@@ -123,7 +149,17 @@ public class SettingView {
Logger.error("Unable to detect Cameras, Msg : "+e.getMessage());
}
MirrorCamera.selectedProperty().addListener(((observable, oldValue, newValue) -> {
System.out.println("Mirror option changed to : "+newValue);
config.setMirrorCamera(newValue);
config.Save();
}));
FlipCamera.selectedProperty().addListener((observable, oldValue, newValue) -> {
System.out.println("Flip option changed to : "+newValue);
config.setFlipCamera(newValue);
config.Save();
});
Platform.runLater(()->{
@@ -166,6 +202,12 @@ public class SettingView {
cascadeScaleFactor.setText(String.valueOf(config.getCascadeScaleFactor()));
cascadeMinSize.setText(String.valueOf(config.getCascadeMinSize()));
cascadeMaxSize.setText(String.valueOf(config.getCascadeMaxSize()));
MirrorCamera.setSelected(config.isMirrorCamera());
FlipCamera.setSelected(config.isFlipCamera());
Sharpness.setText(String.valueOf(config.getSharpnessThreshold()));
});
}

View File

@@ -5,7 +5,7 @@
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<AnchorPane prefHeight="720.0" prefWidth="360.0" xmlns="http://javafx.com/javafx/11.0.14-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="id.co.gtc.erhacam.Cameradetail">
<AnchorPane prefHeight="720.0" prefWidth="360.0" xmlns="http://javafx.com/javafx/17.0.12" xmlns:fx="http://javafx.com/fxml/1" fx:controller="id.co.gtc.erhacam.Cameradetail">
<children>
<GridPane layoutX="5.0" layoutY="5.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
@@ -22,6 +22,9 @@
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="20.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
@@ -37,6 +40,23 @@
<Label fx:id="camerastatus" alignment="CENTER" style="-fx-border-color: black;" text="Camera Status" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" style="-fx-border-color: black;" GridPane.columnIndex="2">
<children>
<Label fx:id="face_indicator" alignment="CENTER" layoutY="8.0" prefHeight="30.0" prefWidth="35.0" text="Face" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" style="-fx-border-color: black;" GridPane.columnIndex="3">
<children>
<Label fx:id="eye_indicator" alignment="CENTER" layoutY="8.0" prefHeight="30.0" prefWidth="35.0" text="Eye" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="4">
<children>
<Label fx:id="sharpness_indicator" alignment="CENTER" layoutX="7.0" layoutY="8.0" prefHeight="30.0" prefWidth="35.0" style="-fx-border-color: black;" text="0.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
</children>
</GridPane>
</children>
@@ -68,9 +88,9 @@
<Insets />
</GridPane.margin>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="1">
<AnchorPane prefHeight="200.0" prefWidth="200.0" scaleZ="0.0" GridPane.rowIndex="1">
<children>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="240.0" prefWidth="140.0" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="240.0" prefWidth="140.0" scaleZ="0.0" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label text="Brightness" />
<Slider fx:id="brightnessSlider" min="-100.0">

View File

@@ -3,7 +3,7 @@
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" prefWidth="1024.0" xmlns="http://javafx.com/javafx/11.0.14-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="id.co.gtc.erhacam.SettingView">
<AnchorPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" prefWidth="1024.0" xmlns="http://javafx.com/javafx/17.0.12" xmlns:fx="http://javafx.com/fxml/1" fx:controller="id.co.gtc.erhacam.SettingView">
<children>
<GridPane layoutX="70.0" layoutY="78.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
@@ -11,7 +11,7 @@
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="200.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="300.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="200.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
@@ -41,7 +41,7 @@
<children>
<GridPane>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="15.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="25.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="15.0" prefWidth="100.0" />
</columnConstraints>
@@ -51,6 +51,9 @@
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<AnchorPane prefHeight="200.0" prefWidth="200.0">
@@ -128,6 +131,41 @@
<Button layoutX="22.0" layoutY="8.0" mnemonicParsing="false" onAction="#ApplyCameraRight90" prefHeight="40.0" prefWidth="88.0" text="Apply" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="5">
<children>
<Label layoutX="60.0" prefHeight="40.0" prefWidth="175.0" text="Mirror Camera" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="5">
<children>
<CheckBox fx:id="MirrorCamera" layoutX="30.0" layoutY="6.0" mnemonicParsing="false" prefHeight="40.0" prefWidth="175.0" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="6">
<children>
<Label prefHeight="40.0" prefWidth="175.0" text="Flip Camera" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="6">
<children>
<CheckBox fx:id="FlipCamera" mnemonicParsing="false" prefHeight="40.0" prefWidth="175.0" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="7">
<children>
<Label layoutX="32.0" layoutY="19.0" prefHeight="37.0" prefWidth="92.0" text="Sharpness Threshold" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="7">
<children>
<TextField fx:id="Sharpness" layoutX="35.0" layoutY="2.0" prefHeight="37.0" prefWidth="368.0" promptText="if Sharpness below this number, photos considered as blurred" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane GridPane.columnIndex="2" GridPane.rowIndex="7">
<children>
<Button mnemonicParsing="false" onAction="#SharpnessApply" prefHeight="37.0" prefWidth="92.0" text="Apply" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
</children>
</GridPane>
<GridPane GridPane.columnIndex="1">
@@ -152,7 +190,7 @@
</AnchorPane>
<AnchorPane GridPane.rowIndex="1">
<children>
<ScrollPane layoutY="0.7999992370605469" prefHeight="200.8" prefWidth="410.4" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0">
<ScrollPane layoutX="5.0" prefHeight="200.8" prefWidth="410.4" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0">
<content>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="200.0" minWidth="0.0" prefWidth="350.0">
<children>