mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 09:41:24 +01:00
v1.4.14
This commit is contained in:
parent
6dd1e22e93
commit
2a67ee5d9f
14 changed files with 1010 additions and 267 deletions
11
index.css
11
index.css
|
|
@ -60,6 +60,13 @@ textarea {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
#preview {
|
||||
position: absolute;
|
||||
bottom: 1em;
|
||||
left: 1em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#pickerContainer {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
|
|
@ -1021,8 +1028,8 @@ div#regimentSelectorBody > div > div {
|
|||
}
|
||||
|
||||
.color-div {
|
||||
width: 2.7em;
|
||||
height: 1.1em;
|
||||
width: 2.5em;
|
||||
height: 1em;
|
||||
display: inline-block;
|
||||
margin: .1em .2em;
|
||||
border: 1px #c5c5c5 groove;
|
||||
|
|
|
|||
27
index.html
27
index.html
|
|
@ -2103,7 +2103,7 @@
|
|||
|
||||
<button id="labelAlign" data-tip="Turn text path into a straight line" class="icon-resize-horizontal"></button>
|
||||
<button id="labelLegend" data-tip="Edit free text notes (legend) for this label" class="icon-edit"></button>
|
||||
<button id="labelRemoveSingle" data-tip="Remove the label. Shortcut: Delete" class="icon-trash"></button>
|
||||
<button id="labelRemoveSingle" data-tip="Remove the label. Shortcut: Delete" class="icon-trash fastDelete"></button>
|
||||
</div>
|
||||
|
||||
<div id="riverEditor" class="dialog" style="display: none">
|
||||
|
|
@ -2130,7 +2130,7 @@
|
|||
<button id="riverElevationProfile" data-tip="Show the elevation profile for the river" class="icon-chart-area"></button>
|
||||
<button id="riverNew" data-tip="Create new river clicking on map" class="icon-map-pin"></button>
|
||||
<button id="riverLegend" data-tip="Edit free text notes (legend) for the river" class="icon-edit"></button>
|
||||
<button id="riverRemove" data-tip="Remove river. Shortcut: Delete" class="icon-trash"></button>
|
||||
<button id="riverRemove" data-tip="Remove river. Shortcut: Delete" class="icon-trash fastDelete"></button>
|
||||
</div>
|
||||
|
||||
<div id="elevationProfile" class="dialog" style="display: none" width="100%">
|
||||
|
|
@ -2153,7 +2153,7 @@
|
|||
<button id="routeSplit" data-tip="Click on a control point to split the route" class="icon-unlink"></button>
|
||||
<button id="routeLegend" data-tip="Edit free text notes (legend) for the route" class="icon-edit"></button>
|
||||
<button id="routeNew" data-tip="Create new route clicking on map" class="icon-map-pin"></button>
|
||||
<button id="routeRemove" data-tip="Remove route. Shortcut: Delete" class="icon-trash"></button>
|
||||
<button id="routeRemove" data-tip="Remove route. Shortcut: Delete" class="icon-trash fastDelete"></button>
|
||||
</div>
|
||||
|
||||
<div id="lakeEditor" class="dialog" style="display: none">
|
||||
|
|
@ -2176,7 +2176,7 @@
|
|||
<button id="iceRandomize" data-tip="Randomize Iceberd shape" class="icon-shuffle"></button>
|
||||
<input id="iceSize" data-tip="Change Iceberg size" type="range" min=".05" max="1" step=".01">
|
||||
<button id="iceNew" data-tip="Add an Iceberg (click on map)" class="icon-plus"></button>
|
||||
<button id="iceRemove" data-tip="Remove the element. Shortcut: Delete" class="icon-trash"></button>
|
||||
<button id="iceRemove" data-tip="Remove the element. Shortcut: Delete" class="icon-trash fastDelete"></button>
|
||||
</div>
|
||||
|
||||
<div id="coastlineEditor" class="dialog" style="display: none">
|
||||
|
|
@ -2322,7 +2322,7 @@
|
|||
<button id="reliefCopy" data-tip="Copy selected relief icon" class="icon-clone"></button>
|
||||
<button id="reliefMoveFront" data-tip="Move selected relief icon to front" class="icon-level-up"></button>
|
||||
<button id="reliefMoveBack" data-tip="Move selected relief icon back" class="icon-level-down"></button>
|
||||
<button id="reliefRemove" data-tip="Remove selected relief icon. Shortcut: Delete" class="icon-trash"></button>
|
||||
<button id="reliefRemove" data-tip="Remove selected relief icon. Shortcut: Delete" class="icon-trash fastDelete"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -2375,7 +2375,7 @@
|
|||
<button id="burgOpenCOA" data-tip="Open burg's COA in the Iron Arachne Heraldry Generator. Ctrl + click to change the seed" class="icon-shield-alt"></button>
|
||||
<button id="burgRelocate" data-tip="Relocate burg" class="icon-target"></button>
|
||||
<button id="burglLegend" data-tip="Edit free text notes (legend) for this burg" class="icon-edit"></button>
|
||||
<button id="burgRemove" data-tip="Remove non-capital burg. Shortcut: Delete" class="icon-trash"></button>
|
||||
<button id="burgRemove" data-tip="Remove non-capital burg. Shortcut: Delete" class="icon-trash fastDelete"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -2416,7 +2416,7 @@
|
|||
<button id="markerToggleBubble" data-tip="Toggle pin (bubble) display" class="icon-info-circled"></button>
|
||||
<button id="markerLegendButton" data-tip="Edit place legend (free text notes)" class="icon-edit"></button>
|
||||
<button id="markerAdd" data-tip="Add additional marker of that type" class="icon-plus"></button>
|
||||
<button id="markerRemove" data-tip="Remove the marker. Shortcut: Delete" class="icon-trash"></button>
|
||||
<button id="markerRemove" data-tip="Remove the marker. Shortcut: Delete" class="icon-trash fastDelete"></button>
|
||||
</div>
|
||||
|
||||
<div id="regimentEditor" class="dialog" style="display: none">
|
||||
|
|
@ -2444,7 +2444,7 @@
|
|||
<button id="regimentAttach" data-tip="Attach regiment to another one (include this regiment to another one)" class="icon-attach"></button>
|
||||
<button id="regimentRegenerateLegend" data-tip="Regenerate legend for this regiment" class="icon-retweet"></button>
|
||||
<button id="regimentLegend" data-tip="Edit free text notes (legend) for this regiment" class="icon-edit"></button>
|
||||
<button id="regimentRemove" data-tip="Remove regiment. Shortcut: Delete" class="icon-trash"></button>
|
||||
<button id="regimentRemove" data-tip="Remove regiment. Shortcut: Delete" class="icon-trash fastDelete"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -2660,8 +2660,8 @@
|
|||
<button id="convertImageLoad" data-tip="Load image to convert" class="icon-upload"></button>
|
||||
<button id="convertAutoLum" data-tip="Auto-assign colors based on liminosity (good to monochrome images)" class="icon-adjust"></button>
|
||||
<button id="convertAutoHue" data-tip="Auto-assign colors based on hue (good to colored images)" class="icon-brush"></button>
|
||||
<button id="convertColorsButton" data-tip="Set number of colors" class="icon-signal"></button>
|
||||
<input id="convertColors" value="18" style="display: none"/>
|
||||
<button id="convertColorsButton" data-tip="Set maximum number of colors" class="icon-signal"></button>
|
||||
<input id="convertColors" value="128" style="display: none"/>
|
||||
<button id="convertComplete" data-tip="Complete the conversion. All unassigned colors will be considered as ocean" class="icon-check"></button>
|
||||
<button id="convertCancel" data-tip="Cancel the conversion. Previous heightmap will be restored" class="icon-cancel"></button>
|
||||
</div>
|
||||
|
|
@ -2675,7 +2675,7 @@
|
|||
<i>Set height: </i>
|
||||
<span id="colorsSelectValue"></span>
|
||||
<span>(<span id="colorsSelectFriendly">0</span>)</span><br>
|
||||
<div id="colorScheme"></div>
|
||||
<div id="imageConverterPalette"></div>
|
||||
</div>
|
||||
|
||||
<div data-tip="Select a color to re-assign the height value" id="colorsAssigned" style="display: none">
|
||||
|
|
@ -3124,7 +3124,7 @@
|
|||
<button id="notesFocus" data-tip="Focus on selected object" class="icon-target"></button>
|
||||
<button id="notesDownload" data-tip="Download notes to PC" class="icon-download"></button>
|
||||
<button id="notesUpload" data-tip="Upload notes from PC" class="icon-upload"></button>
|
||||
<button id="notesRemove" data-tip="Remove this note" class="icon-trash"></button>
|
||||
<button id="notesRemove" data-tip="Remove this note" class="icon-trash fastDelete"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -3658,7 +3658,6 @@
|
|||
<script defer src="modules/ui/battle-screen.js"></script>
|
||||
<script defer src="modules/ui/editors.js"></script>
|
||||
<script defer src="modules/ui/3d.js"></script>
|
||||
<script defer src="libs/quantize.min.js"></script>
|
||||
<script defer src="libs/rgbquant.js"></script>
|
||||
<script defer src="libs/jquery.ui.touch-punch.min.js"></script>
|
||||
<script defer src="libs/publicstorage.js"></script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -1,188 +0,0 @@
|
|||
// https://github.com/Highbrainer/jeevaneo-js-publicstorage. MIT
|
||||
const IFRAME_ROOT_URL = "https://publicstorage.neocities.org/shared-iframe.html";
|
||||
|
||||
class PublicStorageAccess {
|
||||
|
||||
constructor ({debug=false}={}) {
|
||||
this.uid = this.uniqueId();
|
||||
this.debug=debug;
|
||||
}
|
||||
|
||||
uniqueId() {
|
||||
function chr4(){
|
||||
return Math.random().toString(16).slice(-4);
|
||||
}
|
||||
return chr4() + chr4() +
|
||||
'-' + chr4() +
|
||||
'-' + chr4() +
|
||||
'-' + chr4() +
|
||||
'-' + chr4() + chr4() + chr4();
|
||||
}
|
||||
|
||||
_debug(msg) {
|
||||
if(this.debug) {
|
||||
if(console && console.debug) {
|
||||
console.debug(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prepareIFrame() {
|
||||
const that = this;
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.id=that.uid;
|
||||
iframe.src=IFRAME_ROOT_URL + "?uid=init-"+that.uid;
|
||||
iframe.style.cssText="display:none;";
|
||||
return new Promise(function(resolve, reject) {
|
||||
window.addEventListener('message', function mafunc(tkn) {
|
||||
|
||||
if (IFRAME_ROOT_URL.indexOf(tkn.origin)<0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const packet = JSON.parse(tkn.data);
|
||||
|
||||
if(!(packet.frameId === "init-" + that.uid)) {
|
||||
// ignore
|
||||
return;
|
||||
}
|
||||
|
||||
if(packet.ready) {
|
||||
resolve(iframe);
|
||||
}
|
||||
} catch (e) {
|
||||
reject(tkn.data);
|
||||
}
|
||||
window.removeEventListener('message', mafunc);
|
||||
});
|
||||
onLoadThen().then(() => {
|
||||
document.getElementsByTagName("body")[0].appendChild(iframe);
|
||||
});
|
||||
|
||||
setTimeout(()=>reject(`Request ${that.uid} TIMEOUTED!`), 20000);
|
||||
});
|
||||
}
|
||||
|
||||
access(access, prop, value = null, level = "local") {
|
||||
|
||||
if(!(access === "get" || access === "set" || access === "delete")) {
|
||||
throw new Error("access can only be 'set', 'get' or 'delete' - not '" + access + "'");
|
||||
}
|
||||
|
||||
if (!prop) {
|
||||
throw new Error("Prop name is mandatory");
|
||||
}
|
||||
|
||||
if(!(level === "local" || level === "session")) {
|
||||
throw new Error("level can only be 'session' or 'local' - not '" + access + "'");
|
||||
}
|
||||
|
||||
const that = this;
|
||||
|
||||
const promise = new Promise(function(resolve, reject) {
|
||||
that.prepareIFrame().then(iframe => {
|
||||
window.addEventListener('message', function mafunc(tkn) {
|
||||
if (IFRAME_ROOT_URL.indexOf(tkn.origin)<0) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var packet = JSON.parse(tkn.data);
|
||||
|
||||
if(!(packet.uid === that.uid)) {
|
||||
// ignore
|
||||
return;
|
||||
}
|
||||
resolve(packet.body);
|
||||
} catch (e) {
|
||||
reject(tkn.data);
|
||||
}
|
||||
iframe.parentNode.removeChild(iframe);
|
||||
window.removeEventListener('message', mafunc);
|
||||
});
|
||||
|
||||
const request = {uid:that.uid, access:access, prop:prop, value:value, level:level};
|
||||
iframe.contentWindow.postMessage(JSON.stringify(request), '*');
|
||||
setTimeout(()=>reject("TIMEOUTED!"), 20000);
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function __createDebugIFrame() {
|
||||
onLoadThen().then(function(){
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.src=IFRAME_ROOT_URL + "?for-debug-only";
|
||||
iframe.style.cssText="display:none;";
|
||||
document.getElementsByTagName("body")[0].appendChild(iframe);
|
||||
});
|
||||
}
|
||||
|
||||
class PublicStorage {
|
||||
|
||||
constructor({debug=false}={}) {
|
||||
if(debug) {
|
||||
__createDebugIFrame();
|
||||
}
|
||||
}
|
||||
|
||||
sessionGet(prop) {
|
||||
return new PublicStorageAccess().access("get", prop, null, "session");
|
||||
}
|
||||
sessionSet(prop, value) {
|
||||
return new PublicStorageAccess().access("set", prop, value, "session");
|
||||
}
|
||||
sessionUnset(prop) {
|
||||
return new PublicStorageAccess().access("delete", prop, null, "session");
|
||||
}
|
||||
localGet(prop) {
|
||||
return new PublicStorageAccess().access("get", prop, null, "local");
|
||||
}
|
||||
localSet(prop, value) {
|
||||
return new PublicStorageAccess().access("set", prop, value, "local");
|
||||
}
|
||||
localUnset(prop) {
|
||||
return new PublicStorageAccess().access("delete", prop, null, "local");
|
||||
}
|
||||
get(prop) {
|
||||
return this.localGet(prop);
|
||||
}
|
||||
set(prop, value) {
|
||||
return this.localSet(prop, value);
|
||||
}
|
||||
unset(prop) {
|
||||
return this.localUnset(prop);
|
||||
}
|
||||
}
|
||||
|
||||
const publicstorage = new PublicStorage();
|
||||
|
||||
function onLoadThen() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (window) {
|
||||
if(document.getElementsByTagName('BODY')[0]) {
|
||||
resolve();
|
||||
} else {
|
||||
registerOnLoad(function unregisterme() {
|
||||
resolve();
|
||||
window.removeEventListener('load', unregisterme);
|
||||
});
|
||||
}
|
||||
}
|
||||
setTimeout(function() {reject(new Error("Timeout waiting for onLoad!"));}, 10000);
|
||||
});
|
||||
}
|
||||
|
||||
function registerOnLoad(lambda) {
|
||||
if (window.addEventListener) {
|
||||
window.addEventListener('load', lambda);
|
||||
} else if (window.attachEvent) {
|
||||
window.attachEvent('onload', lambda);
|
||||
}
|
||||
}
|
||||
|
||||
onLoadThen().then(() => window.publicstorage = publicstorage).catch(e=> console.error(e));
|
||||
//export {onLoadThen, PublicStorage, publicstorage as default}
|
||||
// module.exports = onLoadThen();
|
||||
1
libs/quantize.min.js
vendored
1
libs/quantize.min.js
vendored
|
|
@ -1 +0,0 @@
|
|||
if(!pv)var pv={map:function(r,n){var o={};return n?r.map(function(r,t){return o.index=t,n.call(o,r)}):r.slice()},naturalOrder:function(r,n){return r<n?-1:r>n?1:0},sum:function(r,n){var o={};return r.reduce(n?function(r,t,u){return o.index=u,r+n.call(o,t)}:function(r,n){return r+n},0)},max:function(r,n){return Math.max.apply(null,n?pv.map(r,n):r)}};var MMCQ=function(){var r=5,n=8-r,o=1e3,t=.75;function u(n,o,t){return(n<<2*r)+(o<<r)+t}function e(r){var n=[],o=!1;function t(){n.sort(r),o=!0}return{push:function(r){n.push(r),o=!1},peek:function(r){return o||t(),void 0===r&&(r=n.length-1),n[r]},pop:function(){return o||t(),n.pop()},size:function(){return n.length},map:function(r){return n.map(r)},debug:function(){return o||t(),n}}}function i(r,n,o,t,u,e,i){var c=this;c.r1=r,c.r2=n,c.g1=o,c.g2=t,c.b1=u,c.b2=e,c.histo=i}function c(){this.vboxes=new e(function(r,n){return pv.naturalOrder(r.vbox.count()*r.vbox.volume(),n.vbox.count()*n.vbox.volume())})}function f(r,n){if(n.count()){var o=n.r2-n.r1+1,t=n.g2-n.g1+1,e=n.b2-n.b1+1,i=pv.max([o,t,e]);if(1==n.count())return[n.copy()];var c,f,a,v,s=0,p=[],l=[];if(i==o)for(c=n.r1;c<=n.r2;c++){for(v=0,f=n.g1;f<=n.g2;f++)for(a=n.b1;a<=n.b2;a++)v+=r[u(c,f,a)]||0;s+=v,p[c]=s}else if(i==t)for(c=n.g1;c<=n.g2;c++){for(v=0,f=n.r1;f<=n.r2;f++)for(a=n.b1;a<=n.b2;a++)v+=r[u(f,c,a)]||0;s+=v,p[c]=s}else for(c=n.b1;c<=n.b2;c++){for(v=0,f=n.r1;f<=n.r2;f++)for(a=n.g1;a<=n.g2;a++)v+=r[u(f,a,c)]||0;s+=v,p[c]=s}return p.forEach(function(r,n){l[n]=s-r}),h(i==o?"r":i==t?"g":"b")}function h(r){var o,t,u,e,i,f=r+"1",a=r+"2",v=0;for(c=n[f];c<=n[a];c++)if(p[c]>s/2){for(u=n.copy(),e=n.copy(),i=(o=c-n[f])<=(t=n[a]-c)?Math.min(n[a]-1,~~(c+t/2)):Math.max(n[f],~~(c-1-o/2));!p[i];)i++;for(v=l[i];!v&&p[i-1];)v=l[--i];return u[a]=i,e[f]=u[a]+1,[u,e]}}}return i.prototype={volume:function(r){var n=this;return n._volume&&!r||(n._volume=(n.r2-n.r1+1)*(n.g2-n.g1+1)*(n.b2-n.b1+1)),n._volume},count:function(r){var n=this,o=n.histo;if(!n._count_set||r){var t,e,i,c=0;for(t=n.r1;t<=n.r2;t++)for(e=n.g1;e<=n.g2;e++)for(i=n.b1;i<=n.b2;i++)c+=o[u(t,e,i)]||0;n._count=c,n._count_set=!0}return n._count},copy:function(){var r=this;return new i(r.r1,r.r2,r.g1,r.g2,r.b1,r.b2,r.histo)},avg:function(n){var o=this,t=o.histo;if(!o._avg||n){var e,i,c,f,a=0,v=1<<8-r,s=0,p=0,l=0;for(i=o.r1;i<=o.r2;i++)for(c=o.g1;c<=o.g2;c++)for(f=o.b1;f<=o.b2;f++)a+=e=t[u(i,c,f)]||0,s+=e*(i+.5)*v,p+=e*(c+.5)*v,l+=e*(f+.5)*v;o._avg=a?[~~(s/a),~~(p/a),~~(l/a)]:[~~(v*(o.r1+o.r2+1)/2),~~(v*(o.g1+o.g2+1)/2),~~(v*(o.b1+o.b2+1)/2)]}return o._avg},contains:function(r){var o=this,t=r[0]>>n;return gval=r[1]>>n,bval=r[2]>>n,t>=o.r1&&t<=o.r2&&gval>=o.g1&&gval<=o.g2&&bval>=o.b1&&bval<=o.b2}},c.prototype={push:function(r){this.vboxes.push({vbox:r,color:r.avg()})},palette:function(){return this.vboxes.map(function(r){return r.color})},size:function(){return this.vboxes.size()},map:function(r){for(var n=this.vboxes,o=0;o<n.size();o++)if(n.peek(o).vbox.contains(r))return n.peek(o).color;return this.nearest(r)},nearest:function(r){for(var n,o,t,u=this.vboxes,e=0;e<u.size();e++)((o=Math.sqrt(Math.pow(r[0]-u.peek(e).color[0],2)+Math.pow(r[1]-u.peek(e).color[1],2)+Math.pow(r[2]-u.peek(e).color[2],2)))<n||void 0===n)&&(n=o,t=u.peek(e).color);return t},forcebw:function(){var r=this.vboxes;r.sort(function(r,n){return pv.naturalOrder(pv.sum(r.color),pv.sum(n.color))});var n=r[0].color;n[0]<5&&n[1]<5&&n[2]<5&&(r[0].color=[0,0,0]);var o=r.length-1,t=r[o].color;t[0]>251&&t[1]>251&&t[2]>251&&(r[o].color=[255,255,255])}},{quantize:function(a,v){if(v++,!a.length||v<2||v>256)return!1;var s,p,l,h,b,g,m=(s=a,g=new Array(1<<3*r),s.forEach(function(r){l=r[0]>>n,h=r[1]>>n,b=r[2]>>n,p=u(l,h,b),g[p]=(g[p]||0)+1}),g);m.forEach(function(){});var x,_,d,w,z,M,y,k,O,E,q=(x=m,z=1e6,M=0,y=1e6,k=0,O=1e6,E=0,a.forEach(function(r){_=r[0]>>n,d=r[1]>>n,w=r[2]>>n,_<z?z=_:_>M&&(M=_),d<y?y=d:d>k&&(k=d),w<O?O=w:w>E&&(E=w)}),new i(z,M,y,k,O,E,x)),A=new e(function(r,n){return pv.naturalOrder(r.count(),n.count())});function C(r,n){for(var t,u=1,e=0;e<o;)if((t=r.pop()).count()){var i=f(m,t),c=i[0],a=i[1];if(!c)return;if(r.push(c),a&&(r.push(a),u++),u>=n)return;if(e++>o)return}else r.push(t),e++}A.push(q),C(A,t*v);for(var Q=new e(function(r,n){return pv.naturalOrder(r.count()*r.volume(),n.count()*n.volume())});A.size();)Q.push(A.pop());C(Q,v-Q.size());for(var j=new c;Q.size();)j.push(Q.pop());return j}}}();
|
||||
935
libs/rgbquant.js
Normal file
935
libs/rgbquant.js
Normal file
|
|
@ -0,0 +1,935 @@
|
|||
/*
|
||||
* Copyright (c) 2015, Leon Sorokin
|
||||
* All rights reserved. (MIT Licensed)
|
||||
*
|
||||
* RgbQuant.js - an image quantization lib
|
||||
*/
|
||||
|
||||
(function(){
|
||||
function RgbQuant(opts) {
|
||||
opts = opts || {};
|
||||
|
||||
// 1 = by global population, 2 = subregion population threshold
|
||||
this.method = opts.method || 2;
|
||||
// desired final palette size
|
||||
this.colors = opts.colors || 256;
|
||||
// # of highest-frequency colors to start with for palette reduction
|
||||
this.initColors = opts.initColors || 4096;
|
||||
// color-distance threshold for initial reduction pass
|
||||
this.initDist = opts.initDist || 0.01;
|
||||
// subsequent passes threshold
|
||||
this.distIncr = opts.distIncr || 0.005;
|
||||
// palette grouping
|
||||
this.hueGroups = opts.hueGroups || 10;
|
||||
this.satGroups = opts.satGroups || 10;
|
||||
this.lumGroups = opts.lumGroups || 10;
|
||||
// if > 0, enables hues stats and min-color retention per group
|
||||
this.minHueCols = opts.minHueCols || 0;
|
||||
// HueStats instance
|
||||
this.hueStats = this.minHueCols ? new HueStats(this.hueGroups, this.minHueCols) : null;
|
||||
|
||||
// subregion partitioning box size
|
||||
this.boxSize = opts.boxSize || [64,64];
|
||||
// number of same pixels required within box for histogram inclusion
|
||||
this.boxPxls = opts.boxPxls || 2;
|
||||
// palette locked indicator
|
||||
this.palLocked = false;
|
||||
// palette sort order
|
||||
// this.sortPal = ['hue-','lum-','sat-'];
|
||||
|
||||
// dithering/error diffusion kernel name
|
||||
this.dithKern = opts.dithKern || null;
|
||||
// dither serpentine pattern
|
||||
this.dithSerp = opts.dithSerp || false;
|
||||
// minimum color difference (0-1) needed to dither
|
||||
this.dithDelta = opts.dithDelta || 0;
|
||||
|
||||
// accumulated histogram
|
||||
this.histogram = {};
|
||||
// palette - rgb triplets
|
||||
this.idxrgb = opts.palette ? opts.palette.slice(0) : [];
|
||||
// palette - int32 vals
|
||||
this.idxi32 = [];
|
||||
// reverse lookup {i32:idx}
|
||||
this.i32idx = {};
|
||||
// {i32:rgb}
|
||||
this.i32rgb = {};
|
||||
// enable color caching (also incurs overhead of cache misses and cache building)
|
||||
this.useCache = opts.useCache !== false;
|
||||
// min color occurance count needed to qualify for caching
|
||||
this.cacheFreq = opts.cacheFreq || 10;
|
||||
// allows pre-defined palettes to be re-indexed (enabling palette compacting and sorting)
|
||||
this.reIndex = opts.reIndex || this.idxrgb.length == 0;
|
||||
// selection of color-distance equation
|
||||
this.colorDist = opts.colorDist == "manhattan" ? distManhattan : distEuclidean;
|
||||
|
||||
// if pre-defined palette, build lookups
|
||||
if (this.idxrgb.length > 0) {
|
||||
var self = this;
|
||||
this.idxrgb.forEach(function(rgb, i) {
|
||||
var i32 = (
|
||||
(255 << 24) | // alpha
|
||||
(rgb[2] << 16) | // blue
|
||||
(rgb[1] << 8) | // green
|
||||
rgb[0] // red
|
||||
) >>> 0;
|
||||
|
||||
self.idxi32[i] = i32;
|
||||
self.i32idx[i32] = i;
|
||||
self.i32rgb[i32] = rgb;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// gathers histogram info
|
||||
RgbQuant.prototype.sample = function sample(img, width) {
|
||||
if (this.palLocked)
|
||||
throw "Cannot sample additional images, palette already assembled.";
|
||||
|
||||
var data = getImageData(img, width);
|
||||
|
||||
switch (this.method) {
|
||||
case 1: this.colorStats1D(data.buf32); break;
|
||||
case 2: this.colorStats2D(data.buf32, data.width); break;
|
||||
}
|
||||
};
|
||||
|
||||
// image quantizer
|
||||
// todo: memoize colors here also
|
||||
// @retType: 1 - Uint8Array (default), 2 - Indexed array, 3 - Match @img type (unimplemented, todo)
|
||||
RgbQuant.prototype.reduce = function reduce(img, retType, dithKern, dithSerp) {
|
||||
if (!this.palLocked)
|
||||
this.buildPal();
|
||||
|
||||
dithKern = dithKern || this.dithKern;
|
||||
dithSerp = typeof dithSerp != "undefined" ? dithSerp : this.dithSerp;
|
||||
|
||||
retType = retType || 1;
|
||||
|
||||
// reduce w/dither
|
||||
if (dithKern)
|
||||
var out32 = this.dither(img, dithKern, dithSerp);
|
||||
else {
|
||||
var data = getImageData(img),
|
||||
buf32 = data.buf32,
|
||||
len = buf32.length,
|
||||
out32 = new Uint32Array(len);
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
var i32 = buf32[i];
|
||||
out32[i] = this.nearestColor(i32);
|
||||
}
|
||||
}
|
||||
|
||||
if (retType == 1)
|
||||
return new Uint8Array(out32.buffer);
|
||||
|
||||
if (retType == 2) {
|
||||
var out = [],
|
||||
len = out32.length;
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
var i32 = out32[i];
|
||||
out[i] = this.i32idx[i32];
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
// adapted from http://jsbin.com/iXofIji/2/edit by PAEz
|
||||
RgbQuant.prototype.dither = function(img, kernel, serpentine) {
|
||||
// http://www.tannerhelland.com/4660/dithering-eleven-algorithms-source-code/
|
||||
var kernels = {
|
||||
FloydSteinberg: [
|
||||
[7 / 16, 1, 0],
|
||||
[3 / 16, -1, 1],
|
||||
[5 / 16, 0, 1],
|
||||
[1 / 16, 1, 1]
|
||||
],
|
||||
FalseFloydSteinberg: [
|
||||
[3 / 8, 1, 0],
|
||||
[3 / 8, 0, 1],
|
||||
[2 / 8, 1, 1]
|
||||
],
|
||||
Stucki: [
|
||||
[8 / 42, 1, 0],
|
||||
[4 / 42, 2, 0],
|
||||
[2 / 42, -2, 1],
|
||||
[4 / 42, -1, 1],
|
||||
[8 / 42, 0, 1],
|
||||
[4 / 42, 1, 1],
|
||||
[2 / 42, 2, 1],
|
||||
[1 / 42, -2, 2],
|
||||
[2 / 42, -1, 2],
|
||||
[4 / 42, 0, 2],
|
||||
[2 / 42, 1, 2],
|
||||
[1 / 42, 2, 2]
|
||||
],
|
||||
Atkinson: [
|
||||
[1 / 8, 1, 0],
|
||||
[1 / 8, 2, 0],
|
||||
[1 / 8, -1, 1],
|
||||
[1 / 8, 0, 1],
|
||||
[1 / 8, 1, 1],
|
||||
[1 / 8, 0, 2]
|
||||
],
|
||||
Jarvis: [ // Jarvis, Judice, and Ninke / JJN?
|
||||
[7 / 48, 1, 0],
|
||||
[5 / 48, 2, 0],
|
||||
[3 / 48, -2, 1],
|
||||
[5 / 48, -1, 1],
|
||||
[7 / 48, 0, 1],
|
||||
[5 / 48, 1, 1],
|
||||
[3 / 48, 2, 1],
|
||||
[1 / 48, -2, 2],
|
||||
[3 / 48, -1, 2],
|
||||
[5 / 48, 0, 2],
|
||||
[3 / 48, 1, 2],
|
||||
[1 / 48, 2, 2]
|
||||
],
|
||||
Burkes: [
|
||||
[8 / 32, 1, 0],
|
||||
[4 / 32, 2, 0],
|
||||
[2 / 32, -2, 1],
|
||||
[4 / 32, -1, 1],
|
||||
[8 / 32, 0, 1],
|
||||
[4 / 32, 1, 1],
|
||||
[2 / 32, 2, 1],
|
||||
],
|
||||
Sierra: [
|
||||
[5 / 32, 1, 0],
|
||||
[3 / 32, 2, 0],
|
||||
[2 / 32, -2, 1],
|
||||
[4 / 32, -1, 1],
|
||||
[5 / 32, 0, 1],
|
||||
[4 / 32, 1, 1],
|
||||
[2 / 32, 2, 1],
|
||||
[2 / 32, -1, 2],
|
||||
[3 / 32, 0, 2],
|
||||
[2 / 32, 1, 2],
|
||||
],
|
||||
TwoSierra: [
|
||||
[4 / 16, 1, 0],
|
||||
[3 / 16, 2, 0],
|
||||
[1 / 16, -2, 1],
|
||||
[2 / 16, -1, 1],
|
||||
[3 / 16, 0, 1],
|
||||
[2 / 16, 1, 1],
|
||||
[1 / 16, 2, 1],
|
||||
],
|
||||
SierraLite: [
|
||||
[2 / 4, 1, 0],
|
||||
[1 / 4, -1, 1],
|
||||
[1 / 4, 0, 1],
|
||||
],
|
||||
};
|
||||
|
||||
if (!kernel || !kernels[kernel]) {
|
||||
throw 'Unknown dithering kernel: ' + kernel;
|
||||
}
|
||||
|
||||
var ds = kernels[kernel];
|
||||
|
||||
var data = getImageData(img),
|
||||
// buf8 = data.buf8,
|
||||
buf32 = data.buf32,
|
||||
width = data.width,
|
||||
height = data.height,
|
||||
len = buf32.length;
|
||||
|
||||
var dir = serpentine ? -1 : 1;
|
||||
|
||||
for (var y = 0; y < height; y++) {
|
||||
if (serpentine)
|
||||
dir = dir * -1;
|
||||
|
||||
var lni = y * width;
|
||||
|
||||
for (var x = (dir == 1 ? 0 : width - 1), xend = (dir == 1 ? width : 0); x !== xend; x += dir) {
|
||||
// Image pixel
|
||||
var idx = lni + x,
|
||||
i32 = buf32[idx],
|
||||
r1 = (i32 & 0xff),
|
||||
g1 = (i32 & 0xff00) >> 8,
|
||||
b1 = (i32 & 0xff0000) >> 16;
|
||||
|
||||
// Reduced pixel
|
||||
var i32x = this.nearestColor(i32),
|
||||
r2 = (i32x & 0xff),
|
||||
g2 = (i32x & 0xff00) >> 8,
|
||||
b2 = (i32x & 0xff0000) >> 16;
|
||||
|
||||
buf32[idx] =
|
||||
(255 << 24) | // alpha
|
||||
(b2 << 16) | // blue
|
||||
(g2 << 8) | // green
|
||||
r2;
|
||||
|
||||
// dithering strength
|
||||
if (this.dithDelta) {
|
||||
var dist = this.colorDist([r1, g1, b1], [r2, g2, b2]);
|
||||
if (dist < this.dithDelta)
|
||||
continue;
|
||||
}
|
||||
|
||||
// Component distance
|
||||
var er = r1 - r2,
|
||||
eg = g1 - g2,
|
||||
eb = b1 - b2;
|
||||
|
||||
for (var i = (dir == 1 ? 0 : ds.length - 1), end = (dir == 1 ? ds.length : 0); i !== end; i += dir) {
|
||||
var x1 = ds[i][1] * dir,
|
||||
y1 = ds[i][2];
|
||||
|
||||
var lni2 = y1 * width;
|
||||
|
||||
if (x1 + x >= 0 && x1 + x < width && y1 + y >= 0 && y1 + y < height) {
|
||||
var d = ds[i][0];
|
||||
var idx2 = idx + (lni2 + x1);
|
||||
|
||||
var r3 = (buf32[idx2] & 0xff),
|
||||
g3 = (buf32[idx2] & 0xff00) >> 8,
|
||||
b3 = (buf32[idx2] & 0xff0000) >> 16;
|
||||
|
||||
var r4 = Math.max(0, Math.min(255, r3 + er * d)),
|
||||
g4 = Math.max(0, Math.min(255, g3 + eg * d)),
|
||||
b4 = Math.max(0, Math.min(255, b3 + eb * d));
|
||||
|
||||
buf32[idx2] =
|
||||
(255 << 24) | // alpha
|
||||
(b4 << 16) | // blue
|
||||
(g4 << 8) | // green
|
||||
r4; // red
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf32;
|
||||
};
|
||||
|
||||
// reduces histogram to palette, remaps & memoizes reduced colors
|
||||
RgbQuant.prototype.buildPal = function buildPal(noSort) {
|
||||
if (this.palLocked || this.idxrgb.length > 0 && this.idxrgb.length <= this.colors) return;
|
||||
|
||||
var histG = this.histogram,
|
||||
sorted = sortedHashKeys(histG, true);
|
||||
|
||||
if (sorted.length == 0)
|
||||
throw "Nothing has been sampled, palette cannot be built.";
|
||||
|
||||
switch (this.method) {
|
||||
case 1:
|
||||
var cols = this.initColors,
|
||||
last = sorted[cols - 1],
|
||||
freq = histG[last];
|
||||
|
||||
var idxi32 = sorted.slice(0, cols);
|
||||
|
||||
// add any cut off colors with same freq as last
|
||||
var pos = cols, len = sorted.length;
|
||||
while (pos < len && histG[sorted[pos]] == freq)
|
||||
idxi32.push(sorted[pos++]);
|
||||
|
||||
// inject min huegroup colors
|
||||
if (this.hueStats)
|
||||
this.hueStats.inject(idxi32);
|
||||
|
||||
break;
|
||||
case 2:
|
||||
var idxi32 = sorted;
|
||||
break;
|
||||
}
|
||||
|
||||
// int32-ify values
|
||||
idxi32 = idxi32.map(function(v){return +v;});
|
||||
|
||||
this.reducePal(idxi32);
|
||||
|
||||
if (!noSort && this.reIndex)
|
||||
this.sortPal();
|
||||
|
||||
// build cache of top histogram colors
|
||||
if (this.useCache)
|
||||
this.cacheHistogram(idxi32);
|
||||
|
||||
this.palLocked = true;
|
||||
};
|
||||
|
||||
RgbQuant.prototype.palette = function palette(tuples, noSort) {
|
||||
this.buildPal(noSort);
|
||||
return tuples ? this.idxrgb : new Uint8Array((new Uint32Array(this.idxi32)).buffer);
|
||||
};
|
||||
|
||||
RgbQuant.prototype.prunePal = function prunePal(keep) {
|
||||
var i32;
|
||||
|
||||
for (var j = 0; j < this.idxrgb.length; j++) {
|
||||
if (!keep[j]) {
|
||||
i32 = this.idxi32[j];
|
||||
this.idxrgb[j] = null;
|
||||
this.idxi32[j] = null;
|
||||
delete this.i32idx[i32];
|
||||
}
|
||||
}
|
||||
|
||||
// compact
|
||||
if (this.reIndex) {
|
||||
var idxrgb = [],
|
||||
idxi32 = [],
|
||||
i32idx = {};
|
||||
|
||||
for (var j = 0, i = 0; j < this.idxrgb.length; j++) {
|
||||
if (this.idxrgb[j]) {
|
||||
i32 = this.idxi32[j];
|
||||
idxrgb[i] = this.idxrgb[j];
|
||||
i32idx[i32] = i;
|
||||
idxi32[i] = i32;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
this.idxrgb = idxrgb;
|
||||
this.idxi32 = idxi32;
|
||||
this.i32idx = i32idx;
|
||||
}
|
||||
};
|
||||
|
||||
// reduces similar colors from an importance-sorted Uint32 rgba array
|
||||
RgbQuant.prototype.reducePal = function reducePal(idxi32) {
|
||||
// if pre-defined palette's length exceeds target
|
||||
if (this.idxrgb.length > this.colors) {
|
||||
// quantize histogram to existing palette
|
||||
var len = idxi32.length, keep = {}, uniques = 0, idx, pruned = false;
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
// palette length reached, unset all remaining colors (sparse palette)
|
||||
if (uniques == this.colors && !pruned) {
|
||||
this.prunePal(keep);
|
||||
pruned = true;
|
||||
}
|
||||
|
||||
idx = this.nearestIndex(idxi32[i]);
|
||||
|
||||
if (uniques < this.colors && !keep[idx]) {
|
||||
keep[idx] = true;
|
||||
uniques++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pruned) {
|
||||
this.prunePal(keep);
|
||||
pruned = true;
|
||||
}
|
||||
}
|
||||
// reduce histogram to create initial palette
|
||||
else {
|
||||
// build full rgb palette
|
||||
var idxrgb = idxi32.map(function(i32) {
|
||||
return [
|
||||
(i32 & 0xff),
|
||||
(i32 & 0xff00) >> 8,
|
||||
(i32 & 0xff0000) >> 16,
|
||||
];
|
||||
});
|
||||
|
||||
var len = idxrgb.length,
|
||||
palLen = len,
|
||||
thold = this.initDist;
|
||||
|
||||
// palette already at or below desired length
|
||||
if (palLen > this.colors) {
|
||||
while (palLen > this.colors) {
|
||||
var memDist = [];
|
||||
|
||||
// iterate palette
|
||||
for (var i = 0; i < len; i++) {
|
||||
var pxi = idxrgb[i], i32i = idxi32[i];
|
||||
if (!pxi) continue;
|
||||
|
||||
for (var j = i + 1; j < len; j++) {
|
||||
var pxj = idxrgb[j], i32j = idxi32[j];
|
||||
if (!pxj) continue;
|
||||
|
||||
var dist = this.colorDist(pxi, pxj);
|
||||
|
||||
if (dist < thold) {
|
||||
// store index,rgb,dist
|
||||
memDist.push([j, pxj, i32j, dist]);
|
||||
|
||||
// kill squashed value
|
||||
delete(idxrgb[j]);
|
||||
palLen--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// palette reduction pass
|
||||
// console.log("palette length: " + palLen);
|
||||
|
||||
// if palette is still much larger than target, increment by larger initDist
|
||||
thold += (palLen > this.colors * 3) ? this.initDist : this.distIncr;
|
||||
}
|
||||
|
||||
// if palette is over-reduced, re-add removed colors with largest distances from last round
|
||||
if (palLen < this.colors) {
|
||||
// sort descending
|
||||
sort.call(memDist, function(a,b) {
|
||||
return b[3] - a[3];
|
||||
});
|
||||
|
||||
var k = 0;
|
||||
while (palLen < this.colors) {
|
||||
// re-inject rgb into final palette
|
||||
idxrgb[memDist[k][0]] = memDist[k][1];
|
||||
|
||||
palLen++;
|
||||
k++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var len = idxrgb.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
if (!idxrgb[i]) continue;
|
||||
|
||||
this.idxrgb.push(idxrgb[i]);
|
||||
this.idxi32.push(idxi32[i]);
|
||||
|
||||
this.i32idx[idxi32[i]] = this.idxi32.length - 1;
|
||||
this.i32rgb[idxi32[i]] = idxrgb[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// global top-population
|
||||
RgbQuant.prototype.colorStats1D = function colorStats1D(buf32) {
|
||||
var histG = this.histogram,
|
||||
num = 0, col,
|
||||
len = buf32.length;
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
col = buf32[i];
|
||||
|
||||
// skip transparent
|
||||
if ((col & 0xff000000) >> 24 == 0) continue;
|
||||
|
||||
// collect hue stats
|
||||
if (this.hueStats)
|
||||
this.hueStats.check(col);
|
||||
|
||||
if (col in histG)
|
||||
histG[col]++;
|
||||
else
|
||||
histG[col] = 1;
|
||||
}
|
||||
};
|
||||
|
||||
// population threshold within subregions
|
||||
// FIXME: this can over-reduce (few/no colors same?), need a way to keep
|
||||
// important colors that dont ever reach local thresholds (gradients?)
|
||||
RgbQuant.prototype.colorStats2D = function colorStats2D(buf32, width) {
|
||||
var boxW = this.boxSize[0],
|
||||
boxH = this.boxSize[1],
|
||||
area = boxW * boxH,
|
||||
boxes = makeBoxes(width, buf32.length / width, boxW, boxH),
|
||||
histG = this.histogram,
|
||||
self = this;
|
||||
|
||||
boxes.forEach(function(box) {
|
||||
var effc = Math.max(Math.round((box.w * box.h) / area) * self.boxPxls, 2),
|
||||
histL = {}, col;
|
||||
|
||||
iterBox(box, width, function(i) {
|
||||
col = buf32[i];
|
||||
|
||||
// skip transparent
|
||||
if ((col & 0xff000000) >> 24 == 0) return;
|
||||
|
||||
// collect hue stats
|
||||
if (self.hueStats)
|
||||
self.hueStats.check(col);
|
||||
|
||||
if (col in histG)
|
||||
histG[col]++;
|
||||
else if (col in histL) {
|
||||
if (++histL[col] >= effc)
|
||||
histG[col] = histL[col];
|
||||
}
|
||||
else
|
||||
histL[col] = 1;
|
||||
});
|
||||
});
|
||||
|
||||
if (this.hueStats)
|
||||
this.hueStats.inject(histG);
|
||||
};
|
||||
|
||||
// TODO: group very low lum and very high lum colors
|
||||
// TODO: pass custom sort order
|
||||
RgbQuant.prototype.sortPal = function sortPal() {
|
||||
var self = this;
|
||||
|
||||
this.idxi32.sort(function(a,b) {
|
||||
var idxA = self.i32idx[a],
|
||||
idxB = self.i32idx[b],
|
||||
rgbA = self.idxrgb[idxA],
|
||||
rgbB = self.idxrgb[idxB];
|
||||
|
||||
var hslA = rgb2hsl(rgbA[0],rgbA[1],rgbA[2]),
|
||||
hslB = rgb2hsl(rgbB[0],rgbB[1],rgbB[2]);
|
||||
|
||||
// sort all grays + whites together
|
||||
var hueA = (rgbA[0] == rgbA[1] && rgbA[1] == rgbA[2]) ? -1 : hueGroup(hslA.h, self.hueGroups);
|
||||
var hueB = (rgbB[0] == rgbB[1] && rgbB[1] == rgbB[2]) ? -1 : hueGroup(hslB.h, self.hueGroups);
|
||||
|
||||
var hueDiff = hueB - hueA;
|
||||
if (hueDiff) return -hueDiff;
|
||||
|
||||
var lumDiff = lumGroup(+hslB.l.toFixed(2)) - lumGroup(+hslA.l.toFixed(2));
|
||||
if (lumDiff) return -lumDiff;
|
||||
|
||||
var satDiff = satGroup(+hslB.s.toFixed(2)) - satGroup(+hslA.s.toFixed(2));
|
||||
if (satDiff) return -satDiff;
|
||||
});
|
||||
|
||||
// sync idxrgb & i32idx
|
||||
this.idxi32.forEach(function(i32, i) {
|
||||
self.idxrgb[i] = self.i32rgb[i32];
|
||||
self.i32idx[i32] = i;
|
||||
});
|
||||
};
|
||||
|
||||
// TOTRY: use HUSL - http://boronine.com/husl/
|
||||
RgbQuant.prototype.nearestColor = function nearestColor(i32) {
|
||||
var idx = this.nearestIndex(i32);
|
||||
return idx === null ? 0 : this.idxi32[idx];
|
||||
};
|
||||
|
||||
// TOTRY: use HUSL - http://boronine.com/husl/
|
||||
RgbQuant.prototype.nearestIndex = function nearestIndex(i32) {
|
||||
// alpha 0 returns null index
|
||||
if ((i32 & 0xff000000) >> 24 == 0)
|
||||
return null;
|
||||
|
||||
if (this.useCache && (""+i32) in this.i32idx)
|
||||
return this.i32idx[i32];
|
||||
|
||||
var min = 1000,
|
||||
idx,
|
||||
rgb = [
|
||||
(i32 & 0xff),
|
||||
(i32 & 0xff00) >> 8,
|
||||
(i32 & 0xff0000) >> 16,
|
||||
],
|
||||
len = this.idxrgb.length;
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
if (!this.idxrgb[i]) continue; // sparse palettes
|
||||
|
||||
var dist = this.colorDist(rgb, this.idxrgb[i]);
|
||||
|
||||
if (dist < min) {
|
||||
min = dist;
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
return idx;
|
||||
};
|
||||
|
||||
RgbQuant.prototype.cacheHistogram = function cacheHistogram(idxi32) {
|
||||
for (var i = 0, i32 = idxi32[i]; i < idxi32.length && this.histogram[i32] >= this.cacheFreq; i32 = idxi32[i++])
|
||||
this.i32idx[i32] = this.nearestIndex(i32);
|
||||
};
|
||||
|
||||
function HueStats(numGroups, minCols) {
|
||||
this.numGroups = numGroups;
|
||||
this.minCols = minCols;
|
||||
this.stats = {};
|
||||
|
||||
for (var i = -1; i < numGroups; i++)
|
||||
this.stats[i] = {num: 0, cols: []};
|
||||
|
||||
this.groupsFull = 0;
|
||||
}
|
||||
|
||||
HueStats.prototype.check = function checkHue(i32) {
|
||||
if (this.groupsFull == this.numGroups + 1)
|
||||
this.check = function() {return;};
|
||||
|
||||
var r = (i32 & 0xff),
|
||||
g = (i32 & 0xff00) >> 8,
|
||||
b = (i32 & 0xff0000) >> 16,
|
||||
hg = (r == g && g == b) ? -1 : hueGroup(rgb2hsl(r,g,b).h, this.numGroups),
|
||||
gr = this.stats[hg],
|
||||
min = this.minCols;
|
||||
|
||||
gr.num++;
|
||||
|
||||
if (gr.num > min)
|
||||
return;
|
||||
if (gr.num == min)
|
||||
this.groupsFull++;
|
||||
|
||||
if (gr.num <= min)
|
||||
this.stats[hg].cols.push(i32);
|
||||
};
|
||||
|
||||
HueStats.prototype.inject = function injectHues(histG) {
|
||||
for (var i = -1; i < this.numGroups; i++) {
|
||||
if (this.stats[i].num <= this.minCols) {
|
||||
switch (typeOf(histG)) {
|
||||
case "Array":
|
||||
this.stats[i].cols.forEach(function(col){
|
||||
if (histG.indexOf(col) == -1)
|
||||
histG.push(col);
|
||||
});
|
||||
break;
|
||||
case "Object":
|
||||
this.stats[i].cols.forEach(function(col){
|
||||
if (!histG[col])
|
||||
histG[col] = 1;
|
||||
else
|
||||
histG[col]++;
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Rec. 709 (sRGB) luma coef
|
||||
var Pr = .2126,
|
||||
Pg = .7152,
|
||||
Pb = .0722;
|
||||
|
||||
// http://alienryderflex.com/hsp.html
|
||||
function rgb2lum(r,g,b) {
|
||||
return Math.sqrt(
|
||||
Pr * r*r +
|
||||
Pg * g*g +
|
||||
Pb * b*b
|
||||
);
|
||||
}
|
||||
|
||||
var rd = 255,
|
||||
gd = 255,
|
||||
bd = 255;
|
||||
|
||||
var euclMax = Math.sqrt(Pr*rd*rd + Pg*gd*gd + Pb*bd*bd);
|
||||
// perceptual Euclidean color distance
|
||||
function distEuclidean(rgb0, rgb1) {
|
||||
var rd = rgb1[0]-rgb0[0],
|
||||
gd = rgb1[1]-rgb0[1],
|
||||
bd = rgb1[2]-rgb0[2];
|
||||
|
||||
return Math.sqrt(Pr*rd*rd + Pg*gd*gd + Pb*bd*bd) / euclMax;
|
||||
}
|
||||
|
||||
var manhMax = Pr*rd + Pg*gd + Pb*bd;
|
||||
// perceptual Manhattan color distance
|
||||
function distManhattan(rgb0, rgb1) {
|
||||
var rd = Math.abs(rgb1[0]-rgb0[0]),
|
||||
gd = Math.abs(rgb1[1]-rgb0[1]),
|
||||
bd = Math.abs(rgb1[2]-rgb0[2]);
|
||||
|
||||
return (Pr*rd + Pg*gd + Pb*bd) / manhMax;
|
||||
}
|
||||
|
||||
// http://rgb2hsl.nichabi.com/javascript-function.php
|
||||
function rgb2hsl(r, g, b) {
|
||||
var max, min, h, s, l, d;
|
||||
r /= 255;
|
||||
g /= 255;
|
||||
b /= 255;
|
||||
max = Math.max(r, g, b);
|
||||
min = Math.min(r, g, b);
|
||||
l = (max + min) / 2;
|
||||
if (max == min) {
|
||||
h = s = 0;
|
||||
} else {
|
||||
d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
switch (max) {
|
||||
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||
case g: h = (b - r) / d + 2; break;
|
||||
case b: h = (r - g) / d + 4; break
|
||||
}
|
||||
h /= 6;
|
||||
}
|
||||
// h = Math.floor(h * 360)
|
||||
// s = Math.floor(s * 100)
|
||||
// l = Math.floor(l * 100)
|
||||
return {
|
||||
h: h,
|
||||
s: s,
|
||||
l: rgb2lum(r,g,b),
|
||||
};
|
||||
}
|
||||
|
||||
function hueGroup(hue, segs) {
|
||||
var seg = 1/segs,
|
||||
haf = seg/2;
|
||||
|
||||
if (hue >= 1 - haf || hue <= haf)
|
||||
return 0;
|
||||
|
||||
for (var i = 1; i < segs; i++) {
|
||||
var mid = i*seg;
|
||||
if (hue >= mid - haf && hue <= mid + haf)
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
function satGroup(sat) {
|
||||
return sat;
|
||||
}
|
||||
|
||||
function lumGroup(lum) {
|
||||
return lum;
|
||||
}
|
||||
|
||||
function typeOf(val) {
|
||||
return Object.prototype.toString.call(val).slice(8,-1);
|
||||
}
|
||||
|
||||
var sort = isArrSortStable() ? Array.prototype.sort : stableSort;
|
||||
|
||||
// must be used via stableSort.call(arr, fn)
|
||||
function stableSort(fn) {
|
||||
var type = typeOf(this[0]);
|
||||
|
||||
if (type == "Number" || type == "String") {
|
||||
var ord = {}, len = this.length, val;
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
val = this[i];
|
||||
if (ord[val] || ord[val] === 0) continue;
|
||||
ord[val] = i;
|
||||
}
|
||||
|
||||
return this.sort(function(a,b) {
|
||||
return fn(a,b) || ord[a] - ord[b];
|
||||
});
|
||||
}
|
||||
else {
|
||||
var ord = this.map(function(v){return v});
|
||||
|
||||
return this.sort(function(a,b) {
|
||||
return fn(a,b) || ord.indexOf(a) - ord.indexOf(b);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// test if js engine's Array#sort implementation is stable
|
||||
function isArrSortStable() {
|
||||
var str = "abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
return "xyzvwtursopqmnklhijfgdeabc" == str.split("").sort(function(a,b) {
|
||||
return ~~(str.indexOf(b)/2.3) - ~~(str.indexOf(a)/2.3);
|
||||
}).join("");
|
||||
}
|
||||
|
||||
// returns uniform pixel data from various img
|
||||
// TODO?: if array is passed, createimagedata, createlement canvas? take a pxlen?
|
||||
function getImageData(img, width) {
|
||||
var can, ctx, imgd, buf8, buf32, height;
|
||||
|
||||
switch (typeOf(img)) {
|
||||
case "HTMLImageElement":
|
||||
can = document.createElement("canvas");
|
||||
can.width = img.naturalWidth;
|
||||
can.height = img.naturalHeight;
|
||||
ctx = can.getContext("2d");
|
||||
ctx.drawImage(img,0,0);
|
||||
case "Canvas":
|
||||
case "HTMLCanvasElement":
|
||||
can = can || img;
|
||||
ctx = ctx || can.getContext("2d");
|
||||
case "CanvasRenderingContext2D":
|
||||
ctx = ctx || img;
|
||||
can = can || ctx.canvas;
|
||||
imgd = ctx.getImageData(0, 0, can.width, can.height);
|
||||
case "ImageData":
|
||||
imgd = imgd || img;
|
||||
width = imgd.width;
|
||||
if (typeOf(imgd.data) == "CanvasPixelArray")
|
||||
buf8 = new Uint8Array(imgd.data);
|
||||
else
|
||||
buf8 = imgd.data;
|
||||
case "Array":
|
||||
case "CanvasPixelArray":
|
||||
buf8 = buf8 || new Uint8Array(img);
|
||||
case "Uint8Array":
|
||||
case "Uint8ClampedArray":
|
||||
buf8 = buf8 || img;
|
||||
buf32 = new Uint32Array(buf8.buffer);
|
||||
case "Uint32Array":
|
||||
buf32 = buf32 || img;
|
||||
buf8 = buf8 || new Uint8Array(buf32.buffer);
|
||||
width = width || buf32.length;
|
||||
height = buf32.length / width;
|
||||
}
|
||||
|
||||
return {
|
||||
can: can,
|
||||
ctx: ctx,
|
||||
imgd: imgd,
|
||||
buf8: buf8,
|
||||
buf32: buf32,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
}
|
||||
|
||||
// partitions a rect of wid x hgt into
|
||||
// array of bboxes of w0 x h0 (or less)
|
||||
function makeBoxes(wid, hgt, w0, h0) {
|
||||
var wnum = ~~(wid/w0), wrem = wid%w0,
|
||||
hnum = ~~(hgt/h0), hrem = hgt%h0,
|
||||
xend = wid-wrem, yend = hgt-hrem;
|
||||
|
||||
var bxs = [];
|
||||
for (var y = 0; y < hgt; y += h0)
|
||||
for (var x = 0; x < wid; x += w0)
|
||||
bxs.push({x:x, y:y, w:(x==xend?wrem:w0), h:(y==yend?hrem:h0)});
|
||||
|
||||
return bxs;
|
||||
}
|
||||
|
||||
// iterates @bbox within a parent rect of width @wid; calls @fn, passing index within parent
|
||||
function iterBox(bbox, wid, fn) {
|
||||
var b = bbox,
|
||||
i0 = b.y * wid + b.x,
|
||||
i1 = (b.y + b.h - 1) * wid + (b.x + b.w - 1),
|
||||
cnt = 0, incr = wid - b.w + 1, i = i0;
|
||||
|
||||
do {
|
||||
fn.call(this, i);
|
||||
i += (++cnt % b.w == 0) ? incr : 1;
|
||||
} while (i <= i1);
|
||||
}
|
||||
|
||||
// returns array of hash keys sorted by their values
|
||||
function sortedHashKeys(obj, desc) {
|
||||
var keys = [];
|
||||
|
||||
for (var key in obj)
|
||||
keys.push(key);
|
||||
|
||||
return sort.call(keys, function(a,b) {
|
||||
return desc ? obj[b] - obj[a] : obj[a] - obj[b];
|
||||
});
|
||||
}
|
||||
|
||||
// expose
|
||||
this.RgbQuant = RgbQuant;
|
||||
|
||||
// expose to commonJS
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = RgbQuant;
|
||||
}
|
||||
|
||||
}).call(this);
|
||||
1
main.js
1
main.js
|
|
@ -1174,6 +1174,7 @@ function rankCells() {
|
|||
const areaMean = d3.mean(cells.area); // to adjust population by cell area
|
||||
|
||||
for (const i of cells.i) {
|
||||
if (cells.h[i] < 20) continue; // no population in water
|
||||
let s = +biomesData.habitability[cells.biome[i]]; // base suitability derived from biome habitability
|
||||
if (!s) continue; // uninhabitable biomes has 0 suitability
|
||||
if (flMean) s += normalize(cells.fl[i] + cells.conf[i], flMean, flMax) * 250; // big rivers and confluences are valued
|
||||
|
|
|
|||
|
|
@ -271,10 +271,14 @@
|
|||
|
||||
// remove river and all its tributaries
|
||||
const remove = function(id) {
|
||||
const cells = pack.cells;
|
||||
const riversToRemove = pack.rivers.filter(r => r.i === id || getBasin(r.i, r.parent, id) === id).map(r => r.i);
|
||||
riversToRemove.forEach(r => rivers.select("#river"+r).remove());
|
||||
pack.cells.r.forEach((r, i) => {
|
||||
if (r && riversToRemove.includes(r)) pack.cells.r[i] = 0;
|
||||
cells.r.forEach((r, i) => {
|
||||
if (!r || !riversToRemove.includes(r)) return;
|
||||
cells.r[i] = 0;
|
||||
cells.fl[i] = grid.cells.prec[cells.g[i]];
|
||||
cells.conf[i] = 0;
|
||||
});
|
||||
pack.rivers = pack.rivers.filter(r => !riversToRemove.includes(r.i));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -294,14 +294,6 @@ async function saveMap() {
|
|||
link.click();
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000);
|
||||
window.URL.revokeObjectURL(URL);
|
||||
|
||||
// send saved files count and size to server for usage analysis (for the future Cloud storage)
|
||||
publicstorage.get("fmg").then(fmg => {
|
||||
if (!fmg) return;
|
||||
fmg.size = (fmg.size * fmg.maps + blob.size) / (fmg.maps + 1);
|
||||
fmg.maps += 1;
|
||||
publicstorage.set("fmg", fmg).then(fmg => console.log(fmg));
|
||||
});
|
||||
}
|
||||
|
||||
// Send .map file to server [test function]
|
||||
|
|
|
|||
|
|
@ -111,12 +111,6 @@ function applySorting(headers) {
|
|||
}).forEach(line => list.appendChild(line));
|
||||
}
|
||||
|
||||
// trigger trash button click on "Delete" keypress
|
||||
function removeElementOnKey() {
|
||||
$(".dialog:visible .icon-trash").click();
|
||||
$("button:visible:contains('Remove')").click();
|
||||
}
|
||||
|
||||
function addBurg(point) {
|
||||
const cells = pack.cells;
|
||||
const x = rn(point[0], 2), y = rn(point[1], 2);
|
||||
|
|
|
|||
|
|
@ -463,3 +463,9 @@ function pressControl() {
|
|||
zonesRemove.classList.contains("pressed") ? zonesRemove.classList.remove("pressed") : zonesRemove.classList.add("pressed");
|
||||
}
|
||||
}
|
||||
|
||||
// trigger trash button click on "Delete" keypress
|
||||
function removeElementOnKey() {
|
||||
$(".dialog:visible .fastDelete").click();
|
||||
$("button:visible:contains('Remove')").click();
|
||||
}
|
||||
|
|
@ -21,7 +21,6 @@ function editHeightmap() {
|
|||
});
|
||||
}()
|
||||
|
||||
let edits = [];
|
||||
restartHistory();
|
||||
viewbox.insert("g", "#terrs").attr("id", "heights");
|
||||
|
||||
|
|
@ -131,6 +130,10 @@ function editHeightmap() {
|
|||
return;
|
||||
}
|
||||
|
||||
delete window.edits; // remove global variable
|
||||
redo.disabled = templateRedo.disabled = true;
|
||||
undo.disabled = templateUndo.disabled = true;
|
||||
|
||||
customization = 0;
|
||||
customizationMenu.style.display = "none";
|
||||
if (document.getElementById("options").querySelector(".tab > button.active").id === "toolsTab") toolsContent.style.display = "block";
|
||||
|
|
@ -141,7 +144,6 @@ function editHeightmap() {
|
|||
closeDialogs();
|
||||
resetZoom();
|
||||
|
||||
restartHistory();
|
||||
if (document.getElementById("preview")) document.getElementById("preview").remove();
|
||||
if (document.getElementById("canvas3d")) enterStandardView();
|
||||
|
||||
|
|
@ -316,9 +318,7 @@ function editHeightmap() {
|
|||
const land = pack.cells.h[i] >= 20;
|
||||
|
||||
// check biome
|
||||
if (!biome[g]) pack.cells.biome[i] = getBiomeId(grid.cells.prec[g], grid.cells.temp[g]);
|
||||
else if (!land && biome[g]) pack.cells.biome[i] = 0;
|
||||
else pack.cells.biome[i] = biome[g];
|
||||
pack.cells.biome[i] = land && biome[g] ? biome[g] : getBiomeId(grid.cells.prec[g], pack.cells.h[i]);
|
||||
|
||||
// rivers data
|
||||
if (!changeHeights.checked) {
|
||||
|
|
@ -482,8 +482,8 @@ function editHeightmap() {
|
|||
|
||||
// restart edits from 1st step
|
||||
function restartHistory() {
|
||||
edits = [];
|
||||
edits.n = 0;
|
||||
window.edits = []; // declare temp global variable
|
||||
window.edits.n = 0;
|
||||
redo.disabled = templateRedo.disabled = true;
|
||||
undo.disabled = templateUndo.disabled = true;
|
||||
updateHistory();
|
||||
|
|
@ -986,7 +986,7 @@ function editHeightmap() {
|
|||
setOverlayOpacity(0);
|
||||
|
||||
document.getElementById("convertImageLoad").classList.add("glow"); // add glow effect
|
||||
tip('Image Converter is opened. Upload the image and assign the height for each of the colors', true, "warn"); // main tip
|
||||
tip('Image Converter is opened. Upload the image and assign height value for each of the colors', true, "warn"); // main tip
|
||||
|
||||
// remove all heights
|
||||
grid.cells.h = new Uint8Array(grid.cells.i.length);
|
||||
|
|
@ -998,8 +998,8 @@ function editHeightmap() {
|
|||
|
||||
// add color pallete
|
||||
void function createColorPallete() {
|
||||
const container = d3.select("#colorScheme");
|
||||
container.selectAll("div").data(d3.range(101)).enter().append("div").attr("data-color", i => i)
|
||||
d3.select("#imageConverterPalette").selectAll("div").data(d3.range(101))
|
||||
.enter().append("div").attr("data-color", i => i)
|
||||
.style("background-color", i => color(1-(i < 20 ? i-5 : i) / 100))
|
||||
.style("width", i => i < 20 || i > 70 ? ".2em" : ".1em")
|
||||
.on("touchmove mousemove", showPalleteHeight).on("click", assignHeight);
|
||||
|
|
@ -1020,7 +1020,7 @@ function editHeightmap() {
|
|||
const height = +this.getAttribute("data-color");
|
||||
colorsSelectValue.innerHTML = height;
|
||||
colorsSelectFriendly.innerHTML = getHeight(height);
|
||||
const former = colorScheme.querySelector(".hoveredColor")
|
||||
const former = imageConverterPalette.querySelector(".hoveredColor")
|
||||
if (former) former.className = "";
|
||||
this.className = "hoveredColor";
|
||||
}
|
||||
|
|
@ -1038,14 +1038,15 @@ function editHeightmap() {
|
|||
convertImageLoad.classList.remove("glow");
|
||||
};
|
||||
|
||||
reader.onloadend = function() {img.src = reader.result;};
|
||||
reader.onloadend = () => img.src = reader.result;
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
function heightsFromImage(count) {
|
||||
const ctx = document.getElementById("canvas").getContext("2d");
|
||||
const imageData = ctx.getImageData(0, 0, graphWidth, graphHeight);
|
||||
const data = imageData.data;
|
||||
const q = new RgbQuant({colors:count});
|
||||
q.sample(ctx);
|
||||
const data = q.reduce(ctx);
|
||||
|
||||
viewbox.select("#heights").selectAll("*").remove();
|
||||
d3.select("#imageConverter").selectAll("div.color-div").remove();
|
||||
|
|
@ -1053,27 +1054,22 @@ function editHeightmap() {
|
|||
colorsUnassigned.style.display = "block";
|
||||
colorsAssigned.style.display = "none";
|
||||
|
||||
const gridColors = grid.points.map(p => {
|
||||
let usedColors = new Set();
|
||||
let gridColors = grid.points.map(p => {
|
||||
const x = Math.floor(p[0]-.01), y = Math.floor(p[1]-.01);
|
||||
const i = (x + y * graphWidth) * 4;
|
||||
const r = data[i], g = data[i+1], b = data[i+2];
|
||||
usedColors.add(`rgb(${r},${g},${b})`);
|
||||
return [r, g, b];
|
||||
});
|
||||
|
||||
const cmap = MMCQ.quantize(gridColors, count);
|
||||
const usedColors = new Set();
|
||||
|
||||
viewbox.select("#heights").selectAll("polygon").data(grid.cells.i).join("polygon")
|
||||
.attr("points", d => getGridPolygon(d))
|
||||
.attr("id", d => "cell"+d).attr("fill", d => {
|
||||
const clr = `rgb(${cmap.nearest(gridColors[d])})`;
|
||||
usedColors.add(clr);
|
||||
return clr;
|
||||
}).on("click", mapClicked);
|
||||
.attr("id", d => "cell"+d).attr("fill", d => `rgb(${gridColors[d].join(",")})`)
|
||||
.on("click", mapClicked);
|
||||
|
||||
const unassigned = [...usedColors].sort((a, b) => d3.lab(a).l - d3.lab(b).l);
|
||||
const unassignedContainer = d3.select("#colorsUnassigned");
|
||||
unassignedContainer.selectAll("div").data(unassigned).enter().append("div")
|
||||
d3.select("#colorsUnassigned").selectAll("div").data(unassigned).enter().append("div")
|
||||
.attr("data-color", i => i).style("background-color", i => i)
|
||||
.attr("class", "color-div").on("click", colorClicked);
|
||||
|
||||
|
|
@ -1092,7 +1088,7 @@ function editHeightmap() {
|
|||
|
||||
const selectedColor = imageConverter.querySelector("div.selectedColor");
|
||||
if (selectedColor) selectedColor.classList.remove("selectedColor");
|
||||
const hoveredColor = colorScheme.querySelector("div.hoveredColor");
|
||||
const hoveredColor = imageConverterPalette.querySelector("div.hoveredColor");
|
||||
if (hoveredColor) hoveredColor.classList.remove("hoveredColor");
|
||||
colorsSelectValue.innerHTML = colorsSelectFriendly.innerHTML = 0;
|
||||
|
||||
|
|
@ -1101,7 +1097,7 @@ function editHeightmap() {
|
|||
|
||||
if (this.dataset.height) {
|
||||
const height = +this.dataset.height;
|
||||
colorScheme.querySelector(`div[data-color="${height}"]`).classList.add("hoveredColor");
|
||||
imageConverterPalette.querySelector(`div[data-color="${height}"]`).classList.add("hoveredColor");
|
||||
colorsSelectValue.innerHTML = height;
|
||||
colorsSelectFriendly.innerHTML = getHeight(height);
|
||||
}
|
||||
|
|
@ -1142,8 +1138,8 @@ function editHeightmap() {
|
|||
const lab = d3.lab(colorFrom);
|
||||
const normalized = type === "hue" ? rn(normalize(lab.b + lab.a / 2, -50, 200), 2) : rn(normalize(lab.l, -15, 100), 2);
|
||||
let heightTo = rn(normalized * 100);
|
||||
if (assinged[heightTo] && heightTo < 100) heightTo += 1; // if height is already added, try increated one
|
||||
if (assinged[heightTo] && heightTo < 100) heightTo += 1; // if height is already added, try increated one
|
||||
if (assinged[heightTo] && heightTo < 100) heightTo += 1; // if height is already added, try increased one
|
||||
if (assinged[heightTo] && heightTo < 100) heightTo += 1; // if height is already added, try increased one
|
||||
if (assinged[heightTo] && heightTo > 3) heightTo -= 3; // if increased one is also added, try decreased one
|
||||
if (assinged[heightTo] && heightTo > 1) heightTo -= 1; // if increased one is also added, try decreased one
|
||||
|
||||
|
|
@ -1158,16 +1154,16 @@ function editHeightmap() {
|
|||
});
|
||||
|
||||
// sort assigned colors by height
|
||||
Array.from(colorsAssigned.children).sort((a, b) => {
|
||||
return +a.dataset.height - +b.dataset.height;
|
||||
}).forEach(line => colorsAssigned.appendChild(line));
|
||||
Array.from(colorsAssigned.children)
|
||||
.sort((a, b) => +a.dataset.height - +b.dataset.height)
|
||||
.forEach(line => colorsAssigned.appendChild(line));
|
||||
|
||||
colorsAssigned.style.display = "block";
|
||||
colorsUnassigned.style.display = "none";
|
||||
}
|
||||
|
||||
function setConvertColorsNumber() {
|
||||
prompt(`Please provide a desired number of colors. <br>An actual number depends on color scheme and may vary from desired`,
|
||||
prompt(`Please set maximum number of colors. <br>An actual number is lower and depends on color scheme`,
|
||||
{default:+convertColors.value, step:1, min:3, max:255}, number => {
|
||||
convertColors.value = number;
|
||||
heightsFromImage(number);
|
||||
|
|
|
|||
|
|
@ -135,14 +135,18 @@ function editMarker() {
|
|||
const id = elSelected.attr("data-id");
|
||||
const used = document.querySelectorAll("use[data-id='"+id+"']");
|
||||
const count = used.length === 1 ? "1 element" : used.length + " elements";
|
||||
alertMessage.innerHTML = "Are you sure you want to remove the marker (" + count + ")?";
|
||||
alertMessage.innerHTML = "Are you sure you want to remove all markers of that type (" + count + ")?";
|
||||
|
||||
$("#alert").dialog({resizable: false, title: "Remove marker",
|
||||
$("#alert").dialog({resizable: false, title: "Remove marker type",
|
||||
buttons: {
|
||||
Remove: function() {
|
||||
$(this).dialog("close");
|
||||
if (id !== "#marker0") d3.select("#defs-markers").select(id).remove();
|
||||
used.forEach(e => e.remove());
|
||||
used.forEach(e => {
|
||||
const index = notes.findIndex(n => n.id === e.id);
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
e.remove();
|
||||
});
|
||||
updateGroupOptions();
|
||||
updateGroupOptions();
|
||||
$("#markerEditor").dialog("close");
|
||||
|
|
@ -187,12 +191,6 @@ function editMarker() {
|
|||
d3.select("#defs-markers").select(id).select("text").attr("y", this.value + "%");
|
||||
}
|
||||
|
||||
function applyCustomUnicodeIcon() {
|
||||
if (!this.value) return;
|
||||
const id = elSelected.attr("data-id");
|
||||
d3.select("#defs-markers").select(id).select("text").text(this.value);
|
||||
}
|
||||
|
||||
function toggleStyleSection() {
|
||||
if (markerStyleSection.style.display === "inline-block") {
|
||||
markerEditor.querySelectorAll("button:not(#markerStyle)").forEach(b => b.style.display = "inline-block");
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ function editNotes(id, name) {
|
|||
document.getElementById("notesDownload").addEventListener("click", downloadLegends);
|
||||
document.getElementById("notesUpload").addEventListener("click", () => legendsToLoad.click());
|
||||
document.getElementById("legendsToLoad").addEventListener("change", function() {uploadFile(this, uploadLegends)});
|
||||
document.getElementById("notesRemove").addEventListener("click", triggernotesRemove);
|
||||
document.getElementById("notesRemove").addEventListener("click", triggerNotesRemove);
|
||||
|
||||
function changeObject() {
|
||||
const note = notes.find(note => note.id === this.value);
|
||||
|
|
@ -96,7 +96,7 @@ function editNotes(id, name) {
|
|||
editNotes(notes[0].id, notes[0].name);
|
||||
}
|
||||
|
||||
function triggernotesRemove() {
|
||||
function triggerNotesRemove() {
|
||||
alertMessage.innerHTML = "Are you sure you want to remove the selected note?";
|
||||
$("#alert").dialog({resizable: false, title: "Remove note",
|
||||
buttons: {
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ function editProvinces() {
|
|||
const capital = p.burg ? pack.burgs[p.burg].name : '';
|
||||
const separable = p.burg && p.burg !== pack.states[p.state].capital;
|
||||
const focused = defs.select("#fog #focusProvince"+p.i).size();
|
||||
lines += `<div class="states" data-id=${p.i} data-name=${p.name} data-form=${p.formName} data-color="${p.color}" data-capital="${capital}" data-state="${stateName}" data-area=${area} data-population=${population}>
|
||||
lines += `<div class="states" data-id=${p.i} data-name="${p.name}" data-form="${p.formName}" data-color="${p.color}" data-capital="${capital}" data-state="${stateName}" data-area=${area} data-population=${population}>
|
||||
<svg data-tip="Province fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${p.color}" class="fillRect pointer"></svg>
|
||||
<input data-tip="Province name. Click to change" class="name pointer" value="${p.name}" readonly>
|
||||
<span data-tip="Click to open province COA in the Iron Arachne Heraldry Generator. Ctrl + click to change the seed" class="icon-coa pointer hide"></span>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue