Search code examples
javascriptvisual-studio-2012html5-canvaswindows-store-appswinjs

WinJS barcode reader issues(Image not loading in canvas)


Am working on a winjs based barcode reader application. Initially I will capture the image using camera capture API and will pass that file object to a canvas element and read its barcode using ZXing library. But the image passed to the canvas is not getting rendered completely as follows. enter image description here Following is my html code

<body>
<p>Decoding test for static images</p>
<canvas id="canvasDecode" height="200" width="200"></canvas>
<h3 id="result"></h3>
<p>Put some content here and leave the text box</p>
<input id="input" class="win-textarea" onchange="generate_barcode()">
<h3 id="content"></h3>
<canvas id="canvasEncode" height="200" width="200"></canvas>
<img class="imageHolder" id="capturedPhoto" alt="image holder" />
</body>

following is my javascript code

(function () {
"use strict";

WinJS.Binding.optimizeBindingReferences = true;

var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;

app.onactivated = function (args) {
    if (args.detail.kind === activation.ActivationKind.launch) {
        if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
            // TODO: This application has been newly launched. Initialize
            // your application here.

            var dialog = new Windows.Media.Capture.CameraCaptureUI();
            var aspectRatio = { width: 1, height: 1 };
            dialog.photoSettings.croppedAspectRatio = aspectRatio;
            dialog.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo).then(function (file) {

                if (file) {
                    // draw the image
                    var canvas = document.getElementById('canvasDecode')
                    var ctx = canvas.getContext('2d');
                    var img = new Image;
                    img.onload = function () {
                        canvas.width = img.width;
                        canvas.height = img.height;
                        ctx.drawImage(img, 0, 0, img.width, img.height);
                    }
                    img.src = URL.createObjectURL(file);
                    // open a stream from the image
                    return file.openAsync(Windows.Storage.FileAccessMode.readWrite);
                }
            })
            .then(function (stream) {
                if (stream) {
                    // create a decoder from the image stream
                    return Windows.Graphics.Imaging.BitmapDecoder.createAsync(stream);
                }
            })
            .done(function (decoder) {
                if (decoder) {
                    // get the raw pixel data from the decoder
                    decoder.getPixelDataAsync().then(function (pixelDataProvider) {
                        var rawPixels = pixelDataProvider.detachPixelData();
                        var pixels, format; // Assign these in the below switch block.

                        switch (decoder.bitmapPixelFormat) {
                            case Windows.Graphics.Imaging.BitmapPixelFormat.rgba16:
                                // Allocate a typed array with the raw pixel data
                                var pixelBufferView_U8 = new Uint8Array(rawPixels);

                                // Uint16Array provides a typed view into the raw 8 bit pixel data.
                                pixels = new Uint16Array(pixelBufferView_U8.buffer);
                                if (decoder.bitmapAlphaMode == Windows.Graphics.Imaging.BitmapAlphaMode.straight)
                                    format = ZXing.BitmapFormat.rgba32;
                                else
                                    format = ZXing.BitmapFormat.rgb32;
                                break;

                            case Windows.Graphics.Imaging.BitmapPixelFormat.rgba8:
                                // For 8 bit pixel formats, just use the returned pixel array.
                                pixels = rawPixels;
                                if (decoder.bitmapAlphaMode == Windows.Graphics.Imaging.BitmapAlphaMode.straight)
                                    format = ZXing.BitmapFormat.rgba32;
                                else
                                    format = ZXing.BitmapFormat.rgb32;
                                break;

                            case Windows.Graphics.Imaging.BitmapPixelFormat.bgra8:
                                // For 8 bit pixel formats, just use the returned pixel array.
                                pixels = rawPixels;
                                if (decoder.bitmapAlphaMode == Windows.Graphics.Imaging.BitmapAlphaMode.straight)
                                    format = ZXing.BitmapFormat.bgra32;
                                else
                                    format = ZXing.BitmapFormat.bgr32;
                                break;
                        }
                        // create a barcode reader
                        var reader = new ZXing.BarcodeReader();
                        reader.onresultpointfound = function (resultPoint) {
                            // do something with the resultpoint location
                        }
                        // try to decode the raw pixel data
                        var result = reader.decode(pixels, decoder.pixelWidth, decoder.pixelHeight, format);
                        // show the result
                        if (result) {
                            document.getElementById("result").innerText = result.text;
                        }
                        else {
                            document.getElementById("result").innerText = "no barcode found";
                        }
                    });
                }
            });
        } else {
            // TODO: This application has been reactivated from suspension.
            // Restore application state here.
        }
        args.setPromise(WinJS.UI.processAll());
    }
};

app.oncheckpoint = function (args) {
    // TODO: This application is about to be suspended. Save any state
    // that needs to persist across suspensions here. You might use the
    // WinJS.Application.sessionState object, which is automatically
    // saved and restored across suspension. If you need to complete an
    // asynchronous operation before your application is suspended, call
    // args.setPromise().
};

app.start();
})();

function generate_barcode() {
// get the content which the user puts into the textbox
var content = document.getElementById("input").value;

// create the barcode writer and set some options
var writer = new ZXing.BarcodeWriter();
writer.options = new ZXing.Common.EncodingOptions();
writer.options.height = 200;
writer.options.width = 200;
writer.format = ZXing.BarcodeFormat.qr_CODE;

// encode the content to a byte array with 4 byte per pixel as BGRA
var imagePixelData = writer.write(content);

// draw the pixel data to the canvas
var ctx = document.getElementById('canvasEncode').getContext('2d');
var imageData = ctx.createImageData(imagePixelData.width, imagePixelData.heigth);
var pixel = imagePixelData.pixel
for (var index = 0; index < pixel.length; index++) {
    imageData.data[index] = pixel[index];
}
ctx.putImageData(imageData, 0, 0);
}

The same code worked well when I was using the file picker API. Let me knew where I went wrong.


Solution

  • I think that you're running into some problems with asynchronicity here. I applaud your use of chained calls to then(), but there's a hidden problem - assignment to img.src begins an asynchronous operation while the image is loaded. Your code continues on BEFORE the img.onload event has been raised, and so the closure which img.onload reaches into for the img variable (the pointer to the file URL) changes before the image has fully loaded.

    Here's some code that worked for me.

    // Inside handler for app.activated ...
    var dialog = new Windows.Media.Capture.CameraCaptureUI();
    var aspectRatio = { width: 1, height: 1 };
    dialog.photoSettings.croppedAspectRatio = aspectRatio;
    dialog.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
        .then(function (file) {
                // draw the image
                var canvas = document.getElementById('canvasDecode')
                var ctx = canvas.getContext('2d');
                var img = new Image;
                img.onload = function () {
                    canvas.width = img.width;
                    canvas.height = img.height;
                    ctx.drawImage(img, 0, 0, img.width, img.height);
    
                    // open a stream from the image
                    decodePic(file);
                }
                img.onerror = function (err) {
                    WinJS.log && WinJS.log("Error loading image");
                }
                img.src = URL.createObjectURL(file);
    
        });
    

    And then I moved the file decoding / barcode reading stuff to a new function.

    function decodePic(file) {
        file.openAsync(Windows.Storage.FileAccessMode.readWrite)
        .then(function (stream) {
            if (stream) {
                // create a decoder from the image stream
                return Windows.Graphics.Imaging.BitmapDecoder.createAsync(stream);
            }
        })
        .done(function (decoder) {
            if (decoder) {
                // get the raw pixel data from the decoder
                decoder.getPixelDataAsync().then(function (pixelDataProvider) {
                    // YOUR BARCODE READING CODE HERE.
                });
            }
        });
    }
    

    I hope this helps!