Canvas实现缩放+涂鸦改进

news2024/11/16 13:36:32

几个月以前,有人问了我一个canvass怎么实现缩放和涂鸦的问题,我基于当时的想法写了一篇博客,但是后来发现当时做的不完善,所以实现上其实还是有一些其他问题的。但是因为前段时间太忙了,也就一直没有机会去改进它。现在总算是有时间了,正好抽空把这个问题解决一下。因为不太熟悉 JS,感觉代码写多了(200行以上),复杂性上来之后,我的能力就无法来维护这个代码了,所以这次换一个面向对象的写法,感觉是好了一点。

演示效果

左边是展示的 Canvas,右边是缓存 Canvas(这个通常是不显示的),这里进行显示是为了让你更好的理解我的思路。移动图片是很好理解的,主要是缩放和绘制,绘制时,需要计算当前点在图片上面的位置,然后计算对应的在缓存 Canvas图片上的位置,然后在对应的位置进行绘制。今天的状态不好,不想写那么详细了,可以去看上一篇博客了解怎么做的,这里主要是代码上面的改进,总体的思路是不变的:
canvas实现图片缩放+涂鸦

  • 拖拽图片进行移动
  • 在图片上面进行绘制
  • 拖拽绘制的图片
    在这里插入图片描述

完整代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Image-Editor</title>
    <style type="text/css">
        body, div {
            margin: 0;
            padding: 0;
        }

        #cs {
            float: left;
        }

        canvas {
            border: red 1px solid;
        }
    </style>
