First version

This commit is contained in:
Ruben van de Ven 2019-12-16 12:19:48 +01:00
commit 1702cce9d1
12 changed files with 825 additions and 0 deletions

22
README.md Normal file
View File

@ -0,0 +1,22 @@
Build array of images sorted by size:
```bash
python zoom_animation.py --annotations ../../datasets/COCO/annotations/instances_train2017.json --output zoom --category_id 18
```
Turn into png
```bash
cd zoom/dog
for file in *.svg; do inkscape -z -f "${file}" -w 640 -e "../dog_png/${file}.png"; done
```
Turn png into mp4
```bash
cd ../dog_png
#ffmpeg -r 1 -i %d_*.png -pix_fmt yuv420p bloch2.mp4
ffmpeg -f image2 -pattern_type glob -i '*.png' ../dog.mp4
```

44
coco.sql Normal file
View File

@ -0,0 +1,44 @@
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS "segments" (
"id" INTEGER UNIQUE,
"annotation_id" INTEGER,
"points" TEXT,
PRIMARY KEY("id")
) WITHOUT ROWID;
CREATE TABLE IF NOT EXISTS "categories" (
"id" INTEGER UNIQUE,
"supercategory" TEXT,
"name" TEXT UNIQUE,
PRIMARY KEY("id")
) WITHOUT ROWID;
CREATE TABLE IF NOT EXISTS "images" (
"id" INTEGER UNIQUE,
"flickr_url" TEXT,
"coco_url" TEXT,
"width" FLOAT,
"height" FLOAT,
"date_captured" DATETIME,
PRIMARY KEY("id","id")
) WITHOUT ROWID;
CREATE TABLE IF NOT EXISTS "annotations" (
"id" INTEGER UNIQUE,
"image_id" INTEGER,
"category_id" INTEGER,
"iscrowd" BOOL,
"area" FLOAT,
"bbox_top" FLOAT,
"bbox_left" FLOAT,
"bbox_width" FLOAT,
"bbox_height" FLOAT,
PRIMARY KEY("id")
) WITHOUT ROWID;
CREATE INDEX IF NOT EXISTS "segments_annotation" ON "segments" (
"annotation_id"
);
CREATE INDEX IF NOT EXISTS "annotations_image" ON "annotations" (
"image_id"
);
CREATE INDEX IF NOT EXISTS "annotations_category" ON "annotations" (
"category_id"
);
COMMIT;

View File

@ -0,0 +1,36 @@
import pycocotools.coco
import argparse
import logging
import tqdm
import urllib.request
import os
logger = logging.getLogger("coco")
argParser = argparse.ArgumentParser(description='Find all images with single segments and generate svg')
argParser.add_argument(
'--annotations',
type=str,
default='../../datasets/COCO/annotations/instances_train2017.json'
)
argParser.add_argument(
'--output',
type=str,
help='Output directory'
)
args = argParser.parse_args()
logger.info(f"Load {args.annotations}")
coco = pycocotools.coco.COCO(args.annotations)
for img_id, annotations in tqdm.tqdm(coco.imgToAnns.items()):
if len(annotations) != 1:
continue
annotation = annotations[0] # we have only one
category = coco.cats[annotation['category_id']]['name']
fn = os.path.join(args.output, f"{category}_{img_id}.jpg")
if not os.path.exists(fn):
urllib.request.urlretrieve(coco.imgs[img_id]['coco_url'], fn)

125
server.py Normal file
View File

