图像Alpha通道映射

6
<img src="circle.png" onclick="alert('clicked')"/>

让我们想象一下,circle.png 是一个400x400像素的透明背景图片,在中间有一个圆形。
现在,整个图像区域(400x400像素)都是可点击的。我想要的是只有圆形(非透明像素)是可点击的。
当然,我知道在这个例子中我可以使用标签和圆形区域,但是我正在寻找一种通用的解决方案,它将考虑实际图像的透明度,并适用于任何类型的图像(即非正规形状)。
我能想到的最复杂的方法是基于每个像素的 alpha 跟踪图像轮廓,转换为路径(可能简化),并应用为地图。
是否有更有效/直接的方法来实现呢?

你需要这样做是因为图像将会动态生成吗?虽然我相信它可以用JavaScript实现,但可能会很慢。在Photoshop中使用“魔术棒”等工具也不容易“做对”。如果您正在动态生成图像,请使用源数据创建图像映射。如果您不是,则为什么不离线处理并使用图像映射呢? - Jamie Treworgy
是的,这将是动态的,因为图像也将是动态加载的... - lviggiani
3个回答

17
使用canvas标签,您可以确定给定点下的像素颜色值。您可以使用事件数据来确定坐标,然后检查透明度。最后将图像加载到画布上即可。
首先,我们需要处理一下:
  var ctx = document.getElementById('canvas').getContext('2d');
  var img = new Image();
  img.onload = function(){
    ctx.drawImage(img,0,0);
  };
  img.src = [YOUR_URL_HERE];

首先,获取画布元素,然后创建一个Image对象。当图像加载完成后,它将被绘制在画布上。这很简单明了!但是......如果图像与代码不在同一域中,则会遇到同一域策略安全问题。为了获取我们的图像数据,我们需要将图像本地托管。您还可以对图像进行Base64编码,但这超出了本回答的范围。(如需了解此项工具,请参见此网址。)

接下来,将点击事件附加到画布。当点击事件发生时,我们将检查透明度并仅对非透明的点击区域进行操作:

    if (isTransparentUnderMouse(this, e))
        return;
    // do whatever you need to do
    alert('will do something!');

魔法发生在函数isTransparentUnderMouse中,它需要两个参数:目标画布元素(在点击处理程序的范围内为this)和事件数据(例如,在此示例中为e)。现在我们来到了关键部分:

var isTransparentUnderMouse = function (target, evnt) {
    var l = 0, t = 0;
    if (target.offsetParent) {
        var ele = target;
        do {
            l += ele.offsetLeft;
            t += ele.offsetTop;
        } while (ele = ele.offsetParent);
    }
    var x = evnt.page.x - l;
    var y = evnt.page.y - t;
    var imgdata = target.getContext('2d').getImageData(x, y, 1, 1).data;
    if (
        imgdata[0] == 0 &&
        imgdata[1] == 0 &&
        imgdata[2] == 0 &&
        imgdata[3] == 0
    ){
        return true;
    }
    return false;
};

首先,我们会跳舞来精确定位目标元素的位置。我们要利用这些信息传递到画布元素上。其中的getImageData将给我们一些内容,其中包括我们指定位置的RGBA。data对象。
如果所有这些值都是0,那么我们正在查看透明度。如果不是,就有一些颜色存在。-编辑-如评论中所述,我们实际上只需要查看上面示例中的最后一个imgdata[3]值。这些值为r(0)g(1)b(2)a(3),透明度由a,alpha决定。您可以使用相同的方法查找任何颜色和任何透明度,只要您知道rgba数据即可。
在此处尝试:http://jsfiddle.net/pJ3MD/1/ (注意:在我的示例中,我使用了base64编码图像,因为我提到的域安全性。您可以忽略该部分代码,除非您也打算使用base64编码)
相同的示例,加入鼠标光标变化的更改以供娱乐: http://jsfiddle.net/pJ3MD/2/ 文档:
- MDN上的Image对象 - https://developer.mozilla.org/en/DOM/Image - 在MDN上使用canvas的图像教程 - https://developer.mozilla.org/en/Canvas_tutorial/Using_images - MDN上的Canvas入口 - https://developer.mozilla.org/en/HTML/Canvas - MDN上的HTML canvas元素(getContext) - https://developer.mozilla.org/en/DOM/HTMLCanvasElement/ - MDN上的CanvasRenderingContext2D(getImageData) - https://developer.mozilla.org/en/DOM/CanvasRenderingContext2D - 在MDN上进行像素操作 - https://developer.mozilla.org/En/HTML/Canvas/Pixel_manipulation_with_canvas/

你为这个答案付出的努力值得点赞。此外,因为这是一个好答案,也值得点赞! - Bojangles
1
当然!从技术上讲,你甚至只需要查看 imgdata[3],因为那是 alpha 通道,所以你可以使它更简单。 - Chris Baker
1
@Chris和我今天实现了这种解决方案,并发现了一些注意点:
  1. 你必须在IE中使用FlashCanvas Pro http://flashcanvas.net/,excanvas不支持getImageData(),FlashCanvas免费版也不支持。
- aron.duby
使用具有不同透明度级别的PNG24实际上会在IE(使用FlashCanvas和未使用FlashCanvas的IE9)以及Mac上的Safari中使RGB值出现一位数字错误。保存没有透明度的图像可以解决这个问题。最后,从getImageData()返回的数组不是普通数组,因此您无法使用join,但是Array.apply([], imgdata).join('|')可以工作。 - aron.duby
我曾经在一个HTML5游戏中看到过这种方法,他们使用R通道来确定撞击检测表面。他们有5种材料可以用于制作障碍物,因此A型墙壁是(0,0,0,1),B型墙壁是(1,0,0,1),依此类推。所有墙壁在屏幕上看起来都是黑色的,我认为这很聪明。 - Chris Baker
显示剩余3条评论

4
您可以使用 HTML5 画布来实现此操作。将图像绘制到画布上,将点击处理程序附加到画布,并在处理程序中检查所单击的像素是否是透明的。

2
哦,我们的想法一致,我没看到你的回答,因为我花了一分钟来理清思路!干杯! - Chris Baker

1
感谢Chris提供这个出色的答案。
我添加了一些代码来处理缩放画布的问题。
现在我所做的是创建一个与图像大小完全相同的画布。例如(对于一个220px*120px的图像):
<canvas width="220" height="120" id="mainMenu_item"></canvas>

使用CSS缩放画布:

#mainMenu_item{ width:110px; }

调整后的isTransparentUnderMouse函数如下:

    var isTransparentUnderMouse = function (target, evnt) {

    var l = 0, t = 0;
    if (target.offsetParent) {
        var ele = target;
        do {
            l += ele.offsetLeft;
            t += ele.offsetTop;
        } while (ele = ele.offsetParent);
    }

    var x = evnt.pageX - l;
    var y = evnt.pageY - t;

    var initialWidth = $(evnt.target).attr('width');
    var clientWidth = evnt.target.clientWidth;
    x = x * (initialWidth/clientWidth);

    var initialHeight = $(evnt.target).attr('height');;
    var clientHeight = evnt.target.clientHeight;
    y = y * (initialHeight/clientHeight);

    var imgdata = target.getContext('2d').getImageData(x, y, 1, 1).data;

    if (
        imgdata[0] == 0 &&
        imgdata[1] == 0 &&
        imgdata[2] == 0 &&
        imgdata[3] == 0
    ){
        return true;
    }
    return false;
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接