EaselJS Alpha Mask Filter

13

我对Canvas还比较新。我一直在尝试在EaselJS Alpha Mask示例中翻转图片,使初始图像清晰,而你绘制的内容模糊。基本上是反过来的演示。

我已经尝试玩了几个小时了,对bitmap变量应用过滤器并从blur变量中删除它们。我所做的每件事情都不起作用。似乎只需交换一下就可以轻松解决,但对于我来说并非如此。

有人有这方面的示例或知道该怎么做吗?我可以提供我的代码示例,但基本上只是猴子在打字机上胡闹。

这是Github上的代码

这是他们示例中相关的代码。

<script id="editable">
    var stage;
    var isDrawing;
    var drawingCanvas;
    var oldPt;
    var oldMidPt;
    var displayCanvas;
    var image;
    var bitmap;
    var maskFilter;
    var cursor;
    var text;
    var blur;

    function init() {
        examples.showDistractor();

        image = new Image();
        image.onload = handleComplete;
        image.src = "../_assets/art/flowers.jpg";

        stage = new createjs.Stage("testCanvas");
        //text = new createjs.Text("Loading...", "20px Arial", "#FFF");
        //text.set({x: stage.canvas.width / 2, y: stage.canvas.height - 40});
        //text.textAlign = "center";
    }

    function handleComplete() {
        examples.hideDistractor();
        createjs.Touch.enable(stage);
        stage.enableMouseOver();

        stage.addEventListener("stagemousedown", handleMouseDown);
        stage.addEventListener("stagemouseup", handleMouseUp);
        stage.addEventListener("stagemousemove", handleMouseMove);
        drawingCanvas = new createjs.Shape();
        bitmap = new createjs.Bitmap(image);

        blur = new createjs.Bitmap(image);
        blur.filters = [new createjs.BlurFilter(24, 24, 2), new createjs.ColorMatrixFilter(new createjs.ColorMatrix(60))];
        blur.cache(0, 0, 960, 400);

        //text.text = "Click and Drag to Reveal the Image.";

        stage.addChild(blur, text, bitmap);
        updateCacheImage(false);

        cursor = new createjs.Shape(new createjs.Graphics().beginFill("#FFFFFF").drawCircle(0, 0, 25));
        cursor.cursor = "pointer";

        stage.addChild(cursor);
    }

    function handleMouseDown(event) {
        oldPt = new createjs.Point(stage.mouseX, stage.mouseY);
        oldMidPt = oldPt;
        isDrawing = true;
    }

    function handleMouseMove(event) {
        cursor.x = stage.mouseX;
        cursor.y = stage.mouseY;

        if (!isDrawing) {
            stage.update();
            return;
        }

        var midPoint = new createjs.Point(oldPt.x + stage.mouseX >> 1, oldPt.y + stage.mouseY >> 1);

        drawingCanvas.graphics.setStrokeStyle(40, "round", "round")
                .beginStroke("rgba(0,0,0,0.2)")
                .moveTo(midPoint.x, midPoint.y)
                .curveTo(oldPt.x, oldPt.y, oldMidPt.x, oldMidPt.y);

        oldPt.x = stage.mouseX;
        oldPt.y = stage.mouseY;

        oldMidPt.x = midPoint.x;
        oldMidPt.y = midPoint.y;

        updateCacheImage(true);
    }

    function handleMouseUp(event) {
        updateCacheImage(true);
        isDrawing = false;
    }

    function updateCacheImage(update) {
        if (update) {
            drawingCanvas.updateCache();
        } else {
            drawingCanvas.cache(0, 0, image.width, image.height);
        }

        maskFilter = new createjs.AlphaMaskFilter(drawingCanvas.cacheCanvas);

        bitmap.filters = [maskFilter];
        if (update) {
            bitmap.updateCache(0, 0, image.width, image.height);
        } else {
            bitmap.cache(0, 0, image.width, image.height);
        }

        stage.update();
    }
</script>

为什么不把模糊图像放在底部而不是顶部呢?stage.addChild(bitmap,text,blur);但是easel并没有明确说明他们如何做事情。如果使用普通的JavaScript自己绘制图像,然后使用“destination-out”绘制蒙版,会更容易一些。只需使用Easel来模糊图像即可,不要让它进行渲染。 - Blindman67
@Blindman67 我尝试过反转图像,但不确定EaselJS是否能够这样工作;我无法做到。你是正确的:文档缺乏。你有你建议的示例吗?我是JavaScript新手 - 大多使用jQuery,并没有太多Canvas的经验。 - timgavin
我在下面添加了一个答案,展示了反转。关键是在添加蒙版时需要保留blurImage上的现有过滤器。希望这能帮到你。 - Lanny
2个回答

