Skip to content

使用canvas进行图片显示,之后进行旋转,马赛克操作

vue模板

vue
<template>
    <div>
        <el-dialog title="图片编辑" :visible.sync="imageDialog" :before-close="cancelDialog" width="650px" append-to-body>
            <div>
                <div id="box" style="max-height: 50vh;overflow-y: auto;">
                    <canvas id="canvas"></canvas>
                    <div id="cur"></div>
                </div>
                <div style="background:#dfe1eb;width:100%;text-align: center;margin-top:10px;border-radius:5px">
                    <img @click="cutImage()" src="@/assets/img/cut.png" style="width:20px;margin: 2px 10px 2px 0;cursor: pointer;">
                    <img @click="mosaicImage()" src="@/assets/img/mosaic.png" style="width:20px;margin: 2px 10px 2px 0;cursor: pointer;">
                    <img @click="rotateLeftImage()" src="@/assets/img/rotate-left.png" style="width:20px;margin: 2px 10px 2px 0;cursor: pointer;">
                    <img @click="rotateRightImage()" src="@/assets/img/rotate-right.png"  style="width:20px;margin: 2px 10px 2px 0;cursor: pointer;">
                    <!-- <img @click="directionRight(index, key, value)" src="@/assets/img/direction-right.png" style="width:20px;margin: 2px 10px 2px 0;cursor: pointer;"> -->
                </div>
            </div>
            <span slot="footer" class="dialog-footer">
                <el-button type="primary" @click="submitDialog">保 存</el-button>
                <el-button @click="cancelDialog">取 消</el-button>
            </span>
        </el-dialog>
    </div>
