Canvas绘制毛玻璃背景分享海报

news2024/11/17 10:02:26

最近重新设计了分享海报,用毛玻璃作为背景,使整体更有质感,如果没有用到canvas,毛玻璃效果其实很好实现,给元素添加一个滤镜即可(比如:filter: blur(32px)),但是实践的过程中发现,canvas在IOS端一直没有效果,查了一个文档发现IOS端不支持filter。。。有点想骂人。。(PS:微信官方有关CanvasRenderingContext2D的文档还比较简略,更加详细的文档大家可以移步至https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D)
在这里插入图片描述

没办法,实在是喜欢毛玻璃的效果,决定想想办法迈过这道坎,继续网上查阅资料,查到大概的方法就是

1、用canvas上下文先画一个image,image的src就是要模糊处理的图片路径,

2、用canvas上下文把步骤1的image提取imageData,然后进行高斯模糊,高斯模糊的算法网上有挺多,但是拿来执行运算时间要7,8s,比较不理想,最后我是找到了一个stackblur-canvas的插件,这个插件也适用于H5,这里只需要调用里面高斯模糊算法的方法(stackBlur.imageDataRGBA)即可,执行下来几百毫秒就能出来,跟网上的算法差距还是很明显啊。。

3、最后把已经模糊处理的imageData绘制到位图中即可。

canvas滤镜的问题搞定了,还有一个小问题是开发者工具上面canvas的层级始终会比其他元素高,但在IOS真机上元素是可以布局在canvas上。由于不知道在其他客户端会不会也会像开发者工具那样有层级显示问题。所以保险起见我干脆就把canvas放在在页面可视区域底部,在可视区域用其他页面元素做了一个分享效果图出来。

底下两张图,左边是预览,右边是最终画布生成图片。可以看出wxss的滤镜效果还是会比通过第三方高斯算法处理后的模糊效果来的更自然舒服,不过整体效果也还在可接受范围内。

在这里插入图片描述
在这里插入图片描述

附上核心代码

组件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>

组件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;
}

组件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);
        },
    }
})
;

工具类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
}

学习更多技术开发知识请关注CRMEB开源商城

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/906593.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTTPS代理搭建技巧分享

今天我们来分享一下如何搭建一个能够实现中间人 检测和防护的HTTPS代理。保护我们的网络通信安全是至关重要的&#xff0c;让我们一起学习如何构建一个安全可靠的HTTPS代理吧&#xff01; 什么是中间人 &#xff1f; 首先&#xff0c;让我们来了解一下什么是中间人 。中间人 是…

html表格中加入斜线,使用css给table表格表头单元格添加斜线

背景&#xff1a;业务给了90张word电子表格&#xff0c;需要用html设计出来。 如图所示&#xff0c;红色区域的下斜线如何实现&#xff1f; 先说结论&#xff1a;html中table没有直接的斜线表头标签&#xff0c;但结合css、svg之类的可以实现。 #lineTd{ background:#FFFFFF u…

Leetcode67 二进制求和

给你两个二进制字符串 a 和 b &#xff0c;以二进制字符串的形式返回它们的和。 代码 class Solution {public String addBinary(String a, String b) {StringBuilder res new StringBuilder();int carry 0;int i a.length() - 1, j b.length() - 1;while(i > 0 || j &…

『吴秋霖赠书活动 | 第一期』《强化学习:原理与Python实战》

文章目录 一、什么是RLHF&#xff1f;二、RLHF适用于哪些任务&#xff1f;三、RLHF和其他构建奖励模型的方法相比有何优劣&#xff1f;四、什么样的人类反馈才是好的反馈五、RLHF算法有哪些类别&#xff0c;各有什么优缺点&#xff1f;七、如何降低人类反馈带来的负面影响&…

2021电赛国一智能送药小车(F题)设计报告

2021电赛国一智能送药小车&#xff08;F题&#xff09;设计报告 【写在前面的话】 电赛是一个很奇妙的过程&#xff0c;可能有些人觉得电赛的门槛太高&#xff0c;那便意味着&#xff0c;当你决定要参加电赛的那一刻起&#xff0c;这一段路、这些日子就注定不会太轻松&#xf…

WPS中的表格错乱少行

用Office word编辑的文档里面包含表格是正常的&#xff0c;但用WPS打开里面的表格就是错乱的&#xff0c;比如表格位置不对&#xff0c;或者是表格的前几行无法显示、丢失了。 有一种可能的原因是&#xff1a; 表格属性里面的文字环绕选成了“环绕”而非“无”&#xff0c;改…

周易卦爻解读笔记——既济

第六十三卦既济 水火既济 坎上离下 既济卦由泰卦所变&#xff0c;泰卦六五与九二换位&#xff0c;象征已经完成。 地天泰 序卦传【有过物者必济&#xff0c;故受之以既济】 既&#xff0c;已经。《谷梁传》云&#xff1a;“既者&#xff0c;尽也。有继之辞也。”济者&#…

DNDC模型建模方法及在土壤碳储量、温室气体排放、农田减排、土地变化、气候变化中的应用

