Introduction
QR code scanning has become essential in modern applications for payments, authentication, and data transfer. This article provides a complete implementation of a QR code scanner GUI in Java using webcam integration and popular libraries.
Project Setup
Maven Dependencies
<!-- pom.xml -->
<properties>
<javafx.version>21</javafx.version>
<openjfx.version>0.0.8</openjfx.version>
<webcam-capture.version>0.3.12</webcam-capture.version>
<zxing.version>3.5.1</zxing.version>
<controlsfx.version>11.1.2</controlsfx.version>
</properties>
<dependencies>
<!-- JavaFX -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
<!-- Webcam Capture -->
<dependency>
<groupId>com.github.sarxos</groupId>
<artifactId>webcam-capture</artifactId>
<version>${webcam-capture.version}</version>
</dependency>
<!-- QR Code Processing -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>${zxing.version}</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>${zxing.version}</version>
</dependency>
<!-- Additional UI Controls -->
<dependency>
<groupId>org.controlsfx</groupId>
<artifactId>controlsfx</artifactId>
<version>${controlsfx.version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>${openjfx.version}</version>
<configuration>
<mainClass>com.qrscanner.QRScannerApp</mainClass>
</configuration>
</plugin>
</plugins>
</build>
Core Scanner Implementation
QR Code Service
package com.qrscanner.service;
import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
public class QRCodeService {
private final MultiFormatReader reader;
private boolean isScanning;
public QRCodeService() {
this.reader = new MultiFormatReader();
this.isScanning = false;
}
public String decodeQRCode(BufferedImage image) {
if (image == null) {
return null;
}
try {
LuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
Map<DecodeHintType, Object> hints = new HashMap<>();
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
hints.put(DecodeHintType.PURE_BARCODE, Boolean.FALSE);
Result result = reader.decode(bitmap, hints);
return result.getText();
} catch (NotFoundException e) {
// No QR code found in the image
return null;
} catch (Exception e) {
System.err.println("Error decoding QR code: " + e.getMessage());
return null;
}
}
public String decodeQRCode(Image fxImage) {
BufferedImage bufferedImage = SwingFXUtils.fromFXImage(fxImage, null);
return decodeQRCode(bufferedImage);
}
public boolean isScanning() {
return isScanning;
}
public void setScanning(boolean scanning) {
this.isScanning = scanning;
}
}
Webcam Service
package com.qrscanner.service;
import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamResolution;
import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.scene.image.Image;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class WebcamService {
private Webcam webcam;
private final ObjectProperty<Image> currentFrame;
private final QRCodeService qrCodeService;
private ScheduledExecutorService executorService;
private boolean isRunning;
public WebcamService() {
this.currentFrame = new SimpleObjectProperty<>();
this.qrCodeService = new QRCodeService();
this.isRunning = false;
initializeWebcam();
}
private void initializeWebcam() {
try {
List<Webcam> webcams = Webcam.getWebcams();
if (webcams.isEmpty()) {
throw new RuntimeException("No webcams found");
}
// Use the default webcam
webcam = webcams.get(0);
// Set resolution
Dimension[] resolutions = WebcamResolution.HD.getSize();
webcam.setViewSize(resolutions[0]);
webcam.open();
} catch (Exception e) {
throw new RuntimeException("Failed to initialize webcam: " + e.getMessage(), e);
}
}
public void startCapture() {
if (isRunning) return;
isRunning = true;
qrCodeService.setScanning(true);
executorService = Executors.newSingleThreadScheduledExecutor();
executorService.scheduleAtFixedRate(this::captureFrame, 0, 100, TimeUnit.MILLISECONDS);
}
public void stopCapture() {
isRunning = false;
qrCodeService.setScanning(false);
if (executorService != null) {
executorService.shutdown();
try {
executorService.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
if (webcam != null && webcam.isOpen()) {
webcam.close();
}
}
private void captureFrame() {
if (!isRunning || !webcam.isOpen()) return;
try {
BufferedImage capturedImage = webcam.getImage();
if (capturedImage != null) {
Image fxImage = SwingFXUtils.toFXImage(capturedImage, null);
Platform.runLater(() -> {
currentFrame.set(fxImage);
});
// Process QR code in background
if (qrCodeService.isScanning()) {
processQRCode(capturedImage);
}
}
} catch (Exception e) {
System.err.println("Error capturing frame: " + e.getMessage());
}
}
private void processQRCode(BufferedImage image) {
String result = qrCodeService.decodeQRCode(image);
if (result != null) {
Platform.runLater(() -> {
// Notify listeners about QR code detection
// This will be handled by the controller
});
}
}
public ObjectProperty<Image> currentFrameProperty() {
return currentFrame;
}
public Image getCurrentFrame() {
return currentFrame.get();
}
public QRCodeService getQrCodeService() {
return qrCodeService;
}
public boolean isRunning() {
return isRunning;
}
public List<Webcam> getAvailableWebcams() {
return Webcam.getWebcams();
}
public void switchWebcam(Webcam newWebcam) {
stopCapture();
if (webcam != null && webcam.isOpen()) {
webcam.close();
}
webcam = newWebcam;
webcam.setViewSize(WebcamResolution.HD.getSize()[0]);
webcam.open();
startCapture();
}
}
Main Application GUI
Main Scanner GUI (QRScannerGUI.fxml)
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.geometry.Insets?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <?import javafx.scene.text.Font?> <VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.qrscanner.controller.QRScannerController" spacing="20" style="-fx-background-color: #2c3e50;" prefWidth="800" prefHeight="700"> <padding> <Insets top="20" right="20" bottom="20" left="20"/> </padding> <!-- Header --> <HBox spacing="20" alignment="CENTER_LEFT"> <Label text="QR Code Scanner" style="-fx-text-fill: white; -fx-font-size: 28; -fx-font-weight: bold;"/> <Region HBox.hgrow="ALWAYS"/> <Button fx:id="settingsButton" text="Settings" style="-fx-background-color: #34495e; -fx-text-fill: white;"/> </HBox> <!-- Camera Preview Section --> <VBox spacing="10" style="-fx-background-color: #34495e; -fx-background-radius: 10; -fx-padding: 20;"> <HBox spacing="10" alignment="CENTER_LEFT"> <Label text="Camera Preview" style="-fx-text-fill: white; -fx-font-size: 18; -fx-font-weight: bold;"/> <Region HBox.hgrow="ALWAYS"/> <ComboBox fx:id="cameraSelector" promptText="Select Camera" style="-fx-background-color: #2c3e50; -fx-text-fill: white;"/> </HBox> <!-- Camera View --> <BorderPane style="-fx-background-color: black; -fx-border-color: #7f8c8d; -fx-border-radius: 5;"> <center> <ImageView fx:id="cameraView" fitWidth="640" fitHeight="480" preserveRatio="true" style="-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 10, 0, 0, 0);"/> </center> <!-- Scanning Overlay --> <StackPane> <Rectangle fx:id="scanOverlay" width="200" height="200" fill="transparent" stroke="#00ff00" strokeWidth="2" strokeDashArray="10 5" visible="false"/> <Label fx:id="scanningLabel" text="Scanning..." style="-fx-text-fill: #00ff00; -fx-font-size: 16; -fx-font-weight: bold;" visible="false"/> </StackPane> </BorderPane> <!-- Camera Controls --> <HBox spacing="10" alignment="CENTER"> <Button fx:id="startButton" text="Start Camera" style="-fx-background-color: #27ae60; -fx-text-fill: white; -fx-font-weight: bold; -fx-padding: 10 20;"/> <Button fx:id="stopButton" text="Stop Camera" style="-fx-background-color: #e74c3c; -fx-text-fill: white; -fx-font-weight: bold; -fx-padding: 10 20;" disable="true"/> <Button fx:id="captureButton" text="Capture QR Code" style="-fx-background-color: #3498db; -fx-text-fill: white; -fx-font-weight: bold; -fx-padding: 10 20;" disable="true"/> </HBox> </VBox> <!-- Results Section --> <VBox spacing="10" style="-fx-background-color: #34495e; -fx-background-radius: 10; -fx-padding: 20;"> <Label text="Scan Results" style="-fx-text-fill: white; -fx-font-size: 18; -fx-font-weight: bold;"/> <HBox spacing="10"> <VBox spacing="10" HBox.hgrow="ALWAYS"> <Label text="Detected QR Code:" style="-fx-text-fill: #bdc3c7;"/> <TextArea fx:id="resultTextArea" promptText="QR code content will appear here..." style="-fx-background-color: #2c3e50; -fx-text-fill: white; -fx-border-color: #7f8c8d;" wrapText="true" prefRowCount="4"/> </VBox> <VBox spacing="10"> <Label text="Actions:" style="-fx-text-fill: #bdc3c7;"/> <Button fx:id="copyButton" text="Copy" style="-fx-background-color: #2980b9; -fx-text-fill: white;" disable="true"/> <Button fx:id="clearButton" text="Clear" style="-fx-background-color: #e67e22; -fx-text-fill: white;"/> <Button fx:id="saveButton" text="Save Result" style="-fx-background-color: #16a085; -fx-text-fill: white;" disable="true"/> </VBox> </HBox> <!-- Result Type Indicator --> <HBox spacing="10" alignment="CENTER_LEFT"> <Label text="Content Type:" style="-fx-text-fill: #bdc3c7;"/> <Label fx:id="contentTypeLabel" text="Unknown" style="-fx-text-fill: #f39c12; -fx-font-weight: bold;"/> <Region HBox.hgrow="ALWAYS"/> <Label fx:id="lastScanLabel" text="Last scan: Never" style="-fx-text-fill: #95a5a6; -fx-font-style: italic;"/> </HBox> </VBox> <!-- Status Bar --> <HBox spacing="10" style="-fx-background-color: #34495e; -fx-background-radius: 5; -fx-padding: 10;"> <Label fx:id="statusLabel" text="Ready to scan" style="-fx-text-fill: #2ecc71; -fx-font-weight: bold;"/> <Region HBox.hgrow="ALWAYS"/> <ProgressIndicator fx:id="progressIndicator" visible="false"/> <Label fx:id="versionLabel" text="v1.0.0" style="-fx-text-fill: #7f8c8d;"/> </HBox> </VBox>
Main Controller
package com.qrscanner.controller;
import com.qrscanner.service.QRCodeService;
import com.qrscanner.service.WebcamService;
import com.github.sarxos.webcam.Webcam;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.shape.Rectangle;
import javafx.stage.FileChooser;
import javafx.util.Duration;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.ResourceBundle;
public class QRScannerController implements Initializable {
@FXML private ImageView cameraView;
@FXML private ComboBox<String> cameraSelector;
@FXML private Button startButton;
@FXML private Button stopButton;
@FXML private Button captureButton;
@FXML private Button copyButton;
@FXML private Button clearButton;
@FXML private Button saveButton;
@FXML private Button settingsButton;
@FXML private TextArea resultTextArea;
@FXML private Label statusLabel;
@FXML private Label contentTypeLabel;
@FXML private Label lastScanLabel;
@FXML private Label scanningLabel;
@FXML private ProgressIndicator progressIndicator;
@FXML private Rectangle scanOverlay;
private WebcamService webcamService;
private QRCodeService qrCodeService;
private Timeline scanningAnimation;
private final StringProperty lastResult = new SimpleStringProperty("");
private static final DateTimeFormatter TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public void initialize(URL location, ResourceBundle resources) {
initializeServices();
setupUI();
setupEventHandlers();
setupAnimations();
loadAvailableCameras();
}
private void initializeServices() {
try {
webcamService = new WebcamService();
qrCodeService = webcamService.getQrCodeService();
// Bind camera view to webcam service
cameraView.imageProperty().bind(webcamService.currentFrameProperty());
} catch (Exception e) {
showError("Initialization Error", "Failed to initialize webcam service: " + e.getMessage());
}
}
private void setupUI() {
// Initial button states
stopButton.setDisable(true);
captureButton.setDisable(true);
copyButton.setDisable(true);
saveButton.setDisable(true);
// Setup result text area
resultTextArea.textProperty().addListener((obs, oldVal, newVal) -> {
boolean hasContent = newVal != null && !newVal.trim().isEmpty();
copyButton.setDisable(!hasContent);
saveButton.setDisable(!hasContent);
if (hasContent) {
analyzeContentType(newVal);
} else {
contentTypeLabel.setText("Unknown");
contentTypeLabel.setStyle("-fx-text-fill: #f39c12;");
}
});
}
private void setupEventHandlers() {
startButton.setOnAction(e -> startCamera());
stopButton.setOnAction(e -> stopCamera());
captureButton.setOnAction(e -> captureQRCode());
copyButton.setOnAction(e -> copyToClipboard());
clearButton.setOnAction(e -> clearResults());
saveButton.setOnAction(e -> saveResults());
settingsButton.setOnAction(e -> showSettings());
cameraSelector.setOnAction(e -> switchCamera());
}
private void setupAnimations() {
// Scanning animation
scanningAnimation = new Timeline(
new KeyFrame(Duration.ZERO, e -> scanningLabel.setVisible(true)),
new KeyFrame(Duration.seconds(0.5), e -> scanningLabel.setVisible(false)),
new KeyFrame(Duration.seconds(1.0), e -> scanningLabel.setVisible(true))
);
scanningAnimation.setCycleCount(Animation.INDEFINITE);
}
private void loadAvailableCameras() {
try {
List<Webcam> webcams = webcamService.getAvailableWebcams();
cameraSelector.getItems().clear();
for (int i = 0; i < webcams.size(); i++) {
Webcam webcam = webcams.get(i);
String cameraName = webcam.getName();
cameraSelector.getItems().add("Camera " + (i + 1) + ": " + cameraName);
}
if (!webcams.isEmpty()) {
cameraSelector.getSelectionModel().select(0);
}
} catch (Exception e) {
showError("Camera Error", "Failed to load available cameras: " + e.getMessage());
}
}
private void startCamera() {
try {
webcamService.startCapture();
startButton.setDisable(true);
stopButton.setDisable(false);
captureButton.setDisable(false);
scanOverlay.setVisible(true);
scanningAnimation.play();
updateStatus("Camera started - Scanning for QR codes...", "success");
} catch (Exception e) {
showError("Camera Start Error", "Failed to start camera: " + e.getMessage());
}
}
private void stopCamera() {
try {
webcamService.stopCapture();
startButton.setDisable(false);
stopButton.setDisable(true);
captureButton.setDisable(true);
scanOverlay.setVisible(false);
scanningAnimation.stop();
scanningLabel.setVisible(false);
updateStatus("Camera stopped", "info");
} catch (Exception e) {
showError("Camera Stop Error", "Failed to stop camera: " + e.getMessage());
}
}
private void captureQRCode() {
Image currentFrame = webcamService.getCurrentFrame();
if (currentFrame != null) {
progressIndicator.setVisible(true);
updateStatus("Processing QR code...", "info");
// Process in background thread
new Thread(() -> {
String result = qrCodeService.decodeQRCode(currentFrame);
Platform.runLater(() -> {
progressIndicator.setVisible(false);
if (result != null) {
handleQRCodeResult(result);
} else {
updateStatus("No QR code detected", "warning");
}
});
}).start();
}
}
private void handleQRCodeResult(String result) {
resultTextArea.setText(result);
lastResult.set(result);
// Update last scan time
String timestamp = LocalDateTime.now().format(TIME_FORMATTER);
lastScanLabel.setText("Last scan: " + timestamp);
updateStatus("QR code scanned successfully!", "success");
// Optional: Auto-copy to clipboard
// copyToClipboard();
}
private void analyzeContentType(String content) {
if (content.startsWith("http://") || content.startsWith("https://")) {
contentTypeLabel.setText("URL");
contentTypeLabel.setStyle("-fx-text-fill: #3498db;");
} else if (content.startsWith("BEGIN:VCARD")) {
contentTypeLabel.setText("Contact");
contentTypeLabel.setStyle("-fx-text-fill: #9b59b6;");
} else if (content.matches("^[\\w\\-\\.]+@([\\w-]+\\.)+[\\w-]{2,}$")) {
contentTypeLabel.setText("Email");
contentTypeLabel.setStyle("-fx-text-fill: #e74c3c;");
} else if (content.matches("^[\\+]?[0-9\\-\\s\\(\\)]{10,}$")) {
contentTypeLabel.setText("Phone");
contentTypeLabel.setStyle("-fx-text-fill: #2ecc71;");
} else if (content.length() < 50 && content.matches("^[A-Za-z0-9]+$")) {
contentTypeLabel.setText("Text");
contentTypeLabel.setStyle("-fx-text-fill: #f39c12;");
} else {
contentTypeLabel.setText("Unknown");
contentTypeLabel.setStyle("-fx-text-fill: #95a5a6;");
}
}
private void copyToClipboard() {
String content = resultTextArea.getText();
if (content != null && !content.trim().isEmpty()) {
javafx.scene.input.Clipboard clipboard = javafx.scene.input.Clipboard.getSystemClipboard();
javafx.scene.input.ClipboardContent clipboardContent = new javafx.scene.input.ClipboardContent();
clipboardContent.putString(content);
clipboard.setContent(clipboardContent);
updateStatus("Copied to clipboard!", "success");
}
}
private void clearResults() {
resultTextArea.clear();
lastResult.set("");
lastScanLabel.setText("Last scan: Never");
updateStatus("Results cleared", "info");
}
private void saveResults() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Save QR Code Result");
fileChooser.getExtensionFilters().add(
new FileChooser.ExtensionFilter("Text Files", "*.txt")
);
fileChooser.setInitialFileName("qr_code_result_" +
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")) + ".txt");
File file = fileChooser.showSaveDialog(cameraView.getScene().getWindow());
if (file != null) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
writer.write("QR Code Scan Result\n");
writer.write("Scanned at: " + LocalDateTime.now().format(TIME_FORMATTER) + "\n");
writer.write("Content Type: " + contentTypeLabel.getText() + "\n");
writer.write("Content:\n" + resultTextArea.getText());
updateStatus("Results saved to: " + file.getName(), "success");
} catch (IOException e) {
showError("Save Error", "Failed to save results: " + e.getMessage());
}
}
}
private void switchCamera() {
int selectedIndex = cameraSelector.getSelectionModel().getSelectedIndex();
if (selectedIndex >= 0) {
try {
List<Webcam> webcams = webcamService.getAvailableWebcams();
if (selectedIndex < webcams.size()) {
webcamService.switchWebcam(webcams.get(selectedIndex));
updateStatus("Switched to camera: " + webcams.get(selectedIndex).getName(), "info");
}
} catch (Exception e) {
showError("Camera Switch Error", "Failed to switch camera: " + e.getMessage());
}
}
}
private void showSettings() {
// Implement settings dialog
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Settings");
alert.setHeaderText("QR Scanner Settings");
alert.setContentText("Settings dialog will be implemented here.");
alert.showAndWait();
}
private void updateStatus(String message, String type) {
Platform.runLater(() -> {
statusLabel.setText(message);
switch (type) {
case "success":
statusLabel.setStyle("-fx-text-fill: #2ecc71; -fx-font-weight: bold;");
break;
case "warning":
statusLabel.setStyle("-fx-text-fill: #f39c12; -fx-font-weight: bold;");
break;
case "error":
statusLabel.setStyle("-fx-text-fill: #e74c3c; -fx-font-weight: bold;");
break;
default:
statusLabel.setStyle("-fx-text-fill: #3498db; -fx-font-weight: bold;");
}
});
}
private void showError(String title, String message) {
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle(title);
alert.setHeaderText(null);
alert.setContentText(message);
alert.showAndWait();
});
}
public void shutdown() {
if (webcamService != null) {
webcamService.stopCapture();
}
if (scanningAnimation != null) {
scanningAnimation.stop();
}
}
}
Advanced Features
Image File QR Code Scanner
package com.qrscanner.service;
import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import javafx.stage.FileChooser;
import javafx.stage.Window;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class FileQRCodeService {
private final MultiFormatReader reader;
public FileQRCodeService() {
this.reader = new MultiFormatReader();
}
public String decodeQRCodeFromFile(File imageFile) throws IOException {
if (imageFile == null || !imageFile.exists()) {
throw new IOException("Image file does not exist");
}
try {
BufferedImage image = ImageIO.read(imageFile);
return decodeQRCode(image);
} catch (IOException e) {
throw new IOException("Failed to read image file: " + e.getMessage(), e);
}
}
public String decodeQRCode(BufferedImage image) {
try {
LuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
Map<DecodeHintType, Object> hints = new HashMap<>();
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
hints.put(DecodeHintType.POSSIBLE_FORMATS,
java.util.Arrays.asList(BarcodeFormat.QR_CODE));
Result result = reader.decode(bitmap, hints);
return result.getText();
} catch (NotFoundException e) {
return null; // No QR code found
} catch (Exception e) {
throw new RuntimeException("Error decoding QR code: " + e.getMessage(), e);
}
}
public File showImageFileChooser(Window ownerWindow) {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Select QR Code Image");
fileChooser.getExtensionFilters().addAll(
new FileChooser.ExtensionFilter("Image Files", "*.png", "*.jpg", "*.jpeg", "*.gif", "*.bmp"),
new FileChooser.ExtensionFilter("All Files", "*.*")
);
return fileChooser.showOpenDialog(ownerWindow);
}
}
Batch QR Code Processor
package com.qrscanner.service;
import javafx.concurrent.Task;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class BatchQRProcessor extends Task<List<BatchResult>> {
private final List<File> imageFiles;
private final FileQRCodeService qrService;
public BatchQRProcessor(List<File> imageFiles) {
this.imageFiles = new ArrayList<>(imageFiles);
this.qrService = new FileQRCodeService();
}
@Override
protected List<BatchResult> call() throws Exception {
List<BatchResult> results = new ArrayList<>();
int totalFiles = imageFiles.size();
for (int i = 0; i < totalFiles; i++) {
if (isCancelled()) {
break;
}
File file = imageFiles.get(i);
updateProgress(i + 1, totalFiles);
updateMessage("Processing: " + file.getName());
try {
String qrContent = qrService.decodeQRCodeFromFile(file);
results.add(new BatchResult(file, qrContent, true, "Success"));
} catch (Exception e) {
results.add(new BatchResult(file, null, false, e.getMessage()));
}
// Small delay to prevent UI freezing
Thread.sleep(50);
}
return results;
}
public static class BatchResult {
private final File file;
private final String content;
private final boolean success;
private final String message;
public BatchResult(File file, String content, boolean success, String message) {
this.file = file;
this.content = content;
this.success = success;
this.message = message;
}
// Getters
public File getFile() { return file; }
public String getContent() { return content; }
public boolean isSuccess() { return success; }
public String getMessage() { return message; }
}
}
Main Application Class
package com.qrscanner;
import com.qrscanner.controller.QRScannerController;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import java.util.Objects;
public class QRScannerApp extends Application {
private QRScannerController controller;
@Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/QRScannerGUI.fxml"));
Parent root = loader.load();
// Get controller for proper shutdown
controller = loader.getController();
Scene scene = new Scene(root);
scene.getStylesheets().add(Objects.requireNonNull(
getClass().getResource("/css/styles.css")).toExternalForm());
primaryStage.setTitle("QR Code Scanner");
primaryStage.getIcons().add(new Image(
Objects.requireNonNull(getClass().getResourceAsStream("/images/qr-icon.png"))));
primaryStage.setScene(scene);
primaryStage.setMinWidth(600);
primaryStage.setMinHeight(700);
// Handle window close
primaryStage.setOnCloseRequest(event -> {
if (controller != null) {
controller.shutdown();
}
});
primaryStage.show();
}
@Override
public void stop() throws Exception {
if (controller != null) {
controller.shutdown();
}
super.stop();
}
public static void main(String[] args) {
launch(args);
}
}
CSS Styling
/* styles.css */
.root {
-fx-font-family: "Segoe UI", Arial, sans-serif;
-fx-font-size: 14px;
}
/* Button Styles */
.button {
-fx-background-radius: 5;
-fx-border-radius: 5;
-fx-padding: 8 16;
-fx-cursor: hand;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 5, 0, 0, 2);
-fx-font-weight: bold;
}
.button:hover {
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.2), 8, 0, 0, 3);
}
.button:pressed {
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 2, 0, 0, 1);
}
.button:disabled {
-fx-opacity: 0.6;
-fx-cursor: default;
}
/* Text Area */
.text-area {
-fx-background-radius: 5;
-fx-border-radius: 5;
-fx-padding: 10;
}
.text-area .content {
-fx-background-radius: 5;
}
/* ComboBox */
.combo-box {
-fx-background-radius: 5;
-fx-border-radius: 5;
}
.combo-box .list-cell {
-fx-background-color: #2c3e50;
-fx-text-fill: white;
}
.combo-box .list-view {
-fx-background-color: #2c3e50;
-fx-background-radius: 5;
}
/* Progress Indicator */
.progress-indicator {
-fx-progress-color: #3498db;
}
/* Custom Scanner Overlay */
.scan-overlay {
-fx-stroke: #00ff00;
-fx-stroke-width: 2;
-fx-stroke-dash-array: 10 5;
}
/* Status Colors */
.status-success {
-fx-text-fill: #2ecc71;
-fx-font-weight: bold;
}
.status-warning {
-fx-text-fill: #f39c12;
-fx-font-weight: bold;
}
.status-error {
-fx-text-fill: #e74c3c;
-fx-font-weight: bold;
}
.status-info {
-fx-text-fill: #3498db;
-fx-font-weight: bold;
}
/* Content Type Labels */
.content-type-url {
-fx-text-fill: #3498db;
-fx-font-weight: bold;
}
.content-type-contact {
-fx-text-fill: #9b59b6;
-fx-font-weight: bold;
}
.content-type-email {
-fx-text-fill: #e74c3c;
-fx-font-weight: bold;
}
.content-type-phone {
-fx-text-fill: #2ecc71;
-fx-font-weight: bold;
}
.content-type-text {
-fx-text-fill: #f39c12;
-fx-font-weight: bold;
}
Build and Run
Build Script (build.sh)
#!/bin/bash # Build the QR Scanner application echo "Building QR Code Scanner..." # Clean previous build mvn clean # Compile and package mvn package echo "Build complete!" echo "Run the application with: java -jar target/qr-scanner.jar"
Run Configuration
// For development running
public class DevRunner {
public static void main(String[] args) {
// Set system properties for webcam capture
System.setProperty("webcam.debug", "false");
System.setProperty("webcam.autoopen", "true");
QRScannerApp.main(args);
}
}
Features Summary
- Real-time webcam QR code scanning
- Multiple camera support
- File-based QR code decoding
- Batch processing capabilities
- Content type detection
- Copy to clipboard functionality
- Result saving and export
- Modern JavaFX GUI
- Responsive design
- Error handling and status reporting
This QR code scanner provides a complete, professional solution for both real-time scanning and file-based decoding with an intuitive graphical interface.