Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Canvas drawing is very slow

I want to display scale with markings which is working fine. On top of that I also want to display mouse location in the scale with red indicator.

So, I draw canvas when I run the app and then I'm redrawing entire canvas when mouse location is changed.

I'm new to canvas and don't understand whats wrong in my code. I have been trying to resolve it but no luck.

Problem might be in this function,

 function drawBlackMarkers(y, coordinateMeasurment){
    const markHightY = scaleTextPadding.initial;
    ctxLeft.moveTo(coordinateMeasurment, y + markHightY);
    ctxLeft.lineTo(completeMarkHight, y + markHightY);
  }

I'm having a big for loop means so many iterations to go through and in that loop I call drawBlackMarkers function that many times as shown below.

function setMarkers(initialValY, rangeValY, coordinateMeasurmentr, divisableVal,
    scaleCountStartValueOfY, scaleCountRangeValueOfY) {
    let count = 0;
    // re-modifying scale staring and ending values based on zoom factor
    const scaleInceremnt = scaleIncementValue;
    for (let y = (initialValY), scaleCountY = scaleCountStartValueOfY;
      y <= (rangeValY) && scaleCountY <= scaleCountRangeValueOfY;
      y += scaleInceremnt, scaleCountY += incrementFactor) {


      switch (count) {
        case displayScale.starting:
          coordinateMeasurment = marktype.bigMark; count++;
          const scaleValY = scaleCountY - divisableVal;

          ctxLeft.strokeStyle = colors.black;

          ctxLeft.font = scaleNumberFont;
          const size = ctxLeft.measureText(scaleValY.toString());
          ctxLeft.save();
          const textX = coordinateMeasurment + ((size.width) / 2);
          const textY = y - scaleTextPadding.alignment;
          ctxLeft.translate(textX, textY);
          ctxLeft.rotate(-Math.PI / 2);
          ctxLeft.translate(-textX, -textY);
          ctxLeft.fillText(scaleValY.toString(), coordinateMeasurment, y - scaleTextPadding.complete);
          ctxLeft.restore();
          break;
        case displayScale.middle:
          coordinateMeasurment = marktype.middleMark; count++;
          break;
        case displayScale.end:
          coordinateMeasurment = marktype.smallMark; count = 0;
          break;
        default:
          coordinateMeasurment = marktype.smallMark; count++;
          break;
      }

      // to draw scale lines on canvas
  // drawBlackMarkers(y, coordinateMeasurment);      
    }
  }

Please check this : http://jsfiddle.net/3v5nt7fe/1/

The problem is if I comment drawBlackMarkers function call, mouse co-ordinate updation is very fast but if I uncomment, it takes so long to update the location.

I really need help to resolve this issue.

like image 474
Nikhil Shah Avatar asked Sep 13 '20 17:09

Nikhil Shah


People also ask

Is Canvas fast?

The Canvas tab loaded in one second and takes up 30MB. It also takes up 13% of CPU time all of the time, regardless of whether or not one is looking at it. Video on the HTML page, while I am not moving objects, is actually perfectly smooth.

Is Canvas more performant?

In short, the canvas and WebGL are more performant than the DOM, and with third-party libraries, its ease-of-use is comparable; furthermore, growing browser support for additional web standards have the potential to further boost canvas performance.

Why does my Canvas look blurry?

However, the Canvas still looks pixelated. This is because the Canvas is rendering to a bitmap of one size then scaling the bitmap to fit the CSS dimensions. To fix this, we modify the Canvas's bitmap dimensions to match the CSS dimensions using JavaScript.


Video Answer


2 Answers

It's not the drawBlackMarkers itself, it's this:

