September 8, 2020

Get Base64 Encode String of Screenshot with Markups

Follow @Xiaodong Liang

The other blog has well described how to make a screenshot with markup of Forge Viewer. 

Recently, one customer uses this code of the other blog to generate screenshot. Then, he wanted to integrate the screenshot with other workflow. So, he tried with the code below (after markup is rendered to the picture). 

markupCore.renderToCanvas(ctx)
var sData = canvas.toDataURL('image/png');
console.log(sData);

sData is a Base64 encoding string. The customer copied the string to some online tool to verify the image, e.g.

https://base64.guru/converter/decode/image

but, no markup with the image! Only the model is available... 

After investigation, I found the issue is because of timing.. If manually call the codes (get base64 encoding string) in the browser console after clicking the button [screenshot], the base64 string will contain markup (see the picture at the bottom)

Why? Because markupCore.renderToCanvas is a callback function. If you print the base64 string right after renderToCanvas, the markup contents has not been available with the screenshot. So, you should print the string in its callback.
 

 markupCore.renderToCanvas(ctx,()=>{
          var sData = canvas.toDataURL('image/png');
           console.log(sData);
  }

BUT, when debugging, I found the condition to trigger callback of renderToCanvas is:  markups collection should not be empty, while if adding SVG markup, it is not counted as a general markup. markup collection is empty.  I have to workaround it by the code below. i.e. make a dummy general markup to make callback working, after that, delete it. The code imports latest viewer version now (v7.25). It can also work with previous version, while we suggest you migrate to new version.

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>Snapshot with Markup</title>
    <link rel="stylesheet" href="https://developer.api.autodesk.com/modelderivative/v2/viewers/style.min.css?v=v7.25" type="text/css">
</head>

<body>

    <body onload="initializeViewer();" style="margin:0px; overflow:hidden">
        <div style="width:49vw; height:100vh; position:relative; display:inline-block;">
            <div id="viewer3d" style="margin:0;">
            </div>
        </div>
        <div style="width:49vw; height:100vh;display:inline-block;">
            <canvas id="snapshot" style="position:absolute;"></canvas>
            <button onclick="snaphot();" style="position:absolute;">Snapshot!</button>
        </div>
    </body>

    <!-- The Viewer JS -->
    <script src="https://developer.api.autodesk.com/modelderivative/v2/viewers/viewer3D.js?v=v7.25"></script>

    <!-- Developer JS -->
    <script>
        var viewer;

        function initializeViewer() {
            // initialize the viewer in "offline" mode (no security from Forge)
            var myViewerDiv = document.getElementById('viewer3d');
            viewer = new Autodesk.Viewing.Private.GuiViewer3D(myViewerDiv);
            var options = {
                'env': 'Local',
                'document': 'http://autodesk-forge.github.io/viewer-javascript-offline.sample/shaver/0.svf'
            };
            Autodesk.Viewing.Initializer(options, function () {
                viewer.start(options.document, options);
                viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, function () {
                    viewer.utilities.fitToView();
                });
            });
        }

        function snaphot() {
            var screenshot = new Image();
            screenshot.onload = function () {
                viewer.loadExtension('Autodesk.Viewing.MarkupsCore').then(function (markupCore) {

                    const LAYER_NAME = 'layerName'
                    // load the markups
                    markupCore.show();
                    markupCore.loadMarkups(markupSVG, LAYER_NAME);

                    //svg markup is not counted as a general markup
                    //in order to make it working with callback of markupCore.renderToCanvas
                    //temporarily add a dummy markup to markup list.
                    var tempSvgMarkup = null
                    if(markupCore.svgLayersMap && markupCore.svgLayersMap[LAYER_NAME].markups.length>0){
                        markupCore.enterEditMode()
                        tempSvgMarkup = markupCore.svgLayersMap.layerName.markups[0]
                        markupCore.addMarkup(tempSvgMarkup)
                        markupCore.leaveEditMode() 
                    }
                    else
                        console.log('no svgLayersMap or svg markup. callback of  markupCore.renderToCanvas will NOT work')
                    // ideally should also restore state of Viewer for this markup

                    // prepare to render the markups
                    var canvas = document.getElementById('snapshot');
                    canvas.width = viewer.container.clientWidth;
                    canvas.height = viewer.container.clientHeight;
                    var ctx = canvas.getContext('2d');
                    ctx.clearRect(0, 0, canvas.width, canvas.height);
                    ctx.drawImage(screenshot, 0, 0, canvas.width, canvas.height);

                    markupCore.renderToCanvas(ctx,()=>{ 

                        //sometimes, the requirement is to get base64 codes to integrate with other appliication
                        var sData = canvas.toDataURL('image/png');
                        console.log(sData);
                        //remove temp svg markup from general markup list.

                        if(tempSvgMarkup)
                            markupCore.removeMarkup(tempSvgMarkup)
                         // we can hide the markups now.
                        markupCore.hide(); 
                    });  

                    // hide the markups when no svg markup. otherwise, leave callback of renderToCanvas to handle
                    if(tempSvgMarkup == null)
                        markupCore.hide(); 
                    
                });
            };

            // Get the full image
            viewer.getScreenShot(viewer.container.clientWidth, viewer.container.clientHeight, function (blobURL) {
                screenshot.src = blobURL;
            });
        }

        var markupSVG = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" baseProfile="full" style="position:absolute; left:0; top:0; transform:scale(1,-1); -ms-transform:scale(1,-1); -webkit-transform:scale(1,-1); -moz-transform:scale(1,-1); -o-transform:scale(1,-1); transformOrigin:0, 0; -ms-transformOrigin:0, 0; -webkit-transformOrigin:0, 0; -moz-transformOrigin:0, 0; -o-transformOrigin:0, 0; " width="510" height="960" viewBox="-531.25 -1000 1062.5 2000" cursor="crosshair" pointer-events="painted"><metadata><markup_document xmlns="http://www.w3.org/1999/xhtml" data-model-version="4"></markup_document></metadata><g cursor="inherit" pointer-events="stroke"><metadata><markup_element xmlns="http://www.w3.org/1999/xhtml" stroke-width="10.416666666666629" stroke-linejoin="miter" stroke-color="#ff0000" stroke-opacity="1" fill-color="#ff0000" fill-opacity="0" type="cloud" position="-48.958333333333336 307.2916666666667" size="418.75 377.08333333333337" rotation="0"></markup_element></metadata><path id="markup" d="M -183.75000000000003 -142.59259259259264 a 20.416666666666668 20.37037037037037 0 1 1 20.416666666666668 -20.37037037037037 c 2.0416666666666674 -23.819444444444446 38.79166666666668 -23.819444444444446 40.83333333333334 0 c 2.0416666666666674 -23.819444444444446 38.79166666666668 -23.819444444444446 40.83333333333334 0 c 2.0416666666666674 -23.819444444444446 38.79166666666668 -23.819444444444446 40.83333333333334 0 c 2.0416666666666674 -23.819444444444446 38.79166666666668 -23.819444444444446 40.83333333333334 0 c 2.0416666666666674 -23.819444444444446 38.79166666666668 -23.819444444444446 40.83333333333334 0 c 2.0416666666666674 -23.819444444444446 38.79166666666668 -23.819444444444446 40.83333333333334 0 c 2.0416666666666674 -23.819444444444446 38.79166666666668 -23.819444444444446 40.83333333333334 0 c 2.0416666666666674 -23.819444444444446 38.79166666666668 -23.819444444444446 40.83333333333334 0 a 20.416666666666668 20.37037037037037 0 1 1 20.416666666666668 20.37037037037037 c 23.76543209876543 2.037037037037037 23.76543209876543 38.7037037037037 0 40.74074074074074 c 23.76543209876543 2.037037037037037 23.76543209876543 38.7037037037037 0 40.74074074074074 c 23.76543209876543 2.037037037037037 23.76543209876543 38.7037037037037 0 40.74074074074074 c 23.76543209876543 2.037037037037037 23.76543209876543 38.7037037037037 0 40.74074074074074 c 23.76543209876543 2.037037037037037 23.76543209876543 38.7037037037037 0 40.74074074074074 c 23.76543209876543 2.037037037037037 23.76543209876543 38.7037037037037 0 40.74074074074074 c 23.76543209876543 2.037037037037037 23.76543209876543 38.7037037037037 0 40.74074074074074 a 20.416666666666668 20.37037037037037 0 1 1 -20.416666666666668 20.37037037037037 c -2.0416666666666674 23.819444444444446 -38.79166666666668 23.819444444444446 -40.83333333333334 0 c -2.0416666666666674 23.819444444444446 -38.79166666666668 23.819444444444446 -40.83333333333334 0 c -2.0416666666666674 23.819444444444446 -38.79166666666668 23.819444444444446 -40.83333333333334 0 c -2.0416666666666674 23.819444444444446 -38.79166666666668 23.819444444444446 -40.83333333333334 0 c -2.0416666666666674 23.819444444444446 -38.79166666666668 23.819444444444446 -40.83333333333334 0 c -2.0416666666666674 23.819444444444446 -38.79166666666668 23.819444444444446 -40.83333333333334 0 c -2.0416666666666674 23.819444444444446 -38.79166666666668 23.819444444444446 -40.83333333333334 0 c -2.0416666666666674 23.819444444444446 -38.79166666666668 23.819444444444446 -40.83333333333334 0 a 20.416666666666668 20.37037037037037 0 1 1 -20.416666666666668 -20.37037037037037 c -23.76543209876543 -2.037037037037037 -23.76543209876543 -38.7037037037037 0 -40.74074074074074 c -23.76543209876543 -2.037037037037037 -23.76543209876543 -38.7037037037037 0 -40.74074074074074 c -23.76543209876543 -2.037037037037037 -23.76543209876543 -38.7037037037037 0 -40.74074074074074 c -23.76543209876543 -2.037037037037037 -23.76543209876543 -38.7037037037037 0 -40.74074074074074 c -23.76543209876543 -2.037037037037037 -23.76543209876543 -38.7037037037037 0 -40.74074074074074 c -23.76543209876543 -2.037037037037037 -23.76543209876543 -38.7037037037037 0 -40.74074074074074 c -23.76543209876543 -2.037037037037037 -23.76543209876543 -38.7037037037037 0 -40.74074074074074 z" stroke-width="10.416666666666629" stroke="rgba(255,0,0,1)" fill="none" transform="translate( -48.958333333333336 , 307.2916666666667 ) rotate( 0 )"/></g></svg>';
    </script>
</body>

</html>

 

 

1

Posted by

Xiaodong Liang has been with Autodesk since 2007, focusing on providing programming support, consulting, training and evangelism to external developers. He started his career China and now lives in Beijing, China. Xiaodong is currently a developer consultant in the team Developer Technical Services (DevTech), the worldwide team of API gurus providing technical services through the Autodesk Developer Network. He supports...

Related Posts