@ -0,0 +1,125 @@
import tornado.ioloop
import tornado.web
import tornado.websocket
import argparse
import logging
import coloredlogs
from coco.storage import COCOStorage
import json
from urllib.parse import urlparse
logger = logging.getLogger('coco.server')
class JsonEncoder(json.JSONEncoder):
def default(self, obj):
method = getattr(obj, "forJson", None)
if callable(method ):
return obj.forJson()
# Let the base class default method raise the TypeError
return json.JSONEncoder.default(self, obj)
class RestHandler(tornado.web.RequestHandler):
def initialize(self, storage: COCOStorage):
self.storage = storage
self.set_header("Content-Type", "application/json")
def get(self, *params):
self.write(json.dumps(self.getData(*params), cls=JsonEncoder))
class CategoryHandler(RestHandler):
def getData(self):
return self.storage.getCategories()
class AnnotationHandler(RestHandler):
def getData(self):
# get specific annotation
annotation_id = self.get_argument('id', None)
annotation_id = None if not annotation_id else int(annotation_id)
# get by category id
category_id = self.get_argument('category', None)
category_id = None if not category_id else int(category_id)
normalise = self.get_argument('normalise', False)
normalise = int(normalise) if normalise is not False else False
# category_id = None if not category_id else int(category_id)
logger.debug(f'Get annotation id: {annotation_id}, category: {category_id}, normalised: {normalise}')
annotation = self.storage.getRandomAnnotation(annotation_id=annotation_id, category_id=category_id)
if normalise:
return annotation.getNormalised(normalise, normalise)
return annotation
class WebSocketHandler(tornado.websocket.WebSocketHandler):
CORS_ORIGINS = ['localhost', 'coco.local', 'r3.local']
def check_origin(self, origin):
parsed_origin = urlparse(origin)
# parsed_origin.netloc.lower() gives localhost:3333
valid = parsed_origin.hostname in self.CORS_ORIGINS
return valid
# the client connected
def open(self, p = None):
WebSocketHandler.connections.add(self)
logger.info("New client connected")
self.write_message("hello!")
# the client sent the message
def on_message(self, message):
logger.debug(f"recieve: {message}")
class StaticFileWithHeaderHandler(tornado.web.StaticFileHandler):
def set_extra_headers(self, path):
"""For subclass to add extra headers to the response"""
if path[-5:] == '.html':
self.set_header("Access-Control-Allow-Origin", "*")
def make_app(db_filename, debug):
storage = COCOStorage(db_filename)
return tornado.web.Application([
(r"/ws(.*)", WebSocketHandler),
(r"/categories.json", CategoryHandler, {'storage': storage}),
(r"/annotation.json", AnnotationHandler, {'storage': storage}),
(r"/(.*)", StaticFileWithHeaderHandler,
{"path": 'www', "default_filename": 'index.html'}),
], debug=debug)
if __name__ == "__main__":
argParser = argparse.ArgumentParser(description='Server for COCO web interface')
argParser.add_argument(
'--port',
'-P',
type=int,
default=8888,
help='Port to listen on'
)
argParser.add_argument(
'--db',
type=str,
metavar='DATABASE',
required=True,
help='Database to serve from'
)
argParser.add_argument(
'--verbose',
'-v',
action='store_true',
help='Increase log level'
)
args = argParser.parse_args()
loglevel = logging.DEBUG if args.verbose else logging.INFO
coloredlogs.install(
level=loglevel,
fmt="%(asctime)s %(hostname)s %(name)s[%(process)d] %(levelname)s %(message)s"
)
app = make_app(args.db, debug=args.verbose )
app.listen(args.port)
tornado.ioloop.IOLoop.current().start()

106
tools.py Normal file
View File

