Canvas draws frosted glass background sharing poster

I recently redesigned my sharing poster, using frosted glass as the background to make the whole thing more textured. If canvas is not used, the frosted glass effect is actually very easy to achieve. Just add a filter to the element (for example: filter: blur(32px)). However, during practice, I found that canvas has no effect on the IOS side. I checked a document and found that the IOS side does not support filter. . . I kind of want to curse. . (PS: WeChat’s official documentation on CanvasRenderingContext2D is relatively brief. For more detailed documentation, you can go to https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D )
Insert image description here

I had no choice, I really liked the effect of frosted glass, so I decided to think of a way to overcome this hurdle, and continued to search for information online, and I found the general method is

1. Use the canvas context to draw an image first. The src of the image is the path of the image to be blurred.

2. Use the canvas context to extract the imageData from the image in step 1, and then perform Gaussian blur. There are many Gaussian blur algorithms on the Internet, but the execution time is 7 or 8 seconds, which is not ideal. Finally, I found a stackblur- Canvas plug-in, this plug-in is also suitable for H5. Here you only need to call the Gaussian blur algorithm method (stackBlur.imageDataRGBA). It can be executed in a few hundred milliseconds. The gap with the algorithms on the Internet is still very obvious. .

3. Finally, draw the blurred imageData into the bitmap.

The problem of the canvas filter has been solved. There is another small problem that the level of the canvas on the developer tools will always be higher than other elements, but on the real IOS device, elements can be laid out on the canvas. Because I don’t know if other clients will have hierarchical display problems like the developer tools. So to be on the safe side, I simply placed the canvas at the bottom of the visible area of ​​the page, and used other page elements in the visible area to create a sharing rendering.

The two pictures below, the left is the preview, and the right is the final canvas generated picture. It can be seen that the filter effect of wxss is still more natural and comfortable than the blur effect processed by the third-party Gaussian algorithm, but the overall effect is still within the acceptable range.

Insert image description here
Insert image description here

Attached is the core code

Component shareList.wxml

<view class="share-box" wx:if="{
   
   {visibility}}">
    <!-- style="display:none;"-->
    <view class="share-view" style="opacity:{
   
   {shareView?1:0}}">
        <view class="cover-image">
            <view class="cover"></view>
            <image class="image" src="{
   
   {shareInfo.imgSrc}}"></image>
        </view>
        <view class="content-box">
            <view class="detail">
                <view class="up">
                    <view class="expand">
                        <view class="time" wx:if="{
   
   {shareInfo.date}}">{
   
   {shareInfo.date}}</view>
                        <view class="place" wx:if="{
   
   {shareInfo.place}}">{
   
   {shareInfo.place}}</view>
                    </view>
                    <image mode="widthFix" class="image" src="{
   
   {shareInfo.imgSrc}}" bind:load="imageLoaded"></image>
                </view>
                <view class="middle flex-end-vertical">
                    <image class="header-img" src="{
   
   {shareInfo.avatarUrl}}"></image>
                    <image class="joiner-header-img" wx:if="{
   
   {shareInfo.joinerAvatarUrl}}" src="{
   
   {shareInfo.joinerAvatarUrl}}"></image>
                    <!--<view class="nickname flex-full">showms</view>-->
                </view>
                <view class="down">
                    <view class="desc"><view class="title" wx:if="{
   
   {shareInfo.title}}">#{
   
   {shareInfo.title}}#</view>{
   
   {shareInfo.content}}</view>
                </view>
            </view>
        </view>
    </view>
    <view class="save-tool-bar {
   
   {device.isPhoneX?'phx_68':''}} flex-center" style="transform: translateY({
   
   {shareView?0:100}}%)">
        <view class="op_btn flex-full">
            <view class="icon-view" bindtap="close">
                <image class="icon" src="/images/share/close.png"></image>
            </view>
            <text class="text" bindtap="close">关闭</text>
        </view>
        <view class="op_btn flex-full {
   
   {!allowSave?'disable':''}}">
            <view class="icon-view" bindtap="save">
                <image class="icon" src="/images/share/save.png"></image>
            </view>
            <text class="text" bindtap="save">保存到相册</text>
        </view>
    </view>
    <!--transform: translateY(100%);-->
    <canvas class="share-canvas"
            type="2d"
            id="myCanvas"
            style="position:absolute;top:0%;width:100%;height:100%;z-index:1000;transform: translateY(100%);"></canvas>
