首页 > 其他分享 >Canvas绘制毛玻璃背景分享海报

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

时间:2023-08-21 12:34:24浏览次数:47  
标签:Canvas console res ctx 海报 param canvas device 毛玻璃

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

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

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

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的滤镜效果还是会比通过第三方高斯算法处理后的模糊效果来的更自然舒服,不过整体效果也还在可接受范围内。

Canvas绘制毛玻璃背景分享海报_前端开发_02

Canvas绘制毛玻璃背景分享海报_产品海报_03

附上核心代码

组件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开源商城

标签:Canvas,console,res,ctx,海报,param,canvas,device,毛玻璃
From: https://blog.51cto.com/u_15723831/7173701

相关文章

  • 2D应用开发是选择WebGL 还是选择Canvas?
    推荐:使用NSDT场景编辑器助你快速搭建可二次编辑的3D应用场景在介绍WebGL和Canvas的区别和联系之前,需要先了解它们各自的定义和特点。WebGL是一种基于标准HTML5的技术,用于在Web浏览器中实时渲染3D图形。它是由KhronosGroup开发的一套API(ApplicationProgrammingInterface),允许开......
  • unity 关于如何调整Canvas画布的大小
    如何调节Canvas画布大小呢?1.先在Hierarchy面板选择Canvas对象2.然后在Inspector找到Canvas3.最后找到RenderMouse,在下拉列表中选择WorldSpace选项;即可更改画布的大小。    ......
  • 使用Canvas API实现交互式绘图和动画:基础知识和实践经验
    CanvasAPI是HTML5中的一个重要特性,它允许开发者在网页上直接绘制图形、图像、文本等内容,并且可以通过JavaScript进行控制和交互。在本文中,我们将介绍CanvasAPI的基础知识,包括如何创建Canvas元素、设置Canvas的属性、绘制基本形状和路径、以及使用图片和文字等高级功能。创建Can......
  • 微信小程序:自动生成打卡海报
    完整项目下载:下载链接1前言在当前的背单词小程序开发中,为满足用户学习完成后的展示需求,计划引入自动生成打卡海报功能,以提升用户参与度与推广效果。除了基本的海报生成功能,还可以考虑以下丰富内容:将用户的学习数据融入海报,展示用户的学习时长、单词掌握率等统计信息,让用户......
  • 前端合成海报并保存到本地
    近期给AI产品增加了推广返佣的能力,涉及到推广就会有分享裂变,个人的专属分享链接及海报。本次文章主要记录前端合成海报并下载到本地的流程,因为产品运行的平台主要是在小程序和PC浏览器,所以主要也是实现这两个平台的图片下载,接下来开始正文。浏览器端实现在实现下载之前首先要将海......
  • 微信小程序解决ec-canvas偶现echarts未显示问题
    使用ec-canvas展示echarts图表,但是在实际操作中,偶现echarts空白问题。解决<viewclass="echarts-con"><ec-canvasid="pie-echarts"canvas-id="pie-echarts":ec="ec"@inited="getData"/></view>constgetData=as......
  • 微信小程序:自动生成打卡海报
    文章目录1前言2界面展示3部分代码展示以及原理解释4结语完整项目下载:下载链接1前言在当前的背单词小程序开发中,为满足用户学习完成后的展示需求,计划引入自动生成打卡海报功能,以提升用户参与度与推广效果。除了基本的海报生成功能,还可以考虑以下丰富内容:将用户的学习数据融入......
  • 【腾讯云 Cloud Studio 实战训练营】在线 IDE 编写 canvas 转换黑白风格头像
    关于CloudStudioCloudStudio是基于浏览器的集成式开发环境(IDE),为开发者提供了一个永不间断的云端工作站。用户在使用CloudStudio时无需安装,随时随地打开浏览器就能在线编程。CloudStudio作为在线IDE,包含代码高亮、自动补全、Git集成、终端等IDE的基础功能,同时支持实......
  • H5用canvas放烟花
    很久很久以前的一个很流行的javaApplet放烟花效果,当初移到android过,这次摸鱼时间翻译成js代码,用canvas实现这么多年,终于能大致看懂这代码了,已经实现透明效果,只需要给body弄个好看的背景图片就行,但需要主色为深色,看到的人谁有兴趣美化下,弄个背景加个声音啥的,不过没啥用......
  • 使用canvas(2d)+js实现一个简单的傅里叶级数绘制方波图
    先看效果查看页面右下角,嘿嘿简要说明创建具有不同半径与角速度的圆集合;(截图中展现的效果为5个,代码是30个,运行后效果会不同)constgetCircles=(N=10)=>{constret=[];for(leti=0;i<N;i+=1){ret.push({r:100/(i*2+1),ω:i*2+1,......