Search code examples
javascripthtmlhtml5-canvas

openfl canvas toDataUrl() return blank image


I am trying to capture a screenshot of this game but when I try it simply return a blank image.

var data = document.getElementsByTagName("canvas")[0].toDataURL('image/png');
var out = document.createElement('img');
out.id = "sc";
out.src = data;
var link = document.createElement("a");
link.download = "sc.png";
link.href = out.src;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);

The image downloads perfectly fine but it is simply blank.

I have tried a number of other methods including just printing the base64 to the console with console.log(document.querySelector("#openfl-content > canvas").toDataURL("image/png").split(',')[1]); but when I put it into a decoder its blank once again.

EDIT: The above code works perfectly fine on my MacBook but does not work on my Windows PC regardless of the browser. I have no idea why.


Solution

  • Your original code wasn't giving you the output you expected, for a couple of reasons.

    Reason 1

    You are not appending the out child to the DOM - there is no document.body.appendChild(out).

    var data = document.getElementsByTagName("canvas")[0].toDataURL('image/png');
    var out = document.createElement('img');
    out.id = "sc";
    out.src = data;
    document.body.appendChild(out); // this is new
    var link = document.createElement("a");
    link.download = "sc.png";
    link.href = out.src;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    document.body.removeChild(out); // this is new
    

    Or you can avoid creating an img element, and just use a, with its href set to the base64 image.

    var data = document.getElementsByTagName("canvas")[0].toDataURL(); // specifying the type of image is not needed
    var link = document.createElement("a");
    link.download = "sc.png";
    link.href = data;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    
    Reason 2 (and a working solution)

    However, the previous will work under certain conditions:

    • since the link you provided is loading a game in an iframe, you would have to open your DevConsole (you're already doing that in order to be able to paste the code to the console), and then click on the iframe and navigate within it, to make it your point of focus, and to enable your Javascript to work. I'm guessing that this is what you are already doing (since you can trigger the downloading)
    • the other condition is due to random chance of actually triggering your code when an animation frame is rendered. If you're lucky, executing the code in the console might match with a fully rendered animation frame of your Friv game, and you will get a screenshot. However, more often than not, you will receive an empty png file.

    The following code addresses both of these issues. I'm also using your initial coding style - var for variables (instead of, const or let), Javascript specific selectors (instead of a generic querySelector(<selector>))

    // This is where the actual game is
    var gameIframe = document.getElementById("gameBox");
    
    // The other one is a fallback, if you're clicking about in the code
    // while inspecting the source, and changing your focus to different elements
    // Based on what I've seen, there's only one canvas, and that's why this
    // solution will work. If there were more canvases or iframes, the code would
    // have to be different
    var canvasElem = document.getElementsByTagName("canvas")[0];
    
    // Let's check if you're in the main document
    if(gameIframe) {
        // Let's get the frame's document
        var gameIframeContent = gameIframe.contentWindow.document;
    
        // Once we have this, we can finally use the initial code to get the screenshot
        // For this, I'm using requestAnimationFrame
        // https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
        // This will execute the initial screengrabbing code before the frame is repainted
        gameIframeContent.defaultView.requestAnimationFrame(function() {
            var data = gameIframeContent.getElementsByTagName("canvas")[0].toDataURL();
            var link = gameIframeContent.createElement("a");
            link.download = "sc.png";
            link.href = data;
            gameIframeContent.body.appendChild(link);
            link.click();
            gameIframeContent.body.removeChild(link);
        });
    
    // All is fine and dandy when we are witihn the main document
    // But when we're not there, we won't get the screenshot
    // The else if will check if we're focused (in the source) 
    // on the canvas element within the iframe
    } else if(canvasElem) {
        // Repeat the logic from the first if check
        document.defaultView.requestAnimationFrame(function() {
            // We already have the canvas
            var data = canvasElem.toDataURL();
            var link = document.createElement("a");
            link.download = "sc.png";
            link.href = data;
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        });
    }
    

    Let's optimize

    While this is working, it's actually not a very good way of doing things - we're repeating some bits of our code. This would be one way of optimizing:

    function getElement(doc,selector) {
        return doc.querySelector(selector);
    }
    
    function createScreenshot(canvasDocument, canvasElement) {
        canvasDocument.defaultView.requestAnimationFrame(function() {
            let data = canvasElement.toDataURL();
            let link = canvasDocument.createElement("a");
            link.download = "sc.png";
            link.href = data;
            canvasDocument.body.appendChild(link);
            link.click();
            canvasDocument.body.removeChild(link);
        });
    }
    
    let gameIframe = getElement(document,"#gameBox");
    let canvasElem = getElement(document,"canvas");
    
    if(gameIframe) {
        let gameIframeContent = gameIframe.contentWindow.document;
        let gameIframeCanvas = getElement(gameIframeContent,"canvas");
        createScreenshot(gameIframeContent,gameIframeCanvas);
    } else if(canvasElem) {
        createScreenshot(document,canvas);
    
    } else {
        alert("Sorry, nothing found!");
    }