canvas图片合成

一、使用场景

图片合成的使用场景是非常广的,虽说强大的PS能将任意图片组合在一起,但它也只能实现固定的几张图片的合成,而Canvas则能动态地将各种不同的图片合为一张图片。

那么,何为动态合成呢?

比如,我们要将用户的头像合到另一张图片上,这时候因为用户是不确定的,不同用户的头像不同,所以我们肯定不能用PS来做这件事,这时候就是Canvas大显身手的时候了。

二、基本原理

HTML5给我们提供了一个很好的图像处理神器Canvas,想要合成图片,我们首先得将图片按照一定顺序、大小和位置一张张绘制到Canvas画布中去,最后再将整个Canvas导出为图片。

Canvas图片合成

三、编码实践

1、创建Canvas画布

 

let canvas = document.createElement('canvas'),
    ctx = canvas.getContext('2d');
canvas.width = 800;
canvas.height = 800;

2、绘制图片至Canvas

 

let img1 = new Image(),
    img2 = new Image();
img1.src = './images/1.jpg';
img2.src = './images/2.jpg';

// 加载img1
let pm1 = new Promise((res,rej)=>{
  img1.onload = ()=>{
    res();
  }
});
// 加载img2
let pm2 = new Promise((res,rej)=>{
  img2.onload = ()=>{
    res();
  }
});

// 两张图片都加载完成后绘制于Canva中
let drawAllImg = Promise.all([pm1, pm2]).then((res)=>{
  ctx.drawImage(img2, 0, 0, 400, 533);
  ctx.drawImage(img1, 0, 0, 100, 100);
});

这里我分别绘制了1.jpg2.jpg两张图片于Canvas画布中,由于图片需要先从本地加载才能进行绘制,所以这里我使用了onload()方法和Promise对象(详见ES6异步编程之Promise(一)ES6异步编程之Promise(二)
),其中Promise对象中的all()方法是为了等两张图片全部加载完后在执行后续操作。

由于我们现在创建的Canvas还未添加到DOM中,所以若是想要看现在的Canvas情况得先加上下面这句:

 

document.body.appendChild(canvas);

当然,实际若是只想要合成图片的话就没必要加上这句了,在离屏Canvas(即手动创建的Canvas但并未添加至DOM中)上绘制即可。

3、将Canvas导出为图片

 

drawAllImg.then(()=>{
  let outputImg = new Image();
  outputImg.src = ctx.canvas.toDataURL();
  document.body.appendChild(outputImg);
});

4、整合代码,最终形成可复用的JS插件

 

// 图片合成插件
class ImgMerge {

    constructor(imgs = [], options){
        // 图片数组默认配置项
        let defaultImgsItem = {
          url: '',
          x: 0,
          y: 0
        };
        // 导出图片的格式与压缩程度默认配置项
        let defaultOpts = {
          type: 'image/jpeg',
          compress: 1
        };

        try {
          imgs.forEach((item,i,arr) => {
            arr[i] = Object.assign({},defaultImgsItem,item)
          });
        }catch (e) {
            throw '请传入一个正确的对象数组作为参数';
        }

        this.imgs = imgs;   // 图片数组配置项
        this.opts = Object.assign({},defaultOpts,options);   // 其他配置项
        this.imgObjs = [];   // 图片对象数组

        this.createCanvas();  // 创建画布
        return this.outputImg();  // 导出图片

    }

    // 创建画布
    createCanvas(){

        let canvas = document.createElement('canvas'),
            ctx = canvas.getContext('2d');

        let w = this.imgs[0].width, h = this.imgs[0].height;

        if(!w){
            throw '第一张图片宽度未设置';
        }
        if(!h){
            throw '第一张图片高度未设置';
        }

        canvas.width = w;
        canvas.height = h;

        this.ctx = ctx;

    }

    // 绘制图片
    drawImg(i){

        let img = new Image();
        img.src = this.imgs[i].url;
        this.imgObjs.push(img);

        return new Promise((resolve)=>{
            img.onload = resolve;
        });

    }

    // 导出图片
    outputImg(){

        let imgArr = [];
        // 将单张图片的Promise对象存入数组
        this.imgs.forEach((item,i) => {
            imgArr.push(this.drawImg(i));
        });

        // 所有图片加载成功后将图片绘制于Canvas中,后将Canvas导出为图片
        return Promise.all(imgArr).then(()=>{
            this.imgs.forEach((item,i) => {
              let drawPara = [this.imgObjs[i], this.imgs[i].x, this.imgs[i].y];
              // 此处判断参数中图片是否设置了宽高,若宽高均设置,则绘制已设置的宽高,否则按照图片默认宽高绘制
              if(this.imgs[i].width && this.imgs[i].height){
                drawPara.push(this.imgs[i].width, this.imgs[i].height);
              }
              this.ctx.drawImage(...drawPara);
            });
            // 以base64格式导出图片
            return Promise.resolve(this.ctx.canvas.toDataURL(this.opts.type),this.opts.compress);
        });

    }

}

window.ImgMerge = ImgMerge;   //  可用于全局引用
export default ImgMerge;   //  可用于模块化引用

然后就可以轻松地使用该插件了。

 

import ImgMerge from './imgMerge.js';

window.onload = function () {

    let imgMerge = new ImgMerge([
        {
            url: require('../images/bg.jpg'),
            width: 640,
            height: 1169
        },
        {
            url: require('../images/1.jpg'),
            width: 200,
            height: 200
        }
    ]);

    imgMerge.then(img => {
      let mergeImg = new Image();
      mergeImg.src = img;
      mergeImg.onload = () => {
        document.body.appendChild(mergeImg);
      };
    });

};

canvas绘制图片,由于浏览器的安全考虑,如果在使用canvas绘图的过程中,使用到了外域的图片资源,那么在toDataURL()时会抛出安全异常:

Uncaught SecurityError: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.

解决方案1.

如果想使用toDataURL()生成图片文件的话,在canvas绘图过程中使用的图片应该是当前域下的。

解决方案2.

访问的服务器允许,资源跨域使用,也就是说设置了CORS跨域配置,Access-Control-Allow-Origin

然后在客户端访问图片资源的时候

var img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.src = url;

实例说明:

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title></title>
    <meta charset="utf-8" />
    <link href="../scripts/Bootstrap-3.3.6/css/bootstrap.min.css" rel="stylesheet" />
    <script src="../scripts/jquery-1.11.3.min.js"></script>
    <script src="../scripts/Bootstrap-3.3.6/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
    <img src="http://www.gongjuji.net/favicon.ico"  crossorigin="anonymous"/>
    <canvas id="canvasOne" width="200" height="200"></canvas>
</div>
<script>
    /*
    * 将canvas图片转换成图片,上传到服务器
    */
    var canvas = document.getElementById('canvasOne');
    var ctx = canvas.getContext('2d');
    ctx.fillStyle = 'red';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    //添加外网图片
    var img = new Image();
    //设置图片跨域访问
    //img.setAttribute('crossOrigin', 'anonymous');
    img.onload = function () {
        //画图
        ctx.drawImage(img, 0, 0, img.width, img.height);
        var data = canvas.toDataURL('image/jpeg');
        alert(data);
    }
    //使用外网图片
    img.src = 'http://www.gongjuji.net/favicon.ico';
</script>
</body>
</html>

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值