</head>
<body>

    <div class="cas">
        <canvas id="cs" width="800" height="600"></canvas>
    </div>

    <div class="cas">
        <canvas id="cache_cs" width="800" height="600"></canvas>
    </div>

    <button id="draw_move" onclick="editor.moveOrDraw()">移动</button>

    <script type="text/javascript">
        let ZOOM = [0.5, 0.6, 0.7, 1.0, 1.1, 1.2, 1.5, 2.0] // 缩放数组
        let editor = {
            isUserMove: true,   // 用户是否在移动,否则就是绘制
            isMouseDown: false, // 用户鼠标是否按下,按下才处理移动和绘制
            isInImage: false,
            zoomIndex: 3,  // 缩放下标
            lineWidth: 10, // 默认线宽
            unit: 400,     // 宽高的最大值
            width: 0,      // 图像的宽
            height: 0,     // 图像的高
            canvas: null,
            ctx: null,
            cacheCanvas: null,
            cacheCtx: null,
            vertexPos: {x:0, y:0},  // 左上顶点的位置
            mousePos: {x:0, y:0},   // 鼠标当前位置

            init: function() {
                this.canvas = document.getElementById("cs")
                this.ctx = this.canvas.getContext("2d")
                this.ctx.lineWidth = this.lineWidth * ZOOM[this.zoomIndex]
                this.ctx.strokeStyle = "red"

                this.cacheCanvas = document.getElementById("cache_cs")
                this.cacheCtx = this.cacheCanvas.getContext("2d")
                this.cacheCtx.lineWidth = this.lineWidth * ZOOM[this.zoomIndex]
                this.cacheCtx.strokeStyle = "black"
            },
            loadImage: function() {
                let img = new Image()
                img.src = "./husky.png"
                img.onload = () => {
                    // 缩放图片
                    if (img.width > img.height) {
                        this.width = 400
                        this.height = this.width * img.height / img.width
                    } else {
                        this.height = 400
                        this.width = this.height * img.width / img.height
                    }

                    // 计算左上顶点的位置
                    this.vertexPos = {
                        x: (this.canvas.width-this.width)/2, 
                        y: (this.canvas.height-this.height)/2
                    }
                    let zoom = ZOOM[this.zoomIndex]
                    this.ctx.drawImage(img, this.vertexPos.x, this.vertexPos.y, this.width*zoom, this.height*zoom)
                    this.cacheCtx.drawImage(img, 0, 0, this.width, this.height)
                }
            },
            addMouseEvent: function() {

                // 鼠标按下
                this.canvas.onmousedown = e => {
                    let x = e.clientX - this.canvas.offsetLeft
				    let y = e.clientY - this.canvas.offsetTop
                    this.isMouseDown = true
                    // 每次按下鼠标时更新顶点的位置
                    let zoom = ZOOM[this.zoomIndex]
                    console.log("vertex: ", this.vertexPos)
        
                    this.mousePos = {x: x, y: y}
                    console.log("On (%d, %d)", x, y)
                    // 判断是否点击在图像上, 否则不做处理
                    if (this.isMouseInImage(x, y)) {
                        console.log("In image")
                        this.isInImage = true
                        // 这里加一个选中提示框
                        if (this.isUserMove) {
                            this.drawChooseRect()
                        } else {
                            // 把画笔移动到鼠标点击处
                            this.ctx.beginPath()
                            this.ctx.moveTo(x, y)
                            console.log("move: ", x, y)
                            // 计算相对位置
                            let cachePos = this.computeRelevantPos(x, y)
                            this.cacheCtx.beginPath()
                            this.cacheCtx.moveTo(cachePos.x, cachePos.y)
                        }
                    } else {
                        console.log("Out image")
                    }
                }

                // 鼠标移动
                this.canvas.onmousemove = e => {
                    // 鼠标按下才处理
                    if (!this.isMouseDown) {
                        return
                    }

                    let x = e.clientX - this.canvas.offsetLeft
				    let y = e.clientY - this.canvas.offsetTop

                    // 鼠标在图像外部不处理
                    if (!this.isMouseInImage(x, y)) {
                        return
                    }

                    let dx = x-this.mousePos.x
                    let dy = y-this.mousePos.y
                    
                    // 更新鼠标的位置
                    this.mousePos.x = x
                    this.mousePos.y = y
                
                    if (this.isUserMove) {
                        // 移动操作
                        // 更新顶点位置
                        this.vertexPos.x = this.vertexPos.x + dx
                        this.vertexPos.y = this.vertexPos.y + dy
                        // 重新绘制
                        this.redraw()
                        this.drawChooseRect()
                    } else {
                        // 绘制操作
                        this.draw(x, y)
                    }
                }

                // 鼠标滚轮
                this.canvas.onmousewheel = e => {
                    // 禁止移动和缩放一起操作
                    if (this.isMouseDown) {
                        return
                    }
                    let x = e.clientX - this.canvas.offsetLeft;
                    let y = e.clientY - this.canvas.offsetTop;              
                    delta = e.wheelDelta;
                    if (delta > 0) {
                        if (this.zoomIndex + 1 < ZOOM.length) {
                            this.zoomIndex += 1;
                        } else {
                            this.zoomIndex = ZOOM.length - 1;
                        }
                    } else {
                        if (this.zoomIndex - 1 >= 0) {
                            this.zoomIndex -= 1;
                        } else {
                            this.zoomIndex = 0;
                        }
                    }
                    // 图像缩放
                    this.redraw()
                }

                let mouseUpAndOut =e => {
                    this.isMouseDown = false
                    this.isInImage = false
                    // 如果是在移动操作中,则清空canvas,重新绘制
                    if (this.isUserMove) {
                        this.redraw()
                    }
                }
                // 鼠标松开和鼠标离开
                this.canvas.onmouseup = mouseUpAndOut
                this.canvas.onmouseout = mouseUpAndOut
            },
            moveOrDraw: function() {
                this.isUserMove = !this.isUserMove
                if (this.isUserMove) {
                    document.getElementById("draw_move").innerText = "移动";
                } else {
                    document.getElementById("draw_move").innerText = "绘制";
                }
            },
            redraw: function() {
                let zoom = ZOOM[this.zoomIndex]
                this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
                
                // 如果从顶点处进行缩放,我感觉不是很好,所以考虑从图像的中心处开始缩放,
                // 所以这里的顶点的计算要多思考一下
                this.ctx.save()
                this.ctx.drawImage(this.cacheCanvas, 0, 0, 
                    this.width, this.height, 
                    this.vertexPos.x + (1-zoom)*this.width/2,
                    this.vertexPos.y + (1-zoom)*this.height/2,
                    this.width*zoom, this.height*zoom)
                this.ctx.restore()

                console.log("zoom vertex: ", this.vertexPos)
            },
            drawChooseRect: function() {
                let zoom = ZOOM[this.zoomIndex]
                this.ctx.save()
                this.ctx.lineWidth = 1
                this.ctx.strokeStyle = "red"
                this.ctx.strokeRect(
                    this.vertexPos.x + (1-zoom)*this.width/2, 
                    this.vertexPos.y + (1-zoom)*this.height/2, 
                    this.width*zoom,
                    this.height*zoom
                )
                this.ctx.restore()
            },
            draw: function(x, y) {
                // 在显示canvas中绘制图像,在缓存canvas中绘制
                let zoom = ZOOM[this.zoomIndex]
                this.ctx.save()
                this.ctx.lineWidth = this.ctx.lineWidth * zoom
                this.ctx.lineTo(x, y)
                this.ctx.stroke()
                this.ctx.restore()

                // 计算在缓存canvas中的相对位置,并进行绘制
                let cachePos = this.computeRelevantPos(x, y)
                this.cacheCtx.save()
                // 缩小对应放大的尺寸
                this.cacheCtx.lineWidth = this.ctx.lineWidth / zoom
                this.cacheCtx.lineTo(cachePos.x, cachePos.y)
                this.cacheCtx.stroke()
                this.cacheCtx.restore()
            },
            isMouseInImage: function(x, y) {
                let zoom = ZOOM[this.zoomIndex]
                let vx = this.vertexPos.x+(1-zoom)*this.width/2
                let vy = this.vertexPos.y+(1-zoom)*this.height/2
                let xInImage = vx <= x && x <= vx+this.width*zoom
                let yInImage = vy <= y && y <= vy+this.height*zoom

                if (xInImage && yInImage) {
                    return true
                }
                return false
            },
            computeRelevantPos: function(x, y) {
                // 对应的缓存画布坐标需要做一个转换
                // 计算相对位置,这里是这个程序最复杂的一部分了
                // 这里需要考虑到显示canvas中图像的顶点位置,缩放尺寸,
                // 然后来计算在对应的缓存canvas上的相对位置
                let zoom = ZOOM[this.zoomIndex]
                let vx = this.vertexPos.x+(1-zoom)*this.width/2
                let vy = this.vertexPos.y+(1-zoom)*this.height/2
                return {
                    x: (x-vx) / zoom,
                    y: (y-vy) / zoom
                }
            },
            run: function() {
                this.init()
                this.loadImage()
                this.addMouseEvent()
            }
        }
        editor.run()
    </script>
