如何在JavaScript画布中将笔画/轮廓添加到透明PNG图像 [英] How to add stroke/outline to transparent PNG image in JavaScript canvas

查看:254
本文介绍了如何在JavaScript画布中将笔画/轮廓添加到透明PNG图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用JavaScript画布向透明PNG图像添加轮廓/笔触效果的最简单方法是什么?



最受欢迎的

  • 如何制作Photoshop笔画效果?

  • 如何使画布勾勒出悬停发光的透明png


  • 解决方案

    这是在图片上添加贴纸效果的一种方法......



    演示: http://jsfiddle.net/m1erickson/Q2j3L/





    首先将原始图片绘制到主画布。





    将图像分解为离散元素。



    离散元素由彼此连接但未连接到其他元素的像素组组成。例如,spritesheet上的每个sprite都是离散元素。



    您可以使用边缘检测算法(如行进方块)找到离散像素组。



    将每个离散元素放在自己的画布上进行进一步处理。同时从主画布中删除该离散元素(因此不会再次处理)。





    检测每个离散元素的轮廓路径。



    您可以再次使用行进方块算法进行边缘检测。行进方块的结果是一个x / y坐标数组,形成元素的外部轮廓



    创建贴纸效果



    您可以通过在每个元素周围添加描边白色轮廓来创建贴纸效果。通过抚摸上面计算的轮廓路径来执行此操作。您可以选择为笔划添加阴影。



    注意:画布笔划始终是半内部和中部。半路外。这意味着贴纸笔划将侵入元素内部。要解决此问题:绘制贴纸笔划后,应将元素重新绘制在顶部。这会覆盖贴纸笔划的入侵部分。





    重新构图包括贴纸效果的最终图像



    通过将每个元素的画布分层到主画布上来重新组合最终图像





    以下是带注释的示例代码:

     <!doctype html> 
    < html>
    < head>
    < link rel =stylesheettype =text / cssmedia =allhref =css / reset.css/> < ;! - reset css - >
    < script src =http://code.jquery.com/jquery.min.js>< / script>
    < script src =marching squares.js>< / script>
    < style>
    body {background-color:silver; }
    canvas {border:1px solid red;}
    < / style>
    < script>
    $(function(){

    //画布相关变量
    var canvas = document.getElementById(canvas);
    var ctx = canvas.getContext( 2d);

    //像素操作中使用的变量
    var canvases = [];
    var imageData,data,imageData1,data1;

    //贴纸大纲的大小
    var strokeWeight = 8;

    //边缘检测方法使用的真/假函数
    var defineNonTransparent = function(x,y){
    返回(data1 [(y * cw + x)* 4 + 3]> 0);
    }

    //接收贴纸效果的图片
    var img = new Image();
    img.crossOrigin =anonymous;
    img.onload = start;
    img.src =https://dl.dropboxusercontent.com/u /139992952/multple/makeIndividual.png;
    //img.src=\"https://dl.dropboxusercontent.com/u/139992952/stackoverflow/angryBirds.png;

    function start(){

    //将主画布调整为图像大小
    canvas.width = CW = img.width;
    canvas.height = ch = img.height;

    //在主画布上绘制图像
    ctx.drawImage(img,0,0);

    //将每个离散元素从主画布移动到单独的画布
    //贴纸效果单独应用于每个离散元素,
    //单独完成每个离散元素的画布
    while(moveDiscreteElementToNewCanvas()){}

    //将贴纸效果添加到所有离散元素(每个画布)
    for(var i = 0; i< canvases.length; i ++){
    addStickerEffect(canvases [i],strokeWeight);
    ctx.drawImage(canvases [i],0,0);
    }

    //重绘原始图像
    //(必要因为贴纸效果
    //稍微侵入离散元素)
    ctx。的drawImage(IMG,0,0);

    }

    //
    函数addStickerEffect(canvas,strokeWeight){
    var url = canvas.toDataURL();
    var ctx1 = canvas.getContext(2d);
    var pts = canvas.outlinePoints;
    addStickerLayer(ctx1,pts,strokeWeight);
    var imgx = new Image();
    imgx.onload = function(){
    ctx1.drawImage(imgx,0,0);
    }
    imgx.src = url;
    }


    函数addStickerLayer(context,points,weight){

    imageData = context.getImageData(0,0,canvas.width,canvas 。高度);
    data1 = imageData.data;

    var points = geom.contour(defineNonTransparent);

    defineGeomPath(context,points)
    context.lineJoin =round;
    context.lineCap =round;
    context.strokeStyle =white;
    context.lineWidth = weight;
    context.stroke();
    }

    //这个函数在图像上找到离散元素
    //(离散元素==一组像素不接触
    //另一组像素 - 例如
    上的每个sprite // spritesheet是一个谨慎的元素)
    function moveDiscreteElementToNewCanvas(){

    //获取主画布的imageData
    的imageData = ctx.getImageData(0,0,canvas.width,canvas.height);
    data1 = imageData.data;

    // test&如果主画布为空,则返回
    //注意:如果画布为空,请执行此操作b / geom.contour将导致致命错误
    var hit = false;
    for(var i = 0; i< data1.length; i + = 4){
    if(data1 [i + 3]> 0){hit = true; break;}
    }
    if(!hit){return;}

    //获取概述离散元素的点路径
    var points = geom.contour(defineNonTransparent);

    //创建一个新画布并将其附加到页面
    var newCanvas = document.createElement('canvas');
    newCanvas.width = canvas.width;
    newCanvas.height = canvas.height;
    document.body.appendChild(newCanvas);
    canvases.push(newCanvas);
    var newCtx = newCanvas.getContext('2d');

    //将轮廓点附加到新画布(稍后需要)
    newCanvas.outlinePoints = points;

    //将该元素绘制到新画布
    defineGeomPath(newCtx,points);
    newCtx.save();
    newCtx.clip();
    newCtx.drawImage(canvas,0,0);
    newCtx.restore();

    //从主画布中删除元素
    defineGeomPath(ctx,points);
    ctx.save();
    ctx.clip();
    ctx.globalCompositeOperation =destination-out;
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.restore();

    return(true);
    }


    //效用函数
    //在画布上定义路径而不抚摸或填充该路径
    函数defineGeomPath(context,points) {
    context.beginPath();
    context.moveTo(points [0] [0],points [0] [1]);
    for(var i = 1; i< points.length; i ++){
    context.lineTo(points [i] [0],points [i] [1]);
    }
    context.lineTo(points [0] [0],points [0] [1]);
    context.closePath();
    }

    }); // end $(function(){});
    < / script>
    < / head>
    < body>
    < canvas id =canvaswidth = 300 height = 300>< / canvas>< br>
    < / body>
    < / html>

    这是一个行进方块边缘检测算法(来自优秀的开源d3库):

      / ** 
    *使用< a
    * href计算给定输入网格函数的轮廓=http://en.wikipedia.org/wiki/Marching_squares>行进
    * square< / a>算法。返回轮廓多边形作为点数组。
    *
    * @param grid一个双输入函数(x,y),对于轮廓内的值
    *返回true,对轮廓外的值返回false。
    * @param在网格上启动一个可选的起点[x,y]。
    * @returns polygon [[x1,y1],[x2,y2],...]

    * /
    (function(){

    geom = {};
    geom.contour = function(grid,start){
    var s = start || d3_geom_contourStart(grid),//起点
    c = [],/ / contour polygon
    x = s [0],//当前x位置
    y = s [1],//当前y位置
    dx = 0,//下一个x方向
    dy = 0,//下一个y方向
    pdx = NaN,//前一个x方向
    pdy = NaN,//前一个y方向
    i = 0;

    do {
    //确定行进方块索引
    i = 0;
    if(grid(x-1,y-1))i + = 1;
    if(grid(x) ,y-1))i + = 2;
    if(grid(x-1,y))i + = 4;
    if(grid(x,y))i + = 8;

    //确定下一个方向
    if(i === 6){
    dx = pdy === -1?-1:1;
    dy = 0 ;
    }否则if(i === 9){
    dx = 0;
    dy = pdx === 1?-1 :1;
    } else {
    dx = d3_geom_contourDx [i];
    dy = d3_geom_contourDy [i];
    }

    //更新轮廓多边形
    if(dx!= pdx&& dy!= pdy){
    c.push([x,y ]);
    pdx = dx;
    pdy = dy;
    }

    x + = dx;
    y + = dy;
    } while(s [0]!= x || s [1]!= y);

    返回c;
    };

    //行进路线的查询表
    var d3_geom_contourDx = [1,0,1,1,-1,0,-1,1,0,0,0,0, - 1,0,-1,NaN],
    d3_geom_contourDy = [0,-1,0,0,0,-1,0,0,1,-1,1,1,0,-1,0大,NaN];

    函数d3_geom_contourStart(grid){
    var x = 0,
    y = 0;

    //搜索起点;从原点开始
    //继续向外扩展对角线
    而(true){
    if(grid(x,y)){
    return [x,y];
    }
    if(x === 0){
    x = y + 1;
    y = 0;
    } else {
    x = x - 1;
    y = y + 1;
    }
    }
    }

    })();

    注意:此代码将贴纸轮廓应用的过程分为单独的功能。如果你想在你的离散元素周围有多个图层,那就完成了。例如,您可能需要在贴纸笔划的外侧使用第二个灰色边框。如果您不需要应用图层,那么您可以在 moveDiscreteElementToNewCanvas 函数中应用贴纸笔划。


    What is the easiest way to add an outline/stroke effect to a transparent PNG image using JavaScript canvas?

    Most popular image effect libraries I found does not have stroke effect. The closest solution on StackOverflow I found is using blur to give it a glow effect instead of outline stroke.

    Original picture

    Transparent PNG image that can have multiple separated shapes:

    Resulting image

    Transparent image with outline stroke and shadow applied to it.

    The search continues...

    I'll update this list as I search for the easiest way to accomplish the stroke effect. Related questions:

    解决方案

    Here's one way to add a "sticker effect" on your image...

    A Demo: http://jsfiddle.net/m1erickson/Q2j3L/

    Start by drawing your original image to the main canvas.

    Decompose the image into "discrete elements".

    Discrete elements consist of groups of pixels that are connected to each other but not connected to other elementss. For example, each individual sprite on a spritesheet would be a discrete element.

    You can find discrete pixel groups using an edge detection algorithm like "marching squares".

    Put each discrete element on its own canvas for further processing. Also erase that discrete element from the main canvas (so it's not processed again).

    Detect the outline-path of each discrete element.

    You can again use the "marching squares" algorithm to do edge detection. The result of marching squares is an array of x/y coordinates that form the outside outline of the element

    Create the "sticker effect"

    You can create a sticker effect by putting a stroked white outline around each element. Do this by stroking the outline path which you calculated above. You can optionally add a shadow to the stroke.

    Note: canvas strokes are always drawn half-inside & half-outside the path. This means that the sticker-stroke will intrude inside the element. To fix this: After you have drawn the sticker-stroke you should redraw the element back on top. This overwrites the intruding part of the sticker-stroke.

    Recompose the final image including the sticker-effect

    Recompose the final image by layering each element's canvas onto the main canvas

    Here is annotated example code:

    <!doctype html>
    <html>
    <head>
    <link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
    <script src="http://code.jquery.com/jquery.min.js"></script>
    <script src="marching squares.js"></script>
    <style>
        body{ background-color:silver; }
        canvas{border:1px solid red;}
    </style>
    <script>
    $(function(){
    
        // canvas related variables
        var canvas=document.getElementById("canvas");
        var ctx=canvas.getContext("2d");
    
        // variables used in pixel manipulation
        var canvases=[];
        var imageData,data,imageData1,data1;
    
        // size of sticker outline
        var strokeWeight=8;
    
        // true/false function used by the edge detection method
        var defineNonTransparent=function(x,y){
            return(data1[(y*cw+x)*4+3]>0);
        }
    
        // the image receiving the sticker effect
        var img=new Image();
        img.crossOrigin="anonymous";
        img.onload=start;
        img.src="https://dl.dropboxusercontent.com/u/139992952/multple/makeIndividual.png";
        //img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/angryBirds.png";
    
        function start(){
    
            // resize the main canvas to the image size
            canvas.width=cw=img.width;
            canvas.height=ch=img.height;
    
            // draw the image on the main canvas
            ctx.drawImage(img,0,0);
    
            // Move every discrete element from the main canvas to a separate canvas
            // The sticker effect is applied individually to each discrete element and
            // is done on a separate canvas for each discrete element
            while(moveDiscreteElementToNewCanvas()){}
    
            // add the sticker effect to all discrete elements (each canvas)
            for(var i=0;i<canvases.length;i++){
                addStickerEffect(canvases[i],strokeWeight);
                ctx.drawImage(canvases[i],0,0);
            }
    
            // redraw the original image
            //   (necessary because the sticker effect 
            //    slightly intrudes on the discrete elements)
            ctx.drawImage(img,0,0);
    
        }
    
        // 
        function addStickerEffect(canvas,strokeWeight){
            var url=canvas.toDataURL();
            var ctx1=canvas.getContext("2d");
            var pts=canvas.outlinePoints;
            addStickerLayer(ctx1,pts,strokeWeight);
            var imgx=new Image();
            imgx.onload=function(){
                ctx1.drawImage(imgx,0,0);
            }
            imgx.src=url;    
        }
    
    
        function addStickerLayer(context,points,weight){
    
            imageData=context.getImageData(0,0,canvas.width,canvas.height);
            data1=imageData.data;
    
            var points=geom.contour(defineNonTransparent);
    
            defineGeomPath(context,points)
            context.lineJoin="round";
            context.lineCap="round";
            context.strokeStyle="white";
            context.lineWidth=weight;
            context.stroke();
        }
    
        // This function finds discrete elements on the image
        // (discrete elements == a group of pixels not touching
        //  another groups of pixels--e.g. each individual sprite on
        //  a spritesheet is a discreet element)
        function moveDiscreteElementToNewCanvas(){
    
            // get the imageData of the main canvas
            imageData=ctx.getImageData(0,0,canvas.width,canvas.height);
            data1=imageData.data;
    
            // test & return if the main canvas is empty
            // Note: do this b/ geom.contour will fatal-error if canvas is empty
            var hit=false;
            for(var i=0;i<data1.length;i+=4){
                if(data1[i+3]>0){hit=true;break;}
            }
            if(!hit){return;}
    
            // get the point-path that outlines a discrete element
            var points=geom.contour(defineNonTransparent);
    
            // create a new canvas and append it to page
            var newCanvas=document.createElement('canvas');
            newCanvas.width=canvas.width;
            newCanvas.height=canvas.height;
            document.body.appendChild(newCanvas);
            canvases.push(newCanvas);
            var newCtx=newCanvas.getContext('2d');
    
            // attach the outline points to the new canvas (needed later)
            newCanvas.outlinePoints=points;
    
            // draw just that element to the new canvas
            defineGeomPath(newCtx,points);
            newCtx.save();
            newCtx.clip();
            newCtx.drawImage(canvas,0,0);
            newCtx.restore();
    
            // remove the element from the main canvas
            defineGeomPath(ctx,points);
            ctx.save();
            ctx.clip();
            ctx.globalCompositeOperation="destination-out";
            ctx.clearRect(0,0,canvas.width,canvas.height);
            ctx.restore();
    
            return(true);
        }
    
    
        // utility function
        // Defines a path on the canvas without stroking or filling that path
        function defineGeomPath(context,points){
            context.beginPath();
            context.moveTo(points[0][0],points[0][1]);  
            for(var i=1;i<points.length;i++){
                context.lineTo(points[i][0],points[i][1]);
            }
            context.lineTo(points[0][0],points[0][1]);
            context.closePath();    
        }
    
    }); // end $(function(){});
    </script>
    </head>
    <body>
        <canvas id="canvas" width=300 height=300></canvas><br>
    </body>
    </html>
    

    This is a marching squares edge detection algorithm (from the excellent open-source d3 library):

    /** 
     * Computes a contour for a given input grid function using the <a 
     * href="http://en.wikipedia.org/wiki/Marching_squares">marching 
     * squares</a> algorithm. Returns the contour polygon as an array of points. 
     * 
     * @param grid a two-input function(x, y) that returns true for values 
     * inside the contour and false for values outside the contour. 
     * @param start an optional starting point [x, y] on the grid. 
     * @returns polygon [[x1, y1], [x2, y2], ...] 
    
     */
     (function(){ 
    
    geom = {}; 
    geom.contour = function(grid, start) { 
      var s = start || d3_geom_contourStart(grid), // starting point 
          c = [],    // contour polygon 
          x = s[0],  // current x position 
          y = s[1],  // current y position 
          dx = 0,    // next x direction 
          dy = 0,    // next y direction 
          pdx = NaN, // previous x direction 
          pdy = NaN, // previous y direction 
          i = 0; 
    
      do { 
        // determine marching squares index 
        i = 0; 
        if (grid(x-1, y-1)) i += 1; 
        if (grid(x,   y-1)) i += 2; 
        if (grid(x-1, y  )) i += 4; 
        if (grid(x,   y  )) i += 8; 
    
        // determine next direction 
        if (i === 6) { 
          dx = pdy === -1 ? -1 : 1; 
          dy = 0; 
        } else if (i === 9) { 
          dx = 0; 
          dy = pdx === 1 ? -1 : 1; 
        } else { 
          dx = d3_geom_contourDx[i]; 
          dy = d3_geom_contourDy[i]; 
        } 
    
        // update contour polygon 
        if (dx != pdx && dy != pdy) { 
          c.push([x, y]); 
          pdx = dx; 
          pdy = dy; 
        } 
    
        x += dx; 
        y += dy; 
      } while (s[0] != x || s[1] != y); 
    
      return c; 
    }; 
    
    // lookup tables for marching directions 
    var d3_geom_contourDx = [1, 0, 1, 1,-1, 0,-1, 1,0, 0,0,0,-1, 0,-1,NaN], 
        d3_geom_contourDy = [0,-1, 0, 0, 0,-1, 0, 0,1,-1,1,1, 0,-1, 0,NaN]; 
    
    function d3_geom_contourStart(grid) { 
      var x = 0, 
          y = 0; 
    
      // search for a starting point; begin at origin 
      // and proceed along outward-expanding diagonals 
      while (true) { 
        if (grid(x,y)) { 
          return [x,y]; 
        } 
        if (x === 0) { 
          x = y + 1; 
          y = 0; 
        } else { 
          x = x - 1; 
          y = y + 1; 
        } 
      } 
    } 
    
    })();
    

    Note: This code separates the process of applying the sticker outline into a separate function. That's done in case you want to have multiple layers around your discrete element. For example, you might want a second gray border on the outside of the sticker-stroke. If you don't need to apply "layers" then you could apply the sticker-stroke within the moveDiscreteElementToNewCanvas function.

    这篇关于如何在JavaScript画布中将笔画/轮廓添加到透明PNG图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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