html5-qrcode icon indicating copy to clipboard operation
html5-qrcode copied to clipboard

Inverted QR codes

Open doTeam opened this issue 4 years ago • 24 comments

Would it be possible to add functionality to scan inverted QR codes as well? Inverted meaning dark background and white foreground; currently it is not supported.

I thought about adding such functionality thru inverting canvas, but I'd first like your input if there are any better solutions that could be implemented library-wide, seems like quite a missing functionality.

doTeam avatar Jul 29 '20 09:07 doTeam

This is interesting - are there use-cases like that where inverted QR codes are used?

If there is a major use case and you can provide me with few samples - it should be possible to support this. One quick workaround to try is to invert the canvas as you shared.

mebjas avatar Aug 02 '20 09:08 mebjas

I've faced with the same problem. How can I change in the library to make it read the white QR codes?

OlegKireev avatar Mar 31 '21 09:03 OlegKireev

I've faced with the same problem. How can I change in the library to make it read the white QR codes?

I have made it work simply by inverting the colours of the canvas each time it scans and it doesn't detect normal code. I can't really make a pull request, because I hotfixed it on a refactored version, but here's the snippet (line ~1310):

https://pastebin.com/7DBcWpr3

The relevant part is the inverting of canvas (_context.filter = 'invert(100%)')

I'm sure this is not the most reliable fix, but it works wonders for us. Hope it will for you too

thk-root avatar Apr 01 '21 06:04 thk-root

I've faced with the same problem. How can I change in the library to make it read the white QR codes?

I have made it work simply by inverting the colours of the canvas each time it scans and it doesn't detect normal code. I can't really make a pull request, because I hotfixed it on a refactored version, but here's the snippet (line ~1310):

https://pastebin.com/7DBcWpr3

The relevant part is the inverting of canvas (_context.filter = 'invert(100%)')

I'm sure this is not the most reliable fix, but it works wonders for us. Hope it will for you too

Man, you saved my life! It really works at qr camera-scanner, this is awsome! But now I can't make the script works with attaching qr-images. Haven't you fixed the attaching images?

OlegKireev avatar Apr 08 '21 15:04 OlegKireev

I've faced with the same problem. How can I change in the library to make it read the white QR codes?

I have made it work simply by inverting the colours of the canvas each time it scans and it doesn't detect normal code. I can't really make a pull request, because I hotfixed it on a refactored version, but here's the snippet (line ~1310): https://pastebin.com/7DBcWpr3 The relevant part is the inverting of canvas (_context.filter = 'invert(100%)') I'm sure this is not the most reliable fix, but it works wonders for us. Hope it will for you too

Man, you saved my life! It really works at qr camera-scanner, this is awsome! But now I can't make the script works with attaching qr-images. Haven't you fixed the attaching images?

Unforunately no, because we only use the camera scanner. I'm sure the concept is the same (filtering the canvas), it's just a shame this isn't supported in the core code

thk-root avatar Apr 09 '21 07:04 thk-root

This is interesting - are there use-cases like that where inverted QR codes are used?

If there is a major use case and you can provide me with few samples - it should be possible to support this. One quick workaround to try is to invert the canvas as you shared.

You asked for a major use case, so there we go...

My company develops software for industry (asset management, plant maintenance, etc). Each equipment receives an unique ID, printed in a label as a QR-Code. In aggressive environments (heat, chemical solutions, etc.) we have to print the QR-Codes in metal labels (anodized aluminum, stainless steel, etc). In only one customer, we have literally thousands of electric motors about to receive their metal labels with black background and white foreground).

I'll be happy to provide some samples.

IMG_6898

eduardo-digi4ind avatar Dec 08 '21 14:12 eduardo-digi4ind

