可视化学习:实现Canvas图片局部放大镜
前言
最近我在可视化课程中学习了如何在Canvas中利用像素处理来实现滤镜效果,在这节课程的结尾留了一道局部放大镜的题目,提示我们用像素处理的方式去实现这个效果,最终实现随着鼠标移动将图片局部放大,本着把学到的内容落地实践的想法,我就去思考了一番,但很不幸,我思考了好几天也没思考出结果,因为刚开始我想的一直是在一个Canvas上来操作,但是一来我对Canvas API还并不是很熟悉,二来我对像素处理还不够熟练,然后第三是如果原图的部分像素被处理了,那下一次放大就会有问题,因此我最终放弃了这个思路,选择了再增加一个Canvas来完成最终的效果,以下就是利用这种方式实现图片局部放大的效果。
像素处理
在实现这个效果之前,我们先来了解一下如何处理像素,有些小伙伴可能不太清楚,所以这里简单说一下,在屏幕上我们知道所有显示的内容都是由像素点组成的,那么在处理像素之前,我们需要先获取到像素信息,那么Canvas就是提供了一个API叫做getImageData让我们可以获取到画布上的像素信息,最终这个API返回的是一个ImageData类型的值,关于这个API的具体描述可以参考对应的MDN页面。
ImageData类型的数据包含三个属性,包括data、width、height。width和height简单来说,就是被提取像素信息的区域的宽高,最主要的像素信息是在这个data属性中。data属性指向一个数组类型的值,准确来说是Uint8ClampedArray的实例,Uint8ClampedArray表示8 位无符号整型固定数组,也就是说其中的元素是0到255之间的整数,我们知道一个像素的颜色信息可以使用rgba四个分量表示,那么我们就得出在data数组中每四个元素就能表示一个像素点的信息,因此data数组的长度就是width * height * 4。
了解完像素处理,我们就可以开始进行具体的实现了。
具体实现
<canvas ref="canvasRef" width="0" height="0"></canvas>
<canvas ref="magnifier" width="0" height="0"></canvas><!-- 放大镜 -->
1. 准备工作
在实现放大效果之前,我们需要先把图片加载到Canvas上:
(async function() {
const img = await loadImage('src/assets/girl1.jpg');
canvasRef.value.width = img.width;
canvasRef.value.height = img.height;
context.drawImage(img, 0, 0);
}());
这里loadImage方法是通过Image对象来异步加载图片,然后通过drawImage方法将图片绘制到画布上。
接着设置一个要放大的区域,也就是以鼠标坐标为中心,多少半径以内的内容要被放大,这里我设置一个变量originSize用于存储原图大小,并设置一个5倍的放大倍数。
let originSize = 40; // 原图大小
let zoom = 5; // 放大倍数
(async function() {
// ...
magnifier.value.width = originSize * zoom;
magnifier.value.height = originSize * zoom;
}());
用作于放大镜的magnifier,我们使用originSize * zoom来设置它的宽高。
2. 鼠标移动事件监听
接下来就是主要的代码实现。
首先是添加鼠标移动事件的监听:
const addEvent = () => {
canvasRef.value.addEventListener('mousemove', mouseDownHandler);
};
addEvent();
然后我们就来实现mouseDownHandler函数。
首先我们获取鼠标坐标在Canvas中的相对坐标,并通过
Math.floor取整const mouseDownHandler = e => {
// 相对于画布的坐标
const center = {
x: Math.floor(e.pageX - left),
y: Math.floor(e.pageY - top)
};
};
然后利用
getImageData方法获取指定区域的像素信息,这里我们用到了OffscreenCanvas,它提供了一个可以脱离屏幕渲染的 canvas 对象,可以提升渲染性能;这样我们就得到了待放大区域的像素信息。const mouseDownHandler = e => {
// 相对于画布的坐标
// ...
// 待放大区域的imageData
const originImageData = getImageData(img, [center.x - originSize / 2, center.y - originSize / 2, originSize, originSize]);
};
现在我们需要一个ImageData类型的变量,用于存储放大后的像素信息,因为最终要渲染到magnifier这个Canvas上,我们就使用magnifier的2d上下文对象调用
createImageData方法来创建一个ImageData对象,关于这个方法的使用具体可查看MDN文档。const mouseDownHandler = e => {
// 相对于画布的坐标
// ...
// 待放大区域的imageData
// ...
// 构建一个imageData
const areaImageData = mContext.createImageData(magnifier.value.width, magnifier.value.height);
};
接下来就是具体的像素遍历和处理,按照areaImageData的宽高来进行遍历,这里迭代的增量使用
+zoom是因为,当我们放大zoom倍数之后,原图1个像素的信息在magnifier使用zoom*zoom个像素来放大,也就是zoom*zoom个像素点的色值和原图中对应的那个像素的色值是一样的。在我们这段代码中设置zoom为5,也就是放大后使用5*5=25个像素点表示之前的一个像素点。const mouseDownHandler = e => {
// 相对于画布的坐标
// ...
// 待放大区域的imageData
// ...
// 构建一个imageData
// ...
let count = 0;
for (let j = 0; j < originSize * zoom; j += zoom) {
for (let i = 0; i < originSize * zoom; i += zoom) { // ... }
}
};
所以我们继续使用两个for循环k和m,把areaImageData的data数组中的对应元素赋值为原图对应像素的色值,完成赋值后我们就可以通过putImageData方法将像素信息渲染到magnifier画布上。
const mouseDownHandler = e => {
// 相对于画布的坐标
// ...
// 待放大区域的imageData
// ...
// 构建一个imageData
// ...
let count = 0;
for (let j = 0; j < originSize * zoom; j += zoom) {
for (let i = 0; i < originSize * zoom; i += zoom) { for (let k = j; k < j + zoom; k ++) {
for (let m = i; m < i + zoom; m ++) {
const index = (k * originSize * zoom + m) * 4;
areaImageData.data[index] = originImageData.data[count];
areaImageData.data[index + 1] = originImageData.data[count + 1];
areaImageData.data[index + 2] = originImageData.data[count + 2];
areaImageData.data[index + 3] = originImageData.data[count + 3]; }
}
count += 4; }
}
mContext.putImageData(areaImageData, 0, 0);
};
至此我们就实现了基本的局部放大,但现在放大镜不在原图Canvas的上方,并且放大镜是一个正方形,我们继续简单优化一下。
3. 简单优化
首先因为我对Canvas API还不太熟悉,所以我现在通过css把放大镜改为圆形,并加上一个阴影
box-shadow来优化视觉效果。#magnifier {
position: absolute;
box-shadow: 0 0 10px 4px rgba(12, 12, 12, .5);
border-radius: 50%;
}
然后给两个Canvas外层加一个div容器,把放大镜设置绝对定位,把它放到鼠标坐标的位置,在鼠标移动过程中更新放大镜的位置。
<div class="canvas-container" ref="containerRef" :style="{width: containerWidth + 'px'}">
<canvas ref="canvasRef" width="0" height="0"></canvas>
<canvas ref="magnifier" width="0" height="0" id="magnifier" :style="position"></canvas>
</div>
const position = reactive({
left: 0,
top: 0
});
const containerWidth = ref(0); containerWidth.value = img.width;
// 在鼠标移动过程中更新放大镜的位置
position.top = (center.y - originSize * zoom / 2) + 'px';
position.left = (center.x - originSize * zoom / 2) + 'px';
.canvas-container {
position: relative;
overflow: hidden;
}
这个时候放大镜的位置就和我们预想的一致了,但是现在还有一个问题,就是放大镜在原图的上方,在移动的过程中会看到放大镜的渲染有点卡顿,这是因为鼠标移动事件是加在原图Canvas上的,当鼠标悬浮在放大镜上时,这个移动事件的监听就不连贯了,此时我们可以考虑把鼠标移动监听加改为加在外层容器上,这样就能看到移动过程中放大镜的渲染是比较流畅了。
const addEvent = () => {
containerRef.value.addEventListener('mousemove', mouseDownHandler);
};
至此就完成了简单的局部放大效果,虽然还存在一些问题吧。
总结
以上的实现比较简单粗暴,就是遍历imageData然后赋值,不算什么很高明的思路,就当作是抛砖引玉吧。
可视化学习:实现Canvas图片局部放大镜的更多相关文章
- iOS UIImage 图片局部拉伸的一些学习要点
之前 做纯色局部拉伸 通过 top bottom left right 相交的阴影拉伸 屡试不爽 实施方法: imageView.image = [[UIImage imageNamed: @&q ...
- HTML5学习总结——canvas绘制象棋(canvas绘图)
一.HTML5学习总结——canvas绘制象棋 1.第一次:canvas绘制象棋(笨方法)示例代码: <!DOCTYPE html> <html> <head> & ...
- JavaScript修改Canvas图片
用JavaScript修改Canvas图片的分辨率(DPI) 应用场景: 仓库每次发货需要打印标签, Canvas根据从数据库读取的产品信息可以生成标签JPG, 但是这个JPG图片的默认分辨率(D ...
- 可视化学习Tensorboard
可视化学习Tensorboard TensorBoard 涉及到的运算,通常是在训练庞大的深度神经网络中出现的复杂而又难以理解的运算.为了更方便 TensorFlow 程序的理解.调试与优化,发布了一 ...
- python中学习K-Means和图片压缩
python中学习K-Means和图片压缩 大家在学习python中,经常会使用到K-Means和图片压缩的,我们在此给大家分享一下K-Means和图片压缩的方法和原理,喜欢的朋友收藏一下吧. 通俗的 ...
- canvas 图片拖拽旋转之二——canvas状态保存(save和restore)
引言 在上一篇日志“canvas 图片拖拽旋转之一”中,对坐标转换有了比较深入的了解,但是仅仅利用坐标转换实现的拖拽旋转,会改变canvas坐标系的状态,从而影响画布上其他元素的绘制.因此,这个时候需 ...
- HTML5开发笔记:初窥CANVAS,上传canvas图片到服务器
项目做到一个裁切图片的功能,就是让用户上传头像的时候可以裁切一下图片,选择一个合适大小位置来作为头像.之中用到了crop.js这个插件,用canvas直接绘制了用户裁切缩放后的图片.裁切的过程这边就不 ...
- Tensorflow学习笔记3:TensorBoard可视化学习
TensorBoard简介 Tensorflow发布包中提供了TensorBoard,用于展示Tensorflow任务在计算过程中的Graph.定量指标图以及附加数据.大致的效果如下所示, Tenso ...
- [置顶] iOS学习笔记47——图片异步加载之EGOImageLoading
上次在<iOS学习笔记46——图片异步加载之SDWebImage>中介绍过一个开源的图片异步加载库,今天来介绍另外一个功能类似的EGOImageLoading,看名字知道,之前的一篇学习笔 ...
- 学习HTML5 canvas遇到的问题
学习HTML5 canvas遇到的问题 1. 非零环绕原则(nonzZero rule) 非零环绕原则是canvas在进行填充的时候是否要进行填充的判断依据. 在判断填充的区域拉一条线出来,拉到图形的 ...
随机推荐
- ckeditor使用技巧总结
介绍 官方开发者文档:CKEditor 4 documentation 技巧总结 1.挑选需要的插件,打包下载 参考:CKEditor 4.4.1 添加代码高亮显示插件功能--使用官方推荐Code S ...
- 《深入理解Java虚拟机》(三)类加载机制
@ 目录 1.什么是类的加载 2.类加载的过程 加载 连接 验证 文件格式验证 元数据验证 字节码验证 符号引用验证 准备 解析: 类或接口的解析 字段解析 类方法解析 接口方法解析 初始化 结束生命 ...
- 使用Java线程同步工具类CountDownLatch
java.util.concurrent.CountDownLatch是Java并发并发编程中的线程同步工具类,基于AQS(java.util.concurrent.locks.AbstractQue ...
- Mybatis模糊查询无法确定参数$1的数据类型: ERROR: could not determine data type of parameter $1
Mybatis模糊查询无法确定参数$1的数据类型: 报错ERROR: could not determine data type of parameter $1 修改前: SELECT count(0 ...
- 【系统设计】集团内部HR系统完结,项目从0到1总结
最近一年学习了PMP,结合下PMP的知识,分享一下最近HR项目的全生命周期流程管理(需求分析.产品设计.系统开发.测试.上线.运营). 先一句话概括:战略分析-收集需求-流程梳理-关键需求-IT规划- ...
- 【应用服务 App Service】App Service 中部署Java项目,查看Tomcat配置及上传自定义版本
当在Azure 中部署Java应用时候,通常会遇见下列的问题: 如何部署一个Java的项目呢? 部署成功后,怎么来查看Tomcat的服务器信息呢? 如果Azure提供的默认Tomcat版本的配置跟应用 ...
- 【Azure Developer】如何通过Azure REST API 获取到虚拟机(VM)所使用的公共IP地址信息
问题描述 如何通过Azure REST API 获取到虚拟机(VM)所使用的公共IP地址信息 问题解答 由于直接获取到的虚拟机信息(Virtual Machines - Get)中,并不会包含虚拟机的 ...
- AutoNumber VsCode插件开发
AutoNumber VsCode插件开发 ::: details 目录 目录 AutoNumber VsCode插件开发 Step. 2: 安装脚手架 Step. 3: 创建空项目 Step. 4: ...
- 逆向通达信Level-2 续六 (调试pad控件)
调试终端面版单元, 以及宿主窗口 调试大数据面版单元, 以及宿主窗口 逆向通达信Level-2 续十一 (无帐号登陆itrend研究版) 逆向通达信Level-2 续十 (trace脱壳) 逆向通达信 ...
- Elasticsearch - Docker安装Elasticsearch8.12.2
前言 最近在学习 ES,所以需要在服务器上装一个单节点的 ES 服务器环境:centos 7.9 安装 下载镜像 目前最新版本是 8.12.2 docker pull docker.elastic.c ...