I’m working on a project that draws repeatedly to an HTML Canvas using a combination of the lineTo and drawImage methods. As far as I can tell, the actual production of the Canvas works as intended. (Not all tiles are intended to be populated, so the below image is an accurate translation from the text file into a visual representation of it.)
a 5×4 tile grid, each tile is 120px in size. SVG images are placed in some (but not all) tiles
The first segment of code I’ll share acts as something of a driver for the rest of the functionality. Essentially, a local text file (formatted according to specifics outlined elsewhere) is opened, read from to populate two vectors, and those vectors accessed by drawGrid() and tileParse().
The reader:
document
.getElementById('FileInput')
.addEventListener('change', function (event) {
let file = event.target.files[0];
let reader = new FileReader();
reader.onload = function (event) {
read(event);
drawGrid(tilesWidth,tilesHeight);
tileParse();
//once map is drawn, convert to image for display
//attempts have gone here, removed for legibility and elaboration further down
};
reader.onerror = (event) => alert(event.error.name);
reader.readAsText(file);
});
This draws a grid to the canvas:
function drawGrid(width,height){
canvas = document.getElementById("canvas");
canvas.width = width + 20;
canvas.height = height + 20;
if (canvas.getContext) {
const ctxt = canvas.getContext("2d");
ctxt.save();
ctxt.beginPath();
ctxt.strokeStyle = "black";
var pad = 10; //padding
//adjust width and height to account for external grid padding
for(var x = 0; x <= width; x += 120){
ctxt.moveTo(0.5 + x + pad, pad);
ctxt.lineTo(0.5 + x + pad, height+pad);
//ctxt.stroke();
}
for (var y = 0; y <=height; y+=120){
ctxt.moveTo(pad, 0.5 + y + pad);
ctxt.lineTo(width + pad, 0.5 + y + pad);
//ctxt.stroke();
}
ctxt.stroke();
ctxt.closePath();
tileParse();
}
}
This parses the vectors to add tiles
function tileParse(){
for (let row = 0; row < tilesArray.length ; row++) {
for (let col = 0; col < tilesArray[row].length ; col++) {
let type = tilesArray[row][col][0];
let extremeness = tilesArray[row][col][1];
switch(type) {
case 'R':
drawRiver(row,col, extremeness);
break;
case 'M':
drawMountain(row,col, extremeness);
break;
case 'V':
drawValley(row,col, extremeness);
break;
case 'C':
drawSettlement(row,col, extremeness);
break;
case 'F':
drawForest(row,col, extremeness);
break;
default:
//leave blank
}
}
}
}
And this is one of (the drawRiver() method) the methods called within the tile parser, for reference. All the methods are very very similarly structured, just with changes in line with the different types (Mountain instead of River, for instance).
function drawRiver(tileRow, tileCol, extremeness){
const canvas = document.getElementById("canvas");
if (canvas.getContext) {
const ctxt = canvas.getContext("2d");
var image = new Image(120,120);
image.crossOrigin="anonymous";
switch(extremeness){
case '1':
image.src = 'assets/rivers/River1.svg';
break;
case '2':
image.src = 'assets/rivers/River2.svg';
break;
case '3':
image.src = 'assets/rivers/River3.svg';
break;
case '4':
image.src = 'assets/rivers/River4.svg';
break;
case '5':
image.src = 'assets/rivers/River5.svg';
break;
default:
//use original tile as default
image.src = 'assets/rivers/River.svg';
}
image.onload = () => {
startX = (tileCol*120)+10;
startY = (tileRow*120)+10;
ctxt.drawImage(image, startX, startY, image.width, image.height);
}
}
}
All of this background is because when I attempt to convert the HTML Canvas into a PNG (for display and downloading, but I haven’t implemented any attempts at downloading functionality), the grid (put on the canvas with lineTo()) displays, but none of the SVGs have captured. The below image is a side-by-side of the HTML canvas and the PNG that is currently captured by my code.
a side-by-side comparison of the canvas and the captured png. the png only shows the grid, without any of the svg images
I want to generate a png that captures everything that was added to the canvas. So that side-by-side comparison, if the project were working as intented, should show two elements that look identical when displayed. (No visual differences, that is.)
Note: the SVG files being accessed are stored locally on my computer, and are stored in an assets folder within the GitHub repo that I’m pushing to, so there shouldn’t(?) be any external origin issues with the files.
I have attempted the following attempts at implementation, the first two of which were extracted from this question: Capture HTML canvas as GIF/JPG/PNG/PDF?
const canvas = document.getElementById("canvas");
const url = canvas.toDataURL();
console.log(url);
document.getElementById('finMap').src = url;
canvas.toBlob((blob) => {
const url = URL.createObjectURL(blob);
document.getElementById('finMap').src = url;
})
Both of these techniques, implemented within the reader (the first code snippet I shared), produced the png that only had the grid on it.
This following technique utilizes hongru’s Canvas2Image, but I haven’t been able to get it to work. It throws the following error: InvalidStateError: CanvasRenderingContext2D.drawImage: Canvas exceeds max size.
import("./canvas2image").then((convertToPNG) => {
console.log(Canvas2Image.convertToPNG(canvas, tilesWidth*120, tilesHeight*120));
})
Everything that I’ve read suggests that one of these methods should have worked, or I should have been generating an entirely blank PNG. I can’t figure out where in my implementation things are going awry to be able to further drill down in my troubleshooting.