</template>
vue
<script>
import { getOssClient, getFileUrl, ossUploadFile } from '@/utils/ossUtils.js'
import { rotateImage } from '@/utils/rotateImage.js'
export default {
    data() {
        return {
            imageDialog: false,
            newImage: {}, // 图片内容
            base64: '', // 文件base64编码
            imageFile: '', // 文件file
            imageName: '', // 文件名称
            // 父组件数组位置,为了定位旧值
            parentIndex: '', // 父组件位置
            parentKey: '', // 父组件key
            // canvas
            canvas: '',
            ctx: '',
        }
    },
    methods: {
        openDialog(index, key, value) {
            this.imageDialog = true
            this.imageName = value.fileName
            this.parentIndex = index
            this.parentKey = key
            this.$nextTick(() => {
                this.onLoadCanvas(value.imageUrl)
                this.mosaicImage()
            })
        },

        /*
         *@description: 图片赋值给canvas,初始化
         *@author: 
         *@date: 2022-02-14 09:54:09
        */
        onLoadCanvas(imageUrl) {
            let img = new Image()
            img.src = imageUrl
            img.setAttribute("crossOrigin",'Anonymous') // 设置图片跨域
            img.onload = () => {
                this.newImage.img = img
                this.newImage.width = img.width
                this.newImage.height = img.height
                this.newImage.r = 20
                // canvas渲染图片
                this.canvas = document.getElementById("canvas")
                this.ctx = this.canvas.getContext("2d")
                let cur = document.getElementById("cur")
                // 图片等比缩小
                if(img.width < 600) {
                    this.canvas.width = img.width
                    this.canvas.height = img.height
                    cur.style.width = img.width
                    cur.style.height = img.height
                    this.ctx.drawImage(img, 0, 0, img.width, img.height)
                }
                if(img.width >= 600) {
                    let scale = img.width / img.height
                    this.canvas.width = 600
                    this.canvas.height = 600 / scale
                    cur.style.width = 600
                    cur.style.height = 600 / scale
                    this.ctx.drawImage(img, 0, 0, 600, 600 / scale)
                }
                // 获取整个画布的像素数据
                this.newImage.imageData = this.ctx.getImageData(0,0,img.width, img.height)
                // 像素点数组
                this.newImage.pixels = this.newImage.imageData.data
            }
        },

        // 输出canvas文件
        findCanvasFile() {
            let canvas = document.getElementById("canvas")
            let base64 = canvas.toDataURL("image/png")
            return base64
        },

        // 图片旋转
        async rotateLeftImage() {
            this.rotateImage('left')
        },
        async rotateRightImage() {
            this.rotateImage('right')
        },
        rotateImage(orientation) {
            let img = new Image()
            img.src = this.findCanvasFile() // 渲染新的图片
            img.setAttribute("crossOrigin",'Anonymous') // 设置图片跨域
            img.onload = () => {
                if(img.width > img.height) {
                    this.canvas.width = img.height // 设置canvas的宽高,否则图片大小会不对
                    this.canvas.height = img.width
                }
                if(img.width < img.height) {
                    this.canvas.width = img.height // 设置canvas的宽高,否则图片大小会不对
                    this.canvas.height = img.width
                }
                if(img.width == img.height) {
                    this.canvas.width = img.width // 设置canvas的宽高,否则图片大小会不对
                    this.canvas.height = img.height
                }
                this.ctx.width = img.width // canvas 图像的宽高
                this.ctx.height = img.height
                if(orientation == 'left') {
                    this.ctx.rotate(270 * Math.PI / 180);
                    this.ctx.drawImage(img, -img.width, 0);
                }
                if(orientation == 'right') {
                    this.ctx.rotate(90 * Math.PI / 180);
                    this.ctx.drawImage(img, 0, -img.height);
                }
                this.onLoadCanvas(this.findCanvasFile())
            }
        },

        /*
         *@description: 开始渲染马赛克
         *@author: 
         *@date: 2022-02-25 18:00:39
        */
        mosaicImage() {
            let box = document.getElementById("box")
            let isDown = false
            box.addEventListener('mousedown', () => {
                isDown = true
            })
            box.addEventListener('mouseup', () => {
                isDown = false
            })
            // 鼠标在外层盒子区域移动时,监听
            box.addEventListener('mousemove', (e) => {
                console.log(1111, isDown)
                /**
                 * 获取外层盒子距离屏幕两边的距离
                 * 区域出现滚动条设置
                 * 马赛克,鼠标出现的位置
                 * +20 解决马赛克高度和鼠标高低不一样问题
                 */
                let top = box.getBoundingClientRect().top - document.getElementById("box").scrollTop + 20
                let left = box.getBoundingClientRect().left + document.body.scrollLeft
                // 没有滚动条
                // let top = box.getBoundingClientRect().top + document.body.scrollTop + 20

                let absPageX = e.pageX - left
                let absPageY = e.pageY - top
                let x = absPageX - this.newImage.r / 20
                let y = absPageY - this.newImage.r / 20
                let cur = document.getElementById("cur")
                cur.style.left = x
                cur.style.top = y
                // 鼠标按下,进行马赛克绘制
                if(isDown) {
                    this.fnDraw(x,y)
                }
            })
        },

        fnDraw(x,y) {
            let tileR = 8
            let arrPos = this.getPos(x,y)
            for(let i = 0; i< arrPos.length; i++) {
                // 获取像素中心点坐标
                let tmp = arrPos[i]
                let ty = tmp[0] * tileR + tileR / 2
                let tx = tmp[1] * tileR + tileR / 2

                let pos = (Math.floor(ty) * (this.newImage.imageData.width * 4)) + (Math.floor(tx) * 4)
                let red = this.newImage.pixels[pos]
                let green = this.newImage.pixels[pos+1]
                let blue = this.newImage.pixels[pos+2]
                this.ctx.fillStyle = "rgb("+ red + ","+ green + ","+ blue + ")"
                this.ctx.fillRect(tx, ty, tileR, tileR)
            }
        },

        getPos(x,y) {
            let tileR = 8
            let curR = 20
            let rs = Math.floor(y/tileR)
            let cs = Math.floor(x/tileR)
            let number = Math.floor(curR/tileR)
            let tmp = []
            for(let i = rs; i < rs+number; i++) {
                for(let j = cs; j < cs+number; j++) {
                    tmp.push([i,j])
                }
            }
            return tmp
        },

        dataURLtoFile(dataUrl, imageName) {
            var arr = dataUrl.split(','),
            bstr = atob(arr[1]),
            n = bstr.length,
            u8arr = new Uint8Array(n);
            while(n--) {
                u8arr[n] = bstr.charCodeAt(n);
            }
            return new File([u8arr], imageName, {
                type: 'image'
            })
        },

        // 裁剪图片
        cutImage() {
            let imageUrl = this.findCanvasFile()
            this.$refs.OnmcCropperImage.openDialog(imageUrl)
        },

        /*
         *@description: 保存编辑
         *@author: 
         *@date: 2022-02-15 09:24:44
        */
        async submitDialog() {
            this.findImageFile()
            //图片同步上传,返回存储路径和图片地址
            let newImageObject = await this.uploadRotateImage(this.imageFile)
            this.$emit('findImageInfo', newImageObject, this.parentIndex, this.parentKey)
            this.cancelDialog()
        },

        /*
         *@description: 获取canvas图片文件
         *@author: 
         *@date: 2022-02-14 17:46:07
        */
        findImageFile() {
            this.base64 = this.canvas.toDataURL("image/png")
            this.imageFile = this.dataURLtoFile(this.base64, this.imageName)
        },

        /*
         *@description: oss同步上传
         *@author: 
         *@date: 2022-02-15 09:34:23
        */
        async uploadRotateImage(file) {
            // 调用oss上传方法, 获取token
            let bucketName = 'onmc-poss'
            let date = new Date()
            let time = date.getTime()
            let data = {}
            data.bucketName = bucketName
            data.key = 'sit/coco/' + time + '/' + this.imageName
            data.durationSeconds = 3600
            let ossClient = await getOssClient(data)
            let uploadFile = await ossUploadFile(ossClient, data.key, file)
            let newImageUrl = await getFileUrl(ossClient, data.key)
            // 上传成功后,获取文件路径
            let fileOssKey = bucketName + ',' + data.key
            let imageObj = {
                fileOssKey: fileOssKey,
                newImageUrl: newImageUrl
            }
            return imageObj
        },

        cancelDialog() {
            this.newImage = {},
            this.base64 = '',
            this.imageFile = '',
            this.imageName = '',
            this.parentIndex = '',
            this.parentKey = '',
            this.imageDialog = false;
        }
    }
}
</script>

样式

vue
<style lang="stylus" scoped>
.cur
    width 10px
    height 10px
    position absolute
    background-color rgba(0,0,0,0.8)
    left 0
    top 0
    display none
</style>

Released under the MIT License.