7

使用Canvas 2D上下文API纯Javascript方式。

您需要创建一个canvas,加载图片,创建mask(遮罩)图像和模糊图像。由于我不想编写模糊效果,因此已经对图像进行了模糊处理。

对象imageTools中的以下函数创建canvas/images,并加载图像。请注意,canvas和图像是可互换的。Canvas没有src,除此之外不能绘制图像。我将所有图像转换为canvas并将上下文附加到它们上面。我也称它们为图像。

/** ImageTools.js begin **/
var imageTools = (function () {
    var tools = {
        canvas : function (width, height) {  // create a blank image (canvas)
            var c = document.createElement("canvas");
            c.width = width;
            c.height = height;
            return c;
        },
        createImage : function (width, height) {
            var image = this.canvas(width, height);
            image.ctx = image.getContext("2d");
            return image;
        },
        loadImage : function (url, callback) {
            var image = new Image();
            image.src = url;
            image.addEventListener('load', callback);
            image.addEventListener('error', callback);
            return image;
        }
    };
    return tools;
})();

然后我使用imageTools加载所需的图像并创建遮罩,当我拥有与图像分辨率匹配的遮罩分辨率时,我会将遮罩与图像进行匹配。

// load the images and create the mask
var imageLoadedCount = 0;
var error = false;
var maskImage;
var flowerImage = imageTools.loadImage("http://www.createjs.com/demos/_assets/art/flowers.jpg", function (event) {
    if (event.type === "load") {
        imageLoadedCount += 1;
    } else {
        error = true;
    }
});
var flowerImageBlur = imageTools.loadImage("http://i.stack.imgur.com/3S5m8.jpg", function () {
    if (event.type === "load") {
        maskImage = imageTools.createImage(this.width, this.height);
        imageLoadedCount += 1;
    } else {
        error = true;
    }
});

我使用 requestAnimationFrame 来创建一个等待图像加载并将三个图层显示在画布上的 60 帧每秒的画布绘制函数。

   // ctx is the main canvas context.
   // drawImageCentered scales the image to fit. See Demo for code.

   // draw the unblured image that will appear at the top
   ctx.globalCompositeOperation = "source-over";
   drawImageCentered(ctx, flowerImage, cw, ch);
   drawText(ctx, "Click drag to blur the image via mask", 40 + Math.sin(time / 100), cw, ch - 30, "White");

   // Mask out the parts when the mask image has pixels
   ctx.globalCompositeOperation = "destination-out";
   drawImageCentered(ctx, maskImage, cw, ch);

   // draw the blured image only where the destination has been masked
   ctx.globalCompositeOperation = "destination-atop";
   drawImageCentered(ctx, flowerImageBlur, cw, ch);

首先绘制出在没有蒙版像素可见时显示的图像,然后绘制一些说明性文字。

接下来是使用 destination-out 的蒙版。这意味着对于蒙版中alpha > 0的像素,从目标(画布)中删除相应的alpha值。因此,如果一个蒙版像素的alpha为50,而目标(画布)的alpha为255,则使用 destination-out 渲染该蒙版后,该像素的结果将是 255 - 50 = 205。这实际上在画布上放置了洞,每个洞都与蒙版上的像素对应。

现在我们可以用模糊图像填充这些洞,并使用 destination-atop 进行渲染,这意味着只在目标 alpha 小于255的地方绘制源(模糊图像)像素。

这就完成了分层蒙版,现在我们只需要在蒙版上进行绘制即可。为此,我们只需监听鼠标事件,如果按下鼠标键,则在鼠标所在的位置在蒙版上绘制一个圆。我的示例对图像进行了缩放,因此还有一些额外的工作,但基本原理如下:

// draws circle with gradient
function drawCircle(ctx, x, y, r) {
    var gr = ctx.createRadialGradient(x, y, 0, x, y, r)
        gr.addColorStop(1, "rgba(0,0,0,0)")
        gr.addColorStop(0.5, "rgba(0,0,0,0.08)")
        gr.addColorStop(0, "rgba(0,0,0,0.1)")
        ctx.fillStyle = gr;
    ctx.beginPath();
    ctx.arc(x, y, r, 0, Math.PI * 2);
    ctx.fill();
}
// draw a circle on the mask where the mouse is.
drawCircle(maskImage.ctx, mouse.x, mouse.y, 20);

