From d7d78d4a4e8cf82c56379d25efbe679b3b823abc Mon Sep 17 00:00:00 2001 From: Pushkar Joshi Date: Thu, 1 Mar 2012 11:22:46 -0800 Subject: show feedback on mouseover on a part of the path where a new anchor will be added if clicked --- images/cursors/penAdd.png | Bin 0 -> 3043 bytes js/helper-classes/RDGE/GLSubpath.js | 147 ++++++++++++++++++++++++++++++------ js/tools/PenTool.js | 12 ++- 3 files changed, 135 insertions(+), 24 deletions(-) create mode 100644 images/cursors/penAdd.png diff --git a/images/cursors/penAdd.png b/images/cursors/penAdd.png new file mode 100644 index 00000000..c306cc85 Binary files /dev/null and b/images/cursors/penAdd.png differ diff --git a/js/helper-classes/RDGE/GLSubpath.js b/js/helper-classes/RDGE/GLSubpath.js index f3d8ad36..2fb91d33 100755 --- a/js/helper-classes/RDGE/GLSubpath.js +++ b/js/helper-classes/RDGE/GLSubpath.js @@ -505,7 +505,6 @@ GLSubpath.prototype._isWithinBoundingBox = function(point, ctrlPts, radius) { GLSubpath.prototype.pickAnchor = function (pickX, pickY, pickZ, radius) { var numAnchors = this._Anchors.length; var selAnchorIndex = -1; - var retCode = this.SEL_NONE; var radSq = radius * radius; var minDistance = Infinity; //check if the clicked location is close to the currently selected anchor position @@ -515,9 +514,23 @@ GLSubpath.prototype.pickAnchor = function (pickX, pickY, pickZ, radius) { if (distSq < minDistance && distSq < radSq) { selAnchorIndex = this._selectedAnchorIndex; minDistance = distSq; - retCode = retCode | this.SEL_ANCHOR; + } else { + //check the prev and next of the selected anchor if the above did not register a hit + distSq = this._Anchors[this._selectedAnchorIndex].getPrevDistanceSq(pickX, pickY, pickZ); + if (distSq < minDistance && distSq < radSq){ + selAnchorIndex = this._selectedAnchorIndex; + minDistance = distSq; + } else { + //check the next for this anchor point + distSq = this._Anchors[this._selectedAnchorIndex].getNextDistanceSq(pickX, pickY, pickZ); + if (distSq<minDistance && distSq<radSq){ + selAnchorIndex = this._selectedAnchorIndex; + minDistance = distSq; + } + } } } + //now check if the click location is close to any anchor position if (selAnchorIndex===-1) { for (var i = 0; i < numAnchors; i++) { @@ -532,12 +545,97 @@ GLSubpath.prototype.pickAnchor = function (pickX, pickY, pickZ, radius) { return selAnchorIndex; } +GLSubpath.prototype.isWithinBBox =function(x,y,z){ + if (this._BBoxMin[0]>x || this._BBoxMin[1]>y || this._BBoxMin[2]>z){ + return false; + } + if (this._BBoxMax[0]<x || this._BBoxMax[1]<y || this._BBoxMax[2]<z){ + return false; + } + return true; +} + +//pick the path point closest to the specified location, return null if some anchor point (or its handles) is within radius, else return the parameter distance +GLSubpath.prototype.pathHitTest = function (pickX, pickY, pickZ, radius) { + var numAnchors = this._Anchors.length; + var selAnchorIndex = -1; + var retParam = null; + var radSq = radius * radius; + var minDistance = Infinity; + + //check if the location is close to the currently selected anchor position + if (this._selectedAnchorIndex>=0 && this._selectedAnchorIndex<this._Anchors.length){ + var distSq = this._Anchors[this._selectedAnchorIndex].getDistanceSq(pickX, pickY, pickZ); + //check the anchor point + if (distSq < minDistance && distSq < radSq) { + selAnchorIndex = this._selectedAnchorIndex; + minDistance = distSq; + } + } + //check the prev and next of the selected anchor if the above did not register a hit + if (this._selectedAnchorIndex>=0 && selAnchorIndex === -1) { + var distSq = this._Anchors[this._selectedAnchorIndex].getPrevDistanceSq(pickX, pickY, pickZ); + if (distSq < minDistance && distSq < radSq){ + selAnchorIndex = this._selectedAnchorIndex; + minDistance = distSq; + } else { + //check the next for this anchor point + distSq = this._Anchors[this._selectedAnchorIndex].getNextDistanceSq(pickX, pickY, pickZ); + if (distSq<minDistance && distSq<radSq){ + selAnchorIndex = this._selectedAnchorIndex; + minDistance = distSq; + } + } + } + + //now check if the location is close to any anchor position + if (selAnchorIndex===-1) { + for (var i = 0; i < numAnchors; i++) { + var distSq = this._Anchors[i].getDistanceSq(pickX, pickY, pickZ); + //check the anchor point + if (distSq < minDistance && distSq < radSq) { + selAnchorIndex = i; + minDistance = distSq; + } + }//for every anchor i + } + + //finally check if the location is close to the curve itself + if (selAnchorIndex===-1) { + //first check if the input location is within the bounding box + if (this.isWithinBBox(pickX,pickY,pickZ)){ + var numSegments = this._isClosed ? numAnchors : numAnchors-1; + for (var i = 0; i < numSegments; i++) { + var nextIndex = (i+1)%numAnchors; + //check if the point is close to the bezier segment between anchor i and anchor nextIndex + var controlPoints = Vector.create([Vector.create([this._Anchors[i].getPosX(),this._Anchors[i].getPosY(),this._Anchors[i].getPosZ()]), + Vector.create([this._Anchors[i].getNextX(),this._Anchors[i].getNextY(),this._Anchors[i].getNextZ()]), + Vector.create([this._Anchors[nextIndex].getPrevX(),this._Anchors[nextIndex].getPrevY(),this._Anchors[nextIndex].getPrevZ()]), + Vector.create([this._Anchors[nextIndex].getPosX(),this._Anchors[nextIndex].getPosY(),this._Anchors[nextIndex].getPosZ()])]); + var point = Vector.create([pickX, pickY, pickZ]); + if (this._isWithinBoundingBox(point, controlPoints, radius)) { + //var intersectParam = this._checkIntersection(controlPoints, 0.0, 1.0, point, radius); + var intersectParam = this._checkIntersectionWithSamples(this._anchorSampleIndex[i], this._anchorSampleIndex[nextIndex], point, radius); + console.log("intersectParam:"+intersectParam); + if (intersectParam){ + selAnchorIndex=i; + retParam = intersectParam-i; //make the retParam go from 0 to 1 + break; + } + } + }//for every anchor i + }//if is within bbox + } + return [selAnchorIndex,retParam]; +} //GLSubpath.pathHitTest function + //pick the path point closest to the specified location, return null if some anchor point (or its handles) is within radius, else return the parameter distance GLSubpath.prototype.pickPath = function (pickX, pickY, pickZ, radius) { var numAnchors = this._Anchors.length; var selAnchorIndex = -1; var retCode = this.SEL_NONE; + var retParam = null; var radSq = radius * radius; var minDistance = Infinity; //check if the clicked location is close to the currently selected anchor position @@ -580,33 +678,36 @@ GLSubpath.prototype.pickPath = function (pickX, pickY, pickZ, radius) { } } } - var retParam = null; + if (retCode !== this.SEL_NONE) { retCode = retCode | this.SEL_PATH; //ensure that path is also selected if anything else is selected this._selectedAnchorIndex = selAnchorIndex; } else { this._selectedAnchorIndex = -1; - var numSegments = this._isClosed ? numAnchors : numAnchors-1; - for (var i = 0; i < numSegments; i++) { - var nextIndex = (i+1)%numAnchors; - //check if the point is close to the bezier segment between anchor i and anchor nextIndex - var controlPoints = Vector.create([Vector.create([this._Anchors[i].getPosX(),this._Anchors[i].getPosY(),this._Anchors[i].getPosZ()]), - Vector.create([this._Anchors[i].getNextX(),this._Anchors[i].getNextY(),this._Anchors[i].getNextZ()]), - Vector.create([this._Anchors[nextIndex].getPrevX(),this._Anchors[nextIndex].getPrevY(),this._Anchors[nextIndex].getPrevZ()]), - Vector.create([this._Anchors[nextIndex].getPosX(),this._Anchors[nextIndex].getPosY(),this._Anchors[nextIndex].getPosZ()])]); - var point = Vector.create([pickX, pickY, pickZ]); - if (this._isWithinBoundingBox(point, controlPoints, radius)) { - //var intersectParam = this._checkIntersection(controlPoints, 0.0, 1.0, point, radius); - var intersectParam = this._checkIntersectionWithSamples(this._anchorSampleIndex[i], this._anchorSampleIndex[nextIndex], point, radius); - console.log("intersectParam:"+intersectParam); - if (intersectParam){ - retCode = retCode | this.SEL_PATH; - retParam = intersectParam-i; //make the retParam go from 0 to 1 - this._selectedAnchorIndex = i; - break; + //first check if the input location is within the bounding box + if (this.isWithinBBox(pickX,pickY,pickZ)){ + var numSegments = this._isClosed ? numAnchors : numAnchors-1; + for (var i = 0; i < numSegments; i++) { + var nextIndex = (i+1)%numAnchors; + //check if the point is close to the bezier segment between anchor i and anchor nextIndex + var controlPoints = Vector.create([Vector.create([this._Anchors[i].getPosX(),this._Anchors[i].getPosY(),this._Anchors[i].getPosZ()]), + Vector.create([this._Anchors[i].getNextX(),this._Anchors[i].getNextY(),this._Anchors[i].getNextZ()]), + Vector.create([this._Anchors[nextIndex].getPrevX(),this._Anchors[nextIndex].getPrevY(),this._Anchors[nextIndex].getPrevZ()]), + Vector.create([this._Anchors[nextIndex].getPosX(),this._Anchors[nextIndex].getPosY(),this._Anchors[nextIndex].getPosZ()])]); + var point = Vector.create([pickX, pickY, pickZ]); + if (this._isWithinBoundingBox(point, controlPoints, radius)) { + //var intersectParam = this._checkIntersection(controlPoints, 0.0, 1.0, point, radius); + var intersectParam = this._checkIntersectionWithSamples(this._anchorSampleIndex[i], this._anchorSampleIndex[nextIndex], point, radius); + console.log("intersectParam:"+intersectParam); + if (intersectParam){ + retCode = retCode | this.SEL_PATH; + retParam = intersectParam-i; //make the retParam go from 0 to 1 + this._selectedAnchorIndex = i; + break; + } } - } - }//for every anchor i + }//for every anchor i + }//is within bounding box } this._selectMode = retCode; return retParam; diff --git a/js/tools/PenTool.js b/js/tools/PenTool.js index ddc8bc04..02367328 100755 --- a/js/tools/PenTool.js +++ b/js/tools/PenTool.js @@ -275,6 +275,9 @@ exports.PenTool = Montage.create(ShapeTool, { this.application.ninja.stage.clearDrawingCanvas(); this._hoveredAnchorIndex = -1; + //set the cursor to be the default cursor + this.application.ninja.stage.drawingCanvas.style.cursor = "auto"; + if (this._isDrawing) { var point = webkitConvertPointFromPageToNode(this.application.ninja.stage.canvas, new WebKitPoint(event.pageX, event.pageY)); //go through the drawing toolbase to get the position of the mouse @@ -345,6 +348,14 @@ exports.PenTool = Montage.create(ShapeTool, { var selAnchor = this._selectedSubpath.pickAnchor(currMousePos[0], currMousePos[1], currMousePos[2], this._PICK_POINT_RADIUS); if (selAnchor >=0) { this._hoveredAnchorIndex = selAnchor; + } else { + //detect if the current mouse position will hit the path + var pathHitTestData = this._selectedSubpath.pathHitTest(currMousePos[0], currMousePos[1], currMousePos[2], this._PICK_POINT_RADIUS); + if (pathHitTestData[0]!==-1){ + //change the cursor + var cursor = "url('images/cursors/penAdd.png') 10 10,default"; + this.application.ninja.stage.drawingCanvas.style.cursor = cursor; + } } } } //else of if (this._isDrawing) { @@ -353,7 +364,6 @@ exports.PenTool = Montage.create(ShapeTool, { if (this._selectedSubpath){ this.DrawSubpathAnchors(this._selectedSubpath); } - }//value: function(event) },//HandleMouseMove -- cgit v1.2.3 From 0b7f2f54738d2c1ea480b9bac7d3a750b1ef4df6 Mon Sep 17 00:00:00 2001 From: Pushkar Joshi Date: Thu, 1 Mar 2012 15:29:17 -0800 Subject: first attempt at rendering the brush stroke as a collection of polylines (number of polylines equals the stroke width) --- js/helper-classes/RDGE/GLBrushStroke.js | 47 +++++++++++++++++++++++++++++++-- js/tools/BrushTool.js | 4 +-- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/js/helper-classes/RDGE/GLBrushStroke.js b/js/helper-classes/RDGE/GLBrushStroke.js index 5d773c2d..f5b0bce1 100755 --- a/js/helper-classes/RDGE/GLBrushStroke.js +++ b/js/helper-classes/RDGE/GLBrushStroke.js @@ -139,7 +139,8 @@ function GLBrushStroke() { var numPoints = this._Points.length; //**** add samples to the path if needed...linear interpolation for now - if (numPoints>1) { + //if (numPoints>1) { + if (0){ var threshold = this._WETNESS_FACTOR*this._strokeWidth; var prevPt = this._Points[0]; var prevIndex = 0; @@ -331,7 +332,7 @@ function GLBrushStroke() { } */ - + /* var R2 = this._strokeWidth; var R = R2*0.5; var hardness = 0; //for a pencil, this is always 1 //TODO get hardness parameter from user interface @@ -359,6 +360,48 @@ function GLBrushStroke() { //ctx.globalCompositeOperation = 'source-in'; //ctx.rect(x-R, y-R, R2, R2); } + */ + + //todo test how to render the path as a bunch of moveTo and lineTos (for calligraphic brush styles) + var numTraces = this._strokeWidth; + var halfNumTraces = numTraces/2; + var deltaDisplacement = [1,0];//[this._strokeWidth/numTraces, 0]; //a horizontal line brush + var startPos = [-this._strokeWidth/2,0]; + for (var t=0;t<numTraces;t++){ + var disp = [startPos[0]+t*deltaDisplacement[0], startPos[1]+t*deltaDisplacement[1]]; + //ctx.globalCompositeOperation = 'source-over'; + var distFromMiddle = Math.abs(halfNumTraces-t); + var alphaVal = 1.0 - (distFromMiddle/halfNumTraces); + ctx.save(); + ctx.lineWidth=this._strokeWidth/10;//4; + if (ctx.lineWidth<2) + ctx.lineWidth=2; + ctx.lineJoin="bevel"; + ctx.lineCap="butt"; + if (t<numTraces/2) + ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")"; + else + ctx.strokeStyle="rgba("+255+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")"; + ctx.translate(disp[0],disp[1]); + ctx.beginPath(); + ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]); + for (var i=0;i<numPoints;i++){ + ctx.lineTo(this._Points[i][0]-bboxMin[0], this._Points[i][1]-bboxMin[1]); + } + ctx.stroke(); + ctx.restore(); + } + + /* + ctx.beginPath(); + ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]); + for (var i=1;i<numPoints;i++){ + ctx.lineTo(this._Points[i][0]-bboxMin[0], this._Points[i][1]-bboxMin[1]); + } + ctx.lineWidth=1.0; + ctx.strokeStyle = "black"; + ctx.stroke(); + */ ctx.restore(); } //render() diff --git a/js/tools/BrushTool.js b/js/tools/BrushTool.js index fec89eb2..dc9e022e 100644 --- a/js/tools/BrushTool.js +++ b/js/tools/BrushTool.js @@ -72,7 +72,7 @@ exports.BrushTool = Montage.create(ShapeTool, { this._selectedBrushStroke.addPoint(currMousePos); //TODO get these values from the options - this._selectedBrushStroke.setStrokeWidth(20); + this._selectedBrushStroke.setStrokeWidth(10); } NJevent("enableStageMove");//stageManagerModule.stageManager.enableMouseMove(); } //value: function (event) { @@ -112,7 +112,7 @@ exports.BrushTool = Montage.create(ShapeTool, { if (this._isDrawing) { var currMousePos = this._getUnsnappedPosition(event.pageX, event.pageY); - if (this._selectedBrushStroke && this._selectedBrushStroke.getNumPoints()<100){ + if (this._selectedBrushStroke && this._selectedBrushStroke.getNumPoints()<1000){ this._selectedBrushStroke.addPoint(currMousePos); } this.ShowCurrentBrushStrokeOnStage(); -- cgit v1.2.3 From 1b68bb87c458877cb850a96d8a093d6064bc41dc Mon Sep 17 00:00:00 2001 From: Pushkar Joshi Date: Fri, 2 Mar 2012 12:23:44 -0800 Subject: Catmull-Rom spline sampling for the brush stroke, and options for stroke size, stroke hardness and both stroke colors (left and right --- temporarily using the stroke and fill colors respectively) --- .../brush-properties.reel/brush-properties.html | 35 ++++++++++- .../brush-properties.reel/brush-properties.js | 17 ++--- js/helper-classes/RDGE/GLBrushStroke.js | 72 ++++++++++++++++++++-- js/tools/BrushTool.js | 17 ++++- 4 files changed, 120 insertions(+), 21 deletions(-) diff --git a/js/components/tools-properties/brush-properties.reel/brush-properties.html b/js/components/tools-properties/brush-properties.reel/brush-properties.html index 38f7f856..af07b3f2 100755 --- a/js/components/tools-properties/brush-properties.reel/brush-properties.html +++ b/js/components/tools-properties/brush-properties.reel/brush-properties.html @@ -11,12 +11,39 @@ <script type="text/montage-serialization"> { + "strokeSizeHT": { + "module": "js/components/hottextunit.reel", + "name": "HotTextUnit", + "properties": { + "element": {"#": "strokeSize"}, + "minValue": 1, + "maxValue": 100, + "value": 1, + "decimalPlace": 10, + "acceptableUnits" : ["px", "pt"] + } + }, + + "strokeHardnessHT": { + "module": "js/components/hottextunit.reel", + "name": "HotTextUnit", + "properties": { + "element": {"#": "strokeHardness"}, + "minValue": 0, + "maxValue": 100, + "value": 100, + "decimalPlace": 10, + "acceptableUnits" : ["px", "pt"] + } + }, "owner": { "module": "js/components/tools-properties/brush-properties.reel", "name": "BrushProperties", "properties": { - "element": {"#": "brushProperties"} + "element": {"#": "brushProperties"}, + "_strokeSize": {"@": "strokeSizeHT"}, + "_strokeHardness": {"@": "strokeHardnessHT"} } } } @@ -26,6 +53,12 @@ <body> <div id="brushProperties" class="subToolHolderPanel"> + <div id="strokesContainer" class="leftLabel" style="margin-left:25px; padding-top: 3px;"> + <label class="label"> Stroke:</label> + <div id="strokeSize" class="label"></div> + <label class="label"> Hardness:</label> + <div id="strokeHardness" class="label"></div> + </div> </div> </body> diff --git a/js/components/tools-properties/brush-properties.reel/brush-properties.js b/js/components/tools-properties/brush-properties.reel/brush-properties.js index 92da98cc..d2fcf888 100755 --- a/js/components/tools-properties/brush-properties.reel/brush-properties.js +++ b/js/components/tools-properties/brush-properties.reel/brush-properties.js @@ -9,17 +9,10 @@ var Component = require("montage/ui/component").Component; var ToolProperties = require("js/components/tools-properties/tool-properties").ToolProperties; exports.BrushProperties = Montage.create(ToolProperties, { - - - _subPrepare: { - value: function() { - //this.divElement.addEventListener("click", this, false); - } + strokeSize: { + get: function() { return this._strokeSize; } }, - - handleClick: { - value: function(event) { - // this.selectedElement = event._event.target.id; - } + strokeHardness: { + get: function() { return this._strokeHardness; } } -}); \ No newline at end of file +}); diff --git a/js/helper-classes/RDGE/GLBrushStroke.js b/js/helper-classes/RDGE/GLBrushStroke.js index f5b0bce1..c1469977 100755 --- a/js/helper-classes/RDGE/GLBrushStroke.js +++ b/js/helper-classes/RDGE/GLBrushStroke.js @@ -31,6 +31,8 @@ function GLBrushStroke() { //stroke information this._strokeWidth = 0.0; this._strokeColor = [0.4, 0.4, 0.4, 1.0]; + this._secondStrokeColor = this._strokeColor; + this._strokeHardness = 100; this._strokeMaterial; this._strokeStyle = "Solid"; @@ -39,7 +41,7 @@ function GLBrushStroke() { this._WETNESS_FACTOR = 0.25; //prevent extremely long paths that can take a long time to render - this._MAX_ALLOWED_SAMPLES = 500; + this._MAX_ALLOWED_SAMPLES = 5000; //drawing context this._world = null; @@ -106,6 +108,8 @@ function GLBrushStroke() { this.setStrokeMaterial = function (m) { this._strokeMaterial = m; } this.getStrokeColor = function () { return this._strokeColor; } this.setStrokeColor = function (c) { this._strokeColor = c; } + this.setSecondStrokeColor = function(c){this._secondStrokeColor=c;} + this.setStrokeHardness = function(h){this._strokeHardness=h;} this.getStrokeStyle = function () { return this._strokeStyle; } this.setStrokeStyle = function (s) { this._strokeStyle = s; } @@ -133,7 +137,7 @@ function GLBrushStroke() { this._Points[i][2]+=tz; } } - + this.computeMetaGeometry = function(){ if (this._dirty){ var numPoints = this._Points.length; @@ -172,6 +176,44 @@ function GLBrushStroke() { } } + //**** add samples to the long sections of the path --- Catmull-Rom spline interpolation + if (numPoints>1) { + var numInsertedPoints = 0; + var threshold = 5;//0.25*this._strokeWidth; //this determines whether a segment between two sample is too long + var prevPt = this._Points[0]; + for (var i=1;i<numPoints;i++){ + var pt = this._Points[i]; + var diff = [pt[0]-prevPt[0], pt[1]-prevPt[1]]; + var distance = Math.sqrt(diff[0]*diff[0]+diff[1]*diff[1]); + if (distance>threshold){ + //build the control polygon for the Catmull-Rom spline (prev. 2 points and next 2 points) + var prev = (i===1) ? i-1 : i-2; + var next = (i===numPoints-1) ? i : i+1; + var ctrlPts = [this._Points[prev], this._Points[i-1], this._Points[i], this._Points[next]]; + //insert points along the prev. to current point + var numNewPoints = Math.floor(distance/threshold); + for (var j=0;j<numNewPoints;j++){ + var param = (j+1)/(numNewPoints+1); + var newpt = this._CatmullRomSplineInterpolate(ctrlPts, param); + //insert new point before point i + this._Points.splice(i, 0, newpt); + i++; + numInsertedPoints++; + } + this._dirty=true; + } + prevPt=pt; + //update numPoints to match the new length + numPoints = this._Points.length; + + //end this function if the numPoints has gone above the max. size specified + if (numPoints> this._MAX_ALLOWED_SAMPLES){ + console.log("leaving the resampling because numPoints is greater than limit:"+this._MAX_ALLOWED_SAMPLES); + break; + } + } + console.log("Inserted "+numInsertedPoints+" additional CatmullRom points"); + } // *** compute the bounding box ********* this._BBoxMin = [Infinity, Infinity, Infinity]; this._BBoxMax = [-Infinity, -Infinity, -Infinity]; @@ -362,7 +404,6 @@ function GLBrushStroke() { } */ - //todo test how to render the path as a bunch of moveTo and lineTos (for calligraphic brush styles) var numTraces = this._strokeWidth; var halfNumTraces = numTraces/2; var deltaDisplacement = [1,0];//[this._strokeWidth/numTraces, 0]; //a horizontal line brush @@ -371,17 +412,20 @@ function GLBrushStroke() { var disp = [startPos[0]+t*deltaDisplacement[0], startPos[1]+t*deltaDisplacement[1]]; //ctx.globalCompositeOperation = 'source-over'; var distFromMiddle = Math.abs(halfNumTraces-t); - var alphaVal = 1.0 - (distFromMiddle/halfNumTraces); + var alphaVal = 1.0 - (100-this._strokeHardness)*(distFromMiddle/halfNumTraces)/100; ctx.save(); ctx.lineWidth=this._strokeWidth/10;//4; if (ctx.lineWidth<2) ctx.lineWidth=2; + if (t===numTraces-1){ + ctx.lineWidth = 1; + } ctx.lineJoin="bevel"; ctx.lineCap="butt"; if (t<numTraces/2) ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")"; else - ctx.strokeStyle="rgba("+255+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")"; + ctx.strokeStyle="rgba("+parseInt(255*this._secondStrokeColor[0])+","+parseInt(255*this._secondStrokeColor[1])+","+parseInt(255*this._secondStrokeColor[2])+","+alphaVal+")"; ctx.translate(disp[0],disp[1]); ctx.beginPath(); ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]); @@ -437,4 +481,20 @@ function GLBrushStroke() { return true; } -} //function GLSubpath ...class definition \ No newline at end of file +} //function GLBrushStroke ...class definition + +GLBrushStroke.prototype._CatmullRomSplineInterpolate = function(ctrlPts, t) +{ + //perform CatmullRom interpolation on the spline...assume t is in [0,1] + var t2 = t*t; + var t3 = t2*t; + var retPoint = [0,0,0]; + for (var i=0;i<3;i++){ + retPoint[i] = 0.5 *( + (2*ctrlPts[1][i]) + + (-ctrlPts[0][i] + ctrlPts[2][i]) * t + + (2*ctrlPts[0][i] - 5*ctrlPts[1][i] + 4*ctrlPts[2][i] - ctrlPts[3][i]) * t2 + + (-ctrlPts[0][i] + 3*ctrlPts[1][i]- 3*ctrlPts[2][i] + ctrlPts[3][i]) * t3); + } + return retPoint; +} \ No newline at end of file diff --git a/js/tools/BrushTool.js b/js/tools/BrushTool.js index dc9e022e..8b0f60bb 100644 --- a/js/tools/BrushTool.js +++ b/js/tools/BrushTool.js @@ -5,6 +5,7 @@ No rights, expressed or implied, whatsoever to this software are provided by Mot </copyright> */ var ShapeTool = require("js/tools/ShapeTool").ShapeTool; +var ShapesController = require("js/controllers/elements/shapes-controller").ShapesController; var DrawingToolBase = require("js/tools/drawing-tool-base").DrawingToolBase; var defaultEventManager = require("montage/core/event/event-manager").defaultEventManager; var Montage = require("montage/core/core").Montage; @@ -67,12 +68,24 @@ exports.BrushTool = Montage.create(ShapeTool, { if (this.application.ninja.colorController.colorToolbar.stroke.webGlColor){ this._selectedBrushStroke.setStrokeColor(this.application.ninja.colorController.colorToolbar.stroke.webGlColor); } + if (this.application.ninja.colorController.colorToolbar.fill.webGlColor){ + this._selectedBrushStroke.setSecondStrokeColor(this.application.ninja.colorController.colorToolbar.fill.webGlColor); + } //add this point to the brush stroke in case the user does a mouse up before doing a mouse move var currMousePos = this._getUnsnappedPosition(event.pageX, event.pageY); this._selectedBrushStroke.addPoint(currMousePos); - //TODO get these values from the options - this._selectedBrushStroke.setStrokeWidth(10); + var strokeSize = 1; + if (this.options.strokeSize) { + strokeSize = ShapesController.GetValueInPixels(this.options.strokeSize.value, this.options.strokeSize.units); + } + this._selectedBrushStroke.setStrokeWidth(strokeSize); + + var strokeHardness = 100; + if (this.options.strokeHardness){ + strokeHardness = ShapesController.GetValueInPixels(this.options.strokeHardness.value, this.options.strokeHardness.units); + } + this._selectedBrushStroke.setStrokeHardness(strokeHardness); } NJevent("enableStageMove");//stageManagerModule.stageManager.enableMouseMove(); } //value: function (event) { -- cgit v1.2.3 From 264e3d8e6d3624083d2fab9fe2560234553bb2ad Mon Sep 17 00:00:00 2001 From: Pushkar Joshi Date: Tue, 6 Mar 2012 17:03:06 -0800 Subject: draw brush stroke as concentric paths overlaid on top of each other...allows us to simulate softness for circular brushes --- js/helper-classes/RDGE/GLBrushStroke.js | 135 +++++++++++++++++++++++--------- 1 file changed, 98 insertions(+), 37 deletions(-) diff --git a/js/helper-classes/RDGE/GLBrushStroke.js b/js/helper-classes/RDGE/GLBrushStroke.js index c1469977..26c922a3 100755 --- a/js/helper-classes/RDGE/GLBrushStroke.js +++ b/js/helper-classes/RDGE/GLBrushStroke.js @@ -81,7 +81,7 @@ function GLBrushStroke() { //add the point only if it is some epsilon away from the previous point var numPoints = this._Points.length; if (numPoints>0) { - var threshold = this._WETNESS_FACTOR*this._strokeWidth; + var threshold = 1;//this._WETNESS_FACTOR*this._strokeWidth; var prevPt = this._Points[numPoints-1]; var diffPt = [prevPt[0]-pt[0], prevPt[1]-pt[1]]; var diffPtMag = Math.sqrt(diffPt[0]*diffPt[0] + diffPt[1]*diffPt[1]); @@ -375,46 +375,58 @@ function GLBrushStroke() { */ /* - var R2 = this._strokeWidth; - var R = R2*0.5; - var hardness = 0; //for a pencil, this is always 1 //TODO get hardness parameter from user interface - var innerRadius = (hardness*R)-1; - if (innerRadius<1) - innerRadius=1; - - var r = ctx.createRadialGradient(0,0,innerRadius, 0,0,R); - var midColor = "rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+",1)"; - r.addColorStop(0, midColor); - var endColor = "rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+",0.0)"; - r.addColorStop(1, endColor); - ctx.fillStyle = r; - - for (var i = 0; i < numPoints; i++) { - var pt = this._Points[i]; - ctx.globalCompositeOperation = 'source-over'; - var x = pt[0]-bboxMin[0]; - var y = pt[1]-bboxMin[1]; - ctx.save(); - ctx.translate(x,y); - ctx.arc(0, 0, R, 0, 2 * Math.PI, false); - ctx.fill(); - ctx.restore(); - //ctx.globalCompositeOperation = 'source-in'; - //ctx.rect(x-R, y-R, R2, R2); - } - */ - + //build the stamp for the brush stroke + //todo get this directly from the UI + var t=0; var numTraces = this._strokeWidth; var halfNumTraces = numTraces/2; - var deltaDisplacement = [1,0];//[this._strokeWidth/numTraces, 0]; //a horizontal line brush var startPos = [-this._strokeWidth/2,0]; - for (var t=0;t<numTraces;t++){ - var disp = [startPos[0]+t*deltaDisplacement[0], startPos[1]+t*deltaDisplacement[1]]; + var brushStamp = []; + + //build an angled (calligraphic) brush stamp + var deltaDisplacement = [1,1];//[this._strokeWidth/numTraces, 0]; //a horizontal line brush + for (t=0;t<numTraces;t++){ + var brushPt = [startPos[0]+t*deltaDisplacement[0], startPos[1]+t*deltaDisplacement[1]]; + brushStamp.push(brushPt); + } + + + //make a circular brush stamp + brushStamp=[]; + numTraces = this._strokeWidth*Math.PI; //figure out how to + var radius = this._strokeWidth/2; + for (t=0;t<numTraces;t++) + { + var angle = Math.PI*(360*t/numTraces)/180; + var brushPt = [radius*Math.cos(angle), radius*Math.sin(angle)]; + brushStamp.push(brushPt); + } + +// //make a square brush stamp +// STOPPED HERE +// brushStamp = []; +// numTraces = 4*strokeWidth; +// for (t=0;t<numTraces;t++){ +// if (t<numTraces*0.25){ +// var brushPt = [startPos[0]+t*deltaDisplacement[0], startPos[1]+t*deltaDisplacement[1]]; +// } else if (t<numTraces*0.5){ +// +// } else if (t<numTraces*0.75){ +// +// } else { +// +// } +// brushStamp.push(brushPt); +// } + + for (t=0;t<numTraces;t++){ + var disp = [brushStamp[t][0], brushStamp[t][1]]; //ctx.globalCompositeOperation = 'source-over'; var distFromMiddle = Math.abs(halfNumTraces-t); var alphaVal = 1.0 - (100-this._strokeHardness)*(distFromMiddle/halfNumTraces)/100; + alphaVal = 0.2; ctx.save(); - ctx.lineWidth=this._strokeWidth/10;//4; + ctx.lineWidth=this._strokeWidth/10;//todo figure out the correct formula for the line width if (ctx.lineWidth<2) ctx.lineWidth=2; if (t===numTraces-1){ @@ -422,10 +434,10 @@ function GLBrushStroke() { } ctx.lineJoin="bevel"; ctx.lineCap="butt"; - if (t<numTraces/2) + //if (t<numTraces/2) ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")"; - else - ctx.strokeStyle="rgba("+parseInt(255*this._secondStrokeColor[0])+","+parseInt(255*this._secondStrokeColor[1])+","+parseInt(255*this._secondStrokeColor[2])+","+alphaVal+")"; + //else + // ctx.strokeStyle="rgba("+parseInt(255*this._secondStrokeColor[0])+","+parseInt(255*this._secondStrokeColor[1])+","+parseInt(255*this._secondStrokeColor[2])+","+alphaVal+")"; ctx.translate(disp[0],disp[1]); ctx.beginPath(); ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]); @@ -435,8 +447,10 @@ function GLBrushStroke() { ctx.stroke(); ctx.restore(); } + */ /* + //debugging path ctx.beginPath(); ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]); for (var i=1;i<numPoints;i++){ @@ -447,6 +461,53 @@ function GLBrushStroke() { ctx.stroke(); */ + var numlayers = this._strokeWidth/2; + var alphaVal = 1.0/(numlayers-1); + for (var l=0;l<numlayers;l++){ + ctx.beginPath(); + ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]); + for (var i=1;i<numPoints;i++){ + ctx.lineTo(this._Points[i][0]-bboxMin[0], this._Points[i][1]-bboxMin[1]); + } + ctx.lineCap = "round"; + ctx.lineJoin="round"; + ctx.lineWidth=l+1; + ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")"; + ctx.stroke(); + } + + /* + var R2 = this._strokeWidth; + var R = R2*0.5; + var hardness = 0; //for a pencil, this is always 1 //TODO get hardness parameter from user interface + var innerRadius = (hardness*R)-1; + if (innerRadius<1) + innerRadius=1; + + var r = ctx.createRadialGradient(0,0,innerRadius, 0,0,R); + var midColor = "rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+",1)"; + r.addColorStop(0, midColor); + var endColor = "rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+",0.0)"; + r.addColorStop(1, endColor); + ctx.fillStyle = r; + + for (var i = 0; i < numPoints; i++) { + var pt = this._Points[i]; + ctx.globalCompositeOperation = 'source-over'; + var x = pt[0]-bboxMin[0]; + var y = pt[1]-bboxMin[1]; + ctx.save(); + ctx.translate(x,y); + ctx.arc(0, 0, R, 0, 2 * Math.PI, false); + ctx.fill(); + ctx.restore(); + break; + //ctx.globalCompositeOperation = 'source-in'; + //ctx.rect(x-R, y-R, R2, R2); + } + */ + + ctx.restore(); } //render() -- cgit v1.2.3 From 40f3fc5feae866c99af818a886e8bf9d8cf2b8dd Mon Sep 17 00:00:00 2001 From: Pushkar Joshi Date: Tue, 6 Mar 2012 17:23:58 -0800 Subject: merge Valerio's architecture changes into pen tool --- js/lib/geom/sub-path.js | 84 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/js/lib/geom/sub-path.js b/js/lib/geom/sub-path.js index ab54d1e9..0a65511b 100755 --- a/js/lib/geom/sub-path.js +++ b/js/lib/geom/sub-path.js @@ -584,6 +584,90 @@ GLSubpath.prototype.pickAnchor = function (pickX, pickY, pickZ, radius) { return selAnchorIndex; }; +GLSubpath.prototype.isWithinBBox =function(x,y,z){ + if (this._BBoxMin[0]>x || this._BBoxMin[1]>y || this._BBoxMin[2]>z){ + return false; + } + if (this._BBoxMax[0]<x || this._BBoxMax[1]<y || this._BBoxMax[2]<z){ + return false; + } + return true; +} + +//pick the path point closest to the specified location, return null if some anchor point (or its handles) is within radius, else return the parameter distance +GLSubpath.prototype.pathHitTest = function (pickX, pickY, pickZ, radius) { + var numAnchors = this._Anchors.length; + var selAnchorIndex = -1; + var retParam = null; + var radSq = radius * radius; + var minDistance = Infinity; + + //check if the location is close to the currently selected anchor position + if (this._selectedAnchorIndex>=0 && this._selectedAnchorIndex<this._Anchors.length){ + var distSq = this._Anchors[this._selectedAnchorIndex].getDistanceSq(pickX, pickY, pickZ); + //check the anchor point + if (distSq < minDistance && distSq < radSq) { + selAnchorIndex = this._selectedAnchorIndex; + minDistance = distSq; + } + } + //check the prev and next of the selected anchor if the above did not register a hit + if (this._selectedAnchorIndex>=0 && selAnchorIndex === -1) { + var distSq = this._Anchors[this._selectedAnchorIndex].getPrevDistanceSq(pickX, pickY, pickZ); + if (distSq < minDistance && distSq < radSq){ + selAnchorIndex = this._selectedAnchorIndex; + minDistance = distSq; + } else { + //check the next for this anchor point + distSq = this._Anchors[this._selectedAnchorIndex].getNextDistanceSq(pickX, pickY, pickZ); + if (distSq<minDistance && distSq<radSq){ + selAnchorIndex = this._selectedAnchorIndex; + minDistance = distSq; + } + } + } + + //now check if the location is close to any anchor position + if (selAnchorIndex===-1) { + for (var i = 0; i < numAnchors; i++) { + var distSq = this._Anchors[i].getDistanceSq(pickX, pickY, pickZ); + //check the anchor point + if (distSq < minDistance && distSq < radSq) { + selAnchorIndex = i; + minDistance = distSq; + } + }//for every anchor i + } + + //finally check if the location is close to the curve itself + if (selAnchorIndex===-1) { + //first check if the input location is within the bounding box + if (this.isWithinBBox(pickX,pickY,pickZ)){ + var numSegments = this._isClosed ? numAnchors : numAnchors-1; + for (var i = 0; i < numSegments; i++) { + var nextIndex = (i+1)%numAnchors; + //check if the point is close to the bezier segment between anchor i and anchor nextIndex + var controlPoints = [[this._Anchors[i].getPosX(),this._Anchors[i].getPosY(),this._Anchors[i].getPosZ()], + [this._Anchors[i].getNextX(),this._Anchors[i].getNextY(),this._Anchors[i].getNextZ()], + [this._Anchors[nextIndex].getPrevX(),this._Anchors[nextIndex].getPrevY(),this._Anchors[nextIndex].getPrevZ()], + [this._Anchors[nextIndex].getPosX(),this._Anchors[nextIndex].getPosY(),this._Anchors[nextIndex].getPosZ()]]; + var point = [pickX, pickY, pickZ]; + if (this._isWithinBoundingBox(point, controlPoints, radius)) { + //var intersectParam = this._checkIntersection(controlPoints, 0.0, 1.0, point, radius); + var intersectParam = this._checkIntersectionWithSamples(this._anchorSampleIndex[i], this._anchorSampleIndex[nextIndex], point, radius); + console.log("intersectParam:"+intersectParam); + if (intersectParam){ + selAnchorIndex=i; + retParam = intersectParam-i; //make the retParam go from 0 to 1 + break; + } + } + }//for every anchor i + }//if is within bbox + } + return [selAnchorIndex,retParam]; +} //GLSubpath.pathHitTest function + //pick the path point closest to the specified location, return null if some anchor point (or its handles) is within radius, else return the parameter distance GLSubpath.prototype.pickPath = function (pickX, pickY, pickZ, radius) { var numAnchors = this._Anchors.length; -- cgit v1.2.3 From 353bfa04a6153b0146a0c73b6e6bb0e5f4b23194 Mon Sep 17 00:00:00 2001 From: Pushkar Joshi Date: Tue, 6 Mar 2012 17:40:22 -0800 Subject: merge Valerio's architecture changes --- js/lib/geom/brush-stroke.js | 177 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 172 insertions(+), 5 deletions(-) diff --git a/js/lib/geom/brush-stroke.js b/js/lib/geom/brush-stroke.js index 9a934928..39af5c5c 100755 --- a/js/lib/geom/brush-stroke.js +++ b/js/lib/geom/brush-stroke.js @@ -32,6 +32,8 @@ var BrushStroke = function GLBrushStroke() { //stroke information this._strokeWidth = 0.0; this._strokeColor = [0.4, 0.4, 0.4, 1.0]; + this._secondStrokeColor = this._strokeColor; + this._strokeHardness = 100; this._strokeMaterial = null; this._strokeStyle = "Solid"; @@ -114,7 +116,7 @@ var BrushStroke = function GLBrushStroke() { //add the point only if it is some epsilon away from the previous point var numPoints = this._Points.length; if (numPoints>0) { - var threshold = this._WETNESS_FACTOR*this._strokeWidth; + var threshold = 1;//this._WETNESS_FACTOR*this._strokeWidth; var prevPt = this._Points[numPoints-1]; var diffPt = [prevPt[0]-pt[0], prevPt[1]-pt[1]]; var diffPtMag = Math.sqrt(diffPt[0]*diffPt[0] + diffPt[1]*diffPt[1]); @@ -173,6 +175,14 @@ var BrushStroke = function GLBrushStroke() { this._strokeColor = c; }; + this.setSecondStrokeColor = function(c){ + this._secondStrokeColor=c; + } + + this.setStrokeHardness = function(h){ + this._strokeHardness=h; + } + this.getStrokeStyle = function () { return this._strokeStyle; }; @@ -219,7 +229,8 @@ var BrushStroke = function GLBrushStroke() { var numPoints = this._Points.length; //**** add samples to the path if needed...linear interpolation for now - if (numPoints>1) { + //if (numPoints>1) { + if (0){ var threshold = this._WETNESS_FACTOR*this._strokeWidth; var prevPt = this._Points[0]; var prevIndex = 0; @@ -250,6 +261,44 @@ var BrushStroke = function GLBrushStroke() { } } } + //**** add samples to the long sections of the path --- Catmull-Rom spline interpolation + if (numPoints>1) { + var numInsertedPoints = 0; + var threshold = 5;//0.25*this._strokeWidth; //this determines whether a segment between two sample is too long + var prevPt = this._Points[0]; + for (var i=1;i<numPoints;i++){ + var pt = this._Points[i]; + var diff = [pt[0]-prevPt[0], pt[1]-prevPt[1]]; + var distance = Math.sqrt(diff[0]*diff[0]+diff[1]*diff[1]); + if (distance>threshold){ + //build the control polygon for the Catmull-Rom spline (prev. 2 points and next 2 points) + var prev = (i===1) ? i-1 : i-2; + var next = (i===numPoints-1) ? i : i+1; + var ctrlPts = [this._Points[prev], this._Points[i-1], this._Points[i], this._Points[next]]; + //insert points along the prev. to current point + var numNewPoints = Math.floor(distance/threshold); + for (var j=0;j<numNewPoints;j++){ + var param = (j+1)/(numNewPoints+1); + var newpt = this._CatmullRomSplineInterpolate(ctrlPts, param); + //insert new point before point i + this._Points.splice(i, 0, newpt); + i++; + numInsertedPoints++; + } + this._dirty=true; + } + prevPt=pt; + //update numPoints to match the new length + numPoints = this._Points.length; + + //end this function if the numPoints has gone above the max. size specified + if (numPoints> this._MAX_ALLOWED_SAMPLES){ + console.log("leaving the resampling because numPoints is greater than limit:"+this._MAX_ALLOWED_SAMPLES); + break; + } + } + console.log("Inserted "+numInsertedPoints+" additional CatmullRom points"); + } // *** compute the bounding box ********* this._BBoxMin = [Infinity, Infinity, Infinity]; @@ -412,7 +461,7 @@ var BrushStroke = function GLBrushStroke() { } */ - + /* var R2 = this._strokeWidth; var R = R2*0.5; var hardness = 0; //for a pencil, this is always 1 //TODO get hardness parameter from user interface @@ -440,6 +489,109 @@ var BrushStroke = function GLBrushStroke() { //ctx.globalCompositeOperation = 'source-in'; //ctx.rect(x-R, y-R, R2, R2); } + */ + + /* + //build the stamp for the brush stroke + //todo get this directly from the UI + var t=0; + var numTraces = this._strokeWidth; + var halfNumTraces = numTraces/2; + var startPos = [-this._strokeWidth/2,0]; + var brushStamp = []; + + //build an angled (calligraphic) brush stamp + var deltaDisplacement = [1,1];//[this._strokeWidth/numTraces, 0]; //a horizontal line brush + for (t=0;t<numTraces;t++){ + var brushPt = [startPos[0]+t*deltaDisplacement[0], startPos[1]+t*deltaDisplacement[1]]; + brushStamp.push(brushPt); + } + + + //make a circular brush stamp + brushStamp=[]; + numTraces = this._strokeWidth*Math.PI; //figure out how to + var radius = this._strokeWidth/2; + for (t=0;t<numTraces;t++) + { + var angle = Math.PI*(360*t/numTraces)/180; + var brushPt = [radius*Math.cos(angle), radius*Math.sin(angle)]; + brushStamp.push(brushPt); + } + +// //make a square brush stamp +// STOPPED HERE +// brushStamp = []; +// numTraces = 4*strokeWidth; +// for (t=0;t<numTraces;t++){ +// if (t<numTraces*0.25){ +// var brushPt = [startPos[0]+t*deltaDisplacement[0], startPos[1]+t*deltaDisplacement[1]]; +// } else if (t<numTraces*0.5){ +// +// } else if (t<numTraces*0.75){ +// +// } else { +// +// } +// brushStamp.push(brushPt); +// } + + for (t=0;t<numTraces;t++){ + var disp = [brushStamp[t][0], brushStamp[t][1]]; + //ctx.globalCompositeOperation = 'source-over'; + var distFromMiddle = Math.abs(halfNumTraces-t); + var alphaVal = 1.0 - (100-this._strokeHardness)*(distFromMiddle/halfNumTraces)/100; + alphaVal = 0.2; + ctx.save(); + ctx.lineWidth=this._strokeWidth/10;//todo figure out the correct formula for the line width + if (ctx.lineWidth<2) + ctx.lineWidth=2; + if (t===numTraces-1){ + ctx.lineWidth = 1; + } + ctx.lineJoin="bevel"; + ctx.lineCap="butt"; + //if (t<numTraces/2) + ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")"; + //else + // ctx.strokeStyle="rgba("+parseInt(255*this._secondStrokeColor[0])+","+parseInt(255*this._secondStrokeColor[1])+","+parseInt(255*this._secondStrokeColor[2])+","+alphaVal+")"; + ctx.translate(disp[0],disp[1]); + ctx.beginPath(); + ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]); + for (var i=0;i<numPoints;i++){ + ctx.lineTo(this._Points[i][0]-bboxMin[0], this._Points[i][1]-bboxMin[1]); + } + ctx.stroke(); + ctx.restore(); + } + */ + + /* + //debugging path + ctx.beginPath(); + ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]); + for (var i=1;i<numPoints;i++){ + ctx.lineTo(this._Points[i][0]-bboxMin[0], this._Points[i][1]-bboxMin[1]); + } + ctx.lineWidth=1.0; + ctx.strokeStyle = "black"; + ctx.stroke(); + */ + + var numlayers = this._strokeWidth/2; + var alphaVal = 1.0/(numlayers-1); + for (var l=0;l<numlayers;l++){ + ctx.beginPath(); + ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]); + for (var i=1;i<numPoints;i++){ + ctx.lineTo(this._Points[i][0]-bboxMin[0], this._Points[i][1]-bboxMin[1]); + } + ctx.lineCap = "round"; + ctx.lineJoin="round"; + ctx.lineWidth=l+1; + ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")"; + ctx.stroke(); + } ctx.restore(); }; //render() @@ -473,10 +625,25 @@ var BrushStroke = function GLBrushStroke() { return true; }; -}; //function GLSubpath ...class definition +}; //function BrushStroke ...class definition BrushStroke.prototype = new GeomObj(); +BrushStroke.prototype._CatmullRomSplineInterpolate = function(ctrlPts, t) +{ + //perform CatmullRom interpolation on the spline...assume t is in [0,1] + var t2 = t*t; + var t3 = t2*t; + var retPoint = [0,0,0]; + for (var i=0;i<3;i++){ + retPoint[i] = 0.5 *( + (2*ctrlPts[1][i]) + + (-ctrlPts[0][i] + ctrlPts[2][i]) * t + + (2*ctrlPts[0][i] - 5*ctrlPts[1][i] + 4*ctrlPts[2][i] - ctrlPts[3][i]) * t2 + + (-ctrlPts[0][i] + 3*ctrlPts[1][i]- 3*ctrlPts[2][i] + ctrlPts[3][i]) * t3); + } + return retPoint; +} if (typeof exports === "object") { exports.BrushStroke = BrushStroke; -} \ No newline at end of file +} -- cgit v1.2.3 From 703fb3d06e88257ac73c1d1a0ec6ca33a64f4371 Mon Sep 17 00:00:00 2001 From: Pushkar Joshi Date: Wed, 7 Mar 2012 14:33:21 -0800 Subject: implement stroke hardness such that it is percentage of the stroke width that's fully the color of the brush AND add a smoothing flag for the brush options --- .../brush-properties.reel/brush-properties.html | 4 +- .../brush-properties.reel/brush-properties.js | 3 + js/lib/geom/brush-stroke.js | 66 +++++++--------------- js/tools/BrushTool.js | 6 ++ 4 files changed, 31 insertions(+), 48 deletions(-) diff --git a/js/components/tools-properties/brush-properties.reel/brush-properties.html b/js/components/tools-properties/brush-properties.reel/brush-properties.html index af07b3f2..6d4852e6 100755 --- a/js/components/tools-properties/brush-properties.reel/brush-properties.html +++ b/js/components/tools-properties/brush-properties.reel/brush-properties.html @@ -43,7 +43,8 @@ "properties": { "element": {"#": "brushProperties"}, "_strokeSize": {"@": "strokeSizeHT"}, - "_strokeHardness": {"@": "strokeHardnessHT"} + "_strokeHardness": {"@": "strokeHardnessHT"}, + "_doSmoothing": {"@": "doSmoothing"} } } } @@ -58,6 +59,7 @@ <div id="strokeSize" class="label"></div> <label class="label"> Hardness:</label> <div id="strokeHardness" class="label"></div> + <label class="label subOption optionLabel"><input id="doSmoothing" type="checkbox" name="doSmoothingControl" class="checkBoxAlign"/>Smoothing</label> </div> </div> </body> diff --git a/js/components/tools-properties/brush-properties.reel/brush-properties.js b/js/components/tools-properties/brush-properties.reel/brush-properties.js index d2fcf888..0ce685b5 100755 --- a/js/components/tools-properties/brush-properties.reel/brush-properties.js +++ b/js/components/tools-properties/brush-properties.reel/brush-properties.js @@ -14,5 +14,8 @@ exports.BrushProperties = Montage.create(ToolProperties, { }, strokeHardness: { get: function() { return this._strokeHardness; } + }, + doSmoothing:{ + get: function() {return this._doSmoothing; } } }); diff --git a/js/lib/geom/brush-stroke.js b/js/lib/geom/brush-stroke.js index 39af5c5c..3e64e730 100755 --- a/js/lib/geom/brush-stroke.js +++ b/js/lib/geom/brush-stroke.js @@ -36,6 +36,7 @@ var BrushStroke = function GLBrushStroke() { this._strokeHardness = 100; this._strokeMaterial = null; this._strokeStyle = "Solid"; + this._strokeDoSmoothing = false; //the wetness of the brush (currently this is multiplied to the square of the stroke width, but todo should be changed to not depend on stroke width entirely //smaller value means more samples for the path @@ -183,6 +184,10 @@ var BrushStroke = function GLBrushStroke() { this._strokeHardness=h; } + this.setDoSmoothing = function(s){ + this._strokeDoSmoothing = s; + } + this.getStrokeStyle = function () { return this._strokeStyle; }; @@ -262,7 +267,7 @@ var BrushStroke = function GLBrushStroke() { } } //**** add samples to the long sections of the path --- Catmull-Rom spline interpolation - if (numPoints>1) { + if (this._strokeDoSmoothing && numPoints>1) { var numInsertedPoints = 0; var threshold = 5;//0.25*this._strokeWidth; //this determines whether a segment between two sample is too long var prevPt = this._Points[0]; @@ -507,35 +512,6 @@ var BrushStroke = function GLBrushStroke() { brushStamp.push(brushPt); } - - //make a circular brush stamp - brushStamp=[]; - numTraces = this._strokeWidth*Math.PI; //figure out how to - var radius = this._strokeWidth/2; - for (t=0;t<numTraces;t++) - { - var angle = Math.PI*(360*t/numTraces)/180; - var brushPt = [radius*Math.cos(angle), radius*Math.sin(angle)]; - brushStamp.push(brushPt); - } - -// //make a square brush stamp -// STOPPED HERE -// brushStamp = []; -// numTraces = 4*strokeWidth; -// for (t=0;t<numTraces;t++){ -// if (t<numTraces*0.25){ -// var brushPt = [startPos[0]+t*deltaDisplacement[0], startPos[1]+t*deltaDisplacement[1]]; -// } else if (t<numTraces*0.5){ -// -// } else if (t<numTraces*0.75){ -// -// } else { -// -// } -// brushStamp.push(brushPt); -// } - for (t=0;t<numTraces;t++){ var disp = [brushStamp[t][0], brushStamp[t][1]]; //ctx.globalCompositeOperation = 'source-over'; @@ -566,30 +542,26 @@ var BrushStroke = function GLBrushStroke() { } */ - /* - //debugging path - ctx.beginPath(); - ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]); - for (var i=1;i<numPoints;i++){ - ctx.lineTo(this._Points[i][0]-bboxMin[0], this._Points[i][1]-bboxMin[1]); - } - ctx.lineWidth=1.0; - ctx.strokeStyle = "black"; - ctx.stroke(); - */ - var numlayers = this._strokeWidth/2; - var alphaVal = 1.0/(numlayers-1); + var minStrokeWidth = (this._strokeHardness*this._strokeWidth)/100; //the hardness is the percentage of the stroke width that's fully opaque + var numlayers = 1 + (this._strokeWidth-minStrokeWidth)/2; + var alphaVal = 1.0/(numlayers); + ctx.lineCap = "round"; + ctx.lineJoin="round"; + ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")"; + ctx.globalCompositeOperation = 'lighter'; //we wish to add up the colors + ctx.globalAlpha = this._strokeColor[3]; for (var l=0;l<numlayers;l++){ ctx.beginPath(); ctx.moveTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]); + if (numPoints===1){ + ctx.lineTo(this._Points[0][0]-bboxMin[0], this._Points[0][1]-bboxMin[1]+0.01); + } for (var i=1;i<numPoints;i++){ ctx.lineTo(this._Points[i][0]-bboxMin[0], this._Points[i][1]-bboxMin[1]); } - ctx.lineCap = "round"; - ctx.lineJoin="round"; - ctx.lineWidth=l+1; - ctx.strokeStyle="rgba("+parseInt(255*this._strokeColor[0])+","+parseInt(255*this._strokeColor[1])+","+parseInt(255*this._strokeColor[2])+","+alphaVal+")"; + + ctx.lineWidth=2*l+minStrokeWidth; ctx.stroke(); } diff --git a/js/tools/BrushTool.js b/js/tools/BrushTool.js index dd201f33..1e1df0a4 100644 --- a/js/tools/BrushTool.js +++ b/js/tools/BrushTool.js @@ -88,6 +88,12 @@ exports.BrushTool = Montage.create(ShapeTool, { strokeHardness = ShapesController.GetValueInPixels(this.options.strokeHardness.value, this.options.strokeHardness.units); } this._selectedBrushStroke.setStrokeHardness(strokeHardness); + + var doSmoothing = false; + if (this.options.doSmoothing){ + doSmoothing = this.options.doSmoothing.value; + } + this._selectedBrushStroke.setDoSmoothing(doSmoothing); } NJevent("enableStageMove");//stageManagerModule.stageManager.enableMouseMove(); } //value: function (event) { -- cgit v1.2.3 From fcab2a14f8de302948d38f55db41e2c1c92baa34 Mon Sep 17 00:00:00 2001 From: Pushkar Joshi Date: Thu, 8 Mar 2012 16:03:17 -0800 Subject: brush stroke options for calligraphic brush style --- .../brush-properties.reel/brush-properties.html | 23 +++- .../brush-properties.reel/brush-properties.js | 8 +- js/lib/geom/brush-stroke.js | 147 ++++++++++++--------- js/tools/BrushTool.js | 18 ++- 4 files changed, 129 insertions(+), 67 deletions(-) diff --git a/js/components/tools-properties/brush-properties.reel/brush-properties.html b/js/components/tools-properties/brush-properties.reel/brush-properties.html index 6d4852e6..d96bd1ba 100755 --- a/js/components/tools-properties/brush-properties.reel/brush-properties.html +++ b/js/components/tools-properties/brush-properties.reel/brush-properties.html @@ -37,6 +37,19 @@ } }, + "strokeAngleHT": { + "module": "js/components/hottextunit.reel", + "name": "HotTextUnit", + "properties": { + "element": {"#": "strokeAngle"}, + "minValue": -90, + "maxValue": 90, + "value": 0, + "decimalPlace": 10, + "acceptableUnits" : ["px", "pt"] + } + }, + "owner": { "module": "js/components/tools-properties/brush-properties.reel", "name": "BrushProperties", @@ -44,7 +57,9 @@ "element": {"#": "brushProperties"}, "_strokeSize": {"@": "strokeSizeHT"}, "_strokeHardness": {"@": "strokeHardnessHT"}, - "_doSmoothing": {"@": "doSmoothing"} + "_doSmoothing": {"#": "doSmoothing"}, + "_useCalligraphic":{"#": "useCalligraphic"}, + "_strokeAngle": {"@": "strokeAngleHT"} } } } @@ -59,7 +74,11 @@ <div id="strokeSize" class="label"></div> <