</body>
</html>

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

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

相关文章

超详细:实现 Swift 与 汇编(Asm)代码混编并在真机或模拟器上运行

功能需求 虽然现在  开发的绝对主角是 Swift 语言&#xff0c;不过我们也希望有时 Swift 能够调用小段汇编代码以完成特殊功能。 在本篇博文中&#xff0c;您将学到如下内容&#xff1a; Swift 与 汇编语言混编的基本原理&#xff1b;如何在模拟器中使用 Swift x64 汇编指…

小黑带领阿黄中老黑一起跑步完成了小怪兽,晚上一起吃烤肠西瓜,买了帐篷准备一起露营的leetcode之旅:438. 找到字符串中所有字母异位词

小黑代码 class Solution:def findAnagrams(self, s: str, p: str) -> List[int]:# 串p长度n_p len(p)# 串s长度n_s len(s)# 计数字典flags collections.Counter(p)# 统计字典map_ dict((k, 0) for k in p)# 匹配到的字符个数count 0# 头尾指针left right 0# 目标数…

Python给一个exe执行文件注册持续性的快捷键(热键)的代码实例

本篇文章主要讲解通过python给一个exe文件绑定一个快捷键、并取消快捷键(热键)的实操方法。 日期:2023年6月11日 作者:任聪聪 实现按下快捷键即可启动软件的效果说明 启动软件注册热键呼出其他软件或本体的效果说明: 演示材料说明:在download文件目录下存放一个可执行的…

数据结构与算法之美 | 栈

栈结构&#xff1a;后进者先出&#xff0c;先进者后出 栈是一种“操作受限”的线性表 当某个数据集合只涉及在一端插入和删除数据&#xff0c;并且满足后进先出、先进后出的特性&#xff0c;这时我们就应该首选“栈”这种数据结构 栈的实现 使用数组实现&#xff1a;顺序栈…

【数据结构】二叉树(一)

目录 一、树的概念及结构 1、树的概念 2、树的相关概念 3、树的表示 二、二叉树概念及结构 1、二叉树的概念 2、特殊二叉树 3、二叉树的性质 4、二叉树的存储结构 4.1 顺序存储结构 4.2 链式存储结构 三、二叉树顺序结构及实现 1、二叉树的顺序结构 2、堆的概念及结构 3、堆…

OMG--RTPS(Real Time Publish Subscribe Protocol)

OMG--RTPS&#xff08;Real Time Publish Subscribe Protocol&#xff09; 1 概述2 内容缩写DDS 有线协议的要求RTPS 有线协议The RTPS Platform Independent Model (PIM)The Structure ModuleThe Messages ModuleThe Behavior ModuleThe Discovery Module The RTPS Platform S…

Xuperchain多节点网络搭建+加节点+测试

环境准备 创建网络部署环境 # 在xuperchain目录执行 make testnet 种子节点 # 查看node1节点连接地址netURL cd node1 ./bin/xchain-cli netURL preview # 得到如下结果,实际使用时,需要将ip配置节点的真实ip,port配置成 /ip4/{{ip}}/tcp/{{port}}/p2p/Qmf2HeHe4sspGkfR…

深度学习应用篇-计算机视觉-OCR光学字符识别[7]:OCR综述、常用CRNN识别方法、DBNet、CTPN检测方法等、评估指标、应用场景

【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍&#xff1a;【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化…

上课补充的知识

