美文网首页
# sharp拼合头像

# sharp拼合头像

作者: nymlc | 来源:发表于2019-01-06 14:47 被阅读0次

    前言

    因为项目需要做个建群的时候取建群时群成员的头像拼合成一张图片作为群头像,所以用了这个库:sharp
    实际操作一番,发现有些地方费劲,特此记录一下

    直接上代码

    const sharp = require('sharp');
    const https = require('https');
    const http = require('http');
    /**
     * 读取本地
     * 
        const fs = require('fs');
        let dir = 'avatar/'
        let files = fs.readdirSync(dir);
        files = files.filter((item) => {
            const validExtRegex = /(?:bmp|gif|jpe?g|png)/
            return fs.statSync(dir + item).size != 0 && validExtRegex.test(item); // remove empty image
        }).map((item) => {
            return item = dir + item;
        })
     */
    buildGetImageTasks = (urls) => {
        const tasks = []
        const buildTask = (url) => {
            return new Promise((resolve, reject) => {
                (url.startsWith('https') ? https : http).get(url, (res) => {
                    const b = [];
                    res.on('data', function (c) {
                        b.push(c);
                    });
                    res.on('end', function () {
                        resolve(Buffer.concat(b))
                    });
                    res.on('error', () => {
                        reject()
                    });
                }).on('error', () => {
                    reject()
                })
            })
        }
        if (!Array.isArray(urls)) {
            tasks.push(buildTask(urls))
        } else {
            urls.forEach(url => {
                tasks.push(buildTask(url))
            })
        }
        return tasks
    }
    
    compositeImage = (files, {
        size = 48,
        subSize = 12,
        padding = {
            top: 2,
            right: 2,
            bottom: 2,
            left: 2
        },
        margin = {
            top: 4,
            right: 4,
            bottom: 4,
            left: 4
        }
    } = {}) => {
        // 创建底图层
        const base = sharp({
            create: {
                width: size,
                height: size,
                channels: 4,
                background: {
                    r: 255,
                    g: 255,
                    b: 255,
                    alpha: 128
                }
            }
        }).raw().toBuffer();
        // 拼合图片选项
        const options = {
            raw: {
                width: size,
                height: size,
                channels: 4
            }
        }
        // 计算多少行
        let columns = Math.ceil(Math.sqrt(files.length));
        // 最后一行差掉的间隙
        const hGap = (columns - (files.length % columns)) * (subSize + padding.left) / 2
        // 垂直差距
        const vGap = Math.ceil(files.length / columns) < columns ? (subSize + padding.top) / 2 : 0
        // 行列索引
        let x = 0;
        let y = 0;
        // 开始拼合
        const composite = files.reduce((input, overlay, index) => {
            return input.then(async (data) => {
                let temp = sharp(data, options).overlayWith(await sharp(overlay).rotate(180).resize(subSize, subSize).toBuffer(), {
                    top: subSize * y + margin.top + padding.top * Math.floor(index / columns) + vGap,
                    left: Math.floor(subSize * x + margin.left + padding.left * (index % columns) + (files.length % columns && (files.length - index) <= files.length % columns ? hGap : 0))
                }).raw().toBuffer();
                x += 1;
                if (x == columns) {
                    x = 0;
                    y += 1;
                }
                return temp;
            });
        }, base)
        return composite
    }
    
    bufferToFile = (composite, size, i) => {
        // 拼合结果转图片
        composite.then((data) => {
            sharp(data, {
                raw: {
                    width: size,
                    height: size,
                    channels: 4
                }
            }).rotate(180)
                .toFile(`avatar/result${i}.jpg`, (err, output) => {
                    console.log(output)
                    if (err) {
                        console.log(err);
                    }
                });
        });
    }
    
    for (let i = 3; i <= 9; i++) {
        const imgUrls = new Array(i).fill('http://img.downza.xzstatic.com/edu/pc/wlgj-1008/2016-07-26/c67d6f564bd270760be84cfb7d6fca3d.jpg')
        Promise.all(buildGetImageTasks(imgUrls)).then((res) => {
            let option = {
                size: 96,
                padding: {
                    top: 4,
                    right: 4,
                    bottom: 4,
                    left: 4
                },
                margin: {
                    top: 8,
                    right: 8,
                    bottom: 8,
                    left: 8
                }
            }
            switch (res.length) {
                case 3:
                case 4:
                    Object.assign(option, {
                        subSize: 38
                    })
                    break
                default:
                    Object.assign(option, {
                        subSize: 24
                    })
            }
            bufferToFile(compositeImage(res, option), 96, i)
        })
    }
    
    • 注意点1:网络取图不同于本地取图,它需要异步,可以使用Promise包裹nodejs的http、https模块取图。多张图的话可用Promise.all
    • 注意点2:sharp拼合图片很简单,就和PS图层操作一样,在一张底图之上把要拼合的图片按照以左上角为原点的笛卡尔坐标拼合,计算好每一张图片的偏移量就好了
    • 注意点3:像是微信群头像之类的以建群时头像来拼合(待拼合的头像数量不确定),可以将将读取到的图片buffer数组reserve,最后把拼合的图片rotate(180)即可

    相关文章

      网友评论

          本文标题:# sharp拼合头像

          本文链接:https://www.haomeiwen.com/subject/oyssrqtx.html