在网格中填充多个不同图像的画布形状 [英] Filling canvas shapes with multiple different images in a grid

查看:93
本文介绍了在网格中填充多个不同图像的画布形状的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用画布创建一个六边形网格,我试图从图像中填充每个瓷砖的特定模式。下面的代码是我正在使用的。



最后的结果是一个六角形网格,具有图案全部具有相同的图像...它不应该是。我想发生的是,它正在为每个瓷砖的模式创建一个覆盖,但该图像基本上覆盖所有瓷砖...我只有最后看到最后一个图像调用。



我的印象是,我的fill()只填充那个小六角形形状...而不是所有的。我如何做到这样每个单独的十六进制形状可以有自己的形象?



这个代码在for循环中运行以创建网格。这是我的drawHex()方法。我不能想象,我需要一个全新的画布为每个瓷砖,使这种情况发生。

  var numberOfSides = 6,
size = hex.properties.radius,
Xcenter = hexObj.x +(hex.properties.width / 2),
Ycenter = hexObj.y +(hex.properties.height / 2) ;

var img = new Image();
if(hexObj.t ==grassland){
img.src =/static/grass.jpg;
} else {
img.src =/static/mountain.jpg;
}

var pattern = context.createPattern(img,repeat);
context.fillStyle = pattern;
context.beginPath();
context.moveTo(Xcenter + size * Math.cos(0),Ycenter + size * Math.sin(0));
for(var i = 1; i <= numberOfSides; i + = 1){
context.lineTo(Xcenter + size * Math.cos(i * 2 * Math.PI / numberOfSides), Ycenter + size * Math.sin(i * 2 * Math.PI / numberOfSides));
}
context.fill();
context.closePath();
context.stroke();


解决方案

首先重要的是了解图片加载是如何工作的。这是一个异步操作,这意味着您必须等待映像加载,然后才能继续。



这种方法会将策略指定为每次需要时设置映像源改变一个图像也会迫使你处理它的异步方面会有延迟等等。此外,如果在创建模式时尚未加载图片,样式将无法呈现。



因此,更好的方法是预加载所有您将使用的图像(或单个sprite表)。



绑定图块






$ b b $ b

然后,您可以存储引用每个图像的多个 CanvasPattern 对象。一种方法是构建一个tile对象,其中包含该tile的所有信息。



  function Tile(ctx,x,y,radius,img){
this.pattern = ctx.createPattern(img,repeat);
//在这里存储其他属性像x,y,radius等。
}
Tile.prototype.render = function(ctx){
ctx.beginPath();
//在这里创建十六进制形状
ctx.fillStyle = this.pattern;
ctx.fill();
}

现在您可以创建 Tile object并将其保存在数组(或母对象)中:

  var tiles = []; 
tiles.push(new Tile(ctx,x2,y2,radius,img1); // img1已经加载了(onload)
tiles.push ); // img2也已加载(onload)
//等

立即呈现它们:

  tiles.forEach(function(tile){tile.render(ctx)}); 



加载图片



以加载所有图像,缺点是用户必须等待图像加载,除非你有一个前端可以占据用户的同时图像在后台加载。



装载器不必复杂,但是对于生产,您将需要处理错误( onerror / onabort )。当所有图像都已加载时,此示例将调用 start()函数:

  var images = []; 