@ -0,0 +1,106 @@
import pycocotools.coco
import argparse
import logging
import os
import pprint
import sqlite3
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("coco")
argParser = argparse.ArgumentParser(description='Create shape SVG\'s')
argParser.add_argument(
'--annotations',
type=str,
default='../../datasets/COCO/annotations/instances_val2017.json'
)
argParser.add_argument(
'--categories',
action='store_true',
help='Show categories'
)
argParser.add_argument(
'--propagate',
type=str,
metavar='DATABASE',
help='Store data in sqlite db'
)
args = argParser.parse_args()
logger.info(f"Load {args.annotations}")
coco = pycocotools.coco.COCO(args.annotations)
if args.categories:
cats = {}
for id, cat in coco.cats.items():
if cat['supercategory'] not in cats:
cats[cat['supercategory']] = []
cats[cat['supercategory']].append(cat)
# pp = pprint.PrettyPrinter(indent=4)
pprint.pprint(cats, sort_dicts=False)
if args.propagate:
if not os.path.exists(args.propagate):
con = sqlite3.connect(args.propagate)
cur = con.cursor()
with open('coco.sql', 'r') as fp:
cur.executescript(fp.read())
con.close()
con = sqlite3.connect(args.propagate)
logger.info("Create categories")
cur = con.cursor()
cur.executemany('INSERT OR IGNORE INTO categories(id, supercategory, name) VALUES (:id, :supercategory, :name)', coco.cats.values())
con.commit()
logger.info("Images...")
cur.executemany('''
INSERT OR IGNORE INTO images(id, flickr_url, coco_url, width, height, date_captured)
VALUES (:id, :flickr_url, :coco_url, :width, :height, :date_captured)
''', coco.imgs.values())
con.commit()
logger.info("Annotations...")
def annotation_generator():
for c in coco.anns.values():
ann = c.copy()
ann['bbox_top'] = ann['bbox'][1]
ann['bbox_left'] = ann['bbox'][0]
ann['bbox_width'] = ann['bbox'][2]
ann['bbox_height'] = ann['bbox'][3]
yield ann
cur.executemany('''
INSERT OR IGNORE INTO annotations(id, image_id, category_id, iscrowd, area, bbox_top, bbox_left, bbox_width, bbox_height)
VALUES (:id, :image_id, :category_id, :iscrowd, :area, :bbox_top, :bbox_left, :bbox_width, :bbox_height)
''', annotation_generator())
con.commit()
logger.info("Segments...")
def segment_generator():
for ann in coco.anns.values():
for i, seg in enumerate(ann['segmentation']):
yield {
'id': ann['id']*10 + i, # create a uniqe segment id, supports max 10 segments per annotation
'annotation_id': ann['id'],
'points': str(seg)[1:-1],
}
cur.executemany('''
INSERT OR IGNORE INTO segments(id, annotation_id, points)
VALUES (:id, :annotation_id, :points)
''', segment_generator())
con.commit()
logger.info("Done...")
# for id, cat in coco.cats.items():
# cur = con.cursor()
# cur.execute

234
www/coco.js Normal file
View File

@ -0,0 +1,234 @@
//////////////////////////
// use texture.js without d3 to create discerning patterns on the email blocks
/////////////////////////////////////
// some faked dom, see https://github.com/riccardoscalco/textures/issues/17
function dom(name) {
this.name = name;
this.els = [];
this.attrs = {};
}
dom.prototype.append = function(name) {
if (name === "defs") return this;
if (this.name === undefined) {
this.name = name;
return this;
}
var el = new dom(name);
this.els.push(el);
return el;
}
dom.prototype.attr = function(key, value) {
this.attrs[key] = value;
return this;
}
dom.prototype.toString = function() {
var attrs = [];
var k;
for (k in this.attrs) {
attrs += " " + k + "='" + this.attrs[k] + "'";
}
if (this.els.length) {
return "<"+this.name+attrs+">"+this.els.map(function(el) { return el.toString()}).join('\n')+"</"+this.name+">";
} else {
return "<"+this.name+attrs+"/>"
}
}
//////////// end of faked dom ///////////////////////////////
let categoryMap = {};
let textureMap = {};
function getColoredTexture(i, color) {
let j = i % 11; // update when adding items to switch:
switch(j) {
case 0:
return textures.lines().size(4).strokeWidth(1).stroke(color);
case 1:
return textures.circles().radius(2).size(5).fill('none').strokeWidth(1).stroke(color).complement();
case 2:
return textures.lines().size(10).orientation("3/8").stroke(color);
case 3:
return textures.lines().heavier(4).thinner(.8).stroke(color);
case 4:
return textures.paths().d("hexagons").size(3).strokeWidth(1).stroke(color);
case 5:
return textures.lines().orientation("vertical", "horizontal").size(4).strokeWidth(1).shapeRendering("crispEdges").stroke(color);
case 6:
return textures.paths().d("hexagons").size(5).strokeWidth(2).stroke("rgba(0,0,0,0)").fill(color);
case 7:
return textures.circles().size(4).stroke(color);
case 8:
return textures.circles().thicker().complement().stroke(color);
case 9:
return textures.paths().d("caps").lighter().thicker().size(5).stroke(color);
case 10:
return textures.paths().d("hexagons").size(4).strokeWidth(2).stroke(color);
// textures.lines().size(4).strokeWidth(1).orientation("-3/8"),
}
};
const categoryColors = {
"person": "#f00",
"vehicle": "#0f0",
"outdoor": "#006",
"animal": "#ff0",
"food": "#0ff",
"furniture": "#f0f",
"indoor": "#fff",
"electronic": "#390",
"kitchen": "#930",
"accessory": "#f90",
"sports": "#f09",
}
function getColorForSuperCategory(name) {
return categoryColors[name];
}
function getTextureForCategory(id) {
let hash = id;
if(!textureMap.hasOwnProperty(hash)) {
let color = categoryColors[categoryMap[id]['supercategory']];
textureMap[hash] = getColoredTexture(id, color);
}
return textureMap[hash];
}
class CocoCanvas {
start(){
this.catNavEl = document.getElementById('catNav');
this.canvas = document.getElementById('svgCanvas');
this.loadNav()
}
loadNav() {
let r = new Request('/categories.json');
fetch(r)
.then(response => response.json())
.then(categories => {
for(let cat of categories) {
categoryMap[cat['id']] = cat;
}
this.buildNav(categories);
}).catch(function(e){
console.error(e);
});
}
buildNav(categories) {
let ulEl = crel('ul');
for(let cat of categories) {
ulEl.appendChild(
crel('li', {
'id': 'category-' + cat['id'],
'on': {
'click': (e) => {
this.requestAnnotation(cat['id']);
}
}
}, cat['name'])
);
}
this.catNavEl.appendChild(ulEl);
let defsEl = document.createElementNS("http://www.w3.org/2000/svg", 'defs');
for(let cat of categories) {
let texture = getTextureForCategory(cat['id']);
let sel = new dom();
texture(sel);
defsEl.innerHTML += sel.toString();
}
this.canvas.appendChild(defsEl);
}
requestAnnotation(category_id) {
let r = new Request(`/annotation.json?category=${category_id}&normalise=100`);
fetch(r)
.then(response => response.json())
.then(annotation => {
this.addAnnotationAsShape(annotation);
}).catch(function(e){
console.error(e);
});;
}
pointsToD(points) {
let start = points.shift()
let d = `M${start[0].toPrecision(4)} ${start[1].toPrecision(4)} L `;
points = points.map((p) => `${p[0].toPrecision(4)} ${p[1].toPrecision(4)}`);
d += points.join(' ');
return d;
}
getMousePosition(evt) {
// from http://www.petercollingridge.co.uk/tutorials/svg/interactive/dragging/
let CTM = this.canvas.getScreenCTM();
return {
x: (evt.clientX - CTM.e) / CTM.a,
y: (evt.clientY - CTM.f) / CTM.d
};
}
addAnnotationAsShape(annotation) {
console.log('Add annotation', annotation);
let category = categoryMap[annotation['category_id']]
let texture = getTextureForCategory(category['id']);
let x = 500 - annotation['bbox'][2]/2;
let y = 500 - annotation['bbox'][3]/2;
let annEl = crel(document.createElementNS("http://www.w3.org/2000/svg", 'g'), {
'data-id': annotation['id'],
'transform': `translate(${x}, ${y})`,
'on': {
'mousedown': function(downE) {
console.log(downE);
console.log(this)
let offset = this.getMousePosition(downE);
// offset.x -= parseFloat(downE.target.getAttributeNS(null, "x"));
// offset.y -= parseFloat(downE.target.getAttributeNS(null, "y"));
// Get initial translation amount
console.log(annEl.transform.baseVal);
let transform = annEl.transform.baseVal.getItem(0);
offset.x -= transform.matrix.e;
offset.y -= transform.matrix.f;
let moveEvent = (moveE) => {
let coord = this.getMousePosition(moveE);
transform.matrix.e = coord.x - offset.x;
transform.matrix.f = coord.y - offset.y;
// annEl.setAttributeNS(null, "x", coord.x - offset.x);
// annEl.setAttributeNS(null, "y", coord.y - offset.y);
};
document.addEventListener('mousemove', moveEvent);
document.addEventListener('mouseup', (upE) => {
document.removeEventListener('mousemove', moveEvent);
});
}.bind(this)
}
});
for(let segment of annotation['segments']) {
let pathEl = crel(document.createElementNS("http://www.w3.org/2000/svg", 'path'), {
'fill': texture.url(),
'd': this.pointsToD(segment)
});
annEl.appendChild(pathEl);
}
console.log(annEl);
this.canvas.appendChild(annEl);
}
}
let cc = new CocoCanvas();
cc.start();

