1

I have the following multi-step pixelation animation. It animates from low-to-high pixelation very slowly to show you that it jolts in between some of the steps, as if the image is slightly moved between renderings. I can't figure out why this is happening or how to make it appear as if the image is staying in one place.

var c = document.createElement('canvas')
c.style.display = 'flex'
c.style.width = '100vw'
c.style.height = '100vh'
c.style['image-rendering'] = 'pixelated'
document.body.appendChild(c)

var x = c.getContext('2d')

x.webkitImageSmoothingEnabled = false
x.mozImageSmoothingEnabled = false
x.msImageSmoothingEnabled = false
x.imageSmoothingEnabled = false

var src = c.getAttribute('data-draw')
var small = `https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/M101_hires_STScI-PRC2006-10a.jpg/307px-M101_hires_STScI-PRC2006-10a.jpg`
var large = `https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/M101_hires_STScI-PRC2006-10a.jpg/1280px-M101_hires_STScI-PRC2006-10a.jpg`

// c.width = c.clientWidth
// c.height = c.clientHeight

var stack = []
var start = false
var place = 0

function queue(image, ratio, width, height) {
  stack.push({ image, ratio, width, height })

  if (start) return
  start = true

  setTimeout(proceed, 0)
}

function proceed() {
  let point = stack.shift()
  let w
  let h

  if (point.ratio) {
    w = c.width = c.clientWidth * point.ratio
    h = c.height = c.clientHeight * point.ratio
  } else {
    w = point.width
    h = point.height
  }

  if (!stack.length) {
    x.webkitImageSmoothingEnabled = true
    x.mozImageSmoothingEnabled = true
    x.msImageSmoothingEnabled = true
    x.imageSmoothingEnabled = true
    c.classList.remove('px')
  }

  drawImageProp(x, point.image, 0, 0, w, h)

  if (stack.length) {
    setTimeout(proceed, 1000)
  }
}

var s = new Image()
s.onload = function(){
  queue(s, 0.01)
  queue(s, 0.03)

  var i = new Image()
  i.onload = function(){
    queue(i, 0.03)
    queue(i, 0.11)
    queue(i, 1)
  }
  i.src = large
}
s.src = small

function drawImageProp(ctx, img, x, y, w, h, offsetX, offsetY) {
  if (arguments.length === 2) {
      x = y = 0;
      w = ctx.canvas.width;
      h = ctx.canvas.height;
  }

  // default offset is center
  offsetX = typeof offsetX === "number" ? offsetX : 0.5;
  offsetY = typeof offsetY === "number" ? offsetY : 0.5;

  // keep bounds [0.0, 1.0]
  if (offsetX < 0) offsetX = 0;
  if (offsetY < 0) offsetY = 0;
  if (offsetX > 1) offsetX = 1;
  if (offsetY > 1) offsetY = 1;

  var iw = img.width,
      ih = img.height,
      r = Math.min(w / iw, h / ih),
      nw = iw * r,   // new prop. width
      nh = ih * r,   // new prop. height
      cx, cy, cw, ch, ar = 1;

  // decide which gap to fill
  if (nw < w) ar = w / nw;
  if (Math.abs(ar - 1) < 1e-14 && nh < h) ar = h / nh;  // updated
  nw *= ar;
  nh *= ar;

  // calc source rectangle
  cw = iw / (nw / w);
  ch = ih / (nh / h);

  cx = (iw - cw) * offsetX;
  cy = (ih - ch) * offsetY;

  // make sure source rectangle is valid
  if (cx < 0) cx = 0;
  if (cy < 0) cy = 0;
  if (cw > iw) cw = iw;
  if (ch > ih) ch = ih;

  // fill image in dest. rectangle
  ctx.drawImage(img, cx, cy, cw, ch,  x, y, w, h);
}

Even in between the last two steps it shifts slightly. Wondering what's going wrong and how to fix it.

FYI, there are two images, a small one and a big one, both the same thing. It first loads the small image at low resolution, then loads the large one. While the large one is loading, it does a few animation steps using the small image to start the animation. Then once the large one is done, it picks up where the small one left off and does a few more animation steps using the large image.

Lance
  • 75,200
  • 93
  • 289
  • 503
  • I'm wondering if it has something to do with the fact you are changing the `canvas` elements `width` and `height` to whole numbers on each iteration - this would mean the aspect ratio is _slightly_ off in some cases, i.e. when you check `if (point.ratio)` – chazsolo Jun 04 '19 at 21:28
  • I don't think they are changing to whole numbers, I think I am multiplying by decimals so it should work there. – Lance Jun 04 '19 at 22:40

0 Answers0