This will invert the qr before scan eternally. Just add this part before you initialize your scanner.

   import {Html5Qrcode} from "html5-qrcode";

    //....start of stuff to add
    Html5Qrcode.prototype.foreverScan = function(internalConfig, qrCodeSuccessCallback, qrCodeErrorCallback) {
      var _this = this;
      if (!this.shouldScan) {
        return;
      }
      if (!this.localMediaStream) {
        return;
      }
      var videoElement = this.videoElement;
      var widthRatio = videoElement.videoWidth / videoElement.clientWidth;
      var heightRatio = videoElement.videoHeight / videoElement.clientHeight;
      if (!this.qrRegion) {
        throw "qrRegion undefined when localMediaStream is ready.";
      }
      var sWidthOffset = this.qrRegion.width * widthRatio;
      var sHeightOffset = this.qrRegion.height * heightRatio;
      var sxOffset = this.qrRegion.x * widthRatio;
      var syOffset = this.qrRegion.y * heightRatio;
      this.context.drawImage(videoElement, sxOffset, syOffset, sWidthOffset, sHeightOffset, 0, 0, this.qrRegion.width, this.qrRegion.height);
      this.context.filter = 'invert(100%)'; // <-- Thx to [thk-root](https://github.com/thk-root)
      var triggerNextScan = function() {
        _this.foreverScanTimeout = setTimeout(function() {
          _this.foreverScan(internalConfig, qrCodeSuccessCallback, qrCodeErrorCallback);
        }, _this.getTimeoutFps(internalConfig.fps));
      };
      this.scanContext(qrCodeSuccessCallback, qrCodeErrorCallback).then(function(isSuccessfull) {
        if (!isSuccessfull && internalConfig.disableFlip !== true) {
          _this.context.translate(_this.context.canvas.width, 0);
          _this.context.scale(-1, 1);
          _this.scanContext(qrCodeSuccessCallback, qrCodeErrorCallback).finally(function() {
            triggerNextScan();
          });
        } else {
          triggerNextScan();
        }
      }).catch(function(error) {
        _this.logger.logError("Error happend while scanning context", error);
        triggerNextScan();
      });
    };
    
    //....end stuff to add
    html5QrCode = new Html5Qrcode('[YOUR_DIV_ID]');

The part who inverts the image is this: this.context.filter = invert(100%); part. You can add some if around it so it is not inverted all the time. That's up to you.

Silinator avatar Mar 17 '22 18:03 Silinator

Hi all, I've added the Html5Qrcode.prototype.foreverScan handler with no luck. still cannot read inverted qrcode. has anyone have any alternatives/workaround for inverted qrcode detection TIA

nivthetool avatar Mar 24 '22 13:03 nivthetool

I've faced with the same problem. How can I change in the library to make it read the white QR codes?

I have made it work simply by inverting the colours of the canvas each time it scans and it doesn't detect normal code. I can't really make a pull request, because I hotfixed it on a refactored version, but here's the snippet (line ~1310): https://pastebin.com/7DBcWpr3 The relevant part is the inverting of canvas (_context.filter = 'invert(100%)') I'm sure this is not the most reliable fix, but it works wonders for us. Hope it will for you too

Man, you saved my life! It really works at qr camera-scanner, this is awsome! But now I can't make the script works with attaching qr-images. Haven't you fixed the attaching images?

can you please guide me too i could not make it work - which files and what code should i change TIA mate really appreciate

nivthetool avatar Mar 25 '22 13:03 nivthetool

I've faced with the same problem. How can I change in the library to make it read the white QR codes?

I have made it work simply by inverting the colours of the canvas each time it scans and it doesn't detect normal code. I can't really make a pull request, because I hotfixed it on a refactored version, but here's the snippet (line ~1310):

https://pastebin.com/7DBcWpr3

The relevant part is the inverting of canvas (_context.filter = 'invert(100%)')

I'm sure this is not the most reliable fix, but it works wonders for us. Hope it will for you too

can you please guide me too, i could not make it work - which files and what code should i change TIA mate really appreciate

nivthetool avatar Mar 25 '22 13:03 nivthetool

to whom it might help, i have found this library that works with inverted qrcode https://github.com/cozmo/jsQR thank you all for your help

