diff --git a/README.md b/README.md index bb220adc..2695b69f 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,18 @@ # Inji Verify -Injiverify is a web interface to verify the validity of the QR / credential using a browser from smartphone / tablet / computer. A user should be able to do primariliy 4 key actions - Scan, Validate, Fetch, Display. +Injiverify is a web interface to verify the validity of the QR / credential using a browser from smartphone / tablet / computer. A user should be able to do primariliy 4 key actions - Scan, Validate, Fetch, Display. # Contents: + This document contains the following sections: -* Installations -* Folder Structure -* Configuration -* Developer Setup -* Demo Setup -* Troubleshoot + +- Installations +- Folder Structure +- Configuration +- Developer Setup +- Demo Setup +- Troubleshoot + --- # Installations: @@ -20,40 +23,43 @@ Prerequisites: Can be installed using [nvm](https://github.com/nvm-sh/nvm). Run following commands to install node - ```shell - $ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash - $ nvm install 18 - ``` + ```shell + $ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash + $ nvm install 18 + ``` - **Docker** - - [Install on Ubuntu](https://docs.docker.com/engine/install/ubuntu/) - - [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/) - - [Other platforms](https://docs.docker.com/engine/install/) - + - [Install on Ubuntu](https://docs.docker.com/engine/install/ubuntu/) + - [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/) + - [Other platforms](https://docs.docker.com/engine/install/) + - **Docker Compose** - - ```Note: Requires installation of Docker. This step can be skippped if Docker desktop(Windows) is installed as it comes along with docker compose. Please install Docker using above links before proceeding for the installation of docker compose``` - - - [Install as plugin to docker command](https://docs.docker.com/compose/install/#scenario-two-install-the-compose-plugin) - - [Install the Compose standalone](https://docs.docker.com/compose/install/#scenario-three-install-the-compose-standalone) + + `Note: Requires installation of Docker. This step can be skippped if Docker desktop(Windows) is installed as it comes along with docker compose. Please install Docker using above links before proceeding for the installation of docker compose` + + - [Install as plugin to docker command](https://docs.docker.com/compose/install/#scenario-two-install-the-compose-plugin) + - [Install the Compose standalone](https://docs.docker.com/compose/install/#scenario-three-install-the-compose-standalone) Once installed, use Docker compose option below to run the Inji Verify application for a quick demo. + --- # Folder Structure: + Once the repo is cloned, following folders can be found under the inji-verify repository folder: -* **helm:** folder contains helm charts required to deploy on K8S -* **inji-verify:** contains the application source code, Dockerfile and docker-compose.yml files - * src (source code) - * Dockerfile - * docker-compose.yml -* **ui-test:** contains the ui automation tests +- **helm:** folder contains helm charts required to deploy on K8S +- **inji-verify:** contains the application source code, Dockerfile and docker-compose.yml files + - src (source code) + - Dockerfile + - docker-compose.yml +- **ui-test:** contains the ui automation tests --- # Configuration: + The configuration to the Inji Verify application can be passed using the .env file which is present inside the **inji-verify** folder. It accepts INTERNET_CONNECTIVITY_CHECK_ENDPOINT and INTERNET_CONNECTIVITY_CHECK_TIMEOUT variables at this moment. These are used to check the availability of the internet connection and can be configured when required. The default values are added in the .env file. @@ -63,6 +69,7 @@ It accepts INTERNET_CONNECTIVITY_CHECK_ENDPOINT and INTERNET_CONNECTIVITY_CHECK_ # Developer Setup: Once the repo is cloned, move into the inji-verify repository folder and run the following command to check out to the develop branch: + ```shell cd inji-verify # move into the repository folder git checkout develop @@ -70,36 +77,51 @@ cd inji-verify # contains source code, Dockerfile and docker-compose.yml ``` ### Development server: + To get a development server up and running, run the following commands: + ```shell npm install npm start ``` ### Run Docker Image: - (Note: Make sure that the following commands are run in the directory where Dockerfile is present) + +(Note: Make sure that the following commands are run in the directory where Dockerfile is present) Run the following commands to build and test the application as docker images + ```shell docker build -t : . -docker run -it -d -p 3000:80 --env-file ./.env --name inji-verify-dev : +docker run -it -d -p 3000:8000 --env-file ./.env --name inji-verify-dev : +``` + +To build the Docker image locally, use the following command. Ensure you are in the directory containing the Dockerfile: + +```shell +docker build -t inji-verify:local ``` Stop and delete the docker containers using the following commands: + ```shell docker stop inji-verify-dev docker rm inji-verify-dev ``` ### Run Using Docker Compose: - (Note: Make sure that the following commands are run in the directory where docker-compose.yml file is present) + +(Note: Make sure that the following commands are run in the directory where docker-compose.yml file is present) Use the above image `:` in the docker-compose.yml file and run the following commands to run as docker compose: + ```shell $ docker-compose up -d # if docker compose is installed as a standalone command. $ docker compose up -d # if docker compose is installed as a plugin to docker command ``` + To stop the application, run the following command: + ```shell $ docker-compose down # if docker compose is installed as a standalone command. $ docker compose down # if docker compose is installed as a plugin to docker command @@ -110,34 +132,49 @@ Once started, the application is accessible at http://localhost:3000. --- # Demo Setup: + This section helps to quickly get started with a demo of the Inji Verify application Once the repository is cloned, move into the inji-verify repository directory. -Choose one of the release branches that are currently available for the demo: -* release-0.8.0 -* release-0.8.1 -* release-0.9.0 -* master +Choose one of the branches that are currently available for the demo: + +release branches: +- release-0.8.x +- release-0.9.x + +tags : +- v0.9.0 +- v0.8.1 +- v0.8.0 + +active branches: +- master +- develop ```shell cd ./inji-verify # repository folder -git checkout release-0.9.0 # choose from any of the above branches +git checkout branchName/tagname # choose from any of the above branches ``` + To start the application, run the following commands: + ```shell $ cd ./inji-verify # source code folder $ docker-compose up -d # if docker compose is installed as a standalone command. $ docker compose up -d # if docker compose is installed as a plugin to docker command ``` + The application is now accessible at http://localhost:3000. Once the demo is done, cleanup using the following command: + ```shell $ docker-compose down # if docker compose is installed as a standalone command. $ docker compose down # if docker compose is installed as a plugin to docker command ``` # Troubleshoot: + This section contains some common problems that could occur during the setup and steps to resolve then: ## Issue with starting docker compose: @@ -145,13 +182,16 @@ This section contains some common problems that could occur during the setup and ``` no configuration file provided: not found ``` + or + ``` Can't find a suitable configuration file in this directory or any parent. Are you in the right directory? Supported filenames: docker-compose.yml, docker-compose.yaml, compose.yml, compose.yaml ``` + ### Solution: Make sure that you are in the right directory `inji-verify/inji-verify` and the docker-compose.yml file is present in this directory. @@ -159,16 +199,21 @@ Make sure that you are in the right directory `inji-verify/inji-verify` and the Check using `ls` command in ubuntu terminal or `dir` command in windows command prompt for the contents of the current directory ## Issue with ports: + ``` Error response from daemon: Ports are not available: exposing port TCP 0.0.0.0:3000 -> 0.0.0.0:0: listen tcp 0.0.0.0:80: bind: An attempt was made to access a socket in a way forbidden by its access permissions. ``` + ### Solution: Try updating the port in the docker-compose.yml file from 3000:80 to :80 and try again + ## Issue with building docker image: + ``` ERROR: failed to solve: failed to read dockerfile: no such file or directory ``` + ### Solution: Make sure that you are in the right directory `inji-verify/inji-verify` and the Dockerfile is present in this directory. @@ -176,15 +221,17 @@ Make sure that you are in the right directory `inji-verify/inji-verify` and the Check using `ls` command in ubuntu terminal or `dir` command in windows command prompt for the contents of the current directory ## Issue with docker engine: + ``` docker engine/socket not available ``` + ### Solution: In Windows: Start/Restart Docker desktop application In Ubuntu: Run the following command to make sure that the docker service is running + ```shell sudo systemctl restart docker.service ``` - diff --git a/inji-verify/Dockerfile b/inji-verify/Dockerfile index 8088d64b..258a9856 100644 --- a/inji-verify/Dockerfile +++ b/inji-verify/Dockerfile @@ -60,8 +60,8 @@ COPY --from=build /app/build /usr/share/nginx/html COPY ./nginx.conf /etc/nginx/conf.d/default.conf -# Expose port 80 -EXPOSE 80 +# Expose port 8000 +EXPOSE 8000 ENTRYPOINT [ "./configure_start.sh" ] diff --git a/inji-verify/docker-compose.yml b/inji-verify/docker-compose.yml index b0cab6f3..a245d659 100644 --- a/inji-verify/docker-compose.yml +++ b/inji-verify/docker-compose.yml @@ -4,7 +4,7 @@ services: inji-verify: image: mosipid/inji-verify:latest ports: - - "3000:80" + - "3000:8000" environment: - INTERNET_CONNECTIVITY_CHECK_ENDPOINT=${INTERNET_CONNECTIVITY_CHECK_ENDPOINT-https://dns.google/} - INTERNET_CONNECTIVITY_CHECK_TIMEOUT=${INTERNET_CONNECTIVITY_CHECK_TIMEOUT-10000} diff --git a/inji-verify/package.json b/inji-verify/package.json index 53ed7339..c188bb73 100644 --- a/inji-verify/package.json +++ b/inji-verify/package.json @@ -4,7 +4,6 @@ "private": true, "dependencies": { "@mosip/pixelpass": "0.1.6", - "@openhealthnz-credentials/pdf-image-qr-scanner": "1.0.2", "@reduxjs/toolkit": "^2.2.3", "@sunbird-rc/verification-sdk": "0.1.0", "@testing-library/jest-dom": "^5.17.0", @@ -15,8 +14,10 @@ "@types/react-dom": "^18.2.23", "@types/react-redux": "^7.1.33", "@types/redux-thunk": "^2.1.0", - "@yudiel/react-qr-scanner": "2.0.0-beta.3", + "html5-qrcode": "^2.3.8", + "jsqr": "^1.4.0", "patch-package": "^8.0.0", + "pdfjs-dist": "^4.5.136", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^5.2.1", diff --git a/inji-verify/src/components/Home/VerificationSection/QrScanner.tsx b/inji-verify/src/components/Home/VerificationSection/QrScanner.tsx index 012ff0e4..bebdcbef 100644 --- a/inji-verify/src/components/Home/VerificationSection/QrScanner.tsx +++ b/inji-verify/src/components/Home/VerificationSection/QrScanner.tsx @@ -1,105 +1,86 @@ -import React, {useEffect, useRef, useState} from 'react'; -import {Scanner} from '@yudiel/react-qr-scanner'; +import React, { useEffect, useRef, useState } from "react"; import CameraAccessDenied from "./CameraAccessDenied"; -import {ScanSessionExpiryTime} from "../../../utils/config"; -import {useAppDispatch} from "../../../redux/hooks"; -import {goHomeScreen, verificationInit} from "../../../redux/features/verification/verification.slice"; -import {raiseAlert} from "../../../redux/features/alerts/alerts.slice"; +import { ScanSessionExpiryTime } from "../../../utils/config"; +import { useAppDispatch } from "../../../redux/hooks"; +import { + goHomeScreen, + verificationInit, +} from "../../../redux/features/verification/verification.slice"; +import { raiseAlert } from "../../../redux/features/alerts/alerts.slice"; import "./ScanningLine.css"; +import { initiateQrScanning, terminateScanning } from "../../../utils/qr-utils"; let timer: NodeJS.Timeout; function QrScanner() { - const dispatch = useAppDispatch(); - const [isCameraBlocked, setIsCameraBlocked] = useState(false); + const dispatch = useAppDispatch(); + const [isCameraBlocked, setIsCameraBlocked] = useState(false); - const scannerRef = useRef(null); + const scannerRef = useRef(null); - useEffect(() => { - timer = setTimeout(() => { - dispatch(goHomeScreen({})); - dispatch(raiseAlert({ - open: true, - message: "The scan session has expired due to inactivity. Please initiate a new scan.", - severity: "error" - })) - }, ScanSessionExpiryTime); - return () => { - console.log('Clearing timeout'); - clearTimeout(timer) - }; - }, [dispatch]); + const onSuccess = (decodedText: any) => { + dispatch( + verificationInit({ + qrReadResult: { qrData: decodedText, status: "SUCCESS" }, + flow: "SCAN", + }) + ); + clearTimeout(timer); + }; + + useEffect(() => { + timer = setTimeout(() => { + dispatch(goHomeScreen({})); + dispatch( + raiseAlert({ + open: true, + message: + "The scan session has expired due to inactivity. Please initiate a new scan.", + severity: "error", + }) + ); + terminateScanning(); + }, ScanSessionExpiryTime); + initiateQrScanning(timer, onSuccess); + return () => { + console.log("Clearing timeout"); + clearTimeout(timer); + }; + }, [dispatch]); - useEffect(() => { - // Disable inbuilt border around the video - if (scannerRef?.current) { - let svgElements = scannerRef?.current?.getElementsByTagName('svg'); - if (svgElements.length === 1) { - svgElements[0].style.display = 'none'; - } - } - }, [scannerRef]); + useEffect(() => { + // Disable inbuilt border around the video + if (scannerRef?.current) { + let svgElements = scannerRef?.current?.getElementsByTagName("svg"); + if (svgElements.length === 1) { + svgElements[0].style.display = "none"; + } + } + }, [scannerRef]); - return ( -
- { - !isCameraBlocked && ( -
-
-
- ) - } - { - console.log(text, result); - dispatch(verificationInit({qrReadResult: {qrData: text, status: "SUCCESS"}, flow: "SCAN"})); - }} - onError={(error) => { - console.log('Clearing timeout - camera blocked'); - clearTimeout(timer); - setIsCameraBlocked(true); - }} - components={{ - torch: false - }} - options={{ - constraints: { - "width": { - "min": 640, - "ideal": 720, - "max": 1920 - }, - "height": { - "min": 640, - "ideal": 720, - "max": 1080 - }, - facingMode: "environment" - }, - delayBetweenScanSuccess: 1000000 // Scan once - }} - styles={{ - container: { - width: window.innerWidth < 1024 ? "250px" : "316px", - placeContent: "center", - display: "grid", - placeItems: "center", - borderRadius: "12px" - }, - video: { - objectFit: "cover", - objectPosition: "center" - } - }} - /> - { - console.log("closing camera"); - dispatch(goHomeScreen({})); - setIsCameraBlocked(false) - }}/> + return ( +
+ {!isCameraBlocked && ( +
+
- ); + )} + +
+ + { + console.log("closing camera"); + dispatch(goHomeScreen({})); + setIsCameraBlocked(false); + }} + /> +
+ ); } export default QrScanner; diff --git a/inji-verify/src/components/Home/VerificationSection/UploadQrCode.tsx b/inji-verify/src/components/Home/VerificationSection/UploadQrCode.tsx index f3fa39fa..6997d40a 100644 --- a/inji-verify/src/components/Home/VerificationSection/UploadQrCode.tsx +++ b/inji-verify/src/components/Home/VerificationSection/UploadQrCode.tsx @@ -82,11 +82,11 @@ export const UploadQrCode = ({displayMessage, className}: { displayMessage: stri return; } - dispatch(qrReadInit({method: "UPLOAD"})); scanFilesForQr(file) .then(scanResult => { if (scanResult.error) console.error(scanResult.error); if (!!scanResult.data) { + dispatch(qrReadInit({method: "UPLOAD"})); dispatch(raiseAlert({...AlertMessages.qrUploadSuccess, open: true})); dispatch(verificationInit({qrReadResult: {qrData: scanResult.data, status: "SUCCESS"}})); } else { diff --git a/inji-verify/src/components/Home/VerificationSection/Verification.tsx b/inji-verify/src/components/Home/VerificationSection/Verification.tsx index 449466ed..9139b66c 100644 --- a/inji-verify/src/components/Home/VerificationSection/Verification.tsx +++ b/inji-verify/src/components/Home/VerificationSection/Verification.tsx @@ -1,43 +1,49 @@ -import React from 'react'; +import React from "react"; import scanQr from "../../../assets/scanner-ouline.svg"; import Loader from "../../commons/Loader"; import QrScanner from "./QrScanner"; import StyledButton from "./commons/StyledButton"; -import {useAppDispatch} from "../../../redux/hooks"; -import {goHomeScreen} from "../../../redux/features/verification/verification.slice"; -import {VerificationSteps} from "../../../utils/config"; -import {useVerificationFlowSelector} from "../../../redux/features/verification/verification.selector"; +import { useAppDispatch } from "../../../redux/hooks"; +import { goHomeScreen } from "../../../redux/features/verification/verification.slice"; +import { VerificationSteps } from "../../../utils/config"; +import { useVerificationFlowSelector } from "../../../redux/features/verification/verification.selector"; +import { terminateScanning } from "../../../utils/qr-utils"; const Verification = () => { - const dispatch = useAppDispatch(); - const {activeScreen, method} = useVerificationFlowSelector(state => ({activeScreen: state.activeScreen, method: state.method})); - console.log({activeScreen}); - return ( -
-
- { - activeScreen === VerificationSteps[method].Verifying - ? () - : () - } -
-
- { - dispatch(goHomeScreen({})) - }}> - Back - -
-
- ); -} + const dispatch = useAppDispatch(); + const { activeScreen, method } = useVerificationFlowSelector((state) => ({ + activeScreen: state.activeScreen, + method: state.method, + })); + console.log({ activeScreen }); + return ( +
+
+ {activeScreen === VerificationSteps[method].Verifying ? ( + + ) : ( + + )} +
+
+ { + terminateScanning(); + dispatch(goHomeScreen({})); + }} + > + Back + +
+
+ ); +}; export default Verification; diff --git a/inji-verify/src/utils/pdfToQrData.js b/inji-verify/src/utils/pdfToQrData.js new file mode 100644 index 00000000..477828bc --- /dev/null +++ b/inji-verify/src/utils/pdfToQrData.js @@ -0,0 +1,57 @@ +import * as pdfjsLib from "pdfjs-dist/webpack"; +import jsQR from "jsqr"; + +const decodeQrCode = (imageDataUrl) => { + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => { + const canvas = document.createElement("canvas"); + canvas.width = img.width; + canvas.height = img.height; + const ctx = canvas.getContext("2d"); + ctx.drawImage(img, 0, 0); + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const decoded = jsQR(imageData.data, canvas.width, canvas.height); + if (decoded) { + resolve(decoded.data); + } else { + resolve(null); + } + }; + img.onerror = () => reject("Error loading image"); + img.src = imageDataUrl; + }); +}; + +export const pdfToQrData = async (file) => { + try { + const pdfData = await file.arrayBuffer(); + const pdf = await pdfjsLib.getDocument({ data: pdfData }).promise; + let qrData; + + for (let i = 1; i <= pdf.numPages; i++) { + const page = await pdf.getPage(i); + const viewport = page.getViewport({ scale: 2.0 }); + const canvas = document.createElement("canvas"); + const context = canvas.getContext("2d"); + canvas.height = viewport.height; + canvas.width = viewport.width; + const renderContext = { + canvasContext: context, + viewport: viewport, + }; + await page.render(renderContext).promise; + const dataURL = canvas.toDataURL("image/png"); + qrData = await decodeQrCode(dataURL); + + if (qrData) { + break; // Exit loop if QR code is found + } + } + + return qrData; + } catch (err) { + console.error("Error processing PDF:", err); + throw new Error("Failed to process PDF file."); + } +}; diff --git a/inji-verify/src/utils/qr-utils.js b/inji-verify/src/utils/qr-utils.js index b994a17c..1969c460 100644 --- a/inji-verify/src/utils/qr-utils.js +++ b/inji-verify/src/utils/qr-utils.js @@ -1,35 +1,77 @@ -import { scanFile } from "@openhealthnz-credentials/pdf-image-qr-scanner"; -import {decode, generateQRData} from '@mosip/pixelpass'; -import {HEADER_DELIMITER, SUPPORTED_QR_HEADERS} from "./config"; +import { decode, generateQRData } from "@mosip/pixelpass"; +import { HEADER_DELIMITER, SUPPORTED_QR_HEADERS } from "./config"; +import { Html5Qrcode } from "html5-qrcode"; +import { pdfToQrData } from "./pdfToQrData"; export const scanFilesForQr = async (selectedFile) => { - let scanResult = { data: null, error: null }; - try { - scanResult.data = await scanFile(selectedFile); - } catch (e) { - // Example Error Handling - if (e?.name === "InvalidPDFException") { - scanResult.error = "Invalid PDF"; - } else if (e instanceof Event) { - scanResult.error = "Invalid Image"; - } else { - scanResult.error = "Unknown error:" + e; - } + let scanResult = { data: null, error: null }; + const html5QrCode = new Html5Qrcode("upload-qr"); + try { + if (selectedFile.type === "application/pdf") { + const qrResult = await pdfToQrData(selectedFile); + scanResult.data = qrResult; + } else { + const qrData = await html5QrCode.scanFile(selectedFile); + scanResult.data = qrData; } - return scanResult; -} + } catch (e) { + // Example Error Handling + if (e?.name === "InvalidPDFException") { + scanResult.error = "Invalid PDF"; + } else if (e instanceof Event) { + scanResult.error = "Invalid Image"; + } else { + scanResult.error = "Unknown error:" + e; + } + } + return scanResult; +}; export const decodeQrData = (qrData) => { - if (!(!!qrData)) return; - let encodedData = qrData - if (!!HEADER_DELIMITER) { - const splitQrData = qrData.split(HEADER_DELIMITER); - const header = splitQrData[0]; - if (SUPPORTED_QR_HEADERS.indexOf(header) === -1) return; // throw some error and handle it - if (splitQrData.length !== 2) return; // throw some error and handle it - encodedData = splitQrData[1]; - } - return decode(encodedData); -} + if (!!!qrData) return; + let encodedData = qrData; + if (!!HEADER_DELIMITER) { + const splitQrData = qrData.split(HEADER_DELIMITER); + const header = splitQrData[0]; + if (SUPPORTED_QR_HEADERS.indexOf(header) === -1) return; // throw some error and handle it + if (splitQrData.length !== 2) return; // throw some error and handle it + encodedData = splitQrData[1]; + } + return decode(encodedData); +}; export const encodeData = (data) => generateQRData(data); + +let html5QrCode; + +export const initiateQrScanning = (timer, onSuccess) => { + const config = { + fps: 10, + disableFlip: false, + aspectRatio: 1.0, + }; + if (!html5QrCode?.getState()) { + html5QrCode = new Html5Qrcode("reader"); + const qrCodeSuccessCallback = (decodedText) => { + onSuccess(decodedText); + html5QrCode.stop(); + html5QrCode = null; + }; + + html5QrCode + .start({ facingMode: "environment" }, config, qrCodeSuccessCallback) + .catch((e) => { + console.error("Error occurred:", e.message); + clearTimeout(timer); + html5QrCode.stop(); + html5QrCode = null; + }); + } +}; + +export const terminateScanning = () => { + if (html5QrCode) { + html5QrCode.stop(); + html5QrCode = null; + } +}; diff --git a/samples/fictitious/Expired/expired-qr-1.jpg b/samples/fictitious/Expired/expired-qr-1.jpg new file mode 100644 index 00000000..4068550a Binary files /dev/null and b/samples/fictitious/Expired/expired-qr-1.jpg differ diff --git a/samples/fictitious/Expired/expired-qr-2.png b/samples/fictitious/Expired/expired-qr-2.png new file mode 100644 index 00000000..66b1e3fc Binary files /dev/null and b/samples/fictitious/Expired/expired-qr-2.png differ diff --git a/samples/fictitious/Invalid or Tampered/invalid-qr -1.png b/samples/fictitious/Invalid or Tampered/invalid-qr -1.png new file mode 100644 index 00000000..71538ae3 Binary files /dev/null and b/samples/fictitious/Invalid or Tampered/invalid-qr -1.png differ diff --git a/samples/fictitious/Working/valid-qr-1.jpg b/samples/fictitious/Working/valid-qr-1.jpg new file mode 100644 index 00000000..c522c2cc Binary files /dev/null and b/samples/fictitious/Working/valid-qr-1.jpg differ diff --git a/samples/fictitious/Working/valid-qr-2.pdf b/samples/fictitious/Working/valid-qr-2.pdf new file mode 100644 index 00000000..8f7efe21 Binary files /dev/null and b/samples/fictitious/Working/valid-qr-2.pdf differ diff --git a/samples/fictitious/blur qrs/blur-1.jpeg b/samples/fictitious/blur qrs/blur-1.jpeg new file mode 100644 index 00000000..a4665c4b Binary files /dev/null and b/samples/fictitious/blur qrs/blur-1.jpeg differ diff --git a/samples/fictitious/blur qrs/blur-2.jpeg b/samples/fictitious/blur qrs/blur-2.jpeg new file mode 100644 index 00000000..03aec355 Binary files /dev/null and b/samples/fictitious/blur qrs/blur-2.jpeg differ diff --git a/samples/fictitious/blur qrs/blur-3.jpg b/samples/fictitious/blur qrs/blur-3.jpg new file mode 100644 index 00000000..6199d8d1 Binary files /dev/null and b/samples/fictitious/blur qrs/blur-3.jpg differ diff --git a/samples/fictitious/blur qrs/blur-4.jpg b/samples/fictitious/blur qrs/blur-4.jpg new file mode 100644 index 00000000..cdfcf47b Binary files /dev/null and b/samples/fictitious/blur qrs/blur-4.jpg differ diff --git a/samples/fictitious/blur qrs/blur-5.jpg b/samples/fictitious/blur qrs/blur-5.jpg new file mode 100644 index 00000000..65d3c7cc Binary files /dev/null and b/samples/fictitious/blur qrs/blur-5.jpg differ diff --git a/samples/fictitious/blur qrs/blur-6.jpg b/samples/fictitious/blur qrs/blur-6.jpg new file mode 100644 index 00000000..55424a5a Binary files /dev/null and b/samples/fictitious/blur qrs/blur-6.jpg differ diff --git a/samples/fictitious/blur qrs/blur-7.png b/samples/fictitious/blur qrs/blur-7.png new file mode 100644 index 00000000..a5533b0b Binary files /dev/null and b/samples/fictitious/blur qrs/blur-7.png differ diff --git a/samples/fictitious/blur qrs/blur-8.png b/samples/fictitious/blur qrs/blur-8.png new file mode 100644 index 00000000..f431a687 Binary files /dev/null and b/samples/fictitious/blur qrs/blur-8.png differ