</view>
        <!--toast-->
<toast id="toast"></toast>

Component shareList.wxss

@import "/app.wxss";
.share-box{
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
}
.share-cover{
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    z-index: 1200;
    background-color: #111;
    opacity: 0.8;
}
.share-view{
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    z-index: 300;
    opacity: 0;
    transition: opacity .3s;
}
.share-view .cover-image{
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
}

.share-view .cover-image .image{
    width: 100%;
    height: 100%;
    transform: scale(3);
    filter: blur(32px);
}
.share-view .cover-image .cover{
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    z-index: 120;
    background-color: rgba(255, 255, 255, .5);
}
.share-view .content-box{
    /*padding: 300rpx 40rpx 40rpx;
    position: relative;*/
    position: fixed;
    left: 40rpx;
    right: 40rpx;
    top: 50%;
    transform: translateY(-50%);
    margin-top: -80rpx;
    z-index: 500;
    box-sizing: border-box;
}
.share-view .content-box .detail{
    background-color: #fff;
    box-sizing: border-box;
    /*padding: 70rpx 30rpx 30rpx;*/
    border-radius: 36rpx;
    overflow: hidden;
}
.share-view .content-box .detail .up{
    box-sizing: border-box;
    position: relative;
}

.share-view .content-box .detail .up .image{
    width: 100%;
    /*height: auto;*/
    display: block;
}
.share-view .content-box .detail .up .expand{
    position: absolute;
    right: 20rpx;
    bottom: 20rpx;
    z-index: 10;
    text-align: right;
    font-size: 22rpx;
    font-weight: 500;
    color: #fff;
    text-shadow: 0px 0px 10rpx rgba(158, 163, 175, 1);
}
.share-view .content-box .detail .middle{
    position: relative;
}
.share-view .content-box .detail .middle .header-img,
.share-view .content-box .detail .middle .joiner-header-img{
    width: 100rpx;
    height: 100rpx;
    border-radius: 50%;
    border: 6rpx solid #fff;
    box-sizing: border-box;
    margin-left: 24rpx;
    margin-right: 10rpx;
    margin-top: -64rpx;
    position: relative;
    z-index: 80;
    display: block;
}
.share-view .content-box .detail .middle .joiner-header-img{
    z-index: 60;
    margin-left: -60rpx;
}
.share-view .content-box .detail .middle .nickname{
    font-size: 30rpx;
    font-weight: 500;
    color: #434343;
    padding-bottom: 10rpx;
    display: none;
}
.share-view .content-box .detail .down{
    padding: 10rpx 30rpx 70rpx;
}
.share-view .content-box .detail .down .title{
    font-size: 28rpx;
    font-weight: 500;
    color: #303133;
    margin-bottom: 10rpx;
    margin-right: 10rpx;
    display: inline;
}
.share-view .content-box .detail .down .desc{
    /*font-size: 28rpx;
    font-weight: 500;
    color: #303133;
    display: inline;*/
    font-size: 28rpx;
    font-weight: 500;
    color: #303133;
    line-clamp: 2;
    box-orient: vertical;

    text-overflow: ellipsis;
    overflow: hidden;
    /*将对象作为弹性伸缩盒子模型显示*/
    display: -webkit-box;
    /*从上到下垂直排列子元素(设置伸缩盒子的子元素排列方式)*/
    -webkit-box-orient: vertical;
    /*这个属性不是css的规范属性,需要组合上面两个属性,表示显示的行数*/
    -webkit-line-clamp: 2;

    height: 76rpx;
}
.share-canvas{

}
.save-tool-bar{
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1600;
    border-radius: 40rpx 40rpx 0 0;
    text-align: center;
    background-color: #f0f0f0;

    transform: translateY(100%);
    transition: transform .3s;
}
.save-tool-bar .op_btn{
    text-align: center;
    padding: 50rpx 0 20rpx;
    transition: opacity .3s;
}
.save-tool-bar .op_btn .icon-view{
    padding: 26rpx;
    background-color: #fff;
    border-radius: 50%;
    display: inline-block;
    margin-bottom: 10rpx;
}
.save-tool-bar .op_btn .icon{
    display: block;
    width: 48rpx;
    height: 48rpx;
}
.save-tool-bar .op_btn .text{
    display: block;
    font-size: 20rpx;
    font-weight: 400;
}