nivthetool avatar Mar 26 '22 19:03 nivthetool

to whom it might help, i have found this library that works with inverted qrcode https://github.com/cozmo/jsQR thank you all for your help

but it freeze after 30 seconds

poonth avatar Apr 18 '22 11:04 poonth

Inverted QR codes still not supported after 2.0 @mebjas Any updates?

poonth avatar Apr 18 '22 12:04 poonth

I monkey-patched the library to support both simultaneously. This reads alternate frames as positive/negative:

https://gist.github.com/johnnyb/8e4c888aa0ca5d4503b5e52391f4207a

Based off of a pull request on ZXing: https://github.com/zxing-js/library/pull/420/commits/7644e279df9fd2e754e044c25f450576d2878e45

johnnyb avatar May 12 '22 21:05 johnnyb

This will invert the qr before scan eternally. Just add this part before you initialize your scanner.

   import {Html5Qrcode} from "html5-qrcode";

    //....start of stuff to add
    Html5Qrcode.prototype.foreverScan = function(internalConfig, qrCodeSuccessCallback, qrCodeErrorCallback) {
      var _this = this;
      if (!this.shouldScan) {
        return;
      }
      if (!this.localMediaStream) {
        return;
      }
      var videoElement = this.videoElement;
      var widthRatio = videoElement.videoWidth / videoElement.clientWidth;
      var heightRatio = videoElement.videoHeight / videoElement.clientHeight;
      if (!this.qrRegion) {
        throw "qrRegion undefined when localMediaStream is ready.";
      }
      var sWidthOffset = this.qrRegion.width * widthRatio;
      var sHeightOffset = this.qrRegion.height * heightRatio;
      var sxOffset = this.qrRegion.x * widthRatio;
      var syOffset = this.qrRegion.y * heightRatio;
      this.context.drawImage(videoElement, sxOffset, syOffset, sWidthOffset, sHeightOffset, 0, 0, this.qrRegion.width, this.qrRegion.height);
      this.context.filter = 'invert(100%)'; // <-- Thx to [thk-root](https://github.com/thk-root)
      var triggerNextScan = function() {
        _this.foreverScanTimeout = setTimeout(function() {
          _this.foreverScan(internalConfig, qrCodeSuccessCallback, qrCodeErrorCallback);
        }, _this.getTimeoutFps(internalConfig.fps));
      };
      this.scanContext(qrCodeSuccessCallback, qrCodeErrorCallback).then(function(isSuccessfull) {
        if (!isSuccessfull && internalConfig.disableFlip !== true) {
          _this.context.translate(_this.context.canvas.width, 0);
          _this.context.scale(-1, 1);
          _this.scanContext(qrCodeSuccessCallback, qrCodeErrorCallback).finally(function() {
            triggerNextScan();
          });
        } else {
          triggerNextScan();
        }
      }).catch(function(error) {
        _this.logger.logError("Error happend while scanning context", error);
        triggerNextScan();
      });
    };
    
    //....end stuff to add
    html5QrCode = new Html5Qrcode('[YOUR_DIV_ID]');

The part who inverts the image is this: this.context.filter = invert(100%); part. You can add some if around it so it is not inverted all the time. That's up to you.

Thanks to @Silinator , i have manage to adapt the code to be able to scan both (inverted and non-inverted) QR Codes.

Just added the following logic to the this.scanContext(qrCodeSuccessCallback, qrCodeErrorCallback) promise.

                if (!isSuccessfull) {
                  _this.invertQR = !_this.invertQR;
                   if (_this.invertQR) {
                       _this.context.filter = 'invert(100%)';
                   }
                   else {
                       _this.context.filter = 'none';
                   }

                   _this.context.drawImage(videoElement, sxOffset, syOffset, sWidthOffset, sHeightOffset, 0, 0, _this.qrRegion.width, _this.qrRegion.height);

                   _this.scanContext(qrCodeSuccessCallback, qrCodeErrorCallback).finally(function () {
                       triggerNextScan();
                   });
               } else {
                   triggerNextScan();
               }

