aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--assets/canvas-runtime.js250
-rwxr-xr-xjs/data/pi/pi-data.js10
-rwxr-xr-xjs/lib/geom/brush-stroke.js1423
-rw-r--r--js/tools/BrushTool.js2
4 files changed, 1001 insertions, 684 deletions
diff --git a/assets/canvas-runtime.js b/assets/canvas-runtime.js
index 4fb0a327..fe5f839c 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 ///////////////////////////////////////////////////////////////////////
@@ -2042,8 +2049,251 @@ NinjaCvsRt.RuntimeSubPath = Object.create(NinjaCvsRt.RuntimeGeomObj, {
2042 } 2049 }
2043 } 2050 }
2044}); 2051});
2052
2045// ************************************************************************** 2053// **************************************************************************
2046// END runtime for the pen tool path 2054// END runtime for the pen tool path
2047// ************************************************************************** 2055// **************************************************************************
2048 2056
2049 2057
2058// ***************************************************************************
2059// runtime for brush tool brush stroke
2060// ***************************************************************************
2061
2062NinjaCvsRt.RuntimeBrushStroke = Object.create(NinjaCvsRt.RuntimeGeomObj, {
2063 // array of brush stroke points
2064 _LocalPoints: { value: null, writable: true },
2065 _OrigLocalPoints: {value: null, writable: true},
2066
2067 _strokeWidth: {value: 0, writable: true},
2068 _strokeColor: {value: 0, writable: true},
2069 _strokeHardness: {value: 0, writable: true},
2070 _strokeUseCalligraphic : {value: 0, writable: true},
2071 _strokeAngle : {value: 0, writable: true},
2072
2073 //stroke smoothing properties
2074 _strokeDoSmoothing: {value: 0, writable: true},
2075 _strokeAmountSmoothing : {value: 0, writable: true},
2076
2077 geomType: {
2078 value: function () {
2079 return this.GEOM_TYPE_BRUSH_STROKE;
2080 }
2081 },
2082
2083 _copyCoordinates3D: {
2084 value: function(srcCoord, destCoord){
2085 var i=0;
2086 var numPoints = srcCoord.length;
2087 for (i=0;i<numPoints;i++){
2088 destCoord[i] = [srcCoord[i][0],srcCoord[i][1],srcCoord[i][2]];
2089 }
2090 }
2091 },
2092
2093 _doSmoothing: {
2094 value: function() {
2095 var numPoints = this._LocalPoints.length;
2096 if (this._strokeDoSmoothing && numPoints>1) {
2097 this._copyCoordinates3D(this._OrigLocalPoints, this._LocalPoints);
2098 //iterations of Laplacian smoothing (setting the points to the average of their neighbors)
2099 var numLaplacianIterations = this._strokeAmountSmoothing;
2100 for (var n=0;n<numLaplacianIterations;n++){
2101 var newPoints = this._LocalPoints.slice(0); //I think this performs a copy by reference, which would make the following a SOR step
2102 for (var i=1;i<numPoints-1;i++) {
2103 var avgPos = [ 0.5*(this._LocalPoints[i-1][0] + this._LocalPoints[i+1][0]),
2104 0.5*(this._LocalPoints[i-1][1] + this._LocalPoints[i+1][1]),
2105 0.5*(this._LocalPoints[i-1][2] + this._LocalPoints[i+1][2])] ;
2106 newPoints[i] = avgPos;
2107 }
2108 this._LocalPoints = newPoints.slice(0);
2109 }
2110 }
2111 }
2112 },
2113
2114 importJSON: {
2115 value: function(jo) {
2116 if (this.geomType()!== jo.geomType){
2117 return;
2118 }
2119 //the geometry for this object
2120 this._LocalPoints = jo.localPoints.slice(0);
2121 this._OrigLocalPoints = jo.origLocalPoints.slice(0);
2122 this._copyCoordinates3D(jo.localPoints, this._LocalPoints); //todo is this necessary in addition to the slice(0) above?
2123 this._copyCoordinates3D(jo.origLocalPoints, this._OrigLocalPoints); //todo <ditto>
2124
2125 //stroke appearance properties
2126 this._strokeWidth = jo.strokeWidth;
2127 this._strokeColor = jo.strokeColor;
2128 this._strokeHardness = jo.strokeHardness;
2129 this._strokeUseCalligraphic = jo.strokeUseCalligraphic;
2130 this._strokeAngle = jo.strokeAngle;
2131
2132 //stroke smoothing properties
2133 this._strokeDoSmoothing = jo.strokeDoSmoothing;
2134 this._strokeAmountSmoothing = jo.strokeAmountSmoothing;
2135
2136 this._doSmoothing(); //after smoothing, the stroke is ready to be rendered
2137 }
2138 },
2139
2140 //buildColor returns the fillStyle or strokeStyle for the Canvas 2D context
2141 buildColor: {
2142 value: function(ctx, //the 2D rendering context (for creating gradients if necessary)
2143 ipColor, //color string, also encodes whether there's a gradient and of what type
2144 w, //width of the region of color
2145 h, //height of the region of color
2146 lw) //linewidth (i.e. stroke width/size)
2147 {
2148 if (ipColor.gradientMode){
2149 var position, gradient, cs, inset; //vars used in gradient calculations
2150 inset = Math.ceil( lw ) - 0.5;
2151
2152 if(ipColor.gradientMode === "radial") {
2153 var ww = w - 2*lw, hh = h - 2*lw;
2154 gradient = ctx.createRadialGradient(w/2, h/2, 0, w/2, h/2, Math.max(ww, hh)/2);
2155 } else {
2156 gradient = ctx.createLinearGradient(inset, h/2, w-inset, h/2);
2157 }
2158 var colors = ipColor.color;
2159
2160 var len = colors.length;
2161 for(n=0; n<len; n++) {
2162 position = colors[n].position/100;
2163 cs = colors[n].value;
2164 gradient.addColorStop(position, "rgba(" + cs.r + "," + cs.g + "," + cs.b + "," + cs.a + ")");
2165 }
2166 return gradient;
2167 } else {
2168 var c = "rgba(" + 255*ipColor[0] + "," + 255*ipColor[1] + "," + 255*ipColor[2] + "," + ipColor[3] + ")";
2169 return c;
2170 }
2171 }
2172 },
2173
2174 render: {
2175 value: function() {
2176 //vars for gradient code
2177 var w,h,useBuildColor=false;
2178
2179 // get the world
2180 var world = this.getWorld();
2181 if (!world) {
2182 throw( "null world in brush stroke render" );
2183 return;
2184 } else {
2185
2186 if (this._strokeColor.gradientMode){
2187 useBuildColor = true;
2188 }
2189 //vars used for the gradient computation in buildColor
2190 w = world.getViewportWidth();
2191 h = world.getViewportHeight();
2192 }
2193 // get the context
2194 var ctx = world.get2DContext();
2195 if (!ctx) {
2196 throw( "null world in brush stroke render" );
2197 return;
2198 }
2199
2200 ctx.save();
2201
2202 //**** BEGIN RENDER CODE BLOCK ****
2203 var points = this._LocalPoints;
2204 var numPoints = points.length;
2205 var tempP, p;
2206 if (this._strokeUseCalligraphic) {
2207 //build the stamp for the brush stroke
2208 var t=0;
2209 var numTraces = this._strokeWidth;
2210 var halfNumTraces = numTraces*0.5;
2211 var opaqueRegionHalfWidth = 0.5*this._strokeHardness*numTraces*0.01; //the 0.01 is to convert the strokeHardness from [0,100] to [0,1]
2212 var maxTransparentRegionHalfWidth = halfNumTraces-opaqueRegionHalfWidth;
2213
2214 //build an angled (calligraphic) brush stamp
2215 var deltaDisplacement = [Math.cos(this._strokeAngle),Math.sin(this._strokeAngle)];
2216 deltaDisplacement = this.vecNormalize(2, deltaDisplacement, 1);
2217 var startPos = [-halfNumTraces*deltaDisplacement[0],-halfNumTraces*deltaDisplacement[1]];
2218
2219 var brushStamp = [];
2220 for (t=0;t<numTraces;t++){
2221 var brushPt = [startPos[0]+t*deltaDisplacement[0], startPos[1]+t*deltaDisplacement[1]];
2222 brushStamp.push(brushPt);
2223 }
2224
2225 ctx.lineJoin="bevel";
2226 ctx.lineCap="butt";
2227 ctx.globalCompositeOperation = 'source-over';
2228 ctx.globalAlpha = this._strokeColor[3];
2229
2230 for (t=0;t<numTraces;t++){
2231 var disp = [brushStamp[t][0], brushStamp[t][1]];
2232 var alphaVal = 1.0;
2233 var distFromOpaqueRegion = Math.abs(t-halfNumTraces) - opaqueRegionHalfWidth;
2234 if (distFromOpaqueRegion>0) {
2235 var transparencyFactor = distFromOpaqueRegion/maxTransparentRegionHalfWidth;
2236 alphaVal = 1.0 - transparencyFactor;//(transparencyFactor*transparencyFactor);//the square term produces nonlinearly varying alpha values
2237 alphaVal *= 0.5; //factor that accounts for lineWidth == 2
2238 }
2239 ctx.save();
2240 if (t === (numTraces-1) || t === 0){
2241 ctx.lineWidth = 1;
2242 } else {
2243 ctx.lineWidth=2;
2244 }
2245 if (!useBuildColor){
2246 ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")";
2247 } else {
2248 ctx.strokeStyle = this.buildColor(ctx, this._strokeColor, w, h, this._strokeWidth, alphaVal);
2249 }
2250 ctx.translate(disp[0],disp[1]);
</