对于演示,有一些额外的代码可以使它们更好地工作,但您可以挑选出所需的部分。

var imageLoadedCount = 0;
var error = false;
var maskImage;
var flowerImage;
var flowerImageBlur;
/** ImageTools.js begin **/
var imageTools = (function () {
    var tools = {
        canvas : function (width, height) {  // create a blank image (canvas)
            var c = document.createElement("canvas");
            c.width = width;
            c.height = height;
            return c;
        },
        createImage : function (width, height) {
            var image = this.canvas(width, height);
            image.ctx = image.getContext("2d");
            return image;
        },
        loadImage : function (url, callback) {
            var image = new Image();
            image.src = url;
            image.addEventListener('load', callback);
            image.addEventListener('error', callback);
            return image;
        }
    };
    return tools;
})();




var mouse;
var demo = function(){
    /** fullScreenCanvas.js begin **/
    var canvas = (function(){
        var canvas = document.getElementById("canv");
        if(canvas !== null){
            document.body.removeChild(canvas);
        }
        // creates a blank image with 2d context
        canvas = document.createElement("canvas"); 
        canvas.id = "canv";    
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight; 
        canvas.style.position = "absolute";
        canvas.style.top = "0px";
        canvas.style.left = "0px";
        canvas.style.zIndex = 1000;
        canvas.ctx = canvas.getContext("2d"); 
        document.body.appendChild(canvas);
        return canvas;
    })();
    var ctx = canvas.ctx;
    
    /** fullScreenCanvas.js end **/
    /** MouseFull.js begin **/
    if(typeof mouse !== "undefined"){  // if the mouse exists 
        if( mouse.removeMouse !== undefined){
            mouse.removeMouse(); // remove previouse events
        }
    }else{
        var mouse;
    }
    var canvasMouseCallBack = undefined;  // if needed
    mouse = (function(){
        var mouse = {
            x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false,
            interfaceId : 0, buttonLastRaw : 0,  buttonRaw : 0,
            over : false,  // mouse is over the element
            bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
            getInterfaceId : function () { return this.interfaceId++; }, // For UI functions
            startMouse:undefined,
            mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
        };
        function mouseMove(e) {
            var t = e.type, m = mouse;
            m.x = e.offsetX; m.y = e.offsetY;
            if (m.x === undefined) { m.x = e.clientX; m.y = e.clientY; }
            m.alt = e.altKey;m.shift = e.shiftKey;m.ctrl = e.ctrlKey;
            if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
            } else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];
            } else if (t === "mouseout") { m.buttonRaw = 0; m.over = false;
            } else if (t === "mouseover") { m.over = true;
            } else if (t === "mousewheel") { m.w = e.wheelDelta;
            } else if (t === "DOMMouseScroll") { m.w = -e.detail;}
            if (canvasMouseCallBack) { canvasMouseCallBack(mouse); }
            e.preventDefault();
        }
        function startMouse(element){
            if(element === undefined){
                element = document;
            }
            mouse.element = element;
            mouse.mouseEvents.forEach(
                function(n){
                    element.addEventListener(n, mouseMove);
                }
            );
            element.addEventListener("contextmenu", function (e) {e.preventDefault();}, false);
        }
        mouse.removeMouse = function(){
            if(mouse.element !== undefined){
                mouse.mouseEvents.forEach(
                    function(n){
                        mouse.element.removeEventListener(n, mouseMove);
                    }
                );
                canvasMouseCallBack = undefined;
            }
        }
        mouse.mouseStart = startMouse;
        return mouse;
    })();
    if(typeof canvas !== "undefined"){
        mouse.mouseStart(canvas);
    }else{
        mouse.mouseStart();
    }
    /** MouseFull.js end **/
    
    // load the images and create the mask
    if(imageLoadedCount === 0){
        imageLoadedCount = 0;
        error = false;
        maskImage;
        flowerImage =    imageTools.loadImage("http://www.createjs.com/demos/_assets/art/flowers.jpg", function (event) {
            if (event.type === "load") {
                imageLoadedCount += 1;
            } else {
                error = true;
            }
        })
        flowerImageBlur = imageTools.loadImage("http://i.stack.imgur.com/3S5m8.jpg", function () {
            if (event.type === "load") {
                maskImage = imageTools.createImage(this.width, this.height);
                imageLoadedCount += 1;
            } else {
                error = true;
            }
        })
    }
    // set up the canvas 
    var w = canvas.width;
    var h = canvas.height;
    var cw = w / 2;
    var ch = h / 2;


    // calculate time to download image using the MS algorithum. As this code is a highly gaurded secret I have obsficated it for your personal safty.
    var calculateTimeToGo= (function(){var b="# SecondQMinuteQHourQDayQWeekQMonthQMomentQTick@.,Some time soon,Maybe Tomorrow.".replace(/Q/g,"@.,# ").split(","),r=Math.random,f=Math.floor,lc=0,pc=0,lt=0,lp=0;var cttg=function(a){if(lc===0){lc=100+r(r()*60);lt=f(r()*40);if(pc===0||r()<(lp/b.length)-0.2){lp=f(r()*b.length);pc=1+f(r()*10)}else{pc-=1}}else{lc-=1}a=lt;if(lp===0){a=lt;if(r()<0.01){lt-=1}}var s=b[lp].replace("#",a);if(a===1){s=s.replace("@","")}else{s=s.replace("@","s")}return s};return cttg})();    

    // draws circle with gradient
    function drawCircle(ctx, x, y, r) {
        var gr = ctx.createRadialGradient(x, y, 0, x, y, r)
            gr.addColorStop(1, "rgba(0,0,0,0)")
            gr.addColorStop(0.5, "rgba(0,0,0,0.08)")
            gr.addColorStop(0, "rgba(0,0,0,0.1)")
            ctx.fillStyle = gr;
        ctx.beginPath();
        ctx.arc(x, y, r, 0, Math.PI * 2);
        ctx.fill();
    }
    // draw text
    function drawText(ctx, text, size, x, y, c) {
        ctx.fillStyle = c;
        ctx.strokeStyle = "black";
        ctx.lineWidth = 5;
        ctx.lineJoin = "round";
        ctx.font = size + "px Arial Black";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        if (c !== "black") {
            ctx.strokeText(text, x, y + 1);
        }
        ctx.fillText(text, x, y);
    }
    // draw the image to fit the current canvas size
    function drawImageCentered(ctx, image, x, y) {
        var scale = Math.min(w / image.width, h / image.height);
        ctx.setTransform(scale, 0, 0, scale, cw, ch);
        ctx.drawImage(image, -image.width / 2, -image.height / 2);
        ctx.setTransform(1, 0, 0, 1, 0, 0);
    }
    // points for filling gaps between mouse moves.
    var lastMX,lastMY;
    // update function will try 60fps but setting will slow this down.    
    function update(time){
        ctx.setTransform(1, 0, 0, 1, 0, 0); // restore transform
        ctx.clearRect(0, 0, w, h); // clear rhe canvas
        // have the images loaded???
        if (imageLoadedCount === 2) {
            // draw the unblured image that will appear at the top
            ctx.globalCompositeOperation = "source-over";
            drawImageCentered(ctx, flowerImage, cw, ch);
            drawText(ctx, "Click drag to blur the image via mask", 20 + Math.sin(time / 100), cw, ch - 30, "White");
            // Mask out the parts when the mask image has pixels
            ctx.globalCompositeOperation = "destination-out";
            drawImageCentered(ctx, maskImage, cw, ch);
            // draw the blured image only where the destination has been masked
            ctx.globalCompositeOperation = "destination-atop";
            drawImageCentered(ctx, flowerImageBlur, cw, ch);

            // is the mouse down
            if (mouse.buttonRaw === 1) {
                // because image has been scaled need to get mouse coords on image
                var scale = Math.min(w / flowerImage.width, h / flowerImage.height);
                var x = (mouse.x - (cw - (maskImage.width / 2) * scale)) / scale;
                var y = (mouse.y - (ch - (maskImage.height / 2) * scale)) / scale;
                // draw circle on mask
                drawCircle(maskImage.ctx, x, y, 20);
                // if mouse is draging then draw some points between to fill the gaps
                if (lastMX !== undefined) {
                    drawCircle(maskImage.ctx, ((x + lastMX) / 2 + x) / 2, ((y + lastMY) / 2 + y) / 2, 20);
                    drawCircle(maskImage.ctx, (x + lastMX) / 2, (y + lastMY) / 2, 20);
                    drawCircle(maskImage.ctx, ((x + lastMX) / 2 + lastMX) / 2, ((y + lastMY) / 2 + lastMY) / 2, 20);
                }
                // save las mouse pos on image
                lastMX = x;
                lastMY = y;
            } else {
                // undefined last mouse pos
                lastMX = undefined;

            }
        } else {
            // Laoding images so please wait.
            drawText(ctx, "Please wait.", 40 + Math.sin(time / 100), cw, ch - 30, "White");
            drawText(ctx, "loading images... ", 12, cw, ch, "black")
            drawText(ctx, "ETA " + calculateTimeToGo(time), 14, cw, ch + 20, "black")
        }
        
        // if not restart the request animation frame
        if(!STOP){
            requestAnimationFrame(update);
        }else{
            var can = document.getElementById("canv");
            if(can !== null){
                document.body.removeChild(can);
            }        
            STOP = false;
            
        }
    }

    update();
   
}
var STOP = false; // flag to tell demo app to stop
function resizeEvent() {
    var waitForStopped = function () {
        if (!STOP) { // wait for stop to return to false
            demo();
            return;
        }
        setTimeout(waitForStopped, 200);
    }
    STOP = true;
    setTimeout(waitForStopped, 100);
}
window.addEventListener("resize", resizeEvent);
demo();
/** FrameUpdate.js end **/