mariusvdw avatar Aug 01 '22 11:08 mariusvdw

@mariusvdw why not to fork the project and create a PR?

ROBERT-MCDOWELL avatar Aug 01 '22 20:08 ROBERT-MCDOWELL

Updates on this feature for the Html5Qrcode class? For me it only works in the Html5QrcodeScanner class.

DeniCarvalho avatar Aug 10 '22 17:08 DeniCarvalho

This will invert the qr before scan eternally. Just add this part before you initialize your scanner.

   import {Html5Qrcode} from "html5-qrcode";

    //....start of stuff to add
    Html5Qrcode.prototype.foreverScan = function(internalConfig, qrCodeSuccessCallback, qrCodeErrorCallback) {
      var _this = this;
      if (!this.shouldScan) {
        return;
      }
      if (!this.localMediaStream) {
        return;
      }
      var videoElement = this.videoElement;
      var widthRatio = videoElement.videoWidth / videoElement.clientWidth;
      var heightRatio = videoElement.videoHeight / videoElement.clientHeight;
      if (!this.qrRegion) {
        throw "qrRegion undefined when localMediaStream is ready.";
      }
      var sWidthOffset = this.qrRegion.width * widthRatio;
      var sHeightOffset = this.qrRegion.height * heightRatio;
      var sxOffset = this.qrRegion.x * widthRatio;
      var syOffset = this.qrRegion.y * heightRatio;
      this.context.drawImage(videoElement, sxOffset, syOffset, sWidthOffset, sHeightOffset, 0, 0, this.qrRegion.width, this.qrRegion.height);
      this.context.filter = 'invert(100%)'; // <-- Thx to [thk-root](https://github.com/thk-root)
      var triggerNextScan = function() {
        _this.foreverScanTimeout = setTimeout(function() {
          _this.foreverScan(internalConfig, qrCodeSuccessCallback, qrCodeErrorCallback);
        }, _this.getTimeoutFps(internalConfig.fps));
      };
      this.scanContext(qrCodeSuccessCallback, qrCodeErrorCallback).then(function(isSuccessfull) {
        if (!isSuccessfull && internalConfig.disableFlip !== true) {
          _this.context.translate(_this.context.canvas.width, 0);
          _this.context.scale(-1, 1);
          _this.scanContext(qrCodeSuccessCallback, qrCodeErrorCallback).finally(function() {
            triggerNextScan();
          });
        } else {
          triggerNextScan();
        }
      }).catch(function(error) {
        _this.logger.logError("Error happend while scanning context", error);
        triggerNextScan();
      });
    };
    
    //....end stuff to add
    html5QrCode = new Html5Qrcode('[YOUR_DIV_ID]');

The part who inverts the image is this: this.context.filter = invert(100%); part. You can add some if around it so it is not inverted all the time. That's up to you.

We put some additional code inside yours, and this works like a charm for us:

