micronyks
micronyks

Reputation: 55443

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.

Upvotes: 7

Views: 2637

Answers (2)

speciesUnknown
speciesUnknown

Reputation: 1763

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>

Upvotes: 1

Simon Sarris
Simon Sarris

Reputation: 63872

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.

Upvotes: 4

Related Questions