aboutsummaryrefslogtreecommitdiff
path: root/assets
diff options
context:
space:
mode:
Diffstat (limited to 'assets')
-rw-r--r--assets/canvas-runtime.js201
1 files changed, 200 insertions, 1 deletions
diff --git a/assets/canvas-runtime.js b/assets/canvas-runtime.js
index 3ed7ed0f..dab1c444 100644
--- a/assets/canvas-runtime.js
+++ b/assets/canvas-runtime.js
@@ -382,6 +382,12 @@ NinjaCvsRt.GLRuntime = Object.create(Object.prototype, {
382 obj = Object.create(NinjaCvsRt.RuntimeSubPath, {_materials: { value:[], writable:true}}); 382 obj = Object.create(NinjaCvsRt.RuntimeSubPath, {_materials: { value:[], writable:true}});
383 obj.importJSON (jObj ); 383 obj.importJSON (jObj );
384 break; 384 break;
385
386 case 6: //brushstroke (created by brush tool)
387 obj = Object.create(NinjaCvsRt.RuntimeBrushStroke, {_materials: { value:[], writable:true}});
388 obj.importJSON (jObj );
389 break;
390
385 default: 391 default:
386 throw new Error( "Attempting to load unrecognized object type: " + type ); 392 throw new Error( "Attempting to load unrecognized object type: " + type );
387 break; 393 break;
@@ -524,6 +530,7 @@ NinjaCvsRt.RuntimeGeomObj = Object.create(Object.prototype, {
524 GEOM_TYPE_LINE: { value: 3, writable: false }, 530 GEOM_TYPE_LINE: { value: 3, writable: false },
525 GEOM_TYPE_PATH: { value: 4, writable: false }, 531 GEOM_TYPE_PATH: { value: 4, writable: false },
526 GEOM_TYPE_CUBIC_BEZIER: { value: 5, writable: false }, 532 GEOM_TYPE_CUBIC_BEZIER: { value: 5, writable: false },
533 GEOM_TYPE_BRUSH_STROKE: { value: 6, writable: false },
527 GEOM_TYPE_UNDEFINED: { value: -1, writable: false }, 534 GEOM_TYPE_UNDEFINED: { value: -1, writable: false },
528 535
529 /////////////////////////////////////////////////////////////////////// 536 ///////////////////////////////////////////////////////////////////////
@@ -2006,8 +2013,200 @@ NinjaCvsRt.RuntimeSubPath = Object.create(NinjaCvsRt.RuntimeGeomObj, {
2006 ctx.restore(); 2013 ctx.restore();
2007 } 2014 }
2008 } 2015 }
2009});// ************************************************************************** 2016});
2017
2018// **************************************************************************
2010// END runtime for the pen tool path 2019// END runtime for the pen tool path
2011// ************************************************************************** 2020// **************************************************************************
2012 2021
2013 2022
2023// ***************************************************************************
2024// runtime for brush tool brush stroke
2025// ***************************************************************************
2026
2027NinjaCvsRt.RuntimeBrushStroke = Object.create(NinjaCvsRt.RuntimeGeomObj, {
2028 // array of brush stroke points
2029 _LocalPoints: { value: null, writable: true },
2030 _OrigLocalPoints: {value: null, writable: true},
2031
2032 _strokeWidth: {value: 0, writable: true},
2033 _strokeColor: {value: null, writable: true},
2034 _strokeWidth: {value: 0, writable: true},
2035 _strokeColor: {value: 0, writable: true},
2036 _strokeHardness: {value: 0, writable: true},
2037 _strokeUseCalligraphic : {value: 0, writable: true},
2038 _strokeAngle : {value: 0, writable: true},
2039
2040 //stroke smoothing properties
2041 _strokeDoSmoothing: {value: 0, writable: true},
2042 _strokeAmountSmoothing : {value: 0, writable: true},
2043
2044 geomType: {
2045 value: function () {
2046 return this.GEOM_TYPE_BRUSH_STROKE;
2047 }
2048 },
2049
2050 _copyCoordinates3D: {
2051 value: function(srcCoord, destCoord){
2052 var i=0;
2053 var numPoints = srcCoord.length;
2054 for (i=0;i<numPoints;i++){
2055 destCoord[i] = [srcCoord[i][0],srcCoord[i][1],srcCoord[i][2]];
2056 }
2057 }
2058 },
2059
2060 _doSmoothing: {
2061 value: function() {
2062 var numPoints = this._LocalPoints.length;
2063 if (this._strokeDoSmoothing && numPoints>1) {
2064 this._copyCoordinates3D(this._OrigLocalPoints, this._LocalPoints);
2065 //iterations of Laplacian smoothing (setting the points to the average of their neighbors)
2066 var numLaplacianIterations = this._strokeAmountSmoothing;
2067 for (var n=0;n<numLaplacianIterations;n++){
2068 var newPoints = this._LocalPoints.slice(0); //I think this performs a copy by reference, which would make the following a SOR step
2069 for (var i=1;i<numPoints-1;i++) {
2070 var avgPos = [ 0.5*(this._LocalPoints[i-1][0] + this._LocalPoints[i+1][0]),
2071 0.5*(this._LocalPoints[i-1][1] + this._LocalPoints[i+1][1]),
2072 0.5*(this._LocalPoints[i-1][2] + this._LocalPoints[i+1][2])] ;
2073 newPoints[i] = avgPos;
2074 }
2075 this._LocalPoints = newPoints.slice(0);
2076 }
2077 }
2078 }
2079 },
2080
2081 importJSON: {
2082 value: function(jo) {
2083 if (this.geomType()!== jo.geomType){
2084 return;
2085 }
2086 //the geometry for this object
2087 this._LocalPoints = jo.localPoints.slice(0);
2088 this._OrigLocalPoints = jo.origLocalPoints.slice(0);
2089 this._copyCoordinates3D(jo.localPoints, this._LocalPoints); //todo is this necessary in addition to the slice(0) above?
2090 this._copyCoordinates3D(jo.origLocalPoints, this._OrigLocalPoints); //todo <ditto>
2091
2092 //stroke appearance properties
2093 this._strokeWidth = jo.strokeWidth;
2094 this._strokeColor = jo.strokeColor;
2095 this._strokeHardness = jo.strokeHardness;
2096 this._strokeUseCalligraphic = jo.strokeUseCalligraphic;
2097 this._strokeAngle = jo.strokeAngle;
2098
2099 //stroke smoothing properties
2100 this._strokeDoSmoothing = jo.strokeDoSmoothing;
2101 this._strokeAmountSmoothing = jo.strokeAmountSmoothing;
2102
2103 this._doSmoothing(); //after smoothing, the stroke is ready to be rendered
2104 }
2105 },
2106
2107 render: {
2108 value: function() {
2109 // get the world
2110 var world = this.getWorld();
2111 if (!world) {
2112 throw( "null world in brush stroke render" );
2113 return;
2114 }
2115
2116 // get the context
2117 var ctx = world.get2DContext();
2118 if (!ctx) {
2119 throw( "null world in brush stroke render" );
2120 return;
2121 }
2122
2123 ctx.save();
2124
2125 //**** BEGIN RENDER CODE BLOCK ****
2126 var points = this._LocalPoints;
2127 var numPoints = points.length;
2128 var tempP, p;
2129 if (this._strokeUseCalligraphic) {
2130 //build the stamp for the brush stroke
2131 var t=0;
2132 var numTraces = this._strokeWidth;
2133 var halfNumTraces = numTraces*0.5;
2134 var opaqueRegionHalfWidth = 0.5*this._strokeHardness*numTraces*0.01; //the 0.01 is to convert the strokeHardness from [0,100] to [0,1]
2135 var maxTransparentRegionHalfWidth = halfNumTraces-opaqueRegionHalfWidth;
2136
2137 //build an angled (calligraphic) brush stamp
2138 var deltaDisplacement = [Math.cos(this._strokeAngle),Math.sin(this._strokeAngle)];
2139 deltaDisplacement = VecUtils.vecNormalize(2, deltaDisplacement, 1);
2140 var startPos = [-halfNumTraces*deltaDisplacement[0],-halfNumTraces*deltaDisplacement[1]];
2141
2142 var brushStamp = [];
2143 for (t=0;t<numTraces;t++){
2144 var brushPt = [startPos[0]+t*deltaDisplacement[0], startPos[1]+t*deltaDisplacement[1]];
2145 brushStamp.push(brushPt);
2146 }
2147
2148 ctx.lineJoin="bevel";
2149 ctx.lineCap="butt";
2150 ctx.globalCompositeOperation = 'source-over';
2151 ctx.globalAlpha = this._strokeColor[3];
2152
2153 for (t=0;t<numTraces;t++){
2154 var disp = [brushStamp[t][0], brushStamp[t][1]];
2155 var alphaVal = 1.0;
2156 var distFromOpaqueRegion = Math.abs(t-halfNumTraces) - opaqueRegionHalfWidth;
2157 if (distFromOpaqueRegion>0) {
2158 var transparencyFactor = distFromOpaqueRegion/maxTransparentRegionHalfWidth;
2159 alphaVal = 1.0 - transparencyFactor;//(transparencyFactor*transparencyFactor);//the square term produces nonlinearly varying alpha values
2160 alphaVal *= 0.5; //factor that accounts for lineWidth == 2
2161 }
2162 ctx.save();
2163 if (t === (numTraces-1) || t === 0){
2164 ctx.lineWidth = 1;
2165 } else {
2166 ctx.lineWidth=2;
2167 }
2168 ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")";
2169 ctx.translate(disp[0],disp[1]);
2170 ctx.beginPath();
2171 p = points[0];
2172 ctx.moveTo(p[0],p[1]);
2173 for (var i=0;i<numPoints;i++){
2174 p = points[i];
2175 ctx.lineTo(p[0],p[1]);
2176 }
2177 ctx.stroke();
2178 ctx.restore();
2179 }
2180 } else {
2181 ctx.globalCompositeOperation = 'lighter'; //we wish to add up the colors
2182 ctx.globalAlpha = this._strokeColor[3];
2183 ctx.lineCap = "round";
2184 ctx.lineJoin="round";
2185 var minStrokeWidth = (this._strokeHardness*this._strokeWidth)/100; //the hardness is the percentage of the stroke width that's fully opaque
2186 var numlayers = 1 + Math.ceil((this._strokeWidth-minStrokeWidth)*0.5);
2187 var alphaVal = 1.0/(numlayers); //this way the alpha at the first path will be 1
2188 ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")";
2189 for (var l=0;l<numlayers;l++){
2190 ctx.beginPath();
2191 p = points[0];
2192 ctx.moveTo(p[0],p[1]);
2193 if (numPoints===1){
2194 //display a tiny segment as a single point
2195 ctx.lineTo(p[0],p[1]+0.01);
2196 }
2197 for (var i=1;i<numPoints;i++){
2198 p = points[i];
2199 ctx.lineTo(p[0],p[1]);
2200 }
2201 ctx.lineWidth=2*l+minStrokeWidth;
2202 ctx.stroke();
2203 }//for every layer l
2204 } //if there is no calligraphic stroke
2205
2206 //**** END RENDER CODE BLOCK ****
2207
2208 ctx.restore();
2209 }
2210 }
2211});
2212