127
www/coco_example.js Normal file
View File

@ -0,0 +1,127 @@
//////////////////////////
// use texture.js without d3 to create discerning patterns on the email blocks
/////////////////////////////////////
// some faked dom, see https://github.com/riccardoscalco/textures/issues/17
function dom(name) {
this.name = name;
this.els = [];
this.attrs = {};
}
dom.prototype.append = function(name) {
if (name === "defs") return this;
if (this.name === undefined) {
this.name = name;
return this;
}
var el = new dom(name);
this.els.push(el);
return el;
}
dom.prototype.attr = function(key, value) {
this.attrs[key] = value;
return this;
}
dom.prototype.toString = function() {
var attrs = [];
var k;
for (k in this.attrs) {
attrs += " " + k + "='" + this.attrs[k] + "'";
}
if (this.els.length) {
return "<"+this.name+attrs+">"+this.els.map(function(el) { return el.toString()}).join('\n')+"</"+this.name+">";
} else {
return "<"+this.name+attrs+"/>"
}
}
//////////// end of faked dom ///////////////////////////////
let textureMap = {};
function getColoredTexture(i, color) {
let j = i % 11; // update when adding items to switch:
switch(j) {
case 0:
return textures.lines().size(4).strokeWidth(1).stroke(color);
case 1:
return textures.circles().radius(2).size(10).fill('none').strokeWidth(1).stroke(color).complement();
case 2:
return textures.lines().size(10).orientation("3/8").stroke(color);
case 3:
return textures.lines().heavier(4).thinner(.8).stroke(color);
case 4:
return textures.paths().d("hexagons").size(7).strokeWidth(1).stroke(color);
case 5:
return textures.lines().orientation("vertical", "horizontal").size(4).strokeWidth(1).shapeRendering("crispEdges").stroke(color);
case 6:
return textures.paths().d("hexagons").size(5).strokeWidth(2).stroke("rgba(0,0,0,0)").fill(color);
case 7:
return textures.circles().size(4).stroke(color);
case 8:
return textures.circles().thicker().complement().stroke(color);
case 9:
return textures.paths().d("caps").lighter().thicker().stroke(color);
case 10:
return textures.paths().d("hexagons").size(4).strokeWidth(2).stroke(color);
// textures.lines().size(4).strokeWidth(1).orientation("-3/8"),
}
};
const categoryColors = {
"person": "#f00",
"vehicle": "#0f0",
"outdoor": "#006",
"animal": "#ff0",
"food": "#0ff",
"furniture": "#f0f",
"indoor": "#fff",
"electronic": "#390",
"kitchen": "#930",
"accessory": "#f90",
"sports": "#f09",
}
function getColorForSuperCategory(name) {
return categoryColors[name];
}
function getTextureForCategory(name, color) {
let hash = name+'-'+color;
if(!textureMap.hasOwnProperty(hash)) {
let i = Object.keys(textureMap).length;
textureMap[hash] = getColoredTexture(i, color);
}
return textureMap[hash];
}
// turn HTMLCollection into Array, to prevent dynamic updating of the collection during loop
pathEls = Array.from(document.getElementsByTagName('path'));
for(pathEl of pathEls){
let defsEls = pathEl.parentNode.getElementsByTagName('defs');
let defsEl;
if(defsEls.length < 1) {
defsEl = document.createElement("DEFS");
pathEl.parentNode.appendChild(defsEl);
} else {
defsEl = defsEls[0];
}
console.log(pathEl,pathEl.classList);
if(pathEl.classList.length != 2) continue;
let super_name = pathEl.classList.item(0).substr(6);
let cat_name = pathEl.classList.item(1).substr(4);
let color = getColorForSuperCategory(super_name)
let texture = getTextureForCategory(cat_name, color);
if(!defsEl.parentNode.getElementById(texture.id())) {
let sel = new dom();
texture(sel);
console.log(sel, sel.toString())
defsEl.innerHTML += sel.toString();
}
pathEl.style.fill = texture.url();
}