Component shareList.js

//获取应用实例
const app = getApp();
const tabbar = require('../../utils/tabbar.js');
const canvasHelper = require('../../utils/canvasHelper');
const fonts = require("../../utils/fonts.js");

let ctx, canvas;
Component({
    /**
     * 组件的属性列表
     */
    properties: {},

    /**
     * 组件的初始数据
     */
    /*{left: 'rgba(26, 152, 252, 0.8)', right: 'rgba(26, 152, 252, 1)'}*/
    data: {
        visibility: false,
        paddingLeft: 34,
        letterSpace: 2,
        width: 300,
        height: 380,
        shareView: false,
        shareInfo: {
            /*imgSrc: "cloud://ydw-49d951.7964-ydw-49d951-1259010930/love/images/default/dangao.jpg",
            avatarUrl: "cloud://test-wjaep.7465-test-wjaep-1259010930/images/ozzW05Gch7jMMhsn1r_SWLGdGtF0/avatarUrl_1668440942555.webp",
            joinerAvatarUrl: "
https://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83erg8t0El6jTaZY87icvjR71ww52VMibg8fONBgggRtYHTnR2tXibB0IRQ45dCVgNCX5BRhY0KibjfxjGA/132
",
            title: "一起去旅游",
            content: "这里是内容啊,怎么没写内容呢这里是内容啊,怎么没写内容呢,我特知道当时的回复对方法国发过快速减肥",
            date: "2022-12-28",
            place: "四川成都",
            shareQrcode: "cloud://ydw-49d951.7964-ydw-49d951-1259010930/qrcode/mini-qrcode.jpg"*/
        },
        allowSave: false,
    },

    ready() {
        const device = app.getSystemInfo();
        this.setData({
            device,
        });
        this.toast = this.selectComponent('#toast');
    },

    /**
     * 组件的方法列表
     */
    methods: {
        save() {
            const {device} = this.data;
            let that = this;
            that.toast.showLoadingToast({text: "保存中", mask: false});

            canvasHelper.saveImage(
                this,
                canvas,
                0,
                0,
                device.screenWidth,
                device.screenHeight,
                device.screenWidth * 5,
                device.screenHeight * 5
            ).then(res => {
                that.toast.hideLoadingToast();
                that.toast.showToast({text: "保存成功"});
                that.close();
                console.log(res);
            }).catch(res => {
                console.error("保存失败:", JSON.stringify(res));
                that.toast.showToast({text: "保存失败"});
                that.toast.hideLoadingToast();
            });
        },

        imageLoaded: function (e) {
            console.log("图片加载完毕:", e);
            this.setData({shareView: true});
        },

        show(shareInfo) {
            console.log("开始显示画布:", shareInfo);
            this.setData({
                shareInfo
            });
            tabbar.hideTab(this);
            this.setData({visibility: true}, () => {
                canvasHelper.init(app, this, "#myCanvas").then(async (res) => {
                    ctx = res.ctx;
                    canvas = res.canvas;

                    this.toast.showLoadingToast({text: "生成中", mask: false});
                    const {device} = this.data;
                    //加大尺寸
                    const largerSize = 100;
                    console.log("1.绘制毛玻璃背景图片:", {
                        width: device.screenHeight + largerSize,
                        height: device.screenHeight + largerSize,
                    });

                    /*await canvasHelper.drawImage(
                        canvas,
                        ctx,
                        shareInfo.imgSrc,
                        -(device.screenHeight - device.screenWidth) / 2.0, -largerSize / 2,
                        device.screenHeight + largerSize,
                        device.screenHeight + largerSize,
                        190);*/

                    await canvasHelper.drawBlurImage(
                        canvas,
                        ctx,
                        shareInfo.imgSrc,
                        -(device.screenHeight - device.screenWidth) / 2.0, 0,
                        device.screenHeight,
                        device.screenHeight,
                        180);

                    console.log("2.绘制毛玻璃覆盖层灰色背景");
                    canvasHelper.drawRoundRect(ctx, 0, 0, device.screenWidth, device.screenHeight, 0, 'rgba(255, 255, 255, .5)');

                    console.log("3.绘制内容承载区域");
                    const leftPadding = 20,//边距20
                        headerImgHeight = 50,//头像尺寸
                        descHeight = 40,//内容区域高度
                        descPaddingTop = 0,//内容区域paddingTop
                        descPaddingBottom = 25,//内容区域paddingBottom
                        adjustHeight = 40;//调节高度,人为设定

                    const contentWidth = device.screenWidth - leftPadding * 2;
                    const contentHeight = contentWidth + headerImgHeight + descHeight + descPaddingTop + descPaddingBottom;
                    canvasHelper.drawRoundRect(
                        ctx,
                        (device.screenWidth - contentWidth) / 2.0,
                        (device.screenHeight - contentHeight) / 2.0 - adjustHeight,
                        contentWidth,
                        contentHeight,
                        18,
                        'rgba(255, 255, 255, 1)'
                    );

                    console.log("4.绘制内容区域图片");
                    ctx.clip();//裁剪后父元素的圆角才会显示
                    await canvasHelper.drawImage(
                        canvas,
                        ctx,
                        shareInfo.imgSrc,
                        (device.screenWidth - contentWidth) / 2.0,
                        (device.screenHeight - contentHeight) / 2.0 - adjustHeight,
                        contentWidth,
                        contentWidth,
                        0,
                    );
                    ctx.restore();

                    console.log("5.绘制头像边框");
                    const headerSize = 50, borderWidth = 3, headerMarginLeft = 12;

                    if (shareInfo.joinerAvatarUrl) {
                        console.log("5.1.绘制共享对象头像");
                        await canvasHelper.drawCircleImage(
                            canvas,
                            ctx,
                            shareInfo.joinerAvatarUrl,
                            leftPadding + headerMarginLeft + 30,
                            (device.screenHeight - contentHeight) / 2.0 - adjustHeight + contentWidth - headerSize / 2,
                            headerSize,
                            borderWidth,
                            "#fff",
                        );
                        console.log("5.2.绘制当前用户头像");

                        await canvasHelper.drawCircleImage(
                            canvas,
                            ctx,
                            shareInfo.avatarUrl,
                            leftPadding + headerMarginLeft,
                            (device.screenHeight - contentHeight) / 2.0 - adjustHeight + contentWidth - headerSize / 2,
                            headerSize,
                            borderWidth,
                            "#fff",
                        );
                    } else {
                        console.log("5.1.绘制当前用户头像");
                        await canvasHelper.drawCircleImage(
                            canvas,
                            ctx,
                            shareInfo.avatarUrl,
                            leftPadding + headerMarginLeft,
                            (device.screenHeight - contentHeight) / 2.0 - adjustHeight + contentWidth - headerSize / 2,
                            headerSize,
                            borderWidth,
                            "#fff",
                        );
                    }

                    console.log("6.绘制日期和地点");
                    let textPositionY = (device.screenHeight - contentHeight) / 2.0 - adjustHeight + contentWidth - headerSize / 2 + 14;
                    if (shareInfo.place) {
                        console.log("6.1.绘制地点");
                        canvasHelper.drawText(
                            ctx,
                            shareInfo.place,
                            leftPadding + contentWidth - 10,
                            textPositionY,
                            "right",
                            11,
                            400,
                            fonts.list["SFRounded-Regular"].name,
                            "#fff",
                            "rgba(158, 163, 175, 1)",
                            0,
                            0,
                            5);

                        textPositionY = textPositionY - 16
                    }
                    if (shareInfo.date) {
                        console.log("6.2.绘制日期");
                        canvasHelper.drawText(
                            ctx,
                            shareInfo.date,
                            leftPadding + contentWidth - 10,
                            textPositionY,
                            "right",
                            11,
                            400,
                            fonts.list["SFRounded-Regular"].name,
                            "#fff",
                            "rgba(158, 163, 175, 1)",
                            0,
                            0,
                            5);
                    }

                    if (shareInfo.title || shareInfo.desc) {
                        console.log("7.绘制标题和内容", contentWidth);
                        let leftContent = (shareInfo.title ? "#" + shareInfo.title + "#  " : "") + shareInfo.content;
                        //显示区域宽度
                        const displayWidth = contentWidth - 40;
                        textPositionY = (device.screenHeight - contentHeight) / 2.0 - adjustHeight + contentWidth + headerSize;
                        const contentFontSize = 14, contentFontWeight = 600;
                        for (let i = 1; i <= 2; i++) {
                            let dynamicText = "";
                            for (let j = 0; j < leftContent.length;) {
                                ctx.font = contentFontWeight + " " + contentFontSize + "px " + fonts.list["SFRounded-Semibold"].name;
                                let metrics = ctx.measureText(dynamicText + leftContent[j]);
                                if (metrics.width >= displayWidth) {
                                    //最后一行,最后一个字替换成省略号
                                    if (i === 2) {
                                        dynamicText = dynamicText.substring(0, dynamicText.length - 1);
                                        dynamicText += "…"
                                    }
                                    break;
                                }
                                dynamicText += leftContent[j];
                                leftContent = leftContent.slice(1);
                            }
                            //console.log("文本内容:", dynamicText);
                            canvasHelper.drawText(
                                ctx,
                                dynamicText,
                                leftPadding + 20,
                                textPositionY,
                                "left",
                                contentFontSize,
                                contentFontWeight,
                                fonts.list["SFRounded-Semibold"].name,
                                "#303133",
                                "",
                                0,
                                0,
                                0
                            );
                            textPositionY = textPositionY + 20;
                        }
                    }

                    console.log("8.绘制二维码");
                    const qrcodeSize = 66, qrcodeHolderSize = 70;

                    canvasHelper.drawRoundRect(
                        ctx,
                        (device.screenWidth - qrcodeHolderSize) / 2.0,
                        (device.screenHeight - contentHeight) / 2.0 + contentHeight,
                        qrcodeHolderSize,
                        qrcodeHolderSize,
                        10,
                        'rgba(255, 255, 255, 1)'
                    );
                    ctx.clip();
                    await canvasHelper.drawImage(
                        canvas,
                        ctx,
                        shareInfo.shareQrcode,
                        (device.screenWidth - qrcodeSize) / 2.0,
                        (device.screenHeight - contentHeight) / 2.0 + contentHeight + 2,
                        qrcodeSize,
                        qrcodeSize,
                        0,
                    );
                    ctx.restore();

                    /** await canvasHelper.drawCircleImage(
                     canvas,
                     ctx,
                     shareInfo.shareQrcode,
                     (device.screenWidth - qrcodeSize) / 2.0,
                     (device.screenHeight - contentHeight) / 2.0 + contentHeight,
                     qrcodeSize,
                     0
                     );*/

                    ctx.fill();
                    this.setData({allowSave: true});
                    this.toast.hideLoadingToast();
                });

            });
        },

        /**
         * 下载头像
         * @param avatarUrl
         * @returns {Promise}
         * @private
         */
        _downloadHeaderImg(avatarUrl) {
            return new Promise(((resolve, reject) => {
                wx.downloadFile({
                    url: avatarUrl, //下载头像
                    success(res) {
                        console.log("下载结束:", res);
                        if (res.statusCode === 200) {
                            //res.tempFilePath
                            resolve(res);
                        }
                    }, fail(res) {
                        reject(res);
                    }
                });
            }));
        },

        /**
         * 关闭分享页面
         */
        close() {
            this.toast.hideLoadingToast();
            this.setData({visibility: false, shareView: false, allowSave: false});
            tabbar.showTab(this);
        },
    }
})
;

