【问题标题】:Canvas createPattern() and fill() with an offset带有偏移量的画布 createPattern() 和 fill()
【发布时间】:2020-03-28 11:19:10
【问题描述】:

我正在使用画布绘制简单的矩形并用地板图案 (PNG) 填充它们。但是,如果我使用“相机”脚本来处理 HTML5 画布的变换偏移,矩形形状会适当移动,但填充图案似乎总是从屏幕上的固定点绘制(我假设是左上角)。有没有办法“确定”填充图案,使其始终与矩形对齐,无论画布变换,还是在 fill() 上添加可以在代码中其他地方计算的偏移量的方法?我当然可以只使用 drawImage(),但绘制矩形对我的目的来说更通用。

        sampleDrawFunction(fillTexture, x1, y1, x2, y2) {
        // This is oversimplified, but best I can do with a ~10k lines of code
        x1 -= Camera.posX;
        x2 -= Camera.posX;
        y1 = -1 * y1 - Camera.posY;
        y2 = -1 * y2 - Camera.posY;

        // Coordinates need to be adjusted for where the camera is positioned

        var img = new Image();
        img.src = fillTexture;
        var pattern = this.ctx.createPattern(img, "repeat");
        this.ctx.fillStyle = pattern;

        // Translate canvas's coordinate pattern to match what the camera sees
        this.ctx.save();
        this.ctx.translate(Camera.posX - Camera.topLeftX, Camera.posY - Camera.topLeftY);
        this.ctx.fillStyle = pattern;
        this.ctx.fillRect(x1, y1, x2 - x1, y2 - y1);
        this.ctx.restore();
}

谢谢。

【问题讨论】:

  • 一旦你在画布上画了一些东西,它就变成了像素。绘制完所有内容后应用变换不应最终给出您观察到的结果。你确定你做事的顺序是正确的吗?有没有办法提供minimal, reproducible example?
  • 我没提,刷新60帧,每帧重绘。每个绘制的项目都会应用变换。

标签: javascript html canvas


【解决方案1】:

画布填充(CanvasPatterns 和 CanvasGradients)总是相对于上下文的变换矩阵,所以它们确实默认在画布的左上角,并不真正关心使用它们的路径在哪里。

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = begin; //!\ ALWAYS WAIT FOR YOUR IMAGE TO LOAD BEFORE DOING ANYTHING WITH IT!
img.crossOrigin = "anonymous";
img.src = "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png";

function begin() {
  const rect_size = 20;
  ctx.fillStyle = ctx.createPattern( img, 'no-repeat' );
  // drawing a checkerboard of several rects shows that the pattern doesn't move
  for ( let y = 0; y < canvas.height; y += rect_size ) {
    for ( let x = (y / rect_size % 2) ? rect_size : 0 ; x < canvas.width; x += rect_size * 2 ) {
      ctx.fillRect( x, y, rect_size, rect_size );
    }
  }

}
&lt;canvas id="canvas" width="500" height="500"&gt;&lt;/canvas&gt;

现在,因为它们是相对于上下文变换矩阵的,这意味着我们也可以通过改变变换矩阵来移动它们:

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = begin; //!\ ALWAYS WAIT FOR YOUR IMAGE TO LOAD BEFORE DOING ANYTHING WITH IT!
img.crossOrigin = "anonymous";
img.src = "https://upload.wikimedia.org/wikipedia/commons/f/f7/Cool_bunny_sprite.png";


function begin() {
  const rect_size = 244;
  const max_speed = 5;
  let x_speed = Math.random() * max_speed;
  let y_speed = Math.random() * max_speed;

  ctx.fillStyle = ctx.createPattern( img, 'repeat' );
  
  let x = 0;
  let y = 0;
  requestAnimationFrame( anim );
  
  function anim( now ) {

    clear();

    x = (x + x_speed);
    y = (y + y_speed);
    
    if( x > canvas.width || x < -rect_size ) {
      x_speed = Math.random() * max_speed * -Math.sign( x_speed );
      x = Math.min( Math.max( x, -rect_size ), canvas.width );
    }
    if( y > canvas.height || y < -rect_size ) {
      y_speed = Math.random() * max_speed * -Math.sign( y_speed )
      y = Math.min( Math.max( y, -rect_size ), canvas.height );
    }

    // we change the transformation matrix of our context
    ctx.setTransform( 1, 0, 0, 1, x, y );
    // we thus always draw at coords 0,0
    ctx.fillRect( 0, 0, rect_size, rect_size );
    ctx.strokeRect( 0, 0, rect_size, rect_size );

    requestAnimationFrame( anim );

  }
  function clear() {
    // since we changed the tranform matrix we need to reset it to identity
    ctx.setTransform( 1, 0, 0, 1, 0, 0 );
    ctx.clearRect( 0, 0, canvas.width, canvas.height );

  }
}
&lt;canvas id="canvas" width="300" height="300"&gt;&lt;/canvas&gt;

我们甚至可以通过在声明子路径后更改转换矩阵来将路径声明与填充分离,当然还可以将 速记 fillRect() 替换为 beginPath(); rect(); fill()

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = begin; //!\ ALWAYS WAIT FOR YOUR IMAGE TO LOAD BEFORE DOING ANYTHING WITH IT!
img.crossOrigin = "anonymous";
img.src = "https://upload.wikimedia.org/wikipedia/commons/f/f7/Cool_bunny_sprite.png";


function begin() {
  const rect_size = 244;
  const max_speed = 5;
  let x_speed = Math.random() * max_speed;
  let y_speed = Math.random() * max_speed;

  ctx.fillStyle = ctx.createPattern( img, 'repeat' );
  
  let x = 0;
  let y = 0;
  requestAnimationFrame( anim );
  
  function anim( now ) {

    clear();

    x = (x + x_speed);
    y = (y + y_speed);
    
    if( x > canvas.width || x < -rect_size ) {
      x_speed = Math.random() * max_speed * -Math.sign( x_speed );
      x = Math.min( Math.max( x, -rect_size ), canvas.width );
    }
    if( y > canvas.height || y < -rect_size ) {
      y_speed = Math.random() * max_speed * -Math.sign( y_speed )
      y = Math.min( Math.max( y, -rect_size ), canvas.height );
    }

    // we declare the sub-path first, with identity matrix applied
    ctx.beginPath();
    ctx.rect( 50, 50, rect_size, rect_size );
    // we change the transformation matrix of our context
    ctx.setTransform( 1, 0, 0, 1, x, y );
    // and now we fill
    ctx.fill();
    ctx.stroke();

    requestAnimationFrame( anim );

  }
  function clear() {
    // since we changed the tranform matrix we need to reset it to identity
    ctx.setTransform( 1, 0, 0, 1, 0, 0 );
    ctx.clearRect( 0, 0, canvas.width, canvas.height );

  }
}
&lt;canvas id="canvas" width="300" height="300"&gt;&lt;/canvas&gt;

但在您的情况下,听起来转换整个绘图是最简单和最惯用的方法。不太清楚为什么要修改与相机相关的 x 和 y 坐标。一般来说,如果我们使用相机对象,它是因为场景中的对象不必关心它,并且保持相对于世界。

【讨论】:

  • 我想我明白你的意思了。我一直在为变换添加一个额外的步骤,计算距相机视图中心的偏移量。为什么不让相机和变换都转到世界坐标,根据您的描述,这应该可以解决问题。而且,“从变换矩阵计算”正是我所寻求的描述。非常感谢您的帮助!
  • 作为后续,这个小小的改动让一切变得如此简单。非常感谢您指出这一点。我很感激。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-07
  • 1970-01-01
相关资源
最近更新 更多