47
www/crel.min.js vendored Normal file
View File

@ -0,0 +1,47 @@
!function(n,e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):n.crel=e()}(this,function(){function n(a){var d,s=arguments,p=s[1],y=2,m=s.length,x=n[o];if(a=n[u](a)?a:c.createElement(a),m>1){if((!e(p,r)||n[f](p)||Array.isArray(p))&&(--y,p=null),m-y==1&&e(s[y],"string"))a.textContent=s[y];else for(;y<m;++y)null!==(d=s[y])&&l(a,d);for(var v in p)if(x[v]){var A=x[v];e(A,t)?A(a,p[v]):a[i](A,p[v])}else e(p[v],t)?a[v]=p[v]:a[i](v,p[v])}return a}var e=function(n,e){return typeof n===e},t="function",r="object",i="setAttribute",o="attrMap",f="isNode",u="isElement",c=document,a=function(n){return n instanceof Node},d=function(n){return n instanceof Element},l=function(e,t){if(Array.isArray(t))return void t.map(function(n){l(e,n)});n[f](t)||(t=c.createTextNode(t)),e.appendChild(t)};return n[o]={},n[u]=d,n[f]=a,e(Proxy,"undefined")||(n.proxy=new Proxy(n,{get:function(e,t){return!(t in n)&&(n[t]=n.bind(null,t)),n[t]}})),n});
/*
* Extended CREL, originally for PillowTalk
* - event listeners with 'on'
* - Array with 'options' for 'select'
* - input type 'checkbox' has 'checked_value', which validates as boolean to set checked state
*/
// for input type checkbox, map value to a checkbox
crel.attrMap['checked_value'] = function(element, value) {
if(value) {
element.checked = 'checked';
}
};
crel.attrMap['on'] = function(element, value) {
for (var eventName in value) {
element.addEventListener(eventName, value[eventName]);
}
};
crel.attrMap['options'] = function(element, values, a, b) {
if(element.tagName != "SELECT") {
return;
}
let selectedValue = element.value ? element.value : element.attributes.value.value;
if(Array.isArray(values)) {
for (let option of values) {
if(selectedValue == option) {
element.appendChild(crel('option', {'selected': 'selected'}, option));
} else {
element.appendChild(crel('option', option));
}
}
} else {
for (let option in values) {
if(selectedValue == option) {
element.appendChild(crel('option', {'selected': 'selected','value': option}, values[option]));
}else{
element.appendChild(crel('option', {'value': option}, values[option]));
}
}
}
};

17
www/example.html Normal file

File diff suppressed because one or more lines are too long

56
www/index.html Normal file
View File