谢谢,这正是我要找的。 :) - timgavin
当调整大小时,页面会变空白(图像消失)。有什么我应该注意以防止这种情况发生? - timgavin
当你调整画布大小时,它会自动清除。这时候你需要重新绘制。 - Lanny
@timgavin 我已经修改了演示,使其保留现有的图像和掩码。我只是将图像和掩码设为全局变量,并在窗口调整大小时检查它们是否已加载。如果它们已经加载,我就不会再次加载。 - Blindman67
@Blindman67,非常感谢您的辛勤工作和帮助。我必须把答案和赏金授予Lanny,因为他提供了我所需要的东西,即使用EaselJS和EaselJS演示文稿来完成此操作的方法。我感到很遗憾我更改了接受的答案,并希望我能为您做些什么。如果有办法给您一些积分,请告诉我,我会尽力而为。再次感谢您的努力。谢谢! - timgavin
4
@timgavin 谢谢...我不关心赏金或积分,只是在这里尽我所能地帮助别人。 :) - Blindman67

3

要完成此操作,需要进行几个步骤。其中大部分步骤可能已经完成:

1)改变添加元素的顺序。由于您希望揭示模糊效果,因此请倒序添加元素。这将使模糊效果位于顶层。

stage.addChild(bitmap, text, blur);

