v1.5.04 - emblem gallery generator

This commit is contained in:
Azgaar 2021-02-01 14:58:30 +03:00
parent 3aaddcf1a7
commit 68dc822121
7 changed files with 174 additions and 42 deletions

View file

@ -1581,9 +1581,18 @@ rect.fillRect {
width: 9em; width: 9em;
} }
#emblemsBottom { #emblemsBottom {
margin-top: 4px; margin-top: 4px;
text-align: center;
}
#emblemDownloadControl {
margin-top: .3em;
text-align: center;
}
#emblemDownloadControl > input {
width: 4.1em;
} }
#picker text { #picker text {

View file

@ -3343,6 +3343,7 @@
<div> <div>
<b id="emblemArmiger"></b> <b id="emblemArmiger"></b>
</div> </div>
<hr/>
<div data-tip="Select state"> <div data-tip="Select state">
<div class="label">State:</div> <div class="label">State:</div>
<select id="emblemStates"></select> <select id="emblemStates"></select>
@ -3424,10 +3425,16 @@
<button id="emblemsRegenerate" data-tip="Regenerate emblem" class="icon-shuffle"></button> <button id="emblemsRegenerate" data-tip="Regenerate emblem" class="icon-shuffle"></button>
<button id="emblemsArmoria" data-tip="Edit the emblem in Armoria: our dedicated rich heraldry editor" class="icon-brush"></button> <button id="emblemsArmoria" data-tip="Edit the emblem in Armoria: our dedicated rich heraldry editor" class="icon-brush"></button>
<button id="emblemsUpload" data-tip="Upload png, jpg or svg image from Armoria or other sources as emblem" class="icon-upload"></button> <button id="emblemsUpload" data-tip="Upload png, jpg or svg image from Armoria or other sources as emblem" class="icon-upload"></button>
<button id="emblemsDownload" data-tip="Download emblem as png image" class="icon-download"></button> <button id="emblemsDownload" data-tip="Set size, select file format and download emblem image" class="icon-download"></button>
<button id="emblemsGallery" data-tip="Download emblems gallery as html table (open in browser)" class="icon-layer-group"></button> <button id="emblemsGallery" data-tip="Download emblems gallery as html document (open in browser; downloading takes some time)" class="icon-layer-group"></button>
<button id="emblemsFocus" data-tip="Show emblem associated area or place" class="icon-target"></button> <button id="emblemsFocus" data-tip="Show emblem associated area or place" class="icon-target"></button>
</div> </div>
<div id="emblemDownloadControl" class="hidden">
<input id="emblemsDownloadSize" data-tip="Set image size in pixels" type="number" value="500" step="100" min="100" max="10000"/>
<button id="emblemsDownloadSVG" data-tip="Download as SVG: scalable vector image. Best quality, can be opened in browser or Inkscape">SVG</button>
<button id="emblemsDownloadPNG" data-tip="Download as PNG: lossless raster image with transparent background">PNG</button>
<button id="emblemsDownloadJPG" data-tip="Download as JPG: lossy compressed raster image with solid white background">JPG</button>
</div>
</div> </div>
<div id="unitsEditor" class="dialog stable" style="display: none"> <div id="unitsEditor" class="dialog stable" style="display: none">

View file

@ -209,6 +209,8 @@
// test in FF // test in FF
// generate all? // generate all?
// layout preset // layout preset
// burg editor - add emblem
// other editors
const t1 = P(kinship) ? parent.t1 : getTincture("field"); const t1 = P(kinship) ? parent.t1 : getTincture("field");
const coa = {t1}; const coa = {t1};

View file

@ -672,7 +672,7 @@
moriaOrc: "0 0 200 200" moriaOrc: "0 0 200 200"
} }
async function draw(id, coa) { function draw(id, coa) {
const {division, ordinaries = [], charges = []} = coa; const {division, ordinaries = [], charges = []} = coa;
const ordinariesRegular = ordinaries.filter(o => !o.above); const ordinariesRegular = ordinaries.filter(o => !o.above);
const ordinariesAboveCharges = ordinaries.filter(o => o.above); const ordinariesAboveCharges = ordinaries.filter(o => o.above);
@ -877,7 +877,7 @@
} }
// async render coa if it does not exist // async render coa if it does not exist
const trigger = async function(id, coa) { const trigger = function(id, coa) {
if (!document.getElementById(id)) draw(id, coa); if (!document.getElementById(id)) draw(id, coa);
} }

