mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 09:41:24 +01:00
download tiles
This commit is contained in:
parent
af1d369e31
commit
e9fa4cbd6c
5 changed files with 177 additions and 3 deletions
|
|
@ -1513,6 +1513,11 @@ div.states > .coaIcon > use {
|
||||||
width: 5.5em;
|
width: 5.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#saveTilesScreen div.label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 5em;
|
||||||
|
}
|
||||||
|
|
||||||
#regimentBody div {
|
#regimentBody div {
|
||||||
margin: 0.1em 0;
|
margin: 0.1em 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
28
index.html
28
index.html
|
|
@ -3389,6 +3389,7 @@
|
||||||
<button onclick="saveSVG()" data-tip="Download the map as vector image (open in browser or Inkscape)">.svg</button>
|
<button onclick="saveSVG()" data-tip="Download the map as vector image (open in browser or Inkscape)">.svg</button>
|
||||||
<button onclick="savePNG()" data-tip="Download visible part of the map as .png (lossless compressed)">.png</button>
|
<button onclick="savePNG()" data-tip="Download visible part of the map as .png (lossless compressed)">.png</button>
|
||||||
<button onclick="saveJPEG()" data-tip="Download visible part of the map as .jpeg (lossy compressed) image">.jpeg</button>
|
<button onclick="saveJPEG()" data-tip="Download visible part of the map as .jpeg (lossy compressed) image">.jpeg</button>
|
||||||
|
<button onclick="openSaveTiles()" data-tip="Split map into smaller png tiles and download as zip archive">tiles</button>
|
||||||
<button onclick="saveGeoJSON()" data-tip="Download map data in GeoJSON format">.json</button>
|
<button onclick="saveGeoJSON()" data-tip="Download map data in GeoJSON format">.json</button>
|
||||||
<button onclick="quickSave()" data-tip="Save the project to browser storage (unreliable). Shortcut: F6">storage</button>
|
<button onclick="quickSave()" data-tip="Save the project to browser storage (unreliable). Shortcut: F6">storage</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -3396,7 +3397,7 @@
|
||||||
<p style="font-style: italic">Generator uses pop-up window to download files. Please ensure your browser does not block popups.</p>
|
<p style="font-style: italic">Generator uses pop-up window to download files. Please ensure your browser does not block popups.</p>
|
||||||
<div data-tip="Define scale of a saved png/jpeg image (e.g. 5x). Saving big images is slow and may cause a browser crash!" style="margin-bottom: .3em">
|
<div data-tip="Define scale of a saved png/jpeg image (e.g. 5x). Saving big images is slow and may cause a browser crash!" style="margin-bottom: .3em">
|
||||||
PNG / JPEG scale:
|
PNG / JPEG scale:
|
||||||
<input id="pngResolutionInput" data-stored="pngResolution" type="range" min=1 max=8 value=1 style="width: 10.8em" oninput="pngResolutionOutput.value = this.value">
|
<input id="pngResolutionInput" data-stored="pngResolution" type="range" min=1 max=8 value=1 style="width: 14em" oninput="pngResolutionOutput.value = this.value">
|
||||||
<input id="pngResolutionOutput" data-stored="pngResolution" type="number" min=1 max=8 value=1 oninput="pngResolutionInput.value = this.value">
|
<input id="pngResolutionOutput" data-stored="pngResolution" type="number" min=1 max=8 value=1 oninput="pngResolutionInput.value = this.value">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -3410,6 +3411,30 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="saveTilesScreen" style="display: none" class="dialog">
|
||||||
|
<p style="font-style: italic">Map will be split into tiles and downloaded as a single zip file. Avoid saving to big images</p>
|
||||||
|
<div data-tip="Number of columns" style="margin-bottom: .3em">
|
||||||
|
<div class="label">Columns:</div>
|
||||||
|
<input id="tileColsInput" data-stored="tileCols" type="range" min=2 max=20 value=8 style="width: 11em" oninput="tileColsOutput.value = this.value">
|
||||||
|
<input id="tileColsOutput" data-stored="tileCols" type="number" min=2 value=8 oninput="tileColsInput.value = this.value">
|
||||||
|
</div>
|
||||||
|
<div data-tip="Number of rows" style="margin-bottom: .3em">
|
||||||
|
<div class="label">Rows:</div>
|
||||||
|
<input id="tileRowsInput" data-stored="tileRows" type="range" min=2 max=20 value=8 style="width: 11em" oninput="tileRowsOutput.value = this.value">
|
||||||
|
<input id="tileRowsOutput" data-stored="tileRows" type="number" min=2 value=8 oninput="tileRowsInput.value = this.value">
|
||||||
|
</div>
|
||||||
|
<div data-tip="Image scale relative to image size (e.g. 5x)" style="margin-bottom: .3em">
|
||||||
|
<div class="label">Scale:</div>
|
||||||
|
<input id="tileScaleInput" data-stored="tileScale" type="range" min=1 max=4 value=1 style="width: 11em" oninput="tileScaleOutput.value = this.value">
|
||||||
|
<input id="tileScaleOutput" data-stored="tileScale" type="number" min=1 value=1 oninput="tileScaleInput.value = this.value;">
|
||||||
|
</div>
|
||||||
|
<div data-tip="Calculated size of image if combined" style="margin-bottom: .3em">
|
||||||
|
<div class="label">Total size:</div>
|
||||||
|
<div id="tileSize" style="display: inline-block">1000 x 1000 px</div>
|
||||||
|
</div>
|
||||||
|
<div id="tileStatus" style="background-color: #33333310; font-style: italic"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="alert" style="display: none" class="dialog">
|
<div id="alert" style="display: none" class="dialog">
|
||||||
<p id="alertMessage">Warning!</p>
|
<p id="alertMessage">Warning!</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -4164,5 +4189,6 @@
|
||||||
<script defer src="libs/rgbquant.min.js"></script>
|
<script defer src="libs/rgbquant.min.js"></script>
|
||||||
<script defer src="libs/jquery.ui.touch-punch.min.js"></script>
|
<script defer src="libs/jquery.ui.touch-punch.min.js"></script>
|
||||||
<script defer src="libs/pell.min.js"></script>
|
<script defer src="libs/pell.min.js"></script>
|
||||||
|
<script defer src="libs/jszip.min.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
13
libs/jszip.min.js
vendored
Normal file
13
libs/jszip.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -70,13 +70,78 @@ async function saveJPEG() {
|
||||||
TIME && console.timeEnd("saveJPEG");
|
TIME && console.timeEnd("saveJPEG");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// download map as png tiles
|
||||||
|
async function saveTiles() {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
// download schema
|
||||||
|
const urlSchema = await getMapURL("tiles", "schema");
|
||||||
|
const zip = new JSZip();
|
||||||
|
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
canvas.width = graphWidth;
|
||||||
|
canvas.height = graphHeight;
|
||||||
|
|
||||||
|
const imgSchema = new Image();
|
||||||
|
imgSchema.src = urlSchema;
|
||||||
|
imgSchema.onload = function () {
|
||||||
|
ctx.drawImage(imgSchema, 0, 0, canvas.width, canvas.height);
|
||||||
|
canvas.toBlob(blob => zip.file(`fmg_tile_schema.png`, blob));
|
||||||
|
};
|
||||||
|
|
||||||
|
// download tiles
|
||||||
|
const url = await getMapURL("tiles");
|
||||||
|
const tilesX = +document.getElementById("tileColsInput").value;
|
||||||
|
const tilesY = +document.getElementById("tileRowsInput").value;
|
||||||
|
const scale = +document.getElementById("tileScaleInput").value;
|
||||||
|
|
||||||
|
const tileW = (graphWidth / tilesX) | 0;
|
||||||
|
const tileH = (graphHeight / tilesY) | 0;
|
||||||
|
const tolesTotal = tilesX * tilesY;
|
||||||
|
|
||||||
|
canvas.width = graphWidth * scale;
|
||||||
|
canvas.height = graphHeight * scale;
|
||||||
|
|
||||||
|
let loaded = 0;
|
||||||
|
const img = new Image();
|
||||||
|
img.src = url;
|
||||||
|
img.onload = function () {
|
||||||
|
for (let y = 0, i = 0; y < graphHeight; y += tileH) {
|
||||||
|
for (let x = 0; x < graphWidth; x += tileW, i++) {
|
||||||
|
ctx.drawImage(img, x, y, tileW, tileH, 0, 0, canvas.width, canvas.height);
|
||||||
|
const name = `fmg_tile_${i}.png`;
|
||||||
|
canvas.toBlob(blob => {
|
||||||
|
zip.file(name, blob);
|
||||||
|
loaded += 1;
|
||||||
|
if (loaded === tolesTotal) return downloadZip();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function downloadZip() {
|
||||||
|
const name = `${getFileName()}.zip`;
|
||||||
|
zip.generateAsync({type: "blob"}).then(blob => {
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = URL.createObjectURL(blob);
|
||||||
|
link.download = name;
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
|
||||||
|
setTimeout(() => URL.revokeObjectURL(link.href), 5000);
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// parse map svg to object url
|
// parse map svg to object url
|
||||||
async function getMapURL(type, subtype) {
|
async function getMapURL(type, subtype) {
|
||||||
const cloneEl = document.getElementById("map").cloneNode(true); // clone svg
|
const cloneEl = document.getElementById("map").cloneNode(true); // clone svg
|
||||||
cloneEl.id = "fantasyMap";
|
cloneEl.id = "fantasyMap";
|
||||||
document.body.appendChild(cloneEl);
|
document.body.appendChild(cloneEl);
|
||||||
const clone = d3.select(cloneEl);
|
const clone = d3.select(cloneEl);
|
||||||
clone.select("#debug").remove();
|
if (subtype !== "schema") clone.select("#debug").remove();
|
||||||
|
|
||||||
const cloneDefs = cloneEl.getElementsByTagName("defs")[0];
|
const cloneDefs = cloneEl.getElementsByTagName("defs")[0];
|
||||||
const svgDefs = document.getElementById("defElements");
|
const svgDefs = document.getElementById("defElements");
|
||||||
|
|
@ -93,6 +158,7 @@ async function getMapURL(type, subtype) {
|
||||||
clone.attr("width", graphWidth).attr("height", graphHeight);
|
clone.attr("width", graphWidth).attr("height", graphHeight);
|
||||||
clone.select("#viewbox").attr("transform", null);
|
clone.select("#viewbox").attr("transform", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === "svg") removeUnusedElements(clone);
|
if (type === "svg") removeUnusedElements(clone);
|
||||||
if (customization && type === "mesh") updateMeshCells(clone);
|
if (customization && type === "mesh") updateMeshCells(clone);
|
||||||
inlineStyle(clone);
|
inlineStyle(clone);
|
||||||
|
|
|
||||||
|
|
@ -638,7 +638,7 @@ function showSavePane() {
|
||||||
$("#saveMapData").dialog({
|
$("#saveMapData").dialog({
|
||||||
title: "Save map",
|
title: "Save map",
|
||||||
resizable: false,
|
resizable: false,
|
||||||
width: "27em",
|
width: "30em",
|
||||||
position: {my: "center", at: "center", of: "svg"},
|
position: {my: "center", at: "center", of: "svg"},
|
||||||
buttons: {
|
buttons: {
|
||||||
Close: function () {
|
Close: function () {
|
||||||
|
|
@ -719,6 +719,70 @@ document.getElementById("mapToLoad").addEventListener("change", function () {
|
||||||
uploadMap(fileToLoad);
|
uploadMap(fileToLoad);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function openSaveTiles() {
|
||||||
|
closeDialogs();
|
||||||
|
updateTilesOptions();
|
||||||
|
const status = document.getElementById("tileStatus");
|
||||||
|
status.innerHTML = "";
|
||||||
|
|
||||||
|
$("#saveTilesScreen").dialog({
|
||||||
|
resizable: false,
|
||||||
|
title: "Download tiles",
|
||||||
|
width: "23em",
|
||||||
|
buttons: {
|
||||||
|
Download: function () {
|
||||||
|
status.innerHTML = "Preparing for download...";
|
||||||
|
setTimeout(() => (status.innerHTML = "Downloading. It may take some time."), 1000);
|
||||||
|
const loading = setInterval(() => (status.innerHTML += "."), 1000);
|
||||||
|
saveTiles().then(() => {
|
||||||
|
clearInterval(loading);
|
||||||
|
status.innerHTML = `Done. Check file in "Downloads" (crtl + J)`;
|
||||||
|
setTimeout(() => (status.innerHTML = ""), 8000);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
Cancel: function () {
|
||||||
|
$(this).dialog("close");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close: () => debug.selectAll("*").remove()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("saveTilesScreen")
|
||||||
|
.querySelectorAll("input")
|
||||||
|
.forEach(el => el.addEventListener("input", updateTilesOptions));
|
||||||
|
|
||||||
|
function updateTilesOptions() {
|
||||||
|
const tileSize = document.getElementById("tileSize");
|
||||||
|
const tilesX = +document.getElementById("tileColsInput").value;
|
||||||
|
const tilesY = +document.getElementById("tileRowsInput").value;
|
||||||
|
|
||||||
|
// calculate size
|
||||||
|
const scale = +document.getElementById("tileScaleInput").value;
|
||||||
|
const sizeX = graphWidth * scale * tilesX;
|
||||||
|
const sizeY = graphHeight * scale * tilesY;
|
||||||
|
const totalSize = sizeX * sizeY;
|
||||||
|
|
||||||
|
tileSize.innerHTML = `${sizeX} x ${sizeY} px`;
|
||||||
|
tileSize.style.color = totalSize > 1e9 ? "#053305" : totalSize > 1e7 ? "#9e6409" : "#1a941a";
|
||||||
|
|
||||||
|
// draw tiles
|
||||||
|
const rects = [];
|
||||||
|
const labels = [];
|
||||||
|
const tileW = (graphWidth / tilesX) | 0;
|
||||||
|
const tileH = (graphHeight / tilesY) | 0;
|
||||||
|
for (let y = 0, i = 0; y < graphHeight; y += tileH) {
|
||||||
|
for (let x = 0; x < graphWidth; x += tileW, i++) {
|
||||||
|
rects.push(`<rect x=${x} y=${y} width=${tileW} height=${tileH} />`);
|
||||||
|
labels.push(`<text x=${x + tileW / 2} y=${y + tileH / 2}>${i}</text>`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const rectsG = "<g fill='none' stroke='#000'>" + rects.join("") + "</g>";
|
||||||
|
const labelsG = "<g fill='#000' stroke='none' text-anchor='middle' dominant-baseline='central' font-size='24px'>" + labels.join("") + "</g>";
|
||||||
|
debug.html(rectsG + labelsG);
|
||||||
|
}
|
||||||
|
|
||||||
// View mode
|
// View mode
|
||||||
viewMode.addEventListener("click", changeViewMode);
|
viewMode.addEventListener("click", changeViewMode);
|
||||||
function changeViewMode(event) {
|
function changeViewMode(event) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue