serratus / quaggaJS

An advanced barcode-scanner written in JavaScript
https://serratus.github.io/quaggaJS/
MIT License
5.04k stars 978 forks source link

Scanning Timeout & Proper focus. #365

Open ravimantra opened 5 years ago

ravimantra commented 5 years ago
  1. Can We handle a time out, if it does not scan with 5 or 8 seconds ? I need to pass a message "Not able to read barcode", if it does not read the barcode for 5 seconds.

  2. The scanning focus is not proper, sometimes it remains within barcode width or sometimes out of it. How can we customize it ?

ericblade commented 5 years ago
  1. use setTimeout

  2. I have absolutely no idea if there's any access to focus capabilities in the media api, but if you do find something, let me know. or are you asking about how to get quagga to read from a certain area? because that's in the quagga options.

ravimantra commented 5 years ago
  1. setTimeout helps only to hide the canvas, if it does not read within 5 secs. But i want it within "Quagga.onDetected" or "Quagga.onProcessed", if it does not detect.

  2. Alright, i can go with the option, if Quagga has an option to read from certain area. Please help me out on it, as it is very important for me. I tried with rectangle overlay, but didn't work for me.

ericblade commented 5 years ago

To turn off auto-location, specify locate: false, and then area. Area uses CSS like percentages

locate: false,
area: {
    top: '25%',
    right: '0%',
    left: '0%',
    bottom: '25%',
},

... Use a setTimeout() in the callback to Quagga.init(), and when the time has passed, do whatever you need to do when there's been no read. You're not going to get a callback to onDetected or onProcessed if there's nothing to process or detect.

ravimantra commented 5 years ago

@ericblade, thanks. I'll use setTimeout in "Quagga.init()" but the area just not seems to work in my case. I'm not able to see some rectangle box within for that particular area.

ericblade commented 5 years ago

You'll need to draw an outline for it, if that's what you want to do also

ravimantra commented 5 years ago

Didn't work, even i drew an outline :(

Quagga.init({
      inputStream: {
        name: 'Live',
        type: 'LiveStream',
        constraints: {
          width: 640,
          height: 480,
          aspectRatio: 1 / 1
        },
        locate: false,
        area: { // defines rectangle of the detection/localization area
          top: '25%',
          right: '0%',
          left: '0%',
          bottom: '25%'
        },
        singleChannel: false
      },
      debug: true,
      numOfWorkers: navigator.hardwareConcurrency,
      locate: true,
      locator: {
        patchSize: 'x-large',
        halfSample: true
      },
      decoder: {
        readers: [
          'ean_reader',
          'ean_8_reader'
        ],
        debug: {
          drawBoundingBox: true,
          drawScanline: true
        },
        multiple: false
      }
    }, () => {
      Quagga.start();
    });
    Quagga.onDetected(this.onDetected);
    Quagga.onProcessed((result) => {
      const drawingCtx = Quagga.canvas.ctx.overlay;
      const drawingCanvas = Quagga.canvas.dom.overlay;
      if (result) {
        if (result.boxes) {
          drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute('width')), parseInt(drawingCanvas.getAttribute('height'))); //eslint-disable-line
          result.boxes.filter(function (box) { //eslint-disable-line
            return box !== result.box;
          }).forEach(function (box) { //eslint-disable-line
            Quagga.ImageDebug.drawPath(box, { x: 0, y: 1 }, drawingCtx, { color: "green", lineWidth: 2}); //eslint-disable-line
          });
        }

        if (result.box) {
          Quagga.ImageDebug.drawPath(result.box, { x: 0, y: 1 }, drawingCtx, { color: '#00F', lineWidth: 2 });
        }

        if (result.codeResult && result.codeResult.code) {
          Quagga.ImageDebug.drawPath(result.line, { x: 'x', y: 'y' }, drawingCtx, { color: 'red', lineWidth: 3 });
        }
      }
    });
ericblade commented 5 years ago

i'm a bit busy to deconstruct the scanner that I use, but i'll provide the code .. js and css

import React from 'react';
import { connect } from 'react-redux';
import validateBarcode from './BarcodeValidator';

import { Link } from 'react-router-dom';

import Quagga from 'quagga';