Html5Qrcode.prototype.foreverScan = function (internalConfig, qrCodeSuccessCallback, qrCodeErrorCallback) {
    var _this = this;
    if (!this.shouldScan) {
        return;
    }
    if (!this.localMediaStream) {
        return;
    }
    var videoElement = this.videoElement;
    var widthRatio = videoElement.videoWidth / videoElement.clientWidth;
    var heightRatio = videoElement.videoHeight / videoElement.clientHeight;
    if (!this.qrRegion) {
        throw "qrRegion undefined when localMediaStream is ready.";
    }
    var sWidthOffset = this.qrRegion.width * widthRatio;
    var sHeightOffset = this.qrRegion.height * heightRatio;
    var sxOffset = this.qrRegion.x * widthRatio;
    var syOffset = this.qrRegion.y * heightRatio;

    this.context.drawImage(videoElement, sxOffset, syOffset, sWidthOffset, sHeightOffset, 0, 0, this.qrRegion.width, this.qrRegion.height);

    if (this.context.filter) {
        this.context.filter = 'invert(1)';
        this.context.drawImage(videoElement, sxOffset, syOffset, sWidthOffset, sHeightOffset, 0, 0, this.qrRegion.width, this.qrRegion.height);
    } else {
        this.context.drawImage(videoElement, sxOffset, syOffset, sWidthOffset, sHeightOffset, 0, 0, this.qrRegion.width, this.qrRegion.height);
        this.context.globalCompositeOperation = 'difference';

        if (this.context.globalCompositeOperation === 'difference') {
            this.context.fillStyle = 'white';
            this.context.fillRect(0, 0, this.qrRegion.width, this.qrRegion.height);

            var canvas2 = document.createElement('canvas');
            var ctx2 = canvas2.getContext('2d');
            canvas2.width = this.qrRegion.width;
            canvas2.height = this.qrRegion.height;
            ctx2.drawImage(videoElement, sxOffset, syOffset, sWidthOffset, sHeightOffset, 0, 0, this.qrRegion.width, this.qrRegion.height);
            ctx2.globalCompositeOperation = 'source-in';
            ctx2.fillStyle = 'black';
            ctx2.fillRect(0, 0, this.qrRegion.width, this.qrRegion.height);
            ctx2.globalCompositeOperation = 'destination-in';
            this.context.drawImage(canvas2, sxOffset, syOffset, sWidthOffset, sHeightOffset, 0, 0, this.qrRegion.width, this.qrRegion.height);
        } else {
            try {
                var imgData = this.context.getImageData(0, 0, this.qrRegion.width, this.qrRegion.height);
                var data = imgData.data;
                for (var i = 0; i < data.length; i += (i % 4 === 2 ? 2 : 1)) {
                    data[i] = 255 - data[i];
                }
                this.context.putImageData(imgData, 0, 0);
            } catch (e) { }
        }
    }

    var triggerNextScan = function () {
        _this.foreverScanTimeout = setTimeout(function () {
            _this.foreverScan(internalConfig, qrCodeSuccessCallback, qrCodeErrorCallback);
        }, _this.getTimeoutFps(internalConfig.fps));
    };

    this.scanContext(qrCodeSuccessCallback, qrCodeErrorCallback).then(function (isSuccessfull) {
        if (!isSuccessfull) {
            _this.context.drawImage(videoElement, sxOffset, syOffset, sWidthOffset, sHeightOffset, 0, 0, _this.qrRegion.width, _this.qrRegion.height);
            _this.scanContext(qrCodeSuccessCallback, qrCodeErrorCallback).finally(function () {
                triggerNextScan();
            });
        } else {
            triggerNextScan();
        }
    }).catch(function (error) {
        _this.logger.logError("Error happend while scanning context", error);
        triggerNextScan();
    });
};

marcostalder85 avatar Nov 09 '22 19:11 marcostalder85

We've found out, that our code works well on iOS and Webcams, but Android now just scan inverted QR codes...

marcostalder85 avatar Nov 14 '22 15:11 marcostalder85

This is just another example of a place where you will find codes (this time a Data Matrix) in the wild with inverted colors. Parker is a huge supplier of mechanical components so definitely a big player, and one I am trying to scan codes for. unnamed

Parakoos avatar Dec 18 '22 13:12 Parakoos

When will this be solved? All our codes are inverted also printed on anodised aluminium.

ssoima avatar Jun 08 '23 12:06 ssoima

Fix this issue, its unacceptable that this library cant scan inverted codes at least, let alone some other qr code visual modifications, that the majority of qr codes are. Approve this ASAP: https://github.com/fante90/html5-qrcode/commit/b9ee6ef9aca0003168c0547027dcbe8f225e34eb

chrismask avatar Jul 13 '23 18:07 chrismask

Fix this issue, its unacceptable that this library cant scan inverted codes at least, let alone some other qr code visual modifications, that the majority of qr codes are. Approve this ASAP: fante90@b9ee6ef

LGTM!

Jervx avatar Feb 22 '24 03:02 Jervx