Tool class canvasHelper

const stackBlur = require("stackblur-canvas");

/**
 * 初始化画布
 * @param app
 * @param base
 * @param canvasId
 * @returns {Promise}
 */
const init = (app, base, canvasId) => {
    return new Promise((resolve, reject) => {
        const device = app.getSystemInfo();
        const query = base.createSelectorQuery();
        query.select(canvasId)
            .fields({node: true, size: true})
            .exec((res) => {
                const canvas = res[0].node;
                const ctx = canvas.getContext('2d');
                const dpr = device.pixelRatio;
                canvas.width = res[0].width * dpr;
                canvas.height = res[0].height * dpr;
                ctx.scale(dpr, dpr);
                console.log("画布初始化完毕,画布宽:", canvas.width, "画布高:", canvas.height, "设备像素比:", dpr);
                resolve({ctx, canvas});
            });
    });
}

/**
 * 绘制圆角矩形
 * @param ctx
 * @param {number} x 圆角矩形选区的左上角 x坐标
 * @param {number} y 圆角矩形选区的左上角 y坐标
 * @param {number} w 圆角矩形选区的宽度
 * @param {number} h 圆角矩形选区的高度
 * @param {number} r 圆角的半径
 * @param {string} f 填充颜色
 */
const drawRoundRect = (ctx, x, y, w, h, r, f) => {
    ctx.save();
    // 开始绘制
    ctx.beginPath();
    // 因为边缘描边存在锯齿,最好指定使用 transparent 填充
    // 这里是使用 fill 还是 stroke都可以,二选一即可
    ctx.fillStyle = f;
    // ctx.setStrokeStyle('transparent')
    // 左上角
    ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);

    // border-top
    ctx.moveTo(x + r, y);
    ctx.lineTo(x + w - r, y);
    ctx.lineTo(x + w, y + r);
    // 右上角
    ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2);

    // border-right
    ctx.lineTo(x + w, y + h - r);
    ctx.lineTo(x + w - r, y + h);
    // 右下角
    ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5);

    // border-bottom
    ctx.lineTo(x + r, y + h);
    ctx.lineTo(x, y + h - r);
    // 左下角
    ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI);

    // border-left
    ctx.lineTo(x, y + r);
    ctx.lineTo(x + r, y);

    // 这里是使用 fill 还是 stroke都可以,二选一即可,但是需要与上面对应
    ctx.fill();
    // ctx.stroke()
    ctx.closePath();
    // 剪切
    //ctx.clip();
};