import css from './Scanner.module.less';

import * as SettingSelector from '../store/reducers/settings';

import { doCameraScanNotify, enableTorch, disableTorch, lockOrientation } from '../store/actions';

class Scanner extends React.Component {
    state = {
        torch: false,
    }

    toggleTorch = () => {
        this.setState((prevState) => {
            const torch = !prevState.torch;
            if (torch) {
                this.props.torchOn();
            } else {
                this.props.torchOff();
            }
            return { torch };
        });
    }

    startQuagga() {
        const constraints = {
            width: {
                min: 640,
            },
            height: {
                min: 480,
            },
            aspectRatio: {
                min: 1,
                max: 2,
            },
            focusMode: 'continuous',
            ...(!this.props.cameraId && { facingMode: 'environment' }),
            ...(this.props.cameraId && { deviceId: this.props.cameraId }),
        };

        Quagga.init({
            inputStream: {
                type: 'LiveStream',
                target: document.querySelector('#scannerViewport'),
                constraints,
            },
            locator: {
                patchSize: 'medium',
                halfSample: true,
            },
            numOfWorkers: window.navigator.hardwareConcurrency || 2,
            decoder: {
                readers: ['upc_reader', 'ean_reader'],
            },
            locate: this.props.camScanLocate,
            area: {
                top: '25%',
                right: '0%',
                left: '0%',
                bottom: '25%',
            },
        }, (err) => {
            if (err) {
                console.error('*** QUAGGA ERROR', err);
                console.error('*** If you were not in the Scanner, then this error is meaningless.');
                return;
            }
            Quagga.onProcessed((result) => {
                const drawingCtx = Quagga.canvas.ctx.overlay;
                const drawingCanvas = Quagga.canvas.dom.overlay;

                if (result) {
                    // console.warn('* quagga onProcessed', result);
                    if (result.boxes) {
                        drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute('width')), parseInt(drawingCanvas.getAttribute('height')));
                        result.boxes.filter((box) => box !== result.box).forEach((box) => {
                            Quagga.ImageDebug.drawPath(box, { x: 0, y: 1 }, drawingCtx, { color: 'purple', lineWidth: 2 });
                        });
                    }
                    if (result.box) {
                        Quagga.ImageDebug.drawPath(result.box, { x: 0, y: 1 }, drawingCtx, { color: 'blue', lineWidth: 2 });
                    }
                    if (result.codeResult && result.codeResult.code) {
                        const validated = validateBarcode(result.codeResult.code);
                        Quagga.ImageDebug.drawPath(result.line, { x: 'x', y: 'y' }, drawingCtx, { color: validated ? 'green' : 'red', lineWidth: 3 });
                        drawingCtx.font = "24px Arial";
                        drawingCtx.fillStyle = validated ? 'green' : 'red';
                        drawingCtx.fillText(`${validated} ${result.codeResult.code}`, 10, 50);
                    }
                }
            });
            if (this.props.onDetected) {
                Quagga.onDetected(this.props.onDetected);
            } else {
                console.warn('* Quagga initialized, but no onDetected given');
            }
            Quagga.start();
        });
    }
    componentDidMount() {
        console.warn('*********** Scanner componentDidMount props', this.props);
        console.warn('* fullscreenEnabled', document.fullscreenEnabled);
        window.Quagga = Quagga;
        const element = document.querySelector('#scannerViewportContainer');
        console.warn('**** scanner container = ', element, element.requestFullscreen);
        if (element && element.requestFullscreen) {
            element.requestFullscreen()
                .then(() => {
                    this.props.setOrientationLock(true);
                    this.startQuagga();
                })
                .catch((err) => {
                    console.warn('*** error getting fullscreen', err);
                    this.startQuagga();
                })
        } else {
            this.startQuagga();
        }
    }

    componentWillUnmount() {
        // document.exitFullscreen(); // running this here results in "Document not active". weird.
        this.props.setOrientationLock(false);
        Quagga.offDetected(this.props.onDetected);
        Quagga.stop();
    }

    render() {
        return (
            <div id="scannerViewportContainer">
                <div id="scannerViewport" className={css.scannerViewport}/>
                <Link href to="/lookup">
                    <button className={css.backButton}>
                        Return to lookup
                    </button>
                </Link>
                <button onClick={this.toggleTorch} className={css.torchButton}>
                    {this.state.torch ? 'Light On' : 'Light Off'}
                </button>
                {
                    !this.props.camScanLocate ?
                        <div style={{ background: 'transparent', border: '1px solid yellow', position: 'absolute', top: '25%', left: '0%', right: '0%', bottom: '25%' }}>place barcode here</div>
                        :
                        null
                }
            </div>
        )
    }
}