for (let y = (initialValY), scaleCountY = scaleCountStartValueOfY;
  y <= (rangeValY) && scaleCountY <= scaleCountRangeValueOfY;
  y += scaleInceremnt, scaleCountY += incrementFactor) {

This is constantly increasing and happening 640,000 times. You can tell that's the case by writing:

  // to draw scale lines on canvas
  // drawBlackMarkers(y, coordinateMeasurment);
  console.log(y);

and seeing the console result.

So that for loop does very little, because most of it is behind a switch statement, and when it does even this simple drawBlackMarkers outside its showing the true cost of that loop. rangeValY is 640,000, which means the path the canvas context must construct is enormous.

So to fix this you must find a way to ameliorate that problem.

like image 195
Simon Sarris Avatar answered Oct 19 '22 19:10

Simon Sarris


This is doing a lot of unnecessary work

The screen is not 64000 pixels in height. You want to calculate the viewport, and only draw what is in the viewport.

Your function drawBlackMarkers is not the culprit. The system is very slow before that, its simply adding one more thing to be drawn. It was the straw that broke the camel's back.

By reducing the length of what you are drawing, you can very easily avoid the wasted CPU cycles.

In this version, all I have done is re-enable drawBlackMarkers, and shrink the canvas.

const CANVAS_WIDTH = 2000;
const CANVAS_HEIGHT = 50;
const completeMarkHight = 15;
const divisibleValue = 0;
const scaleIncementValue = 10;
const scaleTextPadding = { initial: 0, middle: 5, end: 10, complete: 15, alignment: 18 };
const displayScale = { starting: 0, middle: 5, end: 9 };
const colors = { red: '#FF0000', white: '#D5D6D7', black: '#181c21' };
const marktype = { bigMark: 0, middleMark: 5, smallMark: 10 };
const startingInitialOrigin = { x: 0, y: 0 };
const scaleNumberFont = '10px Titillium Web Regular';
const defaultZoomLevel = 100;
const markingGap = {level1: 400, level2: 200, level3: 100, level4: 50, level5: 20, level6: 10 };
const zoomScaleLevel = {level0: 0, level1: 25, level2: 50, level3: 100, level4: 200, level5: 500, level6: 1000};



var $canvas = $('#canvas');
var ctxLeft = $canvas[0].getContext('2d');
var mousePositionCoordinates;
var pagePositions = { x: 100, y:0 };
var  remainderX;
var  remainderY;
var  scaleCountRemainderX;
var  scaleCountRemainderY;
var  zoomFactor;
var  zoomScale;
var  zoomLevel;
var  multiplyFactor;
var  incrementFactor;
var  markingDistance;
var  timetaken=0;
ctxLeft.fillStyle = colors.white;

function render() {
    clear();
    ctxLeft.beginPath();
    zoomScale = 1000;
    zoomLevel = 1000;
    zoomFactor = zoomLevel / defaultZoomLevel;
    markingDistance = markingGap.level6;
    multiplyFactor = markingDistance / defaultZoomLevel;
    incrementFactor = markingDistance / scaleIncementValue; 

    renderVerticalRuler(startingInitialOrigin.y);
   
}

 function renderVerticalRuler(posY) {
     
    
    const initialValY = - posY / multiplyFactor;
    const rangeValY = (CANVAS_WIDTH - posY) / multiplyFactor;

    const initialValOfYwithMultiplyFactor = -posY;
    const rangeValOfYwithMultiplyFactor = (CANVAS_WIDTH - posY);


    // to adjust scale count get remainder value based on marking gap
    scaleCountRemainderY = initialValOfYwithMultiplyFactor % markingDistance;
    const scaleCountStartValueOfY = initialValOfYwithMultiplyFactor - scaleCountRemainderY;
    const scaleCountRangeValueOfY = rangeValOfYwithMultiplyFactor - scaleCountRemainderY;

    // to get orgin(0,0) values
    remainderY = initialValY % 100;
    const translateY = (posY / multiplyFactor) - remainderY;

    ctxLeft.translate(origin.x, translateY); // x,y
    const coordinateMeasurment = 0;

    const t0 = performance.now();
    setMarkers(initialValY, rangeValY, coordinateMeasurment, divisibleValue, scaleCountStartValueOfY, scaleCountRangeValueOfY);

    const t1 = performance.now()
    console.log("it took " + (t1 - t0) + " milliseconds.");

    ctxLeft.stroke();
    ctxLeft.closePath();
  }
  
function setMarkers(initialValY, rangeValY, coordinateMeasurmentr, divisableVal,
    scaleCountStartValueOfY, scaleCountRangeValueOfY) {
    let count = 0;
    // re-modifying scale staring and ending values based on zoom factor
    const scaleInceremnt = scaleIncementValue;
    for (let y = (initialValY), scaleCountY = scaleCountStartValueOfY;
      y <= (rangeValY) && scaleCountY <= scaleCountRangeValueOfY;
      y += scaleInceremnt, scaleCountY += incrementFactor) {


      switch (count) {
        case displayScale.starting:
          coordinateMeasurment = marktype.bigMark; count++;
          const scaleValY = scaleCountY - divisableVal;

          ctxLeft.strokeStyle = colors.black;

          ctxLeft.font = scaleNumberFont;
          const size = ctxLeft.measureText(scaleValY.toString());
          ctxLeft.save();
          const textX = coordinateMeasurment + ((size.width) / 2);
          const textY = y - scaleTextPadding.alignment;
          ctxLeft.translate(textX, textY);
          ctxLeft.rotate(-Math.PI / 2);
          ctxLeft.translate(-textX, -textY);
          ctxLeft.fillText(scaleValY.toString(), coordinateMeasurment, y - scaleTextPadding.complete);
          ctxLeft.restore();
          break;
        case displayScale.middle:
          coordinateMeasurment = marktype.middleMark; count++;
          break;
        case displayScale.end:
          coordinateMeasurment = marktype.smallMark; count = 0;
          break;
        default:
          coordinateMeasurment = marktype.smallMark; count++;
          break;
      }

      // to draw scale lines on canvas
   drawBlackMarkers(y, coordinateMeasurment);
    }
  }
  
  
 function drawBlackMarkers(y, coordinateMeasurment){
    const markHightY = scaleTextPadding.initial;
    ctxLeft.moveTo(coordinateMeasurment, y + markHightY);
    ctxLeft.lineTo(completeMarkHight, y + markHightY);
  }
  
  
function clear() {
    ctxLeft.resetTransform();
    ctxLeft.clearRect(origin.x, origin.y, CANVAS_HEIGHT, CANVAS_WIDTH);
}


render();
$('.canvas-container').mousemove(function(e) {
   
    
    mousePositionCoordinates = {x:e.clientX, y:e.clientY};
             
        render();
        
        // SHOW RED INDICATOR 
        ctxLeft.beginPath();
        ctxLeft.strokeStyle = colors.red;  // show mouse indicator
        ctxLeft.lineWidth = 2;

        // to display purple indicator based on zoom level
        const mouseX = mousePositionCoordinates.x * zoomFactor;
        const mouseY = mousePositionCoordinates.y * zoomFactor;
        const markHightY =scaleTextPadding.initial + this.remainderY;
        ctxLeft.moveTo(marktype.bigMark, e.clientY );
        ctxLeft.lineTo(completeMarkHight, e.clientY);
        ctxLeft.stroke();
        $('.mouselocation').text(`${mousePositionCoordinates.x},${mousePositionCoordinates.y}`);
   
});
body, html{
  width: 100000px;
  height:100000px;
}
.canvas-container{
    width:100%;
    height:100%;
}

.canvasLeft {
    position: absolute;
    border:1px solid black;
    background: grey;
    border-top: none;
    z-index: 1;
    top:0
}


.mouselocation{
  position: fixed;
    right: 0px;
    top: 50px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<div class="canvas-container">
  <canvas id="canvas" class="canvasLeft" width="30" height="2000"></canvas>
</div>


<div class="mouselocation">
   
</div>
like image 34
speciesUnknown Avatar answered Oct 19 '22 17:10

speciesUnknown