题目 char类型的默认值是\u0000 数组的创建方式 数组的遍历 遍历:从头到尾,依次访问数组每一个位置,获取每一个位置的元素.形式如下: 我们通过数组的下标操作数组,所以for循环变量操作的也是数组下标 开始:开始下标0 结束:结束下标length-1 如何变化: 语法&#xff1a; for…

大学结束啦!!!

前言&#xff1a;相信看到这篇文章的小伙伴都或多或少有一些编程基础&#xff0c;懂得一些linux的基本命令了吧&#xff0c;本篇文章将带领大家服务器如何部署一个使用django框架开发的一个网站进行云服务器端的部署。 文章使用到的的工具 Python&#xff1a;一种编程语言&…

神舟笔记本“性能、娱乐、省电、安静”模式之间的区别

前言&#xff1a;主要是对比神舟笔记本电脑“性能、娱乐、省电、安静”模式之间的区别 工具及硬件 名称版本号电脑Z8D6 2.5k屏鲁大师6.1023.xxx 之所以使用鲁大师&#xff0c;主要是为了节省时间.另外仅仅只是为了做横向对比&#xff0c;不需要太专业的工具。 实验中有两个变…

六级备考6天|CET-6|听力第一二三四讲|复习回顾|长对话篇章|14:00~16:30

长对话 篇章 目录 听写笔记 练习讲义 听写笔记 1. 听力策略 听前&#xff1a;读题——分析文章——预测题目 听中&#xff1a;划出听到的内容——对应程度高为正确选项 听后&#xff1a;不听题目——往下读题 2. 重点词汇 proofread / ˈpruːfriːd / …

CodeWhisperer插件使用体验

官方教程点击跳转 使用工具 1.vscode 2.插件(AWS Toolkit),免费使用 安装以后如何使用 1.首先要有一个aws账号 2.插件下载好以后登录aws账号&#xff0c;我们主要用这款插件的CodeWhisperer这个功能&#xff0c;其它的自行看官方教程了解。 注意事项&#xff1a;我们在从vs…

杭州互联网医疗Java实习一面

目录 1.java集合知道哪些2.ArrayList和LinkedList插入效率对比3.HashMap的底层结构4.HashMap怎么实现线程安全4.介绍下reentrantlock5.Redis分布式锁的实现原理7.知道哪些排序算法8.快排的原理9.Spring的AOP作用和原理10.MySQL的InnoDB索引结构11.网络中TCP和UDP的区别12.JVM的…

delphi 调用youtube-dl命令,下载youtube视频,原理及源代码

一、概要 1、Youtube-dl工具 强大的视频下载命令行工具Youtube-dl项目由Ricardo Garcia创建于2008年&#xff0c;源代码由Python编写&#xff0c;托管在GitHub上&#xff0c; 最初仅支持YouTube&#xff0c;但随着项目的发展&#xff0c;也开始支持其他视频网站&#xff0c;优势…

如何优化selenium webdriver的执行速度

目录 前言 在page_source中断言text比直接使用text属性断言要快 元素越具体&#xff0c;获取text的速度越快 使用变量去缓存没有变化的元素 快速在文本框中输入大文本 使用动态等待进行动态/AJAX 操作而不是固定睡眠 最后 前言 让自动化测试脚本正常工作只是自动化测试的…

微信小程序的自动化测试框架Minium详解,10分钟掌握

目录 前言 minium 是为小程序专门开发的自动化框架 文档使用 框架依赖运行环境部署 使用 打开工具 特别说明&#xff1a; 总结&#xff1a; 前言 微信发布了小程序的自动化测试框架Minium&#xff0c;提供了多种运行验证方式&#xff0c;其特点&#xff1a; 支持一套脚…

Alloy Tutorial(2)LastPass; cacheMemory

文章目录 LastPass整体 solution 代码&#xff1a; cacheMemory LastPass module LastPass/** LastPass password map** A simple example to explain basics of Alloy. ** The PassBook keeps track of a set of users passwords for a set of URLs. * For each User/URL pai…

【阿里云】第一次进行域名注册、备案以及使用全过程

前言 随着ChatGPT的爆火&#xff0c;让我直面感受到了一项技术的突破可以产生堪比原子弹爆炸的威力&#xff0c;因而在品尝过ChatGPT带来的便利与甜头后&#xff0c;就一直在跟进&#xff0c;同时也在能力范围内&#xff0c;让数十位朋友使用上了ChatGPT 前段时间&#xff0c…

ftrace学习 —— user_events的用法

参考 https://docs.kernel.org/trace/user_events.html 测试程序 samples/user_events/example.c tools/testing/selftests/user_events/ftrace_test.c 正文 通过user_event可以实现对应用程序的跟踪&#xff0c;类似linux内核中的tracepoint那样。相似的方法还有借助/sys…