html5画布大尺寸精灵图像动画性能优化

问题描述 投票:0回答:1

我正在使用http://www.williammalone.com/articles/create-html5-canvas-javascript-sprite-animation/在画布上渲染精灵表动画

并且大多数情况下都可以正常工作

但是当Sprite Sheet图像为large时(在我的情况下为30000 * 500,60帧png文件),性能非常差,尤其是在Android Webview上。

我认为图像资源占用了太多内存,但是如何优化其内存使用率?我已经尝试通过“ ImageOptim”压缩图像,但是并没有什么明显的区别。

有人可以在画布上渲染大精灵表格时给我一些有关性能优化的建议吗?

animation canvas html5-canvas sprite-sheet
1个回答
0
投票

使用ImageBitmaps

您的图像可能太大了,无法保存在图形卡的内存中,这意味着每次必须将其再次移动到那里时,这的确会使速度确实变慢。

ImageBitmap对象可以使原始位图数据随时可供GPU使用,而无需额外使用。因此,在初始化时,您需要准备ImageBitmaps并丢弃完整的图像。现在,只有准备好放置的小位图才会传递给GPU。

async function initSprites( img, sprite_width = img.width, sprite_height = img.height ) {
  // you would have to make the sprite factory logic based on your actual spritesheet,
  // here Im using a simple fixed-size grid from wikimedia
  const sprites = [];
  for( let y = 0; y < img.height; y += sprite_height) {
    for( let x = 0; x < img.width; x += sprite_width ) {
      sprites.push(
        await createImageBitmap( img, x, y, sprite_width, sprite_height )
      );
    }
  }
  return sprites;
}
async function loadImage( url ) {
  var img = new Image();
  img.crossOrigin = 'anonymous';
  await new Promise( (res, rej) => {
    img.onload = (e) => res( img );
    img.onerror = rej;
    img.src = url;
  } );
  return img;
}

async function init() {
  const url = "https://upload.wikimedia.org/wikipedia/commons/b/be/SpriteSheet.png";
  const FPS = 10;
  const sprite_width = 132;
  const sprite_height = 97;
  
  // we don't keep any reference to the Image
  const sprites = await initSprites( await loadImage( url ), sprite_width, sprite_height );
  
  const canvas = document.getElementById( 'canvas' );
  const ctx = canvas.getContext( '2d' );
  const frame_time = 1000 / FPS;
  let sprite_index = 0;
  let lastTime = performance.now();
  
  requestAnimationFrame( update );
  
  function update( t ) {
    if( t - lastTime > frame_time ) {
      lastTime = t;
      sprite_index = (sprite_index + 1) % sprites.length;
      draw();
    }
    requestAnimationFrame( update );
  }
  
  function draw() {
    ctx.clearRect( 0, 0, canvas.width, canvas.height );
    // we just reproduce the original spritesheet
    // but animated
    let inner_index = 0;
    for(let y = 0; y < 3; y++) {
      for( let x = 0; x < 4; x ++ ) {
        inner_index ++;
        const index = (sprite_index + inner_index) % sprites.length;
        const sprite = sprites[ index ];
        ctx.drawImage( sprite, x * sprite_width, y * sprite_height );
      }
    }
  }
}

// for Safari and IE
monkeyPatchCreateImageBitmap();

init().catch( console.error );


// a very poor monkey patch, only allowing HTMLImageElement as source
// returns an HTMLCanvasElement
function monkeyPatchCreateImageBitmap() {
  if( typeof window.createImageBitmap === 'undefined' ) {
    window.createImageBitmap = monkeyPatch;
  }
  function monkeyPatch( img, sx = 0 , sy = 0, sw = img.width, sh = img.height ){
    const canvas = document.createElement( 'canvas' );
    canvas.width = sw;
    canvas.height = sh;
    canvas.getContext( '2d' )
      .drawImage( img, sx, sy, sw, sh, 0, 0, sw, sh );
    canvas.close = (e) => canvas.width = 0;
    return Promise.resolve( canvas );
  };
}
<canvas id="canvas" width="528" height="291"></canvas>
© www.soinside.com 2019 - 2024. All rights reserved.