第一讲 DNDC模型介绍 ①碳循环模型简介 ②DNDC模型原理 ③DNDC下载与安装 ④DNDC注意事项 第二讲 DNDC初步操作 ①DNDC界面介绍 ②DNDC数据及格式 ③DNDC点尺度模拟 ④DNDC区域尺度模拟 ⑤DNDC结果查看 第三讲 遥感和GIS基础 ①DNDC中的遥感和GIS技术 ②ArcGIS软件界面 ③坐…

【开发笔记】ubuntu部署指定版本的前后端运行环境(npm nodejs mysql)

目录 1 背景2 环境要求3 部署流程3.1 npm的安装3.2 nodejs的安装3.3 MySQL的安装 4 可能的问题 1 背景 在远程服务器上的Ubuntu系统中&#xff0c;部署指定版本的前后端项目的运行环境 2 环境要求 npm 9.5.1Nodejs v18.16.1MySQL 8.0.33 3 部署流程 3.1 npm的安装 通过安…

docker导出、导入镜像、提交

导出镜像到本地&#xff0c;然后可以通过压缩包的方式传输。 导出&#xff1a;docker image save 镜像名:版本号 > /home/quxiao/javatest.tgz 导入&#xff1a;docker image load -i /home/quxiao/javatest.tgz 删除镜像就得先删除容器&#xff0c;当你每运行一次镜像&…

【三】关系模型 -- 基本概念

基本概念关系模型概述关系模型的提出关系模型研究什么关系模型的三要素 什么是关系概念引入1. 域2. 笛卡尔积3. 关系 关系模式 VS 关系关系的特性1. 列是同质2. R(A:D) 中&#xff0c;A 不可相同&#xff0c;D 可相同3. 行、列位置互换性4. 属性不可再分&#xff08;关系第一范…

用yolov4-tiny检测在电力输电线20种鸟类,灵活运用训练trick,实验较为完备,数据处理丰富度值得参考

Detection of bird species related to transmission line faults based on lightweight convolutional neural network Abstract 输电线路高效防鸟害是电网运行维护面临的长期挑战。本文提出了一种将轻量级卷积神经网络(CNN)、图像处理和目标检测相结合的方法来检测与输电线路…

基于GPT-4和LangChain构建云端定制化PDF知识库AI聊天机器人

参考&#xff1a; GitHub - mayooear/gpt4-pdf-chatbot-langchain: GPT4 & LangChain Chatbot for large PDF docs 1.摘要&#xff1a; 使用新的GPT-4 api为多个大型PDF文件构建chatGPT聊天机器人。 使用的技术栈包括LangChain, Pinecone, Typescript, Openai和Next.js…

Day12-2-面向对象编程

Day12-面向对象编程 一 回顾 变量,数组,对象都是容器,都可以用来存储数据 let n = 10 let arr = [3,5,7] let stu = {name:"张恒",age:18,sex:"女"}二 面向对象思想 面向过程:将开发的步骤按照顺序一步一步往下执行,直到程序结束 面向对象:将项目中…

第2步---MySQL卸载和图形化工具展示

第2步---MySQL卸载和图形化工具展示 1.MySQL的卸载 2.MySQL的图形化工具 2.1常见的图形化工具 SQLyog&#xff1a;简单。SQLyog首页、文档和下载 - MySQL 客户端工具 - OSCHINA - 中文开源技术交流社区 Mysql Workbench &#xff1a;MySQL :: MySQL Workbench DataGrip&…

百度吴甜重磅发布文心一言面向开发者的三大举措,激活生态创新

近日&#xff0c;第九届WAVE SUMMIT深度学习开发者大会在京举办。百度集团副总裁、深度学习技术及应用国家工程研究中心副主任吴甜分享了百度自研大语言模型“文心一言”的最新进展&#xff0c;重磅发布5个原生插件&#xff0c;面向开发者正式推出AI Studio星河大模型社区、插件…

如何限制PDF打印?限制清晰度?

想要限制PDF文件的打印功能&#xff0c;想要限制PDF文件打印清晰度&#xff0c;都可以通过设置限制编辑来达到目的。 打开PDF编辑器&#xff0c;找到设置限制编辑的界面&#xff0c;切换到加密状态&#xff0c;然后我们就看到 有印刷许可。勾选【权限密码】输入一个PDF密码&am…

【Python从入门到进阶】32、bs4的基本使用

接上篇《31、使用JsonPath解析淘票票网站地区接口数据》 上一篇我们介绍了如何使用JSONPath来解析淘票票网站的地区接口数据&#xff0c;本篇我们来学习BeautifulSoup的基本概念&#xff0c;以及bs4的基本使用。 一、BeautifulSoup简介 1、bs4基本概念 BeautifulSoup是一个P…

ssm网络游戏公司官方平台源码和文档

ssm网络游戏公司官方平台源码和文档029 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm 摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它…

git协议实现管理(三个步骤)

GitHub官网访问&#xff1a; https://github.com/dashboard 初次使用git的用户要使用git协议大概需要三个步骤: 一、生成密钥对 二、设置远程仓库(本文以github为例)上的公钥 三、把git的remote url远程仓库URL可访问路径修改为git协议(以上两个步骤初次设置过以后&#xff0c…