2

Im trying to render drawing immediately on lower canvas before mouse up event.

I found a few topics describing this, but i dont understand how i can use it in my project.

Emulate free drawing with fabricjs

Fabrics - How to render free drawing content before mouse up

How to programmatically free draw using Fabric js?

My codepen example

var canvas = this.__canvas = new fabric.Canvas('c', {
  isDrawingMode: true
});
  
canvas {
  border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.1/fabric.min.js"></script>   
<canvas id="c" width="300" height="300"></canvas>
vinkovsky
  • 310
  • 4
  • 16
  • 1
    What's your objective? – ariel Apr 13 '20 at 21:17
  • Hello @ariel ! I am developing a project where I have two canvases. The first canvas supports scaling and drawing, the second canvas is a duplicate of the first canvas, which should display a general view of the first canvas. Please, look at this [codepen](https://codepen.io/vinar22/pen/BaNNGMK). – vinkovsky Apr 14 '20 at 02:34
  • In my prject all changes are displayed from the lower canvas on the second canvas. But in fabric js created path is first rendered on the upper canvas, and after the mouseup event, it renders on the lower canvas. I want to render it right on the lower canvas. Sorry for my english. – vinkovsky Apr 14 '20 at 02:56

1 Answers1

4

I think you have fallen in the XY problem. In the comment is explained what you need, and what you ask is how to do something that you think is the way to get around it.

You setup a particular app that clones the canvas in a static representation in another canvas, at every after:render event.

Now the free drawing brush do not trigger the after:render event, and even if it did, the brush is not on your canvas before the mouse:up event.

So to solve your problem you want to put the brush on the lower canvas, so that your existing code will find it.

This is very hard to do because it means fiddling with the internal logic of the library. Why you can just copy the upper canvas every time a new segment of the brush is added? this will let you get to the goal apparently.

My answer and snippet is not perfect and it does not take in account the transparent brushes that will just copy over themselves many time.

Another solution would be to have a second static canvas layered on top of the first one, to handle those middle stages.

var c1 = document.getElementById("scale");
var c2 = document.getElementById("static");

var ctx1 = c1.getContext("2d");
var ctx2 = c2.getContext("2d");

var canvas = this.__canvas = new fabric.Canvas(c1, {
  isDrawingMode: true,
  renderOnAddRemove: false 
});
  
canvas.add(new fabric.Rect({ top: 100, left: 100, width: 50, height: 50, fill: '#f55' }),
            new fabric.Circle({ top: 140, left: 130, radius: 75, fill: 'green' }),
            new fabric.Triangle({ top: 100, left: 110, width: 100, height: 100, fill: 'blue' }))


fabric.Image.fromURL(
  "https://cdn-images-1.medium.com/max/1800/1*sg-uLNm73whmdOgKlrQdZA.jpeg",
  img => {
    img.scaleToWidth(canvas.width);
    canvas.setBackgroundImage(img);
    canvas.requestRenderAll();
  },
  {
    crossOrigin: "Annoymous"
  }
);


canvas.on('mouse:wheel', function(opt) {
  var delta = opt.e.deltaY;
  var pointer = canvas.getPointer(opt.e);
  var zoom = canvas.getZoom();
  zoom += delta / 500;
  if (zoom > 10) zoom = 10;
  if (zoom < 0.5) zoom = 0.5;
  canvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom);
  this.requestRenderAll();
  opt.e.preventDefault();
  opt.e.stopPropagation();
});


fabric.StaticCanvas.prototype.drawCopyOnCanvas = function(canvasEl) {
  // save values
  var scaledWidth = this.width,
      scaledHeight = this.height,
      vp = this.viewportTransform,
      originalInteractive = this.interactive,
      newVp = [1, 0, 0, 1, 0, 0],
      originalRetina = this.enableRetinaScaling,
      originalContextTop = this.contextTop;
  // reset
  this.contextTop = null;
  this.enableRetinaScaling = false;
  this.interactive = false;
  this.viewportTransform = newVp;
  this.calcViewportBoundaries();
  // draw on copy
  this.renderCanvas(canvasEl.getContext('2d'), this._objects);
  // restore values
  this.viewportTransform = vp;
  this.calcViewportBoundaries();
  this.interactive = originalInteractive;
  this.enableRetinaScaling = originalRetina;
  this.contextTop = originalContextTop;
}

function afterRender() {
  // remove 'after:render' listener as canvas.toCanvasElement()
  // calls renderCanvas(), which results in an infinite recursion
  canvas.off('after:render', afterRender);
  // draw c1 contents on c2
  canvas.drawCopyOnCanvas(c2);
  
  setTimeout(() => {
    // re-attach the listener in the next event loop
    canvas.on('after:render', afterRender);
  });
}

function copyThePencil() {
  if (this._isCurrentlyDrawing) {
    ctx2.save();
    var m = fabric.util.invertTransform(this.viewportTransform);
    ctx2.transform.apply(ctx2, m);
    ctx2.drawImage(this.upperCanvasEl, 0, 0, this.upperCanvasEl.width / this.getRetinaScaling(), this.upperCanvasEl.height / this.getRetinaScaling())
    ctx2.restore();
  }
}

canvas.on('after:render', afterRender);

canvas.on('mouse:move', copyThePencil);





















var $ = function(id){return document.getElementById(id)};

  fabric.Object.prototype.transparentCorners = false;

  var drawingModeEl = $('drawing-mode'),
      drawingOptionsEl = $('drawing-mode-options'),
      drawingColorEl = $('drawing-color'),
      drawingShadowColorEl = $('drawing-shadow-color'),
      drawingLineWidthEl = $('drawing-line-width'),
      drawingShadowWidth = $('drawing-shadow-width'),
      drawingShadowOffset = $('drawing-shadow-offset'),
      clearEl = $('clear-canvas');

  clearEl.onclick = function() { canvas.clear() };

  drawingModeEl.onclick = function() {
    canvas.isDrawingMode = !canvas.isDrawingMode;
    if (canvas.isDrawingMode) {
      drawingModeEl.innerHTML = 'Cancel drawing mode';
      drawingOptionsEl.style.display = '';
    }
    else {
      drawingModeEl.innerHTML = 'Enter drawing mode';
      drawingOptionsEl.style.display = 'none';
    }
  };

  if (fabric.PatternBrush) {
    var vLinePatternBrush = new fabric.PatternBrush(canvas);
    vLinePatternBrush.getPatternSrc = function() {

      var patternCanvas = fabric.document.createElement('canvas');
      patternCanvas.width = patternCanvas.height = 10;
      var ctx = patternCanvas.getContext('2d');

      ctx.strokeStyle = this.color;
      ctx.lineWidth = 5;
      ctx.beginPath();
      ctx.moveTo(0, 5);
      ctx.lineTo(10, 5);
      ctx.closePath();
      ctx.stroke();

      return patternCanvas;
    };

    var hLinePatternBrush = new fabric.PatternBrush(canvas);
    hLinePatternBrush.getPatternSrc = function() {

      var patternCanvas = fabric.document.createElement('canvas');
      patternCanvas.width = patternCanvas.height = 10;
      var ctx = patternCanvas.getContext('2d');

      ctx.strokeStyle = this.color;
      ctx.lineWidth = 5;
      ctx.beginPath();
      ctx.moveTo(5, 0);
      ctx.lineTo(5, 10);
      ctx.closePath();
      ctx.stroke();

      return patternCanvas;
    };

    var squarePatternBrush = new fabric.PatternBrush(canvas);
    squarePatternBrush.getPatternSrc = function() {

      var squareWidth = 10, squareDistance = 2;

      var patternCanvas = fabric.document.createElement('canvas');
      patternCanvas.width = patternCanvas.height = squareWidth + squareDistance;
      var ctx = patternCanvas.getContext('2d');

      ctx.fillStyle = this.color;
      ctx.fillRect(0, 0, squareWidth, squareWidth);

      return patternCanvas;
    };

    var diamondPatternBrush = new fabric.PatternBrush(canvas);
    diamondPatternBrush.getPatternSrc = function() {

      var squareWidth = 10, squareDistance = 5;
      var patternCanvas = fabric.document.createElement('canvas');
      var rect = new fabric.Rect({
        width: squareWidth,
        height: squareWidth,
        angle: 45,
        fill: this.color
      });

      var canvasWidth = rect.getBoundingRect().width;

      patternCanvas.width = patternCanvas.height = canvasWidth + squareDistance;
      rect.set({ left: canvasWidth / 2, top: canvasWidth / 2 });

      var ctx = patternCanvas.getContext('2d');
      rect.render(ctx);

      return patternCanvas;
    };

    var img = new Image();
    img.src = 'https://cdn-images-1.medium.com/max/1800/1*sg-uLNm73whmdOgKlrQdZA.jpeg';

    var texturePatternBrush = new fabric.PatternBrush(canvas);
    texturePatternBrush.source = img;
  }

  $('drawing-mode-selector').onchange = function() {

    if (this.value === 'hline') {
      canvas.freeDrawingBrush = vLinePatternBrush;
    }
    else if (this.value === 'vline') {
      canvas.freeDrawingBrush = hLinePatternBrush;
    }
    else if (this.value === 'square') {
      canvas.freeDrawingBrush = squarePatternBrush;
    }
    else if (this.value === 'diamond') {
      canvas.freeDrawingBrush = diamondPatternBrush;
    }
    else if (this.value === 'texture') {
      canvas.freeDrawingBrush = texturePatternBrush;
    }
    else {
      canvas.freeDrawingBrush = new fabric[this.value + 'Brush'](canvas);
    }

    if (canvas.freeDrawingBrush) {
      canvas.freeDrawingBrush.color = drawingColorEl.value;
      canvas.freeDrawingBrush.width = parseInt(drawingLineWidthEl.value, 10) || 1;
      canvas.freeDrawingBrush.shadow = new fabric.Shadow({
        blur: parseInt(drawingShadowWidth.value, 10) || 0,
        offsetX: 0,
        offsetY: 0,
        affectStroke: true,
        color: drawingShadowColorEl.value,
      });
    }
  };

  drawingColorEl.onchange = function() {
    canvas.freeDrawingBrush.color = this.value;
  };
  drawingShadowColorEl.onchange = function() {
    canvas.freeDrawingBrush.shadow.color = this.value;
  };
  drawingLineWidthEl.onchange = function() {
    canvas.freeDrawingBrush.width = parseInt(this.value, 10) || 1;
    this.previousSibling.innerHTML = this.value;
  };
  drawingShadowWidth.onchange = function() {
    canvas.freeDrawingBrush.shadow.blur = parseInt(this.value, 10) || 0;
    this.previousSibling.innerHTML = this.value;
  };
  drawingShadowOffset.onchange = function() {
    canvas.freeDrawingBrush.shadow.offsetX = parseInt(this.value, 10) || 0;
    canvas.freeDrawingBrush.shadow.offsetY = parseInt(this.value, 10) || 0;
    this.previousSibling.innerHTML = this.value;
  };

  if (canvas.freeDrawingBrush) {
    canvas.freeDrawingBrush.color = drawingColorEl.value;
    canvas.freeDrawingBrush.width = parseInt(drawingLineWidthEl.value, 10) || 1;
    canvas.freeDrawingBrush.shadow = new fabric.Shadow({
      blur: parseInt(drawingShadowWidth.value, 10) || 0,
      offsetX: 0,
      offsetY: 0,
      affectStroke: true,
      color: drawingShadowColorEl.value,
    });
  }
canvas {
  border: 1px solid black;
}
#static {
  position: relative;
  top: -300px;
  left: 330px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.3/fabric.js"></script>
This is interactive canvas
<canvas id="scale" width="300" height="300"></canvas>
<canvas id="static" width="300" height="300"></canvas>
<div style="display: inline-block; margin: 0px 0px 0px 210px">
  <button id="drawing-mode" class="btn btn-info">Cancel drawing mode</button><br>
  <button id="clear-canvas" class="btn btn-info">Clear</button><br>

  <div id="drawing-mode-options">
    <label for="drawing-mode-selector">Mode:</label>
    <select id="drawing-mode-selector">
      <option>Pencil</option>
      <option>Circle</option>
      <option>Spray</option>
      <option>Pattern</option>

      <option>hline</option>
      <option>vline</option>
      <option>square</option>
      <option>diamond</option>
      <option>texture</option>
    </select><br>

    <label for="drawing-line-width">Line width:</label>
    <span class="info">30</span><input type="range" value="30" min="0" max="150" id="drawing-line-width"><br>

    <label for="drawing-color">Line color:</label>
    <input type="color" value="#005E7A" id="drawing-color"><br>

    <label for="drawing-shadow-color">Shadow color:</label>
    <input type="color" value="#005E7A" id="drawing-shadow-color"><br>

    <label for="drawing-shadow-width">Shadow width:</label>
    <span class="info">0</span><input type="range" value="0" min="0" max="50" id="drawing-shadow-width"><br>

    <label for="drawing-shadow-offset">Shadow offset:</label>
    <span class="info">0</span><input type="range" value="0" min="0" max="50" id="drawing-shadow-offset"><br>
  </div>
</div>
AndreaBogazzi
  • 14,323
  • 3
  • 38
  • 63
  • 1
    Hello! Thanks for the answer! Your solution looks amazing. Tell me, please, is it possible to add freehand drawn content from the top level of one fabric canvas to another low-level fabric canvas? Please, look at this codepen https://codepen.io/vinar22/pen/vYNmzXp – vinkovsky Apr 28 '20 at 02:30