首页 > 技术文章 > 别再@官方啦,普天同庆加国旗

muamaker 2019-09-24 21:23 原文

别再@官方啦,普天同庆加国旗

国庆就要来了!今年是新中国成立70周年,大家的热情都很高涨。这不,我今天一翻朋友圈,被齐刷刷地带国旗的头像给刷屏了!

不过还有些朋友不明就里,还在不停地@微信官方,希望能自动给头像加上一面国旗。然而等了半天,还是毫无反应……

 

当然,程序员朋友,也开始了一顿操作。

python 用 python-opencv 库,直接调。

go 用 image 库,直接调。

那么前端呢? 咱们前端不用库,原生的 js + canvas  简单快捷。

一、思路分析

    大致有两种办法可以实现。

  1.   使用 将图片画在 canvas 上面,然后通过 api 获取 图片的像素值,进行叠加,然后再写入 canvas ,最后导出图片。好处是:可以对透明的图像进行处理,自定义程度高。缺点:控制起来复杂度高,计算量大。
  2.    直接将两张图片分两次画在 canvas 上面,然后导出图片 。 好处:简单快捷。 缺点:透明图像不能处理,可控性小。

这里我采用第二种办法,简单点。。

二、技术点

    1、  获取画布:

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

  

   2、将图片画入canvas

context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);  

 

 

  3、导出图片

canvas.toDataURL("image/png");

 4、有了这些方法,我们的图片从哪里来?

 通过  <input type="file" name="" id="flag" value="" /> 能拿到文件 file ,我们只需要将 file 转化为 图片的 url链接即可。

这里有两种办法:1、使用 FileReader  读取出 base64图片,是一个异步操作。 2、使用 URL.createObjectURL 读取出 blob 的协议链接,是一个同步操作。这里我们采用第二种办法

 

三、正式开始

1、结构和样式

 

        <style type="text/css">
            * {
                padding: 0;
                margin: 0;
                font-size: 14px;
            }
 
            #box {
                width: 400px;
                height: 400px;
                border: 1px solid red;
                margin: 20px auto;
                position: relative;
            }
 
            #operate {
                text-align: center;
                ;
            }
 
            #operate p {
                margin-bottom: 6px;
            }
 
            input[type=file] {
                display: none;
            }
 
            label {
                display: inline-block;
                cursor: pointer;
                background: #38f;
                color: #fff;
                width: 102px;
                height: 38px;
                line-height: 38px;
                border-radius: 4px;
            }
 
        </style>

  

<div id="box">
            <canvas id="canvas" width="400" height="400"></canvas>
        </div>
        <div id="operate">
            <p><label><input type="file" name="" id="bg" value="" />选择头像</label></p>
            <p><label><input type="file" name="" id="flag" value="" />选择上层</label></p>
			<p><label id="create">直接生成</label></p>
        </div>
        <a href="" download="logo.png" title="点击下载" id="down">
            <img src="" id="result">
        </a>
    </body>

  

 

 2、js代码如下

        const canvas = document.getElementById("canvas"); //获取canvas
        const ctx = canvas.getContext("2d"); //获取画布
        const baseW = 400; //头像的最大宽高
        const flagW = 100;	//旗的最大宽高
        let bgConfig;		//画 头像的参数
        let flagConfig;		//画  旗的参数
        document.getElementById("bg").onchange = async function() {
            const file = this.files[0];
            try {
                const img = await getImageObj(file);
                const rate = compress(img, baseW);
                bgConfig = [img, 0, 0, img.width, img.height, 0, 0, rate.w, rate.h];
                drawn();
            } catch (e) {
                console.error(e);
            }
        };
 
        document.getElementById("flag").onchange = async function() {
            const file = this.files[0];
            try {
                const img = await getImageObj(file);
                const rate = compress(img, flagW);
                flagConfig = [img, 0, 0, img.width, img.height, 0, 0, rate.w, rate.h];
                drawn();
            } catch (e) {
                console.error(e);
            }
        };
		//画
        function drawn() {
            ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
            if (bgConfig) {
                ctx.drawImage(...bgConfig);
            }
            if (flagConfig) {
                ctx.drawImage(...flagConfig);
            }
        }
 
        //图片压缩,获取等比缩放后的结果
        function compress(img, base) {
            let w = img.width;
            let h = img.height;
            if (img.width > img.height) {
                if (img.width > base) {
                    //要将宽度缩放
                    w = base;
                    h = (w / img.width) * img.height; // 新的 宽比 高 = 旧的宽比高  h / w = img.heigth/img.width  ;
                }
            } else {
                if (img.height > base) {
                    h = base;
                    w = (h / img.height) * img.width;
                }
            }
            return {
                w,
                h
            };
        }
		//通过 file 获取到图片对象
        function getImageObj(file) {
            const url = getObjectURL(file);
            const img = new Image();
            img.src = url;
            return new Promise((resolve, reject) => {
                img.onload = function() {
                    resolve(img);
                }
                img.onerror = function(e) {
                    reject(e);
                }
            });
        }
 
        //取得该文件的url
        function getObjectURL(file) {
            var url = null;
            if (window.createObjectURL != undefined) {
                url = window.createObjectURL(file);
            } else if (window.URL != undefined) {
                url = window.URL.createObjectURL(file);
            } else if (window.webkitURL != undefined) {
                url = window.webkitURL.createObjectURL(file);
            }
            return url;
        }
		//直接生成
		document.getElementById("create").onclick = function(){
		    exportImg({
		        x:0,
		        y:0,
		        w:canvas.clientWidth,
		        h:canvas.clientHeight,
		    });
		}
		//导出头像的范围
		 function startClip(area) {
			 var canvas = document.createElement("canvas");
			 canvas.width = area.w;
			 canvas.height = area.h;
			 var data = ctx.getImageData(area.x, area.y, area.w, area.h);
			 var context = canvas.getContext("2d");
			 context.putImageData(data, 0, 0);
			 return canvas.toDataURL("image/png");
		 }
		  //导出头像,点击可以下载
		 function exportImg(clipArea){
			 var url = startClip(clipArea);
			 document.getElementById("result").src = url;
			 document.getElementById("down").href = url;
		 }

  

其中: bgConfig 存了 头像的 logo 图  ctx.drawImage 的参数。 flagConfig 存了 旗子 参数。

结果如下:

 

很显然,这并不是我们要的效果,需要控制旗子的位置,也简单。

调整  flagConfig 组数里面的 5 和 6 的位置元素 值,即 x 、y ,例如:

flagConfig[5] = 100;
flagConfig[6] = 200;

  

为了方便修改。增加两个输入框:

 

 在js 里面增加如下代码:

document.getElementById("posX").oninput = function() {
		    let val = Number(this.value) || 0;
		    flagConfig[5] = val;
		    drawn();
		}
		document.getElementById("posY").oninput = function() {
		    let val = Number(this.value) || 0;
		    flagConfig[6] = val;
		    drawn();
		}

  

大功告成:

 

 

 

四、问题

 以为这样就完了吗?还早,,如果传一张不规则的头像,如下:

 

如果遇见这样不规则的图片,最后生成的图片,会出现一片白边。

不过,这可难不到程序员,截掉就可以啦。

后面截取 canvas的代码就不一 贴出来了,最后附近加入完整版的吧!

直接用鼠标截取想要的部分:

 

 

 

 

完整版源码:

链接:https://pan.baidu.com/s/1c4Rj6zmILpJW-x5yjGtSzg
提取码:ol0y

 

 

   

 

推荐阅读