angular借助画布多张图片合成

278 阅读3分钟

最近因为产品需求要多张图片合成为一张图片的功能 效果如图:

image.png
具体的实现方法见代码

<!--上面是图片的数据源展示-->
  <div class="box">
    <img class="box-img" *ngFor="let item of imgSrc" [src]="item" alt="">
</div>
<button *ngFor="let item of gridType" (click)="onSave(item)">合成</button>
<!--点击以后会在隐藏的canvas上画出一个新的合成以后的图片-->
<canvas #can></canvas>
<div class="box">
    <img class="box-img" *ngFor="let item of base" [src]="item" alt="">
</div>

这里设置了一个隐藏的画布,用来生成合成的图片,但是画布是不现实的,只是作为中间层使用

.box{
    width: 100%;
    height: 200px;

    .box-img{
        width: 200px;
        height: 200px;
        display: inline-block;
        margin-right: 10px;
    }

}

canvas{
    display: none;
}

js代码如下

我这里是取的布局方向偏移量最大值的平方,比如最大偏移量是x为1表示向右偏移量为1那么布局格式就是(1+1 * (1+1))最终结果就是将单元格整分为四份以偏移量和图片大小所占的比例来进行画图,此项配置类似于grid布局

import {Component, OnInit, ViewChild, ElementRef} from '@angular/core';

import { fromEvent, Subject } from 'rxjs';
import { filter } from 'rxjs/operators';

export interface GridType {
    cols: number;// 列
    rows: number;//行
    y: number;// 纵向偏移量
    x: number;//  横向偏移量
}

@Component({
    selector: 'app-images',
    templateUrl: './images.component.html',
    styleUrls: ['./images.component.scss']
})
export class ImagesComponent implements OnInit {
    imgSrc: any[] = [];
    base: any[] = [];

    gridType: Array<GridType[]>;
 // 获取到canvas元素
    @ViewChild('can', {static: true}) canRef: ElementRef<HTMLCanvasElement>;
    canImage: CanvasRenderingContext2D;

    constructor() {

        // 图片布局配置
        this.gridType = [
            [
                {cols: 2, rows: 1, x: 0, y: 0},
                {cols: 2, rows: 1, x: 1, y: 0},
            ],
            [
                {cols: 1, rows: 1, x: 0, y: 0},
                {cols: 1, rows: 1, x: 1, y: 0},
                {cols: 1, rows: 1, x: 0, y: 1},
                {cols: 1, rows: 1, x: 1, y: 1},
            ],
            [
                {cols: 2, rows: 2, y: 0, x: 0},
                {cols: 1, rows: 1, y: 0, x: 2},
                {cols: 1, rows: 1, y: 1, x: 2},
                {cols: 1, rows: 1, y: 2, x: 0},
                {cols: 1, rows: 1, y: 2, x: 1},
                {cols: 1, rows: 1, y: 2, x: 2}
            ],
            [
                {cols: 1, rows: 1, y: 0, x: 0},
                {cols: 1, rows: 1, y: 0, x: 1},
                {cols: 1, rows: 1, y: 0, x: 2},
                {cols: 1, rows: 1, y: 1, x: 0},
                {cols: 1, rows: 1, y: 1, x: 1},
                {cols: 1, rows: 1, y: 1, x: 2},
                {cols: 1, rows: 1, y: 2, x: 0},
                {cols: 1, rows: 1, y: 2, x: 1},
                {cols: 1, rows: 1, y: 2, x: 2},
            ],
            [
                {cols: 1, rows: 1, y: 0, x: 0},
                {cols: 1, rows: 1, y: 0, x: 1},
                {cols: 1, rows: 1, y: 0, x: 2},
                {cols: 1, rows: 1, y: 0, x: 3},
                {cols: 1, rows: 1, y: 1, x: 0},
                {cols: 1, rows: 1, y: 1, x: 1},
                {cols: 1, rows: 1, y: 1, x: 2},
                {cols: 1, rows: 1, y: 1, x: 3},
                {cols: 1, rows: 1, y: 2, x: 0},
                {cols: 1, rows: 1, y: 2, x: 1},
                {cols: 1, rows: 1, y: 2, x: 2},
                {cols: 1, rows: 1, y: 2, x: 3},
                {cols: 1, rows: 1, y: 3, x: 0},
                {cols: 1, rows: 1, y: 3, x: 1},
                {cols: 1, rows: 1, y: 3, x: 2},
                {cols: 1, rows: 1, y: 3, x: 3},
            ]
        ];

        // 图片数据源
        this.imgSrc = [
            '../../../assets/images/1.png',
            '../../../assets/images/2.png',
            '../../../assets/images/3.png',
            '../../../assets/images/4.png',
            '../../../assets/images/5.png',
            '../../../assets/images/6.png',
            '../../../assets/images/7.png',
            '../../../assets/images/8.png',
            '../../../assets/images/9.png',
           ];
    }

    ngOnInit() {
        // 获取canvas上下文对象
        this.canImage = this.canRef.nativeElement.getContext('2d');
    }

    // 图片合成方式
    onSave(type): void {
        /*
        * 制定画布宽高
        * 注意的是画布的宽高会影响合成的图片质量
        * 这里推荐使用原图的宽高等比放大缩小
        * 尽量使用原图宽高
        * 因为省事所以制定了画布的宽高与大小所以生成的图像画质为1920*1080
        * 每次合成的时候宽高重新定义以清空画布
        * */
        this.canRef.nativeElement.width = 1920;
        this.canRef.nativeElement.height = 1080;
        let idx = 0;
        type.forEach(item => {
            if (idx < item.x) {
                idx = item.x;
            }
        });
        let idy = 0;
        type.forEach(item => {
            if (idy < item.y) {
                idy = item.y;
            }
        });
        let num: number;
        // 计算布局最大格数
        idx > idy ? num = idx : num = idy;
        const sub = new Subject();
        const iArr = [];
        for (let i = 0; i < type.length; i++) {
        // 判断是否存在图片
        /*
        * 图片数据建议加到GridType的参数中更加灵活用于图片合成的位置
        */
            if (this.imgSrc[i]) {
                iArr.push(i);
                const img = new Image();
                img.src = this.imgSrc[i];
                fromEvent(img, 'load')
                    .subscribe(() => {
                        const x = (1920 / (num + 1)) * type[i].x;
                        const y = (1080 / (num + 1)) * type[i].y;
                        const w = (1920 / (num + 1)) * type[i].rows;
                        const h = (1080 / (num + 1)) * type[i].cols;
                        this.canImage.drawImage(img, x, y, w, h);
                        //  获取画布上生成图片的base64编码
                        sub.next({index: i, toData: this.canRef.nativeElement.toDataURL()});
                    });
            }
        }

        // 获取最后一次循环的时候画布的toDataUrl
        const lastIndex = Math.max(...iArr);
        console.log(lastIndex);
        sub
            .pipe(
                filter(({index}) => index === lastIndex)
            )
            .subscribe((res: any) => {
                this.base.push(res.toData);
            });
    }

}