View file

@ -25,7 +25,11 @@ function editEmblem(type, id, el) {
document.getElementById("emblemShapeSelector").oninput = changeShape; document.getElementById("emblemShapeSelector").oninput = changeShape;
document.getElementById("emblemsRegenerate").onclick = regenerate; document.getElementById("emblemsRegenerate").onclick = regenerate;
document.getElementById("emblemsArmoria").onclick = openInArmoria; document.getElementById("emblemsArmoria").onclick = openInArmoria;
document.getElementById("emblemsDownload").onclick = download; document.getElementById("emblemsDownload").onclick = toggleDownload;
document.getElementById("emblemsDownloadSVG").onclick = () => download("svg");
document.getElementById("emblemsDownloadPNG").onclick = () => download("png");
document.getElementById("emblemsDownloadJPG").onclick = () => download("jpeg");
document.getElementById("emblemsGallery").onclick = downloadGallery;
document.getElementById("emblemsFocus").onclick = showArea; document.getElementById("emblemsFocus").onclick = showArea;
function defineEmblemData(e) { function defineEmblemData(e) {
@ -164,51 +168,161 @@ function editEmblem(type, id, el) {
openURL(url); openURL(url);
} }
function download() { function toggleDownload() {
const buttons = document.getElementById("emblemDownloadControl");
buttons.classList.toggle("hidden");
}
function download(format) {
const coa = document.getElementById(id); const coa = document.getElementById(id);
const size = +emblemsDownloadSize.value;
const url = getURL(coa, el.coa, size);
const link = document.createElement("a");
link.download = getFileName(`Emblem ${el.fullName || el.name}`) + "." + format;
if (format === "svg") downloadSVG(url, link); else downloadRaster(format, url, link, size);
document.getElementById("emblemDownloadControl").classList.add("hidden");
}
function downloadSVG(url, link) {
link.href = url;
document.body.appendChild(link);
link.click();
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
}
function downloadRaster(format, url, link, size) {
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
canvas.width = 500; canvas.width = size;
canvas.height = 500; canvas.height = size;
const url = getURL(coa, el.coa);
const img = new Image(); const img = new Image();
img.src = url; img.src = url;
img.onload = () => { img.onload = async function() {
URL.revokeObjectURL(url); if (format === "jpeg") {
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
ctx.drawImage(img, 0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
drawCanvas(canvas, el); const URL = await canvas.toDataURL("image/" + format, .92);
} link.href = URL;
document.body.appendChild(link);
function drawCanvas(canvas, el) { link.click();
const link = document.createElement("a"); window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
link.download = getFileName(`Emblem ${el.fullName || el.name}`) + ".png";
canvas.toBlob(function (blob) {
link.href = window.URL.createObjectURL(blob);
document.body.appendChild(link);
link.click();
setTimeout(function () {
canvas.remove();
window.URL.revokeObjectURL(link.href);
}, 5000);
});
} }
} }
function getURL(svg, coa) { function getURL(svg, coa, size) {
const serialized = getSVG(svg, coa, size);
const blob = new Blob([serialized], { type: 'image/svg+xml;charset=utf-8' });
const url = window.URL.createObjectURL(blob);
return url;
}
function getSVG(svg, coa, size) {
const clone = svg.cloneNode(true); // clone svg const clone = svg.cloneNode(true); // clone svg
const d = clone.getElementsByTagName("defs")[0]; const d = clone.getElementsByTagName("defs")[0];
clone.removeAttribute("id");
clone.setAttribute("width", size);
clone.setAttribute("height", size);
d.insertAdjacentHTML("beforeend", document.getElementById(coa.shield).outerHTML); // copy shield to defs d.insertAdjacentHTML("beforeend", document.getElementById(coa.shield).outerHTML); // copy shield to defs
svg.querySelectorAll("[fill^=url]").forEach(el => { clone.querySelectorAll("[fill^=url]").forEach(el => {
const id = el.getAttribute("fill").match(/\#([^)]+)\)/)[1]; const id = el.getAttribute("fill").match(/\#([^)]+)\)/)[1];
d.insertAdjacentHTML("beforeend", document.getElementById(id).outerHTML); d.insertAdjacentHTML("beforeend", document.getElementById(id).outerHTML);
}); });
const serialized = (new XMLSerializer()).serializeToString(clone); return (new XMLSerializer()).serializeToString(clone);
const blob = new Blob([serialized], { type: 'image/svg+xml;charset=utf-8' }); }
const url = window.URL.createObjectURL(blob);
return url; function downloadGallery() {
const name = getFileName("Emblems Gallery");
const validStates = states.filter(s => s.i && !s.removed && s.coa);
const validProvinces = provinces.filter(p => p.i && !p.removed && p.coa);
const validBurgs = burgs.filter(b => b.i && !b.removed && b.coa);
triggerCOALoad(validStates, validProvinces, validBurgs);
const timeout = (validStates.length + validProvinces.length + validBurgs.length) * 8;
tip("Preparing to download...", true, "warn", timeout);
d3.timeout(runDownload, timeout);
function runDownload() {
const stateSection = `<div><h2>States</h2>` + validStates.map(state => {
const el = document.getElementById("stateCOA"+state.i);
const svg = getSVG(el, state.coa, 200);
return `<figure id="state_${state.i}"><a href="#provinces_${state.i}"><figcaption>${state.fullName}</figcaption>${svg}</a></figure>`;
}).join("") + `</div>`;
const provinceSections = validStates.map(state => {
const figures = validProvinces.filter(p => p.state === state.i).map(province => {
const el = document.getElementById("provinceCOA"+province.i);
const svg = getSVG(el, province.coa, 200);
return `<figure id="province_${province.i}"><a href="#burgs_${province.i}"><figcaption>${province.fullName}</figcaption>${svg}</a></figure>`;
}).join("");
return `<div id="provinces_${state.i}"><h2>${state.fullName} provinces</h2>${figures}</div>`;
}).join("");
const burgSections = validStates.map(state => {
const stateBurgs = validBurgs.filter(b => b.state === state.i);
let stateBurgSections = validProvinces.filter(p => p.state === state.i).map(province => {
const provinceBurgs = stateBurgs.filter(b => cells.province[b.cell] === province.i);
if (!provinceBurgs.length) return "";
const provinceBurgFigures = provinceBurgs.map(burg => {
const el = document.getElementById("burgCOA"+burg.i);
const svg = getSVG(el, burg.coa, 200);
return `<figure id="burg_${burg.i}"><figcaption>${burg.name}</figcaption>${svg}</figure>`;
}).join("");
return `<div id="burgs_${province.i}"><h2>${province.fullName} burgs</h2>${provinceBurgFigures}</div>`;
}).join("");
const stateBurgOutOfProvinces = stateBurgs.filter(b => !cells.province[b.cell]);
const stateBurgOutOfProvincesFigures = stateBurgOutOfProvinces.map(burg => {
const el = document.getElementById("burgCOA"+burg.i);
const svg = getSVG(el, burg.coa, 200);
return `<figure id="burg_${burg.i}"><figcaption>${burg.name}</figcaption>${svg}</figure>`;
}).join("");
if (stateBurgOutOfProvincesFigures) stateBurgSections += `<div><h2>${state.fullName} burgs under direct control</h2>${stateBurgOutOfProvincesFigures}</div>`;
return stateBurgSections;
}).join("");
const neutralBurgs = validBurgs.filter(b => !b.state);
const neutralsSection = neutralBurgs.length ? "<div><h2>Independent burgs</h2>" + neutralBurgs.map(burg => {
const el = document.getElementById("burgCOA"+burg.i);
const svg = getSVG(el, burg.coa, 200);
return `<figure id="burg_${burg.i}"><figcaption>${burg.name}</figcaption>${svg}</figure>`;
}).join("") + "</div>" : "";
const FMG = `<a href="https://azgaar.github.io/Fantasy-Map-Generator" target="_blank">Azgaar's Fantasy Map Generator</a>`;
const license = `<a target="_blank" href="https://github.com/Azgaar/Armoria#license">the license</a>`;
const html = `<!DOCTYPE html><html><head><title>${mapName.value} Emblems Gallery</title></head>
<style type="text/css">
body { margin: 0; padding: 1em; font-family: serif; }
h1, h2 { font-family: 'Forum'; }
div { width: 100%; max-width: 1018px; margin: 0 auto; border-bottom: 1px solid #ddd; }
figure { margin: 0 0 2em; display: inline-block; transition: .2s; }
figure:hover { background-color: #f6f6f6; }
figcaption { text-align: center; margin: .4em 0; width: 200px; font-family: 'Overlock SC' }
figure > a { color: black; text-decoration: none; }
address { width: 100%; max-width: 1018px; margin: 0 auto; }
</style>
<link href="https://fonts.googleapis.com/css2?family=Forum&family=Overlock+SC" rel="stylesheet">
<body>
<div><h1>${mapName.value} Emblems Gallery</h1></div>
${stateSection}
${provinceSections}
${burgSections}
${neutralsSection}
<address>Generated by ${FMG}. The tool is free, but images may be copyrighted, see ${license}</address>
</body></html>`;
downloadFile(html, name + ".html", "text/plain");
}
}
function triggerCOALoad(states, provinces, burgs) {
states.forEach(state => COArenderer.trigger("stateCOA"+state.i, state.coa));
provinces.forEach(province => COArenderer.trigger("provinceCOA"+province.i, province.coa));
burgs.forEach(burg => COArenderer.trigger("burgCOA"+burg.i, burg.coa));
} }
function dragEmblem() { function dragEmblem() {

View file

@ -330,7 +330,7 @@ document.querySelectorAll("[data-locked]").forEach(function(e) {
event.stopPropagation(); event.stopPropagation();
}); });
e.addEventListener("click", function(event) { e.addEventListener("click", function() {
const id = (this.id).slice(5); const id = (this.id).slice(5);
if (this.className === "icon-lock") unlock(id); if (this.className === "icon-lock") unlock(id);
else lock(id); else lock(id);

View file

@ -87,8 +87,8 @@ function showSupporters() {
Jeppe Skov Jensen,María Martín López,Martin Seeger,Annie Rishor,Aram Sabatés,MadNomadMedia,Eric Foley,Vito Martono,James H. Anthony,Kevin Cossutta, Jeppe Skov Jensen,María Martín López,Martin Seeger,Annie Rishor,Aram Sabatés,MadNomadMedia,Eric Foley,Vito Martono,James H. Anthony,Kevin Cossutta,
Thirty-OneR ,ThatGuyGW ,Dee Chiu,MontyBoosh ,Achillain ,Jaden ,SashaTK,Steve Johnson,Eric Foley,Vito Martono,James H. Anthony,Kevin Cossutta,Thirty-OneR, Thirty-OneR ,ThatGuyGW ,Dee Chiu,MontyBoosh ,Achillain ,Jaden ,SashaTK,Steve Johnson,Eric Foley,Vito Martono,James H. Anthony,Kevin Cossutta,Thirty-OneR,
ThatGuyGW,Dee Chiu,MontyBoosh,Achillain,Jaden,SashaTK,Steve Johnson,Pierrick Bertrand,Jared Kennedy,Dylan Devenny,Kyle Robertson,Andrew Rostaing,Daniel Gill, ThatGuyGW,Dee Chiu,MontyBoosh,Achillain,Jaden,SashaTK,Steve Johnson,Pierrick Bertrand,Jared Kennedy,Dylan Devenny,Kyle Robertson,Andrew Rostaing,Daniel Gill,
Char, Jack, Barna Csíkos, Ian Rousseau, Nicholas Grabstas, Tom Van Orden jr, Bryan Brake, Akylos, Riley Seaman, Char,Jack,Barna Csíkos,Ian Rousseau,Nicholas Grabstas,Tom Van Orden jr,Bryan Brake,Akylos,Riley Seaman,MaxOliver,Evan-DiLeo,Alex Debus,Joshua Vaught,
MaxOliver, Evan-DiLeo, Alex Debus, Joshua Vaught, Kyle S, Eric Moore, Dean Dunakin, Uniquenameosaurus, WarWizardGames`; Kyle S,Eric Moore,Dean Dunakin,Uniquenameosaurus,WarWizardGames,Chance Mena,Jan Ka`;
const array = supporters.replace(/(?:\r\n|\r|\n)/g, "").split(",").map(v => capitalize(v.trim())).sort(); const array = supporters.replace(/(?:\r\n|\r|\n)/g, "").split(",").map(v => capitalize(v.trim())).sort();
alertMessage.innerHTML = "<ul style='column-count: 5; column-gap: 2em'>" + array.map(n => `<li>${n}</li>`).join("") + "</ul>"; alertMessage.innerHTML = "<ul style='column-count: 5; column-gap: 2em'>" + array.map(n => `<li>${n}</li>`).join("") + "</ul>";