1x2x3x4x
1x2x3x4x

Reputation: 604

Convert to base64 captured image in QML

I'm trying to save as base64 a captured image in QML. This is part of the code. I need to have it in a variable.

        Camera {            
        id: camera
        videoRecorder {
            frameRate: 30
        }
        imageCapture {              
            onImageCaptured: {
                foto.source = preview                   
            }
            onImageSaved: {
                var imgPath = camera.imageCapture.capturedImagePath;
                

                t.start()
            }
        }
    }

I suppose I have to do it in onImageSaved, I've tried:

camera.imageCapture.toBase64()

And:

imgPath.toDataURL(mimeType)

But I'm not sure if it's possible ot what I'm doing wrong if it is.

Upvotes: 0

Views: 1067

Answers (1)

Stephen Quan
Stephen Quan

Reputation: 26121

Because you're restricting the solution to QML, your choices are severely limited. I would really question that since it leads to this solution, which is inefficient because it requires you to save the image first and then load the saved image.

You must:

  • Initiate a save by calling camera.imageCapture.capture() or equivalent
  • Wait for the camera.imageCapture.onImageSaved signal
  • This tells you that the image has been encoded and saved to a JPEG file
  • You then can load the JPEG into memory as a binary ArrayBuffer object using XMLHttpRequest
  • Then, you can use Qt.btoa on it to get a base64 string from it

The steps are precisely written above to avoid any mistakes or misunderstandings. It's inefficient, since, if you had access to C++ we could consider processing the raw QImage in-memory and do in-memory conversions to an image format of our choosing as well as an in-memory conversion to base64.

    Camera {
        id: camera
        imageCapture {
            onImageSaved: {
                let filePath = camera.imageCapture.capturedImagePath;
                console.log(filePath); // C:/Users/stephenquan/Pictures/IMG_00000007.jpg
                let fileUrl = Qt.platform.os === 'windows'
                     ? 'file:///' + filePath
                     : 'file://' + filePath;
                 console.log(fileUrl); // file:///C:/Users/stephenquan/Pictures/IMG_00000007.jpg
                 let xhr = new XMLHttpRequest();
                 xhr.open("GET", fileUrl, false);
                 xhr.send();
                 // //XMLHttpRequest: Using GET on a local file is dangerous and will be disabled by default in a future Qt version.Set QML_XHR_ALLOW_FILE_READ to 1 if you wish to continue using this feature.
                 let base64 = Qt.btoa(xhr.response);
                 console.log(base64); // final base64 representation of the JPEG image
                 let datauri = "data:image/jpeg;base64," + base64;
                 console.log(datauri); // a properly formatted datauri for the JPEG image
            }
        }
    }

After it is saved, we need to convert that filePath to a fileUrl.

For me, as I'm running it on windows, I can add a file:/// prefix to turn it into a URL. For other platforms, the prefix is typically file://.

Then we can use Qt.btoa with XMLHttpRequest we can generate the base64 string for that URL.

See the warning about using XMLHttpRequest on local files.

Below is some refactored black box functions to make all this code reusable for other scenarios.

function urlToBase64(url) {
    let xhr = new XMLHttpRequest();
    xhr.open("GET", url, false);
    xhr.send();
    return Qt.btoa(xhr.response);
}

function fileToBase64(filePath) {
    //XMLHttpRequest: Using GET on a local file is dangerous and will be disabled by default in a future Qt version.Set QML_XHR_ALLOW_FILE_READ to 1 if you wish to continue using this feature.
    return urlToBase64(Qt.resolvedUrl("file:///" + filePath));
}

function test() {
    let url = "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png";
    console.log(urlToBase64(fileUrl));
    let filePath = "C:/temp/hello.txt";
    console.log(fileToBase64(filePath));
}

[Edit: Additional answer about Canvas.toDataURL]

Alternatively, you can use Canvas.toDataURL but it requires you to load your graphic into the a Canvas object first and then render it once you are sure it's loaded.

[Edit: Additional answer about asynchronous]

There was a comment earlier regarding the proper usage of XMLHttpRequest. Because the use case was for local files, I used XMLHttpRequest.open with async mode set to false to force it to work in synchronous mode.

If you want an asynchronous version of the solution and use it generically for HTTP URLs, I would choose to use Promise chaining as per below.

function asyncUrlToBinary(url) {
    return new Promise(function (resolve, reject) {
        try {
            let xhr = new XMLHttpRequest();
            xhr.onreadystatechange = () => {
                if (xhr.readyState !== 4) return;
                if (xhr.status !== 200) {
                   reject(new Error(`HTTP Status Error ${xhr.status}: ${xhr.statusText}`));
                   return;
                }
                resolve(xhr.response);
            }
            xhr.open("GET", url);
            xhr.send();
        } catch (err) {
            reject(err);
        }
    } );
}

function asyncBtoA(data) {
    return Promise.resolve(Qt.btoa(data));
}

function asyncTest() {
    let url = "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png";
    asyncUrlToBinary(url)
    .then(data => asyncBtoA(data))
    .then(base64 => console.log(base64))
    .catch(err => { console.error(err.fileName + ":" + err.lineNumber, err.message); throw err; } )
    ;
}

References:

Upvotes: 0

Related Questions