class ScannerContainerBase extends React.Component {
    // https://github.com/serratus/quaggaJS/issues/237#issuecomment-389667599
    static getMedian(arr) {
        arr.sort((a, b) => a - b);
        const half = Math.floor(arr.length / 2);
        if (arr.length % 2 === 1) {
            return arr[half];
        }
        return (arr[half - 1] + arr[half]) / 2;
    }

    // TODO: setup an action to begin the download of data immediately from here,
    // we do *not* need to wait for the lookup page to get loaded!!! this will require
    // moving source of UPC to somewhere other than the URL, and may end up being difficult,
    // but the time savings will be useful.
    onSuccessScan = (result) => {
        try {
            document.exitFullscreen();
        } catch (err) {
            console.warn('**** error exiting fullscreen', err);
        }
        this.props.notifyScan();
        Quagga.offDetected(this.props.onDetected);
        this.props.torchOff();
        Quagga.stop();
        setTimeout(() => {
            // this.props.history.push(`/lookup?upc=${result.codeResult.code}`);
            this.props.history.push(`/lookup?upc=${result}`);
        }, 10);
    }

    onScan = (result) => {
        // TODO: NO FILTER THEN MAP.. USE A REDUCE.
        const errors = result.codeResult.decodedCodes.filter(x => x.error !== undefined).map(x => x.error);
        const median = ScannerContainerBase.getMedian(errors);
        if (median < 0.10) {
            if (validateBarcode(result.codeResult.code)) {
                this.onSuccessScan(result.codeResult.code);
            }
        }
    }

    render() {
        return (
            <div className={css.scanner}>
                {/* <button onClick={this.toggleScanning}>
                    { this.state.scanning ? 'Stop' : 'Start'}
                </button> */}
                {/* { this.state.scanning ? <Scanner onDetected={this.onScan} cameraId={this.props.defaultCamera} /> : null } */}
                <Scanner onDetected={this.onScan} cameraId={this.props.defaultCamera} torchOn={this.props.torchOn} torchOff={this.props.torchOff} camScanLocate={this.props.camScanLocate} setOrientationLock={this.props.setOrientationLock}/>
            </div>
        )
    }
}

const mapStateToProps = (state) => ({
    defaultCamera: SettingSelector.selectedCamera(state),
    camScanLocate: SettingSelector.camScanLocate(state),
});

const mapDispatchToProps = (dispatch) => ({
    notifyScan: () => dispatch(doCameraScanNotify()),
    torchOn: () => dispatch(enableTorch()),
    torchOff: () => dispatch(disableTorch()),
    setOrientationLock: (on) => dispatch(lockOrientation(on)),
});

const ScannerContainer = connect(mapStateToProps, mapDispatchToProps)(ScannerContainerBase);

export default ScannerContainer;
.scanner {
    border: 1px solid red;
}

.scannerViewport {
    border: 1px solid green;
}

/* .scannerViewport video {
    position: relative;
    left: -25%;
    border: 1px solid blue;
} */

.scannerViewport video,canvas {
    position: relative;
    top: 0px;
    left: 0px;
    width: 100%;
    height: 100%;
}

.scannerViewport canvas {
    position: absolute;
    top: 0px;
    left: 0px;
    width: 100%;
    height: 100%;
    border: 3px solid black;
}

.backButton {
    position: absolute;
    top: 0px;
    left: 0px;
    margin-left: 2px;
    margin-top: 2px;
    z-index: 9999 !important;
}

.torchButton {
    position: absolute;
    top: 0px;
    right: 0px;
    margin-right: 2px;
    margin-top: 2px;
    z-index: 9999 !important;
}