'Download HTML Canvas/BLOB as a PNG

I’m programmatically creating multiple house images that look like this:

enter image description here

I'm doing this by simply iterating through a loop which:

  1. Creates a new Canvas object at each iteration
  2. Draws an SVG of the house onto this new Canvas object
  3. Creates a PNG file from that Canvas

To get some variety going, I’m also programmatically changing the colors of each house at each iteration by simply looking up color-schemes from an Array of color-schemes I created.

All this works great.

But what I’m struggling with is getting my script to AUTOMATICALLY DOWNLOAD each newly created House ".PNG" file to my hard-drive.

I’m trying to do this by creating an ANCHOR <a> tag for each of my canvas/PNG’s and then calling the “.click()” method on each (code is below) - but it’s not working.

Chrome is giving me this error:

enter image description here

And Firefox gives me this error:

enter image description here

Any idea what needs to be done here?

My code is below.

  1. Here's the basic House SVG:

         <svg id="HOUSE" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="240.26" height="311.24" viewBox="0 0 240.26 311.24">
             <defs>
                 <style>
                 .roof-class, .window-class, .door-class {
                     stroke: #000;
                     stroke-miterlimit: 10;
                     }
                 </style>
             </defs>
    
             <g id="House">
                 <rect class="house-class" x="30.08" y="131.74" width="173.07" height="179"/>
                 <path d="M270,242V420H98V242H270m1-1H97V421H271V241Z" transform="translate(-67.39 -109.76)"/>
             </g>
    
             <polygon id="Roof" class="roof-class" points="1.11 131.74 239.11 131.74 117.11 0.74 1.11 131.74"/>
             <rect id="Window2" class="window-class" x="145.11" y="160.74" width="30" height="42"/>
             <rect id="Window1" class="window-class" x="58.61" y="160.74" width="30" height="42"/>
             <rect id="Door" class="door-class" x="92.11" y="228.74" width="52" height="82"/>
         </svg>
    
  2. Then I have:

         window.onload = function() {
             alert("window.onload - yo!"); 
    
             let svgHolder = document.getElementById("HOUSE");
             console.log("'svgHolder' = ");
             console.log(svgHolder);
             // console.log("DIR of 'svgHolder' = " + svgHolder );
    
             svgHolder.onload = function() {
                 console.log("==> 'svgHolder.onload' --> 'TheHouse' has been loaded!!!");
             }
         }
    
    
         // GLOBAL VARIABLES:
         const TOTAL_IMAGES = 10;
         const canvasWidth = 250;
         const canvasHeight = 320;
    
         var canvasX = 0;
         var canvasY = 0;
    
         // COLOR-SCHEME VARIABLES:
         var colorCounter = 0;
    
         let houseColorSchemesArray = [ 
             {
                 ".house-class": "fuchsia",
                 ".door-class": "darkblue",
                 ".window-class": "yellow",
                 ".roof-class": "maroon"
             },
    
             {
                 ".house-class": "gold",
                 ".door-class": "purple",
                 ".window-class": "pink",
                 ".roof-class": "crimson"
             },
    
             {
                 ".house-class": "lightblue",
                 ".door-class": "darkslategray",
                 ".window-class": "lightgreen",
                 ".roof-class": "darkred"
             } ,
    
             {
                 ".house-class": "blue",
                 ".door-class": "orange",
                 ".window-class": "pink",
                 ".roof-class": "lime"
             } 
         ];
    
    
         // CLASS-NAMES:
         let classNamesToPaintArray = [".house-class", ".door-class", ".window-class", ".roof-class"];
    
         // SVG Template:
         let houseSVG = document.getElementById("HOUSE");
    
         // var loadedImageCount = 0;
    
         var masterHouseImagesArray = [];
    
    
         function designOneHouse(theCanvas) {
             console.log("= =>>In 'designOneHouse()'!\n");
    
             let context = theCanvas.getContext("2d");
    
             // Now GET-AT and PAINT the Individual SVG Components.
             // STRATEGY:
             // 1. Iterate through the Array containing all the CLASS-NAMES who's color I want to change.
             // 2. For each of these classes, I'll need to iterate through all the HTML elements that are OF that class type
             //    (there may be like 10 elements that are all styled by the same Style; I want all of them to be updated!)
             // 
             let colorScheme = houseColorSchemesArray[colorCounter];
             console.log("==>>Current 'colorScheme' = ");
             console.log(colorScheme);
    
    
             console.log("\n\nNOW Going into a 'forEach' loop!");
             classNamesToPaintArray.forEach(className => {
                 console.log("==>>In 'forEach', current 'className' = " + className);
                 let elementsArray = houseSVG.querySelectorAll(className);
    
                 elementsArray.forEach(element => element.style.fill = colorScheme[className]);
             });
    
    
             var imageData = houseSVG.outerHTML;
             var DOMURL = window.URL || window.webkitURL || window;
             var img = new Image();
             var svg = new Blob([imageData], { type: 'image/svg+xml;charset=utf-8' });
             var url = DOMURL.createObjectURL(svg);
    
             img.onload = function () {
                 context.drawImage(img, 0, 0);
                 DOMURL.revokeObjectURL(url);
    
                 // Now ADD this new House Image to the 'masterHouseImagesArray':
                 masterHouseImagesArray.push(img);
                 console.log("\n  >>>'masterHouseImagesArray' now has " + masterHouseImagesArray.length + " images in it." );
    
    
                 if(masterHouseImagesArray.length == TOTAL_IMAGES) {
                     alert("ALL IMAGES ACCOUNTED FOR!!! \n>Going to make ANCHOR TAGS NOW!!!");
                     createAnchorTags();
                 }
    
             }
    
             img.src = url;
    
    
             // Iterate the ColorCounter - making sure we don't overflow the ColorsArrays:
             colorCounter++;
             if(colorCounter == houseColorSchemesArray.length) {
                 colorCounter = 0;
             }
    
             console.log("\n\nEXITING 'designOneHouse()'!\n");
    
         }
    
  3. Finally, I have this:

         function makeCanvasGrid() {
             console.log("\n\n====>In 'makeCanvasGrid()'!\n");
    
             for(var canvasCounter = 0; canvasCounter < TOTAL_IMAGES; canvasCounter++) {
                 console.log("\n >FOR LOOP - canvasCounter = " + canvasCounter);
    
                 // 1. Create a new Canvas Object:
                 let newCanvas = document.createElement("canvas");
                 newCanvas.setAttribute("width", canvasWidth);
                 newCanvas.setAttribute("height", canvasHeight);
                 newCanvas.setAttribute("id", "newCanvas" + canvasCounter);
                 // Log-out just to verify the "id" property was set correctly:
                 console.log("  >newCanvas.id  = " + newCanvas.id);
    
                 // 2. Place the Canvas at (x,y) (top, left) coordinates:
                 newCanvas.style.backgroundColor = "lightblue";
                 newCanvas.style.position = "absolute";
                 newCanvas.style.left = canvasX + "px";
                 newCanvas.style.top = canvasY + "px";
    
                 document.body.appendChild(newCanvas);
    
                 designOneHouse(newCanvas);
    
    
                 // Check the current Canvas' (X, Y) coords, and if needed, reset X to 0 and SKIP to
                 // the next "ROW" of Canvasses:
                 if(canvasCounter > 0 && canvasCounter % 3 == 0) {
                     console.log("  >>NEXT ROW PLEASE!!!! canvasCount = ", canvasCounter);
                     canvasX = 0;
                     canvasY += canvasHeight + 20;
                 }
                 else {
                     canvasX += canvasWidth + 10;
                     console.log("\n >Increasing 'canvasX' to:" + canvasX);
                 }
             }
    
         }
    
    
    
         makeCanvasGrid();
    
    
    
         function createAnchorTags() {
             console.log("\n\n==========================\n=\n=");
             console.log("==>>In 'createAnchorTags()'!");
    
             for(anchorTagsCounter = 0; anchorTagsCounter < TOTAL_IMAGES; anchorTagsCounter++) {
                 // 1. CREATE a new HTML "a" (anchor) TAG/Element and give it an ID:
                 let newAnchorTag = document.createElement("a");
                 newAnchorTag.id = "anchorTag#" + anchorTagsCounter;
    
                 // 2. ASSIGN a value to its "href" property:
                 newAnchorTag.href = masterHouseImagesArray[anchorTagsCounter].src;
                 console.log("\n >newAnchorTag.href = " + newAnchorTag.href);
    
                 // 3. ASSIGN a value to its "download" property:
                 newAnchorTag.download = "PunkPass#" + anchorTagsCounter;
    
                 // 4. APPEND this newly created ANCHOR tag/element to the page:
                 document.body.appendChild(newAnchorTag);
    
                 console.log("   ... ... ....");
                 console.log("   ->'newAnchorTag' created!");
                 console.log("    >'newAnchorTag.id' = " + newAnchorTag.id);
    
    
                 newAnchorTag.click();
             }
    
         }
    

Would appreciate any and all help. Thanks!



Solution 1:[1]

If I understand you correctly, you should take a closer look at FileSaver.js - a convenient library for downloading files generated on the client. There is even an example of saving canvas in png file.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1