/**
 * 绘制圆形
 * @param ctx
 * @param circlePointX
 * @param circlePointY
 * @param radius
 * @param backgroundColor
 * @param save
 */
const drawCircle = (ctx, circlePointX, circlePointY, radius, backgroundColor, save = true) => {
    if (save) ctx.save();
    //ctx.save();
    ctx.beginPath();
    ctx.arc(circlePointX, circlePointY, radius, 0, 2 * Math.PI, false);
    ctx.fillStyle = backgroundColor;
    ctx.fill();
    ctx.clip();
    //ctx.restore();
    if (save) ctx.restore();
};

/**
 * 绘制圆形白边图像
 * @param canvas
 * @param ctx 图像
 * @param imageUrl 图像
 * @param startPositionX x坐标
 * @param startPositionY y坐标
 * @param size 图像大小,除以2就是圆半径
 * @param borderWidth 边框宽度
 * @param borderColor 边框颜色
 * @returns {Promise<*>}
 * @private
 */
const drawCircleImage = (canvas, ctx, imageUrl, startPositionX, startPositionY, size, borderWidth, borderColor) => {
    return new Promise((resolve, reject) => {
        //叠加圆形的绘制,需要先保存一下原始环境,然后绘制完第一个圆形后恢复绘制环境,即ctx.restore();
        ctx.save();
        drawCircle(
            ctx,
            startPositionX + size / 2,
            startPositionY + size / 2,
            size / 2,
            borderColor, false
        );

        if (borderWidth) {
            drawCircle(
                ctx,
                startPositionX + size / 2,
                startPositionY + size / 2,
                size / 2 - borderWidth,
                borderColor, false
            );
        }
        drawImage(
            canvas,
            ctx,
            imageUrl,
            startPositionX,
            startPositionY,
            size,
            size,
            0,
        ).then(res => {
            ctx.restore();
            resolve(res);
        }).catch(res => {
            reject(res);
        });
    });

};

