Skip to content

Instantly share code, notes, and snippets.

@eeropic
Created September 2, 2021 09:04
Show Gist options
  • Save eeropic/4c4871e3224e4304114da28552992803 to your computer and use it in GitHub Desktop.
Save eeropic/4c4871e3224e4304114da28552992803 to your computer and use it in GitHub Desktop.
scriptui-svg alpha
// @target aftereffects
// altKeyPressed(win), optKeyPressed(mac) ctrlKeyPressed, cmdKeyPressed(mac), shiftKeyPressed, capsLockKeyPressed, numLockKeyPressed
// leftButtonPressed, middleButtonPressed, rightButtonPressed, mouseOver, hasFocus,
//encapsulate the script in a function to avoid global variables
(function (thisObj) {
default xml namespace = "http://www.w3.org/2000/svg";
var xmlString = """
<!-- Generator: Adobe Illustrator 23.1.1, SVG Export Plug-In -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="842px"
height="487.92px" viewBox="0 0 842 487.92" enable-background="new 0 0 842 487.92" xml:space="preserve">
<defs>
</defs>
<line fill="none" stroke="#FFE08E" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="48" y1="183.92" x2="88" y2="183.92"/>
<polygon fill="#FF7C97" points="8,73.92 48,93.92 48,53.92 8,33.92 "/>
<polygon fill="#5F9ED6" points="88,73.92 48,93.92 48,53.92 88,33.92 "/>
<polygon fill="#FFE08E" points="88,33.92 48,53.92 8,33.92 48.56,13.3 "/>
<circle fill="#FFFFFF" cx="48" cy="53.92" r="30"/>
<line fill="none" stroke="#FF7C97" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="48" y1="123.92" x2="8" y2="183.92"/>
<line fill="none" stroke="#5F9ED6" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="48" y1="123.92" x2="88" y2="183.92"/>
<line fill="none" stroke="#FFE08E" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="8" y1="183.92" x2="48" y2="183.92"/>
<circle fill="#FF7C97" cx="118" cy="53.92" r="20"/>
<ellipse fill="#5F9ED6" cx="188" cy="53.92" rx="40" ry="20"/>
<rect x="148" y="33.92" fill="#FFE08E" width="40" height="40"/>
<polyline fill="#FF7C97" stroke="#5F9ED6" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
128,153.92 168,93.92 208,153.92 168,153.92 "/>
<polygon fill="none" stroke="#FF7C97" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
228,93.92 228,133.92 248,113.92 268,133.92 268,93.92 "/>
<polygon fill="none" stroke="#FF7C97" stroke-width="16" stroke-linecap="square" stroke-miterlimit="10" points="248,13.92
248,53.92 268,43.92 288,53.92 288,13.92 "/>
<circle fill="none" stroke="#FF7C97" stroke-width="8" stroke-linecap="square" stroke-miterlimit="10" cx="138" cy="213.92" r="20"/>
<circle fill="#5F9ED6" stroke="#FF7C97" stroke-width="8" stroke-linecap="square" stroke-miterlimit="10" cx="198" cy="213.92" r="20"/>
<ellipse fill="#5F9ED6" stroke="#FF7C97" stroke-width="8" stroke-linecap="square" stroke-miterlimit="10" cx="258" cy="243.92" rx="20" ry="50"/>
<ellipse fill="#FF7C97" stroke="#FFFFFF" stroke-width="8" stroke-linecap="square" stroke-miterlimit="10" cx="278" cy="243.92" rx="20" ry="50"/>
<rect x="118" y="253.92" fill="none" stroke="#5F9ED6" stroke-width="8" stroke-linecap="square" stroke-miterlimit="10" width="40" height="40"/>
<rect x="178" y="253.92" fill="#FF7C97" stroke="#5F9ED6" stroke-width="8" stroke-linecap="square" stroke-miterlimit="10" width="40" height="40"/>
<polygon fill="#5F9ED6" points="68,213.92 28,233.92 38,263.92 58,243.92 88,283.92 88,243.92 "/>
<path fill="none" stroke="#5F9ED6" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M358,103.92h-30l-30,40c0,0-10,40,30,50c0,0,10-40,40-40"/>
<path fill="none" stroke="#5F9ED6" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M377.56,17.56c-1.97-6.28-9.45-8.91-16.01-9.43c-4.23-0.34-8.7-0.13-12.39,1.97c-6.39,3.63-8.66,12.35-6.37,19.33
s8.22,12.26,14.78,15.57s13.81,4.93,20.9,6.85c9.78,2.64,19.4,5.88,28.79,9.68c6.37,2.58,12.77,5.52,17.77,10.23
s8.46,11.55,7.56,18.37c-0.59,4.45-2.99,8.56-6.26,11.64c-5.77,5.45-14.11,7.74-22,6.98c-7.9-0.76-15.34-4.35-21.55-9.3"/>
<path fill="none" stroke="#5F9ED6" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M408,303.92h50v-50h40c0,0,0,30,40,30s40-30,40-30h50v90c-50,0-50,40-50,40h-50c0,0-10-40-40-40s-50,40-50,40c-30,0-40-30-40-30
c-30,0-40-30-40-30"/>
<path fill="none" stroke="#5F9ED6" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M558,143.92c50,0,50,60,100,60s50-60,100-60s30,70,80,70"/>
<polyline fill="none" stroke="#5F9ED6" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
68,373.92 58,383.92 68,403.92 58,413.92 "/>
<polyline fill="none" stroke="#FF7C97" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
88,373.92 78,393.92 88,413.92 "/>
<path fill="none" stroke="#FF7C97" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M108,433.92v-60"/>
<path fill="none" stroke="#FF7C97" stroke-width="8" stroke-linecap="square" stroke-miterlimit="10" d="M128,373.92
c11.05,0,20,8.95,20,20c0,11.05-8.95,20-20,20l20,20"/>
<path fill="#5F9ED6" stroke="#FF7C97" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M208,433.92l-20-20c11.05,0,20-8.95,20-20c0-11.05-8.95-20-20-20h-20v60H208z"/>
<path fill="none" stroke="#FF7C97" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M248,483.92c60-60-10-90,50-90s0,70,60,70"/>
</svg>
"""
var utils = {
hexToArray: function(hexString){
var hexColor = hexString.toString().replace('#', ''), rgbArray = [0,0,0,1];
for(var i=0; i<3; i++) rgbArray[i] = parseInt(hexColor.slice(i*2, (i+1)*2), 16) / 255;
return (hexColor.length > 4 ? rgbArray : null);
},
dashToCamel: function(str){
return str.replace(/\W+(.)/g, function (x, chr){return chr.toUpperCase()});
},
getCoords: function(str){
var coords = str.split(/[, ]/), vals = [];
for(var i = 0; i < coords.length; i = i + 2){
var x = parseInt(coords[i]), y = parseInt(coords[i+1]);
if(!isNaN(x) && !isNaN(y)) vals.push([x,y])
}
return vals
},
importSVG: function(fileName) {
var basePath = new File($.fileName).path;
var svgFile = File(basePath + "/" + fileName)
svgFile.encoding = "UTF-8"
if (svgFile.open('r')) {
svgText = svgFile.read();
svgFile.close();
return svgText
}
},
drawCircles: function(ctx,pts,r,fillColor){
for(var i = 0; i < pts.length; i++){
ctx.newPath()
ctx.ellipsePath(pts[i][0]-r/2,pts[i][1]-r/2,r,r);
ctx.fillPath(ctx.newBrush(ctx.BrushType.SOLID_COLOR, fillColor));
}
},
cmdLength: {a:7, c:6, h:1, l:2, m:2, q:4, s:4, t:2, v:1, z:0},
segPattern: /([astvzqmhlc])([^astvzqmhlc]*)/ig,
numPattern: /-?[0-9]*\.?[0-9]+(?:e[-+]?\d+)?/ig,
parseVals: function(args){
var nums = args.match(this.numPattern)
if(nums != null){
for(var n in nums) nums[n] = parseFloat(nums[n]);
}
return nums || []
},
attributeKeys: "x,y,x1,y1,x2,y2,cx,cy,width,height,r,rx,ry,points,fill,stroke,stroke-width,stroke-linejoin,stroke-linecap,stroke-miterlimit".split(","),
bez2:function(w0,w1,w2,t){
var t2=t*t, mt=1-t, mt2=mt*mt;
return w0*mt2 + w1*2*mt*t + w2*t2;
},
bez3:function(w0,w1,w2,w3,t){
var t2=t*t, t3=t2*t, mt=1-t, mt2=mt*mt, mt3=mt2*mt;
return w0*mt3 + w1*3*mt2*t + w2*3*mt*t2 + w3*t3;
},
lerp: function(a,b,t){
return (1-t) * a + t * b
},
midPoint: function(p1,p2){
return [ ( p1[0] + p2[0] ) / 2, ( p1[1] + p2[1] ) / 2]
}
}
SVG_STEPS = 24
function pathDataToArray(path) {
var data = []
path.replace(utils.segPattern, function(_, cmd, args){
var type = cmd.toLowerCase()
args = utils.parseVals(args)
if (type == 'm' && args.length > 2) {
data.push([cmd].concat(args.splice(0, 2)))
type = 'l'
cmd = cmd == 'm' ? 'l' : 'L'
}
while (true) {
if (args.length == utils.cmdLength[type]) {
args.unshift(cmd)
return data.push(args)
}
if (args.length < cmdLength[type]) throw new Error('malformed path data')
data.push([cmd].concat(args.splice(0, cmdLength[type])))
}
})
return data
}
$.write("kek")
function createUI(thisObj) {
//xmlString = importSVG("testi.svg")
mainPalette = (thisObj instanceof Panel) ? thisObj : new Window('palette', "Testihomma", [100, 100, 300, 300], {resizeable: true});
mainPalette.alignChildren = "fill";
var content = mainPalette.add('group');
content.orientation = 'row';
content.alignment = 'left';
content.svg = new XML(xmlString);
content.svgElements = content.svg.children();
content.minimumSize = [parseFloat(content.svg.@width),parseFloat(content.svg.@height)]
content.onDraw = function(){
var ctx = this.graphics
var items = this.svgElements;
for(var i = 0; i < items.length(); i++){
var a = {}
var item = this.svgElements[i];
for(var k = 0; k < utils.attributeKeys.length; k++){
var keyName = utils.attributeKeys[k]
var val = item.attribute(keyName).toString();
if(val[0] == "#" || val == "none") a[utils.dashToCamel(keyName)] = utils.hexToArray(val)
else a[utils.dashToCamel(keyName)] = isNaN(parseFloat(val)) ? val : parseFloat(val);
}
if (item.localName() == "polyline" || item.localName() == "polygon") a.points = utils.getCoords(item.@points);
var roundJoin = (a.strokeLinejoin == "round")
var roundCap = (a.strokeLinecap == "round")
var tagName = item.localName()
with(a){
switch(tagName) {
case "defs": break;
case "path":
var pathString = item.attribute("d").toString()
var pathData=pathDataToArray(pathString)
if(roundCap){
var cmd = pathData[0][0]
var vals = pathData[0].slice(1)
utils.drawCircles(ctx,[[vals[0],vals[1]]],strokeWidth,stroke)
}
for(var j = 0; j < pathData.length; j++){
var cmd = pathData[j][0]
var vals = pathData[j].slice(1)
switch(cmd){
case "M": ctx.newPath(); ctx.moveTo(vals[0],vals[1]);
break;
case "m":
break;
case "L": case "l":
var x = cmd == "l" ? ctx.currentPoint[0] + vals[0] : vals[0];
var y = cmd == "l" ? ctx.currentPoint[1] + vals[1] : vals[1];
ctx.lineTo(x,y)
break;
case "H": case "h":
var x = cmd == "h" ? ctx.currentPoint[0]+vals[0] : vals[0]
ctx.lineTo(x,ctx.currentPoint[1])
break;
case "V": case "v":
var y = cmd == "v" ? ctx.currentPoint[1]+vals[0] : vals[0]
ctx.lineTo(ctx.currentPoint[0], y)
break;
case "C": case "c": case "S": case "s":
var x0 = ctx.currentPoint[0]
var y0 = ctx.currentPoint[1]
var addX = cmd == cmd.toLowerCase() ? x0 : 0;
var addY = cmd == cmd.toLowerCase() ? y0 : 0;
var steps = SVG_STEPS;
var shiftIndex = 0
if(cmd == "S" || cmd == "s"){
var x1 = 2 * x0 - ctx.currentInTangent[0]
var y1 = 2 * y0 - ctx.currentInTangent[1]
shiftIndex = 2
}
else {
var x1 = vals[0] + addX
var y1 = vals[1] + addY
}
var x2 = vals[2-shiftIndex] + addX
var y2 = vals[3-shiftIndex] + addY
var x3 = vals[4-shiftIndex] + addX
var y3 = vals[5-shiftIndex] + addY
ctx.currentInTangent = [x2,y2]
var p0 = [x0,y0], p1 = [x1,y1], p2 = [x2,y2], p3 = [x3,y3];
var p4 = utils.midPoint(p0, p1);
var p5 = utils.midPoint(p1, p2);
var p6 = utils.midPoint(p2, p3);
var p7 = utils.midPoint(p4, p5);
var p8 = utils.midPoint(p5, p6);
var p9 = utils.midPoint(p7, p8);
// TODO: adaptive subdivision, let's try simple 50% split now
// as extreme handles require too many samples to appear correctly/smooth
var splitCurves = [ [p0, p4, p7, p9], [p9, p8, p6, p3] ]
for(var c=0;c < splitCurves.length;c++){
var curve = splitCurves[c]
for(var s=0;s<SVG_STEPS;s++){
var pos = s / SVG_STEPS;
var x = utils.bez3(curve[0][0],curve[1][0],curve[2][0],curve[3][0],pos)
var y = utils.bez3(curve[0][1],curve[1][1],curve[2][1],curve[3][1],pos)
ctx.lineTo(x,y)
}
}
break;
case "Z": case "z":
ctx.closePath();
break;
default:
break;
/*
TODO:
quadratic "Q" "q" smooth quadratic "T" "t" elliptical arc "A" "a"
*/
}
if(j == pathData.length-1){
var tempPath = ctx.currentPath
//utils.drawCircles(ctx,[ctx.currentPoint],strokeWidth,stroke)
ctx.currentPath = tempPath
}
}
break;
case "rect": ctx.newPath(); ctx.rectPath(x||0 , y||0, width, height); break;
case "circle":
case "ellipse": ctx.newPath(); ctx.ellipsePath(cx-(r||rx), cy-(r||ry), (r||rx)*2, (r||ry)*2); break;
case "line":
if(roundCap) utils.drawCircles(ctx, [[x1, y1],[x2,y2]], strokeWidth, stroke);
ctx.newPath();ctx.moveTo(x1,y1);ctx.lineTo(x2,y2);
break;
case "polyline":
case "polygon":
if(roundCap) utils.drawCircles(ctx, [points[0],points[points.length-1]], strokeWidth, stroke);
if(roundJoin){
utils.drawCircles(ctx,points.slice(1,-1),strokeWidth,stroke);
for(var j = 0; j < points.length; j++){
if(tagName == "polyline" && j == points.length - 1) break;
ctx.newPath();
ctx.moveTo(points[j][0],points[j][1]);
ctx.lineTo(points[ (j+1) % points.length ][0], points[ (j+1) % points.length ][1]);
ctx.strokePath(ctx.newPen(ctx.PenType.SOLID_COLOR, stroke, strokeWidth));
}
}
else {
ctx.newPath();
ctx.moveTo(points[0][0],points[0][1]);
for(var j = 1; j < points.length; j++) ctx.lineTo(points[j][0],points[j][1]);
if(tagName=="polygon")ctx.closePath();
}
default:
}
if(fill) ctx.fillPath(ctx.newBrush(ctx.BrushType.SOLID_COLOR, fill));
if(stroke) ctx.strokePath(ctx.newPen(ctx.PenType.SOLID_COLOR, stroke, strokeWidth));
}
}
}
mainPalette.layout.layout(true);
return mainPalette;
}
var testPanel = createUI(thisObj);
if (testPanel != null && testPanel instanceof Window) {
testPanel.show();
}
})(this);
@eeropic
Copy link
Author

eeropic commented Sep 2, 2021

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment