将对象放置在定义的形状上
Place objects on a defined shape
我正在寻找一种方法,允许仅在图像区域进行拖放。
所以,如果图像如下:
我可以选择在顶部添加另一个图层,其中包含一些文本,但是那个新图层不能去任何地方,而是在该图像的形状内。
所以,经过一些研究,我仍然想知道如何才能实现这样的目标?
我知道我可以使用 map
和 area
来映射图像上的现有元素,但是如何添加仅适合该地图的新元素?有什么想法吗?
为了说明您的解决方案,我们假设您想在云中滴一滴雨滴,并确保所有雨滴像素都完全在云中...
那么你的问题的答案需要问这个问题:
Are all opaque raindrop pixels fully inside the cloud?
要回答这个问题,您必须将雨滴上的每个像素与下面的每个像素进行比较。
- 如果雨滴像素点是透明的,那么就忽略这个像素点,反正这部分雨滴点是透明的。
- 如果雨滴像素不透明而下面的像素是透明的,那么这个雨滴像素不包含在云中。
- 如果雨滴像素和下面的像素都是不透明的,那么这个雨滴像素包含在云中。
您可以通过在 canvas 上绘制雨滴和云的图像然后请求 getImageData
来获得所需的有关雨滴和云的透明度信息。 'getImageData' returns 关于 canvas 上每个像素的红色、绿色、蓝色和 alpha 信息。要回答这个问题,我们只需要 alpha 信息。
这是带注释的代码和演示:
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var offsetX,offsetY;
// load the cloud and raindrop images
var cloudmap,rainmap;
var rain=new Image();
rain.crossOrigin='anonymous';
rain.onload=start;
rain.src='https://dl.dropboxusercontent.com/u/139992952/raindrop1.png';
var cloud=new Image();
cloud.crossOrigin='anonymous';
cloud.onload=start;
cloud.src="https://dl.dropboxusercontent.com/u/139992952/cloud.png";
var cloud1=new Image();
cloud1.crossOrigin='anonymous';
cloud1.onload=start;
cloud1.src="https://dl.dropboxusercontent.com/u/139992952/multple/cloud1.png";
var imageCount=3;
function start(){
if(--imageCount>0){return;}
// resize the canvas to the size of the cloud
// and draw the cloud on the canvas
cw=canvas.width=cloud.width;
ch=canvas.height=cloud.height;
draw();
// create a transparency map of the cloud
cloudmap={
width:cloud.width,
height:cloud.height,
map:transparencyMap(cloud),
};
// create a transparency map of the raindrop
rainmap={
width:rain.width,
height:rain.height,
map:transparencyMap(rain),
}
// listen for mousemove events
$("#canvas").mousemove(function(e){handleMouseMove(e);});
// listen for window scroll events
calcCanvasOffset();
$(window).scroll(function(){ calcCanvasOffset(); });
}
function transparencyMap(img){
// create a temp canvas sized to the img size
var c=document.createElement('canvas');
var cctx=c.getContext('2d');
c.width=img.width;
c.height=img.height;
// draw the img on the canvas
cctx.drawImage(img,0,0);
// get the pixel data for every pixel on the canvas
var data=cctx.getImageData(0,0,c.width,c.height).data;
// create an array that reports the status
// of every pixel on the canvas
// (status: true if opaque, false if transparent)
var map=[];
for(var i=0;i<data.length;i+=4){
map.push(data[i+3]>250);
}
return(map);
}
function draw(mouseX,mouseY,isContained){
// draw the cloud
ctx.clearRect(0,0,cw,ch);
if(isContained){
// draw the blue cloud indicating the raindrop is not fully contained
ctx.drawImage(cloud,0,0);
}else{
// draw the yellow cloud indicating the raindrop is fully contained
ctx.drawImage(cloud1,0,0);
}
// if the mouse position was supplied
if(mouseX){
ctx.drawImage(rain,mouseX-rain.width/2,mouseY-rain.height/2);
}
}
function AcontainsB(ax,ay,amap,bx,by,bmap){
// set a flag indicating of the raindrop is fully contained in the cloud
var isContained=true;
// calc the relative position of the raindrop vs cloud in the canvas
var deltaX=bx-ax;
var deltaY=by-ay;
// test every pixel of B against A
// if B is opaque and a is not opaque then B is not contained by A
var y=0;
while(isContained && y<bmap.height){
var x=0;
while(isContained && x<bmap.width){
// calc the map array indexes for the cloud(A) & raindrop(B)
var mapIndexA=(y+deltaY)*amap.width+(x+deltaX);
var mapIndexB=y*bmap.width+x;
// if the raindrop is opaque at this pixel
if(bmap.map[mapIndexB]){
// ...and if this pixel is off canvas
if(mapIndexA<0 || mapIndexA>=amap.map.length){
// ...then the raindrop is not in the cloud at this pixel
isContained=false;
// ...or if the pixel under the raindrop is transparent
}else if(!amap.map[mapIndexA]){
// ...then the raindrop is not in the cloud at this pixel
isContained=false;
}
}
x++;
}
y++;
}
return(isContained);
}
function handleMouseMove(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get the current mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// calc the top-left corner of the raindrop image
var rainX=parseInt(mouseX-rain.width/2);
var rainY=parseInt(mouseY-rain.height/2);
// ask if the raindrop is fully contained in the cloud
var isContained=AcontainsB(0,0,cloudmap,rainX,rainY,rainmap);
// redraw the cloud & raindrop
draw(mouseX,mouseY,isContained);
}
// recalc the canvas offsetX & offsetY
function calcCanvasOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Use the mouse to drag the raindrop over the canvas<br>The cloud turns blue if the rain is fully inside the cloud</h4>
<canvas id="canvas" width=600 height=500></canvas>
您可以使用此 "transparency mapping" 来测试可以绘制到 canvas 中的任何内容,包括图像和文本。请注意,文本是用 context.fillText
.
绘制的
如果您要删除 canvas 外部的文本元素(可能使用 jqueryUI 或本机 "draggable"),您将必须:
获取水滴的 x,y 位置。
获取拖放元素的文本内容。
创建一个包含文本的临时 canvas。这样做...
function textToCanvas(text,fontsize,fontface){
var c=document.createElement('canvas');
var cctx=c.getContext('2d');
cctx.font=fontsize+'px '+fontface;
var textWidth=cctx.measureText(text).width;
c.width=textWidth;
c.height=fontsize+4;
cctx.font=fontsize+'px '+fontface;
cctx.textBaseline='top';
cctx.fillText(text,0,0);
return(c);
}
像图像一样使用临时canvas创建透明贴图。这是可能的,因为 canvas 将接受另一个 canvas 作为其图像源。
祝你项目顺利。
[来自评论的其他问题]
补充问题:
"How will this react when you have a raindrop placed somewhere in the
cloud (saved it's position), and then you try to add something that's
on top of it?" Said another way: "How can I test if 2 objects are
overlapping?"
答案:您可以再次使用透明度贴图来测试两个对象是否重叠。创建另一个测试 (AintersectsB),测试 A 中的任何像素是否不透明,而 B 中的关联像素是否也是不透明的。您可以从 AcontainsB
开始并修改它以创建 AintersectsB
测试。
补充问题:
"How can I save and later restore dropped object positions?"
答:既然canvas不记得画的是什么,那你一定要记住。这通常是通过为每个掉落的物品创建一个 javascript 对象并将所有这些对象保存在一个数组中来完成的。这样,如果您需要在服务器上保存位置,您可以使用 JSON.stringify
将对象数组转换为字符串并将该字符串发送到服务器以保存在数据库(或文件)中。为了重新创建您的作品,服务器从数据库中提取字符串并将其发送到浏览器。浏览器使用 JSON.parse
将字符串转回 javascript 对象数组。然后您可以使用对象中的信息完全按照原样重新绘制场景。
我正在寻找一种方法,允许仅在图像区域进行拖放。
所以,如果图像如下:
我可以选择在顶部添加另一个图层,其中包含一些文本,但是那个新图层不能去任何地方,而是在该图像的形状内。
所以,经过一些研究,我仍然想知道如何才能实现这样的目标?
我知道我可以使用 map
和 area
来映射图像上的现有元素,但是如何添加仅适合该地图的新元素?有什么想法吗?
为了说明您的解决方案,我们假设您想在云中滴一滴雨滴,并确保所有雨滴像素都完全在云中...
那么你的问题的答案需要问这个问题:
Are all opaque raindrop pixels fully inside the cloud?
要回答这个问题,您必须将雨滴上的每个像素与下面的每个像素进行比较。
- 如果雨滴像素点是透明的,那么就忽略这个像素点,反正这部分雨滴点是透明的。
- 如果雨滴像素不透明而下面的像素是透明的,那么这个雨滴像素不包含在云中。
- 如果雨滴像素和下面的像素都是不透明的,那么这个雨滴像素包含在云中。
您可以通过在 canvas 上绘制雨滴和云的图像然后请求 getImageData
来获得所需的有关雨滴和云的透明度信息。 'getImageData' returns 关于 canvas 上每个像素的红色、绿色、蓝色和 alpha 信息。要回答这个问题,我们只需要 alpha 信息。
这是带注释的代码和演示:
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var offsetX,offsetY;
// load the cloud and raindrop images
var cloudmap,rainmap;
var rain=new Image();
rain.crossOrigin='anonymous';
rain.onload=start;
rain.src='https://dl.dropboxusercontent.com/u/139992952/raindrop1.png';
var cloud=new Image();
cloud.crossOrigin='anonymous';
cloud.onload=start;
cloud.src="https://dl.dropboxusercontent.com/u/139992952/cloud.png";
var cloud1=new Image();
cloud1.crossOrigin='anonymous';
cloud1.onload=start;
cloud1.src="https://dl.dropboxusercontent.com/u/139992952/multple/cloud1.png";
var imageCount=3;
function start(){
if(--imageCount>0){return;}
// resize the canvas to the size of the cloud
// and draw the cloud on the canvas
cw=canvas.width=cloud.width;
ch=canvas.height=cloud.height;
draw();
// create a transparency map of the cloud
cloudmap={
width:cloud.width,
height:cloud.height,
map:transparencyMap(cloud),
};
// create a transparency map of the raindrop
rainmap={
width:rain.width,
height:rain.height,
map:transparencyMap(rain),
}
// listen for mousemove events
$("#canvas").mousemove(function(e){handleMouseMove(e);});
// listen for window scroll events
calcCanvasOffset();
$(window).scroll(function(){ calcCanvasOffset(); });
}
function transparencyMap(img){
// create a temp canvas sized to the img size
var c=document.createElement('canvas');
var cctx=c.getContext('2d');
c.width=img.width;
c.height=img.height;
// draw the img on the canvas
cctx.drawImage(img,0,0);
// get the pixel data for every pixel on the canvas
var data=cctx.getImageData(0,0,c.width,c.height).data;
// create an array that reports the status
// of every pixel on the canvas
// (status: true if opaque, false if transparent)
var map=[];
for(var i=0;i<data.length;i+=4){
map.push(data[i+3]>250);
}
return(map);
}
function draw(mouseX,mouseY,isContained){
// draw the cloud
ctx.clearRect(0,0,cw,ch);
if(isContained){
// draw the blue cloud indicating the raindrop is not fully contained
ctx.drawImage(cloud,0,0);
}else{
// draw the yellow cloud indicating the raindrop is fully contained
ctx.drawImage(cloud1,0,0);
}
// if the mouse position was supplied
if(mouseX){
ctx.drawImage(rain,mouseX-rain.width/2,mouseY-rain.height/2);
}
}
function AcontainsB(ax,ay,amap,bx,by,bmap){
// set a flag indicating of the raindrop is fully contained in the cloud
var isContained=true;
// calc the relative position of the raindrop vs cloud in the canvas
var deltaX=bx-ax;
var deltaY=by-ay;
// test every pixel of B against A
// if B is opaque and a is not opaque then B is not contained by A
var y=0;
while(isContained && y<bmap.height){
var x=0;
while(isContained && x<bmap.width){
// calc the map array indexes for the cloud(A) & raindrop(B)
var mapIndexA=(y+deltaY)*amap.width+(x+deltaX);
var mapIndexB=y*bmap.width+x;
// if the raindrop is opaque at this pixel
if(bmap.map[mapIndexB]){
// ...and if this pixel is off canvas
if(mapIndexA<0 || mapIndexA>=amap.map.length){
// ...then the raindrop is not in the cloud at this pixel
isContained=false;
// ...or if the pixel under the raindrop is transparent
}else if(!amap.map[mapIndexA]){
// ...then the raindrop is not in the cloud at this pixel
isContained=false;
}
}
x++;
}
y++;
}
return(isContained);
}
function handleMouseMove(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get the current mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// calc the top-left corner of the raindrop image
var rainX=parseInt(mouseX-rain.width/2);
var rainY=parseInt(mouseY-rain.height/2);
// ask if the raindrop is fully contained in the cloud
var isContained=AcontainsB(0,0,cloudmap,rainX,rainY,rainmap);
// redraw the cloud & raindrop
draw(mouseX,mouseY,isContained);
}
// recalc the canvas offsetX & offsetY
function calcCanvasOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Use the mouse to drag the raindrop over the canvas<br>The cloud turns blue if the rain is fully inside the cloud</h4>
<canvas id="canvas" width=600 height=500></canvas>
您可以使用此 "transparency mapping" 来测试可以绘制到 canvas 中的任何内容,包括图像和文本。请注意,文本是用 context.fillText
.
如果您要删除 canvas 外部的文本元素(可能使用 jqueryUI 或本机 "draggable"),您将必须:
获取水滴的 x,y 位置。
获取拖放元素的文本内容。
创建一个包含文本的临时 canvas。这样做...
function textToCanvas(text,fontsize,fontface){ var c=document.createElement('canvas'); var cctx=c.getContext('2d'); cctx.font=fontsize+'px '+fontface; var textWidth=cctx.measureText(text).width; c.width=textWidth; c.height=fontsize+4; cctx.font=fontsize+'px '+fontface; cctx.textBaseline='top'; cctx.fillText(text,0,0); return(c); }
像图像一样使用临时canvas创建透明贴图。这是可能的,因为 canvas 将接受另一个 canvas 作为其图像源。
祝你项目顺利。
[来自评论的其他问题]
补充问题:
"How will this react when you have a raindrop placed somewhere in the cloud (saved it's position), and then you try to add something that's on top of it?" Said another way: "How can I test if 2 objects are overlapping?"
答案:您可以再次使用透明度贴图来测试两个对象是否重叠。创建另一个测试 (AintersectsB),测试 A 中的任何像素是否不透明,而 B 中的关联像素是否也是不透明的。您可以从 AcontainsB
开始并修改它以创建 AintersectsB
测试。
补充问题:
"How can I save and later restore dropped object positions?"
答:既然canvas不记得画的是什么,那你一定要记住。这通常是通过为每个掉落的物品创建一个 javascript 对象并将所有这些对象保存在一个数组中来完成的。这样,如果您需要在服务器上保存位置,您可以使用 JSON.stringify
将对象数组转换为字符串并将该字符串发送到服务器以保存在数据库(或文件)中。为了重新创建您的作品,服务器从数据库中提取字符串并将其发送到浏览器。浏览器使用 JSON.parse
将字符串转回 javascript 对象数组。然后您可以使用对象中的信息完全按照原样重新绘制场景。