您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
vectorizer.ai free download
// ==UserScript== // @name vectorizer.ai // @version 0.0.6 // @description vectorizer.ai free download // @icon https://d1j8j7mb8gx2ao.cloudfront.net/p/assets/logos/vectorizer-ai-logo_1c2b7a3ae82c1def79ab4c0e9cc83bcc.svg // @author ml98 // @namespace http://tampermonkey.net/ // @license MIT // @match https://vectorizer.ai/images/* // @grant none // @run-at document-start // ==/UserScript== let state = 0; let data = { width: 1, height: 1, curves: [], gap_fillers: [], style: {}, errors: 0 }; // todo: any other way to inject? Object.defineProperty(Object.prototype, 'isReady', { get() { if(this.vectorInterfaces) { data.gap_fillers = this.vectorInterfaces .filter(i => i.color0.a > 0 && i.color1.a > 0) .map(i => ({ d: i.path.s, stroke: i.css })); create_download_button(); } return this.__isReady; }, set(isReady) { this.__isReady = isReady; return isReady; }, configurable: true, enumerable: false, }); function proxy_functions(object, apply = (f, _this, args) => f.apply(_this, args)) { const descriptors = Object.getOwnPropertyDescriptors(object); Object.keys(descriptors).filter( (name) => descriptors[name].value instanceof Function ).forEach((f) => { object[f] = new Proxy(object[f], { apply }); }); } function F(strings, ...values) { return strings[0] + values.map( (value, i) => value.toFixed(2).replace(/\.?0+$/,'') + strings[i+1] ).join(''); } function path2svg(name, args) { switch (name) { case "moveTo": { let [x, y] = args; return F`M ${x} ${y}`; } case "lineTo": { let [x, y] = args; return F`L ${x} ${y}`; } case "ellipse": { let [cx, cy, rx, ry, rotation, theta1, theta2, ccw] = args; let dx = rx * Math.cos(theta2), dy = ry * Math.sin(theta2); let c = Math.cos(rotation), s = Math.sin(rotation); let ex= cx + c * dx - s * dy, ey = cy + s * dx + c * dy; let angle = rotation * (180 / Math.PI); let large_arc = (theta2 - theta1) % (2 * Math.PI) > Math.PI ? 1 : 0; let sweep = ccw ? 0 : 1; return F`A ${rx} ${ry} ${angle} ${large_arc} ${sweep} ${ex} ${ey}`; } case "bezierCurveTo": { let [cp1x, cp1y, cp2x, cp2y, x, y] = args; return F`C ${cp1x} ${cp1y} ${cp2x} ${cp2y} ${x} ${y}`; } case "quadraticCurveTo": { let [cpx, cpy, x, y] = args; return F`Q ${cpx} ${cpy} ${x} ${y}`; } case "closePath": { return `Z`; } default: { // throw new Error("unimplemented path", name); return `ERROR`; } } } proxy_functions(Path2D.prototype, (f, _this, args) => { _this.s ??= ""; // _this.s += `${f.name}[${args.toString()}] `; _this.s += path2svg(f.name, args); return f.apply(_this, args); }); proxy_functions(CanvasRenderingContext2D.prototype, (f, _this, args) => { if(_this.canvas.id) { // width, height } if(!_this.canvas.id) { switch(f.name) { case 'drawImage': if(state == 0) { data.width = args[3]; data.height = args[4]; state = 1; } break; case 'clip': if(state == 1) { // console.log(_this.lineWidth, _this.lineJoin); data.curves = []; data.errors = 0; data.style = { lineWidth: _this.lineWidth, lineJoin: _this.lineJoin }; state = 2; } break; case 'fill': if(state == 2) { // console.log(f.name, _this.fillStyle, args[0].s); if(!args[0].s) { data.errors++; console.log('error: incomplete'); break; // state = 1; } data.curves.push({fill: _this.fillStyle, d: args[0].s}); } break; case 'stroke': if(state == 2) { // console.log(f.name, _this.strokeStyle, args[0].s); if(!args[0].s) { data.errors++; console.log('error: incomplete'); break; // state = 1; } // data.curves.push({stroke: _this.strokeStyle, d: args[0].s}); } break; case 'restore': if(state == 2) { state = 1; console.log(data); update_download_button(); } break; } } return f.apply(_this, args); }); function svg_element(tag, attributes) { const element = document.createElementNS('http://www.w3.org/2000/svg', tag); for (let attr in attributes) { element.setAttribute(attr, attributes[attr]); } return element; } function create_svg() { const { width, height, curves, gap_fillers, style } = data; const svg = svg_element('svg', { xmlns: 'http://www.w3.org/2000/svg', viewBox: `0 0 ${width} ${height}`, width, height, }); let g = svg_element('g', { 'stroke-width': 2, 'fill': 'none', 'stroke-linecap': 'butt', }); svg.appendChild(g); gap_fillers.forEach(({d, stroke}) => { const element = svg_element('path', {d, stroke, 'vector-effect': 'non-scaling-stroke'}); g.appendChild(element); }); curves.forEach(({d, fill, stroke}) => { if(fill) { const element = svg_element('path', {d, fill}); svg.appendChild(element); } }); return svg; } let a = null; function create_download_button() { if(a) return; const button = document.querySelector('#App-DownloadLink'); a = button.cloneNode(true); a.style.borderRadius = 0; a.querySelector('.showPaid').style.display = 'inline'; a.querySelector('.showFree').style.display = 'block'; a.target = '_blank'; a.download = 'result.svg'; button.before(a); a.addEventListener('click', function(event) { const svg = create_svg(); const content = new XMLSerializer().serializeToString(svg); const type = 'image/svg+xml'; const url = URL.createObjectURL(new Blob([content], {type})); if(this.href) { URL.revokeObjectURL(this.href); } this.href = url; }); } function update_download_button() { if(!a) return; if(data.errors) { a.querySelector('.showPaid').textContent = `[missing ${data.errors}]`; a.style.background = '#4876ff80'; } else { a.querySelector('.showPaid').textContent = '[ok]'; a.style.background = '#4876ff'; } }