var urls = [//image1.jpg,//image2.jpg,...];
var count = urls。 length;

function handler(){
if(! - count)start();
}

urls.forEach(function(url){
var img = new Image;
images.push(img);
img.onload = handler;
//img.onerror = ... //错误处理程序并中止这里
//img.onabort = ...
img.src = url;
})



一个小演示



  var images = [],urls = [//i.imgur.com/DAg71N5.jpg?1,//i.imgur.com/ZO3XQpj.jpg (ctx,x,x,x,y)的函数处理函数(?) y,radius,img){this.pattern = ctx.createPattern(img,repeat); this.x = x; this.y = y; this.radius = radius;} Tile.prototype.render = function(ctx){ctx.beginPath(); for(var i = 0; i  

 < canvas id = c>< / canvas>  



提示



十六进制形状可以这样绘制:

  for(var i = 0; i  ctx.lineTo(this.x + Math.cos(i)* this。 radius,this.y + Math.sin(i)* this.radius); 

注意,这需要 beginPath()。这允许我们传递 moveTo()作为新路径上的第一个 lineTo()将移动路径 - 移动到其起点。



为了保持模式相对于形状,您可以使用 translate()上下文在绘制它们之前相对于(0,0),这也简化了十六进制图形:

  ctx.translate this.y); 
ctx.moveTo(this.radius,0);
for(var i = 0; i ctx.lineTo(Math.cos(i)* this.radius, sin(i)* this.radius);
//如果需要,可在此处取消转换

在较新的浏览器中,您可以使用 setTransform()



迷你更新为了重复使用,您可以考虑创建一个外部模式

//html.spec.whatwg.org/multipage/scripting.html#dom-context-2d-createpatternrel =nofollow> specs有点不清楚什么模式做到复制的图像。唯一的要求是对图像源的更改必须在创建后不会影响模式,这可能意味着也可能不意味着图像在内部被复制,总是或只是当条件需要它时:


修改在
之后创建CanvasPattern对象时使用的图像调用createPattern()方法不得影响模式
呈现CanvasPattern对象。


在任何情况下,在上面的例子中应该有足够的肉给你一个基础和理解如何攻击问题。根据需要修改!


I am creating a hexagonal grid using canvas, and I'm trying to fill each tile with a specific pattern from an image. The following code is what I'm working with.

The end result is a hexagonal grid that has tiles patterned all with the same image... and it shouldn't be. I think what is happening is that it's creating an overlay for the pattern for every tile, but that image basically covers all tiles... and I only ever end up seeing that last image called.

I was under the impression that my fill() was only filling that small hexagon shape... and not all of them. How can I make this so each individual hex shape can have it's own image?

This code is run in a for loop to create the grid. This is my drawHex() method. I can't imagine that I need a whole new canvas for each tile to make this happen.

var numberOfSides = 6,
size = hex.properties.radius,
Xcenter = hexObj.x + (hex.properties.width / 2),
Ycenter = hexObj.y + (hex.properties.height / 2);

var img = new Image();
if (hexObj.t == "grassland"){
    img.src = "/static/grass.jpg";
}else{
    img.src = "/static/mountain.jpg";
}

var pattern = context.createPattern(img, "repeat");
context.fillStyle = pattern;
context.beginPath();
context.moveTo (Xcenter +  size * Math.cos(0), Ycenter +  size *  Math.sin(0)); 
for (var i = 1; i <= numberOfSides;i += 1) {
    context.lineTo (Xcenter + size * Math.cos(i * 2 * Math.PI / numberOfSides), Ycenter + size * Math.sin(i * 2 * Math.PI / numberOfSides));
}
context.fill();
context.closePath();
context.stroke();   

解决方案

First important thing is to understand how image loading is working. This is an asynchronous operation which means that you have to wait for the image to load before continue.

This sort of will dictate the strategy as setting image source each time you want to change an image will also force you to handle the asynchronous aspect of it will the delays and so forth. Also, if the image(s) hasn't been loading at the time of creating a pattern the style will fail to render.

So a better approach is to preload all the images (or a single sprite-sheet) that you will use. Then hold on to them in memory (this is usually not a problem today if the images aren't gigantic).

Bundling tile

You can then store several CanvasPattern objects referencing each image. One way is to build a tile object holding all information about that tile incl. its pattern.

For example:

function Tile(ctx, x, y, radius, img) {
  this.pattern = ctx.createPattern(img, "repeat");
  // store other properties here like x, y, radius etc.
}
Tile.prototype.render = function(ctx) {
  ctx.beginPath();
  // create hex shape here
  ctx.fillStyle = this.pattern;
  ctx.fill();
}

Now you can create a Tile object and hold it in an array (or a mother object):

var tiles = [];
tiles.push(new Tile(ctx, x1, y1, radius, img1);  // img1 has loaded (onload)
tiles.push(new Tile(ctx, x2, y2, radius, img2);  // img2 has also loaded (onload)
// etc.

Then simply render them at once like:

tiles.forEach(function(tile) { tile.render(ctx) });

Loading images

You will need an image loader to load all images. The drawback is that the user has to wait for the images to load unless you have a front that can occupy the user meanwhile the images are loading in the background.

A loader doesn't have to be complicated, but for production you would want to handle errors (onerror/onabort). This example will call the function start() when all images has loaded:

var images = [];
var urls = ["//image1.jpg", "//image2.jpg", ...];
var count = urls.length;

function handler() {
  if (!--count) start();
}

urls.forEach(function(url) {
  var img = new Image;
  images.push(img);
  img.onload = handler;
  //img.onerror = ...  // handler for error and abort here
  //img.onabort = ...
  img.src = url;
})

A small demo

var images = [], urls = ["//i.imgur.com/DAg71N5.jpg?1", "//i.imgur.com/ZO3XQpj.jpg?1"],
    tiles = [], count = urls.length, ctx = c.getContext("2d");

function handler() {if (!--count) start()}

function Tile(ctx, x, y, radius, img) {
  this.pattern = ctx.createPattern(img, "repeat");
  this.x = x;
  this.y = y;
  this.radius = radius;
}
Tile.prototype.render = function(ctx) {
  ctx.beginPath();
  for(var i = 0; i < Math.PI*2; i += Math.PI/3)
    ctx.lineTo(this.x + Math.cos(i) * this.radius, this.y + Math.sin(i) * this.radius);
  ctx.fillStyle = this.pattern;
  ctx.fill();
}

urls.forEach(function(url) {
  var img = new Image;
  images.push(img);
  img.onload = handler;
  img.src = url;
});

function start() {
  tiles.push(new Tile(ctx, 50, 50, 50, images[0]));
  tiles.push(new Tile(ctx, 130, 95, 50, images[1]));
  tiles.forEach(function(tile) { tile.render(ctx) });
}

<canvas id=c></canvas>

Tips

Hex shape can be drawn this way:

for(var i = 0; i < Math.PI*2; i += Math.PI/3)
  ctx.lineTo(this.x + Math.cos(i) * this.radius, this.y + Math.sin(i) * this.radius);

Note that this requires beginPath(). This allows us to pass on the moveTo() as the first lineTo() on the new path will move the path-cursor to its start point.

To keep patterns relative to the shape you can use translate() on the context before drawing them relative to (0,0) which also simplify the hex drawing:

ctx.translate(this.x, this.y);
ctx.moveTo(this.radius, 0);
for(var i = 0; i < Math.PI*2; i += Math.PI/3)
  ctx.lineTo(Math.cos(i) * this.radius, Math.sin(i) * this.radius);
// cancel transforms here if needed

In newer browsers you can use setTransform() on the pattern itself. This is not supported in all browsers so be careful..

Mini-update For reuse purposes you can consider creating a pattern outside the object as part of the loading process, so that you only use a reference to the pattern for each tile.

The specs are a little unclear on what patterns do to images copy-wise. The only requirement is that changes to the image source must not affect the pattern after it has been created, which may or may not mean the image is copied internally, always or just when the condition for it demands it:

Modifying the image used when creating a CanvasPattern object after calling the createPattern() method must not affect the pattern(s) rendered by the CanvasPattern object.

In any case, there should be enough meat in the examples above to give you a basis and understanding to how to attack the problem. Modify as needed!

这篇关于在网格中填充多个不同图像的画布形状的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