2) 在updateCacheImage方法中更改缓存的内容或更新缓存:

if (update) {
    blur.updateCache(0, 0, image.width, image.height);
} else {
    blur.cache(0, 0, image.width, image.height);
}

这可能是你卡住的地方。如果你将blurImage的过滤器仅设置为maskFilter,它看起来不会起作用。实际上,maskFilter正在工作,但已经移除了应用的模糊和颜色过滤器。要添加maskFilter,你必须将其放入当前过滤器数组中。这是我的方法,确保原始的两个过滤器保持完整,只需添加一次maskFilter

blur.filters.length = 2; // Truncate the array to 2
blur.filters.push(maskFilter); // add the new filter

在我看来,这种效果并不是特别明显 - 因此您可能希望增加画笔的不透明度:

drawingCanvas.graphics.setStrokeStyle(40, "round", "round")
    .beginStroke("rgba(0,0,0,0.5)"); // From 0.2

我是EaselJS中原始AlphaMaskFilter演示的作者 - 很高兴你发现它有用和/或有趣!


谢谢!我一定是再次将某些东西放错了位置,因为它仍然无法工作。我已经重新开始,并尝试在从Github下载的.zip文件中包含的/EaselJS-master/examples/AlphaMaskReveal.html文件上使其工作。我肯定在某个地方被绊倒了。如果我能让这个工作,那么我就可以反向工程并找出我想要的东西。我尝试创建一个JSFiddle,但无法在那里使其工作。您能否提供一个完整的可工作示例?我会非常感激! - timgavin
这是我的修改版本:http://playpen.createjs.com/AlphaMaskReveal-reverse.html - Lanny
还创建了一个fiddle。主要更改是添加CORS支持以加载跨域图像。http://jsfiddle.net/lannymcnie/gxj6n6xn/ - Lanny
1
你救了我的命。非常感谢你! - timgavin
如果您使用 createjs.Touch.enable(stage);,它应该可以正常工作。 - Lanny

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