/**
 * 绘制高斯模糊效果的图像
 * @param canvas
 * @param ctx
 * @param imageUrl
 * @param startPositionX
 * @param startPositionY
 * @param width
 * @param height
 * @param blur
 * @returns {Promise}
 * @private
 */
const drawBlurImage = (canvas, ctx, imageUrl, startPositionX, startPositionY, width, height, blur) => {
    return new Promise((resolve, reject) => {
        wx.getImageInfo({
            src: imageUrl,//服务器返回的图片地址
            success: function (res) {
                console.log("=>", res);
                let imgObj = canvas.createImage();
                imgObj.src = res.path;
                imgObj.onload = async function (e) {
                    console.log("=========>", imgObj.width, imgObj.height)
                    ctx.save();
                    ctx.beginPath();
                    ctx.drawImage(imgObj, startPositionX, startPositionY, width, height);
                    //提取图片信息
                    let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

                    //进行高斯模糊
                    let gd = stackBlur.imageDataRGBA(imageData, 0, 0, canvas.width, canvas.height, blur);

                    //绘制模糊图像
                    ctx.putImageData(gd, 0, 0)

                    ctx.restore();
                    console.log("图片加载完毕:", e);
                    resolve();
                };
            },
            fail: function (res) {
                console.log(res);
                reject(res);
            }
        });
    })
};

