0

Does anyone know what is wrong with the following code.

Demo: http://jsbin.com/xecicovi/1/

It runs fine on windows and android but the cursor inconsistently leaves some trail lines on iPad. I am using getImageData and XOR'ing the bits and putImageData to draw and erase a rectangular cursor block.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title> New Document </title>
<script src="jquery-1.11.0.js"></script>
<script language="javascript">
var m_ctx;
var m_canvas;
var m_bCursorOn = false;
var m_nXpos = 0;

function DrawCursor()
{
   var nCellY = 10;
   var nCellH = 24;
   var nCellX = m_nXpos;
   var nCellW = 13;

   m_bCursorOn = !m_bCursorOn;

   // XOR the cursor location image to draw/erase the cursor
   var imgData = m_ctx.getImageData( nCellX, nCellY, nCellW, nCellH );
   var data = imgData.data;
   for(var i = 0; i < data.length; i += 4) 
   {
      data[i] ^= 255;
      data[i + 1] ^= 255;
      data[i + 2] ^= 255;      
   }
   m_ctx.putImageData( imgData, nCellX, nCellY );
}

function MoveCursor()
{
   if (m_bCursorOn)
      DrawCursor();

   m_nXpos += 13;
   if ( m_nXpos >1000)
      m_nXpos = 0;

   DrawCursor();
}

window.onload = function() 
{
   m_canvas = $('#myCanvas')[0];
   m_ctx = m_canvas.getContext("2d");
   m_canvas.width  = window.innerWidth;
   m_canvas.height = window.innerHeight;
   m_ctx.fillStyle = "black";
   m_ctx.fillRect( 0, 0, m_canvas.width, m_canvas.height );
   setInterval( function(){ DrawCursor();}, 301 );  // cursor blink
   setInterval( function(){ MoveCursor();}, 501 );
};

</script>
</head>

<body >
<canvas id="myCanvas" width="800" height="600"></canvas>
</body>
</html>
Rex Hui
  • 71
  • 1
  • 7
  • I'm not sure but I think it could be a problem of sub-pixel smoothing, basically you draw your square on round numbers but for the browser that means that it will have to draw half here and half there, and when it deletes the old cursor it leaves behind those approximations. Try drawing your square to .5 numbers (like 200.5, 157.5, ...) and see if it solves – Jonas Grumann Apr 07 '14 at 19:16
  • Thanks Jonas for the prompt answer. But I would think it would be the other way around if there is sub-pixel smoothing. I mean if I specify a whole number, it should be a specific pixel. Only if I specify a decimal number that it would need to do the thing you are saying, right? – Rex Hui Apr 07 '14 at 19:26
  • That's the thing, in the canvas element it doesn't work like this. – Jonas Grumann Apr 07 '14 at 19:31
  • I answered this some days ago: http://stackoverflow.com/questions/22780675/html-canvas-drawing-shows-through/22781020#22781020 – Jonas Grumann Apr 07 '14 at 19:32
  • thanks. I tried with 0.5 but it is still the same: http://jsbin.com/xecicovi/3/edit – Rex Hui Apr 07 '14 at 19:50
  • And why does this only happen on iPad, but not Android and Windows? – Rex Hui Apr 07 '14 at 21:27
  • The `0.5` trick does not work if zoom is set, (typically `window.devicePixelRatio`), is not 1. – user13500 Apr 07 '14 at 21:50
  • Not that it has, or should have, any effect in your code ... – user13500 Apr 07 '14 at 22:06

1 Answers1

0

This is most likely an issue with how the Safari browser deals with the back-buffer and pixel aspect ratio versus fractional widths. It's not related to sub-pixeling as you are dealing with the pixel buffer directly.

You code is correct per-se; a work-around is to force width values to integer values for the canvas

Modify these lines:

m_canvas.width  = window.innerWidth;
m_canvas.height = window.innerHeight;

to

m_canvas.width  = window.innerWidth|0;  // removes fractions
m_canvas.height = window.innerHeight|0;

and the problem should go away in Safari (modified jsbin here).

Hope this helps.

Added back the original answer:

Add this line:

m_canvas.style.width  = ((m_canvaswindow.width/13)|0)*13+'px';

in this location:

window.onload = function() {
    m_canvas = $('#myCanvas')[0];
    m_ctx = m_canvas.getContext("2d");
    m_canvas.width  = window.innerWidth;
    m_canvas.height = window.innerHeight;

    m_canvas.style.width = ((m_canvas.width/13)|0)*13+'px';
    // possibly you will need this for height as well

    m_ctx.fillStyle = "black";
    m_ctx.fillRect( 0, 0, m_canvas.width, m_canvas.height );
    setInterval( DrawCursor, 301 );
    setInterval( MoveCursor, 501 );
};

This will force the size to fit cursor size (if you change cursor size then the 13 there of course must be updated as well - just use a dynamic value instead).

Beyond that: this is clearly an issue with the Safari browser and the answer here will naturally only function as a temporary work-around. There is not much you can do about the issue than wait for it to be solved (you should consider reporting it to the Safari team).

  • Cryptoburner, thank you very much for your answer. Although your answer seems to help (the issue seems to happen less often), it still does not solve the problem completely. I have updated the demo with a mouse click event to move the cursor. You should see the problem after a few tries. http://jsbin.com/xecicovi/8/ – Rex Hui Apr 08 '14 at 14:40
  • @RexHui I added back in the original answer that I edited out (to simplify the answer). See if that improves it. That being said: this is clearly a Safari issue and it will have to be the Safari team that solves this. The answer will only function as a temporary workaround as it cannot solve the issue itself. –  Apr 08 '14 at 21:22
  • Ok, I appreciate your help. – Rex Hui Apr 09 '14 at 20:38