@ -0,0 +1,56 @@
<html>
<head>
<meta charset='utf-8'>
<style type='text/css'>
body{
font-family:sans-serif;
background: darkblue;
margin:0;
}
#svgCanvas{
width:100vw;
height:100vh;
}
#catNav{
position:absolute;
top:0;
left:0;
color:white;
}
#catNav ul{
list-style:none;
padding:10px;
}
#catNav li:hover{
cursor:pointer;
text-decoration:underline;
}
g{
cursor:grab;
}
g:active{
cursor:grabbing;
}
</style>
</head>
<body>
<nav id='catNav'>
</nav>
<svg
id='svgCanvas'
xmlns="http://www.w3.org/2000/svg"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 1000 1000">
</svg>
<!-- <script src="svg-inject.min.js"></script> -->
<script src="textures.js"></script>
<script src="crel.min.js"></script>
<script src="coco.js"></script>
</body>
</html>

10
www/svg-inject.min.js vendored Normal file
View File

@ -0,0 +1,10 @@
!function(o,l){var r,a,s="createElement",g="getElementsByTagName",b="length",E="style",d="title",y="undefined",k="setAttribute",w="getAttribute",x=null,A="__svgInject",C="--inject-",S=new RegExp(C+"\\d+","g"),I="LOAD_FAIL",t="SVG_NOT_SUPPORTED",L="SVG_INVALID",v=["src","alt","onload","onerror"],j=l[s]("a"),G=typeof SVGRect!=y,f={useCache:!0,copyAttributes:!0,makeIdsUnique:!0},N={clipPath:["clip-path"],"color-profile":x,cursor:x,filter:x,linearGradient:["fill","stroke"],marker:["marker",
"marker-end","marker-mid","marker-start"],mask:x,pattern:["fill","stroke"],radialGradient:["fill","stroke"]},u=1,c=2,O=1;function T(e){return(r=r||new XMLSerializer).serializeToString(e)}function P(e,r){var t,n,i,o,a=C+O++,f=/url\("?#([a-zA-Z][\w:.-]*)"?\)/g,u=e.querySelectorAll("[id]"),c=r?[]:x,l={},s=[],d=!1;if(u[b]){for(i=0;i<u[b];i++)(n=u[i].localName)in N&&(l[n]=1);for(n in l)(N[n]||[n]).forEach(function(e){s.indexOf(e)<0&&s.push(e)});s[b]&&s.push(E);var v,p,m,h=e[g]("*"),y=e;for(i=-1;y!=x;
){if(y.localName==E)(m=(p=y.textContent)&&p.replace(f,function(e,r){return c&&(c[r]=1),"url(#"+r+a+")"}))!==p&&(y.textContent=m);else if(y.hasAttributes()){for(o=0;o<s[b];o++)v=s[o],(m=(p=y[w](v))&&p.replace(f,function(e,r){return c&&(c[r]=1),"url(#"+r+a+")"}))!==p&&y[k](v,m);["xlink:href","href"].forEach(function(e){var r=y[w](e);/^\s*#/.test(r)&&(r=r.trim(),y[k](e,r+a),c&&(c[r.substring(1)]=1))})}y=h[++i]}for(i=0;i<u[b];i++)t=u[i],c&&!c[t.id]||(t.id+=a,d=!0)}return d}function V(e,r,t,n){if(r){
r[k]("data-inject-url",t);var i=e.parentNode;if(i){n.copyAttributes&&function c(e,r){for(var t,n,i,o=e.attributes,a=0;a<o[b];a++)if(n=(t=o[a]).name,-1==v.indexOf(n))if(i=t.value,n==d){var f,u=r.firstElementChild;u&&u.localName.toLowerCase()==d?f=u:(f=l[s+"NS"]("http://www.w3.org/2000/svg",d),r.insertBefore(f,u)),f.textContent=i}else r[k](n,i)}(e,r);var o=n.beforeInject,a=o&&o(e,r)||r;i.replaceChild(a,e),e[A]=u,m(e);var f=n.afterInject;f&&f(e,a)}}else D(e,n)}function p(){for(var e={},r=arguments,
t=0;t<r[b];t++){var n=r[t];for(var i in n)n.hasOwnProperty(i)&&(e[i]=n[i])}return e}function _(e,r){if(r){var t;try{t=function i(e){return(a=a||new DOMParser).parseFromString(e,"text/xml")}(e)}catch(o){return x}return t[g]("parsererror")[b]?x:t.documentElement}var n=l.createElement("div");return n.innerHTML=e,n.firstElementChild}function m(e){e.removeAttribute("onload")}function n(e){console.error("SVGInject: "+e)}function i(e,r,t){e[A]=c,t.onFail?t.onFail(e,r):n(r)}function D(e,r){m(e),i(e,L,r)
}function F(e,r){m(e),i(e,t,r)}function M(e,r){i(e,I,r)}function q(e){e.onload=x,e.onerror=x}function R(e){n("no img element")}var e=function z(e,r){var t=p(f,r),h={};function n(a,f){f=p(t,f);var e=function(r){var e=function(){var e=f.onAllFinish;e&&e(),r&&r()};if(a&&typeof a[b]!=y){var t=0,n=a[b];if(0==n)e();else for(var i=function(){++t==n&&e()},o=0;o<n;o++)u(a[o],f,i)}else u(a,f,e)};return typeof Promise==y?e():new Promise(e)}function u(u,c,e){if(u){var r=u[A];if(r)Array.isArray(r)?r.push(e
):e();else{if(q(u),!G)return F(u,c),void e();var t=c.beforeLoad,n=t&&t(u)||u[w]("src");if(!n)return""===n&&M(u,c),void e();var i=[];u[A]=i;var l=function(){e(),i.forEach(function(e){e()})},s=function f(e){return j.href=e,j.href}(n),d=c.useCache,v=c.makeIdsUnique,p=function(r){d&&(h[s].forEach(function(e){e(r)}),h[s]=r)};if(d){var o,a=function(e){if(e===I)M(u,c);else if(e===L)D(u,c);else{var r,t=e[0],n=e[1],i=e[2];v&&(t===x?(t=P(r=_(n,!1),!1),e[0]=t,e[2]=t&&T(r)):t&&(n=function o(e){
return e.replace(S,C+O++)}(i))),r=r||_(n,!1),V(u,r,s,c)}l()};if(typeof(o=h[s])!=y)return void(o.isCallbackQueue?o.push(a):a(o));(o=[]).isCallbackQueue=!0,h[s]=o}!function m(e,r,t){if(e){var n=new XMLHttpRequest;n.onreadystatechange=function(){if(4==n.readyState){var e=n.status;200==e?r(n.responseXML,n.responseText.trim()):400<=e?t():0==e&&t()}},n.open("GET",e,!0),n.send()}}(s,function(e,r){var t=e instanceof Document?e.documentElement:_(r,!0),n=c.afterLoad;if(n){var i=n(t,r)||t;if(i){
var o="string"==typeof i;r=o?i:T(t),t=o?_(i,!0):i}}if(t instanceof SVGElement){var a=x;if(v&&(a=P(t,!1)),d){var f=a&&T(t);p([a,r,f])}V(u,t,s,c)}else D(u,c),p(L);l()},function(){M(u,c),p(I),l()})}}else R()}return G&&function i(e){var r=l[g]("head")[0];if(r){var t=l[s](E);t.type="text/css",t.appendChild(l.createTextNode(e)),r.appendChild(t)}}('img[onload^="'+e+'("]{visibility:hidden;}'),n.setOptions=function(e){t=p(t,e)},n.create=z,n.err=function(e,r){e?e[A]!=c&&(q(e),G?(m(e),M(e,t)):F(e,t),r&&(m(
e),e.src=r)):R()},o[e]=n}("SVGInject");"object"==typeof module&&"object"==typeof module.exports&&(module.exports=e)}(window,document);

1
www/textures.js Normal file

File diff suppressed because one or more lines are too long