/**
 * 绘制文本
 * @param ctx
 * @param text
 * @param startPositionX
 * @param startPositionY
 * @param textAlign
 * @param fontSize
 * @param fontWeight
 * @param color
 * @param shadowColor 阴影颜色
 * @param shadowOffsetX 阴影水平偏移距离
 * @param shadowOffsetY 阴影垂直偏移距离
 * @param shadowBlur 模糊程度
 * @param letterSpace 间隔
 * @returns {number}
 * @private
 */
const drawText = (ctx, text, startPositionX, startPositionY, textAlign, fontSize, fontWeight, fontFamily, color, shadowColor, shadowOffsetX, shadowOffsetY, shadowBlur, letterSpace = 0) => {
    if (!text) {
        return 0;
    }
    let textWidth = 0;
    ctx.save();
    ctx.beginPath();
    ctx.fillStyle = color;
    ctx.textAlign = textAlign;
    ctx.font = fontWeight + " " + fontSize + "px " + fontFamily;

    if (shadowColor) {
        console.log("设置阴影:", shadowColor);
        // 设置阴影
        ctx.shadowColor = shadowColor; //阴影颜色
        ctx.shadowOffsetX = shadowOffsetX; //偏移
        ctx.shadowOffsetY = shadowOffsetY;
        ctx.shadowBlur = shadowBlur; //模糊程度
    }

    if (!letterSpace) {
        let metrics = ctx.measureText(text);
        console.log("文字[" + text + "]宽度:", metrics.width);
        ctx.fillText(
            text,
            startPositionX,
            startPositionY
        );
        textWidth = metrics.width;
    } else {
        //对齐方式调整为left
        ctx.textAlign = "left";
        let positionXArr = [];//坐标集合
        textWidth = ctx.measureText(text).width + (text.length - 1) * letterSpace;//含letterSpace的文字总宽度
        for (let i = 0; i < text.length; i++) {
            if (i === 0) {
                switch (textAlign) {
                    case "left":
                        positionXArr.push(startPositionX);
                        break;
                    case "center":
                        positionXArr.push(startPositionX - textWidth / 2);
                        break;
                    case "right":
                        positionXArr.push(startPositionX - textWidth);
                        break;
                    default:
                        console.warn("暂不支持的textAlign:", textAlign);
                        break;
                }
            } else {
                let metrics = ctx.measureText(text[i - 1]);
                positionXArr.push(positionXArr[i - 1] + metrics.width + letterSpace);
            }
        }
        for (let i = 0; i < text.length; i++) {
            ctx.fillText(
                text[i],
                positionXArr[i],
                startPositionY
            );
        }
    }

    ctx.restore();
    return textWidth;
};

