javascript - Implementing brush thickness variation in HTML5 canvas (example want to emulate inside) -


i pointed in right direction in terms of algorithm in demo below here http://sta.sh/muro/. canvas tools using - i.e. drawing lines or drawing many arcs, etc

specifically want emulate brush turning cause entire "brush stroke" thicker. see image brush settings want emulate.

ultimately, create paint brush vary in thickness when turned, behaviour below.

enter image description here

to need record mouse points when button down. need check each line segment find direction, length of line , normalised vector of line segment can on sample mouse samples.

so if have set of points taken mouse following required details.

    for(var = 0; < len-1; i++){         var p1 = line[i];         var p2 = line[i+1];         var nx = p2.x - p1.x;         var ny = p2.y - p1.y;         p1.dir = ((math.atan2(ny,nx)%pi2)+pi2)%pi2; // direction         p1.len = math.hypot(nx,ny);  // length         p1.nx = nx/p1.len;  // normalised vector         p1.ny = ny/p1.len;     } 

once have details of each line segment simple matter change drawing parameters according values.

i have added demo. close bothered example line provided did not give options draw image showed. image shows use shadow , sub mouse sample sampling. rounded box draw image quicker drawn box have done. have smoothed line bit there little bit of lag between drawing , being finalised , set background image.

at top of demo set of constants control various settings. take way time add input options , vetting play them if find code useful.

sorry code messy example have pluck apart yourself.

/** hypot.js begin **/  // es6 new math function hypot. sqrt of sum of squares  var hypot = math.hypot;  if(typeof hypot === 'undefined'){      hypot = function(x,y){          return math.sqrt(math.pow(x,2)+math.pow(y,2));      }  }    /** hypot.js end **/    // draw options  const sub_sections = 5; // points between mouse samples  const size_mult = 3; // max size multiplier  const size_min = 0.1 // min size of line  const big_dir = 0.6;  // direction in radians thickest line  const smooth_max = 7;  // number of smoothing steps performed on line. bigger 20 slow rendering down  const shape_alpha = 0.5;  // stoke alpha  const shape_fill_alpha = 0.75; // fill alpha  const shadow_alpha = 0.1;   // shadow alpha  const shadow_blur = 5;  // shadow blur  const shadow_offx = 6;  // shoadow offest x , y  const shadow_offy = 6;  const shape_line_width = 0.6;  // stroke width of shape. constant , not scaled  const shape_width = 4;  // shape drawn width;  const shape_length = 20;  // shape drawn length  const shape_rounding = 2;  // shape rounded corner radius. warning invalid results if rounding greater half width or height ever smallest  const shape_trail = 0;  // offset  draw shape. negivive numbers trail drawing positive infront    var div = document.createelement("div");   div.textcontent = "click drag mouse draw, right click clear."  document.body.appendchild(div);    var mouse;  var demo = function(){            /** fullscreencanvas.js begin **/      var canvas = (function(){          var canvas = document.getelementbyid("canv");          if(canvas !== null){              document.body.removechild(canvas);          }          // creates blank image 2d context          canvas = document.createelement("canvas");           canvas.id = "canv";              canvas.width = window.innerwidth;          canvas.height = window.innerheight;           canvas.style.position = "absolute";          canvas.style.top = "0px";          canvas.style.left = "0px";          canvas.style.zindex = 1000;          canvas.ctx = canvas.getcontext("2d");           document.body.appendchild(canvas);          return canvas;      })();      var ctx = canvas.ctx;            /** fullscreencanvas.js end **/      /** mousefull.js begin **/      if(typeof mouse !== "undefined"){  // if mouse exists           if( mouse.removemouse !== undefined){              mouse.removemouse(); // remove previouse events          }      }      var canvasmousecallback = undefined;  // if needed      mouse = (function(){          var mouse = {              x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false,              interfaceid : 0, buttonlastraw : 0,  buttonraw : 0,              on : false,  // mouse on element              bm : [1, 2, 4, 6, 5, 3], // masks setting , clearing button raw bits;              getinterfaceid : function () { return this.interfaceid++; }, // ui functions              startmouse:undefined,              mouseevents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,dommousescroll".split(",")          };          function mousemove(e) {              var t = e.type, m = mouse;              m.x = e.offsetx; m.y = e.offsety;              if (m.x === undefined) { m.x = e.clientx; m.y = e.clienty; }              m.alt = e.altkey;m.shift = e.shiftkey;m.ctrl = e.ctrlkey;              if (t === "mousedown") { m.buttonraw |= m.bm[e.which-1];              } else if (t === "mouseup") { m.buttonraw &= m.bm[e.which + 2];              } else if (t === "mouseout") { m.buttonraw = 0; m.over = false;              } else if (t === "mouseover") { m.over = true;              } else if (t === "mousewheel") { m.w = e.wheeldelta;              } else if (t === "dommousescroll") { m.w = -e.detail;}              if (canvasmousecallback) { canvasmousecallback(mouse); }              e.preventdefault();          }          function startmouse(element){              if(element === undefined){                  element = document;              }              mouse.element = element;              mouse.mouseevents.foreach(                  function(n){                      element.addeventlistener(n, mousemove);                  }              );              element.addeventlistener("contextmenu", function (e) {e.preventdefault();}, false);          }          mouse.removemouse = function(){              if(mouse.element !== undefined){                  mouse.mouseevents.foreach(                      function(n){                          mouse.element.removeeventlistener(n, mousemove);                      }                  );                  canvasmousecallback = undefined;              }          }          mouse.mousestart = startmouse;          return mouse;      })();      if(typeof canvas !== "undefined"){          mouse.mousestart(canvas);      }else{          mouse.mousestart();      }      /** mousefull.js end **/      /** createimage.js begin **/      // creates blank image 2d context      var createimage=function(w,h){var i=document.createelement("canvas");i.width=w;i.height=h;i.ctx=i.getcontext("2d");return i;}            /** createimage.js end **/      /** frameupdate.js begin **/      var w = canvas.width;      var h = canvas.height;      var cw = w / 2;      var ch = h / 2;      var line = []; // line hold drawing points      var image = createimage(w,h); // background image dump point when soothed            var pi2 = math.pi * 2; // 360 save typing       var pih = math.pi / 2; // 90             // draws rounded rectangle path      function roundedrect(ctx,x, y, w, h, r){            ctx.beginpath();           ctx.arc(x + r, y + r, r, pih * 2, pih * 3);            ctx.arc(x + w - r, y + r, r, pih * 3, pi2);          ctx.arc(x + w - r, y + h - r, r, 0, pih);            ctx.arc(x + r, y + h - r, r, pih, pih * 2);            ctx.closepath();       }        // draws section of line      function drawstroke(ctx,line){          var len = line.length;            ctx.shadowblur = shadow_blur;          ctx.shadowoffsetx = shadow_offx;          ctx.shadowoffsety = shadow_offy;          ctx.shadowcolor = "rgba(0,0,0," + shadow_alpha + ")";          ctx.strokestyle = "rgba(0,0,0," + shape_fill_alpha + ")";          ctx.fillstyle = "rgba(255,255,255," + shape_alpha + ")";          (var = 0; < len - 1; i++) { // each point minus 1              var p1 = line[i];              var p2 = line[i + 1]; // point , 1 ahead              if (p1.dir && p2.dir) { // both points have direction                  // divide distance between points 5 , draw each sub section                  (var k = 0; k < p1.len; k += p1.len / sub_sections) {                      // points between mouse samples                      var x = p1.x + p1.nx * k;                      var y = p1.y + p1.ny * k;                      var kk = k / p1.len; // normalised distance                      // tween direction need check cyclic                      if (p1.dir > math.pi * 1.5 && p2.dir < math.pi / 2) {                          var dir = ((p2.dir + math.pi * 2) - p1.dir) * kk + p1.dir;                      } else                      if (p2.dir > math.pi * 1.5 && p1.dir < math.pi / 2) {                          var dir = ((p2.dir - math.pi * 2) - p1.dir) * kk + p1.dir;                      } else {                          var dir = (p2.dir - p1.dir) * kk + p1.dir;                      }                        // size dependent on direction                      var size = (math.abs(math.sin(dir + big_dir)) + size_min) * size_mult;                      // caculate transform requiered.                      var xdx = math.cos(dir) * size;                      var xdy = math.sin(dir) * size;                      // set line width invers scale remains constant                      ctx.linewidth = shape_line_width * (1 / size); // make sure line width not scale                      // set transform                      ctx.settransform(xdx, xdy, -xdy, xdx, x, y);                      // draw shape                      roundedrect(ctx, -shape_length / 2 - shape_trail, -shape_width / 2, shape_length, shape_width, shape_rounding);                      // fill , stroke                      ctx.fill();                      ctx.stroke();                  }              }          }          // restore transform          ctx.settransform(1, 0, 0, 1, 0, 0);      }        // update function try 60fps setting slow down.          function update(){          // restore transform          ctx.settransform(1, 0, 0, 1, 0, 0);          // clear          ctx.clearrect(0, 0, w, h);          // line length          var len = line.length;                    if (mouse.buttonraw !== 1) { // button draw onto image              drawstroke(image.ctx, line)              line = [];          } else {              // remove trailing line segments no longer being smoothed              if (len > smooth_max * 2) {                  var = line.splice(0, smooth_max - 1)                      a.push(line[0]);                  drawstroke(image.ctx, a)              }          }          // draw background image          ctx.drawimage(image, 0, 0);            // button down          if (mouse.buttonraw === 1) {              // if more 1 point              if (line.length > 0) {                  // add point if mouse has moved.                  if (mouse.x !== line[line.length - 1].x || mouse.y !== line[line.length - 1].y) {                      line.push({                          x : mouse.x,                          y : mouse.y,                          s : 0                      });                  }              } else {                  // add point if no points exist                  line.push({                      x : mouse.x,                      y : mouse.y,                      s : 0                  });              }          }          // number of points          var len = line.length;                               if(mouse.buttonraw === 1){  // mouse down simple running average smooth              // smooth continue refine points untill outside              // smoothing range/              (var = 0; < len - 3; i++) {                  var p1 = line[i];                  var p2 = line[i + 1];                  var p3 = line[i + 2];                  if (p1.s < smooth_max) {                      p1.s += 1;                      p2.x = ((p1.x + p3.x) / 2 + p2.x * 2) / 3;                      p2.y = ((p1.y + p3.y) / 2 + p2.y * 2) / 3;                  }              }              // caculate direction, length , normalised vector              // each line segment , add point              for(var = 0; < len-1; i++){                  var p1 = line[i];                  var p2 = line[i + 1];                  var nx = p2.x - p1.x;                  var ny = p2.y - p1.y;                  p1.dir = ((math.atan2(ny, nx) % pi2) + pi2) % pi2; // direction                  p1.len = hypot(nx, ny); // length                  p1.nx = nx / p1.len; // normalised vector                  p1.ny = ny / p1.len;                    }              // draw line points onto canvas.              drawstroke(ctx,line)          }          if((mouse.buttonraw & 4)=== 4){              line = [];              image.ctx.clearrect(0,0,w,h);              ctx.clearrect(0,0,w,h);              mouse.buttonraw = 0;          }          if(!stop){              requestanimationframe(update);          }else{              var can = document.getelementbyid("canv");              if(can !== null){                  document.body.removechild(can);              }                   stop = false;                           }      }        update();    }  var stop = false;  // flag tell demo app stop   function resizeevent(){      var waitforstopped = function(){          if(!stop){  // wait stop return false              demo();              return;          }          settimeout(waitforstopped,200);      }      stop = true;      settimeout(waitforstopped,100);  }  window.addeventlistener("resize",resizeevent);  demo();  /** frameupdate.js end **/


Comments

Popular posts from this blog

c++ - llvm function pass ReplaceInstWithInst malloc -

Cross-Compiling Linux Kernel for Raspberry Pi - ${CCPREFIX}gcc -v does not work -

java.lang.NoClassDefFoundError When Creating New Android Project -