/**
 * 绘制图图像
 * @param canvas
 * @param ctx
 * @param startPositionX
 * @param startPositionY
 * @param width
 * @param height
 * @param blur
 * @param imageUrl
 * @returns {Promise}
 * @private
 */
const drawImage = (canvas, ctx, imageUrl, startPositionX, startPositionY, width, height, blur) => {
    return new Promise((resolve, reject) => {
        wx.getImageInfo({
            src: imageUrl,//服务器返回的图片地址
            success: function (res) {
                console.log("=>", res);
                let imgObj = canvas.createImage();
                imgObj.src = res.path;
                imgObj.onload = function (e) {
                    ctx.save();
                    ctx.beginPath();
                    ctx.filter = 'blur(' + blur + 'px)';
                    // ctx.globalAlpha = 0.6
                    ctx.drawImage(imgObj, startPositionX, startPositionY, width, height);
                    ctx.restore();
                    console.log("图片加载完毕:", e);
                    resolve(res);
                };
            },
            fail: function (res) {
                console.log(res);
                reject(res);
            }
        });
    })

};

/**
 * 将画布内容保存为图片
 * @param base
 * @param canvas
 * @param x
 * @param y
 * @param width
 * @param height
 * @param destWidth
 * @param destHeight
 * @param quality
 * @param fileType
 * @returns {Promise}
 */
const saveImage = (base, canvas, x, y, width, height, destWidth, destHeight, quality = 1, fileType = "png") => {
    return new Promise((resolve, reject) => {
        wx.canvasToTempFilePath({
            x,
            y,
            width,
            height,
            destWidth,
            destHeight,
            quality,
            fileType,
            canvas,
            success(res) {
                console.log(res.tempFilePath);
                wx.saveImageToPhotosAlbum({
                    filePath: res.tempFilePath,
                    success: (res) => {
                        resolve(res);
                    },
                    fail: (err) => {
                        console.error(err)
                        reject(err);
                    }
                })
            },
            fail(res) {
                console.error("保存失败:", JSON.stringify(res));
                reject(res);
            }
        }, base)
    });

}

module.exports = {
    init, drawCircle, drawCircleImage, drawImage, drawBlurImage, drawText, drawRoundRect, saveImage
}

To learn more technical development knowledge, please pay attention to the CRMEB open source mall

Guess you like

Origin blog.csdn.net/CRMEB/article/details/132405610