web功能实例 - Canvas裁剪工具

嗯,手撸官方文档2天,发现没啥用,尤其是动画,那种计算出来的,根本想不到。因此学着学了抱着要做个东西的想法,去网上找相关案例,最终做出了这个裁剪工具。

PS :先说一下思路:

  1. 核心实现有3个canvas图层, 其中一个负责图片的预览。
  2. 另外2个叠加到一起,底层canvas负责图片的渲染; 上层的canvas负责蒙版的绘制,和选择框(挖空)区域的绘制。
  3. 我们将,移动选择框,记录的坐标和宽高,一个是同步到蒙版canvas里面,实现挖空。
  4. 第二个同步到图片渲染canvas,通过getImageData()方法,拿取选择框挖空的区域的像素数据,渲染到图片预览canvas里面,然后通过canvas的toBlob()方法将预览canvas,导出成blobUrl,实现图片下载。

针对部分核心功能进行思路讲解。

1.上传图片

FileReader.readAsDataURL() - Web API 接口参考 | MDN (mozilla.org)

在 web 应用程序中使用文件 - Web API 接口参考 | MDN (mozilla.org)

下面,我们点击upBut,从而触发upInpchange事件,从而触发文件上传。我们对文件上传的类型进行是不是图片判断。然后,将图片File ,通过FileReader对象的readAsDataURL(File)方法,将图片File转换为dataUrl,并封装成Img对象。进而绘制到图片渲染canvas里面。

除此以外涉及到的函数,下文都会讲到。

	<input type="file" id="up-inp" name="文件上传" style="display: none" /><button type="button" id="up-but">上传</button>let upBut = document.getElementById('up-but')let upInp = document.getElementById('up-inp')
	upBut.addEventListener('click', (e) => { // 给按钮绑定事件,点击 input type='file',从而弹出文件上传框upInp.click()})// 上传图片const updateFile =  (e) =>{let file = e.target.files[0]if (!file.type.startsWith('image')) {alert('只允许上传图片')return}const reader = new FileReader()reader.onload = (e) => {  // 利用fileReader将file文件转换成dataUrllet img1 = new Image() // 转换成img对象,进而绘画到canvas里面img1.src = e.target.resultimg = img1img1.onload = (e) => { // 读取完毕之后selectCropObj = computeImage({imgWidth: img.width,imgHeight: img.height,width: cropCardbg.width,height: cropCardbg.height,})initImgObj = JSON.parse(JSON.stringify(selectCropObj))drawImage(initImgObj)drawModal()drawClip()drawClipDiv()imgPreview()cropModal.style.display = 'none'clip.style.display = 'block'}}reader.readAsDataURL(file)}upInp.addEventListener('change', updateFile)

2. 蒙版绘制

蒙版说白了,就是占据canvas画布全部半透明矩形

	// 画模版const drawModal =  () =>{ctxCardbg.clearRect(0, 0, cropCardbg.width, cropCardbg.height)ctxCardbg.fillStyle = 'rgba(0,0,0,0.5)'ctxCardbg.fillRect(0, 0, cropCardbg.width, cropCardbg.height)}

3.挖空

挖空:蒙版随选择框的移动要扣除的透明区域。 我们先绘制蒙版,然后用clearRect()方法清除指定区域( 随着选择框移动,对应的在蒙版canvas里面的坐标和宽高围绕的区域,清除这个区域),达到挖空的效果

        // 挖空const drawClip = () => {ctxCardbg.clearRect(selectCropObj.x, selectCropObj.y, selectCropObj.w, selectCropObj.h)}

 4.选择框的绘制

我们先封装一个函数,用来注册拖拽选择框(中心,和8个点)的鼠标按下、移动、抬起事件

 

// 注册选择框拖拽事件const registerEvents = () => {// 注册那8个拖拽点事件const register = (_class) => {let node = document.querySelector(`.${_class}`)node.addEventListener('mousedown', (e) => {down = true})node.addEventListener('mousemove', carMouseMove)node.addEventListener('mouseup', function (e) {down = false})}register('top-center')register('bottom-center')register('left-center')register('right-center')register('bottom-right')register('bottom-left')register('top-right')register('top-left')// 注册拖拽中央移动的事件let clip = document.getElementById('crop-clip')clip.addEventListener('mousedown', (e) => {down = true})clip.addEventListener('mousemove', carMouseMove)clip.addEventListener('mouseup', (e) => {down = false})}

这是拖拽选择框的html 结构。我们的选择框采用html绘制,由于它和另外2个叠加的canvas,由于父元素相对定位,子绝对定位叠在一起,因此选择框的left和top值,就相当于canvas里面的x、y坐标,我们同样将选择框宽高映射到,canvas的挖空区域,从而在拖拽的时候实现实时挖空的效果。

 

	<div id="crop-clip" style="display: none"><div class="dot top-left"></div><div class="dot top-right"></div><div class="dot top-center"></div><div class="dot bottom-left"></div><div class="dot bottom-center"></div><div class="dot bottom-right"></div><div class="dot left-center"></div><div class="dot right-center"></div></div>

下面通过selectCropObj对象,记录选择框在移动期间的x,y坐标,以及选择框变化的宽高

	// 在canvas的裁剪框尺寸和坐标let selectCropObj = {x: 0,y: 0,w: 0,h: 0,}

 这是我们处理选择框移动的函数。根据拖拽的元素(选择框中央、其余8个点)的携带的class不同,从而调用不同的移动处理方法。

	// 通过拖拽事件,调用的方法(将选择框中央拖拽 ,和点拖拽通过一个函数处理)const carMouseMove = (e) => {// 两个都是false,证明我们一个没按下if (img === null) {return}if (!down) {return}let ele = e.targetlet { movementX, movementY } = econst isExistsCls = (_cls) => {for (let i = 0; i < ele.classList.length; i++) {const val = ele.classList[i]if (val === _cls) {return true}}return false}// 中央拖拽if (ele.id == 'crop-clip') {selectCropObj.x += movementXselectCropObj.y += movementY// 点拖拽} else if (isExistsCls('top-left')) {// 坐标和宽高的都要变selectCropObj.x += movementXselectCropObj.y += movementYselectCropObj.w += -movementXselectCropObj.h += -movementY} else if (isExistsCls('top-right')) {selectCropObj.y += movementYselectCropObj.w += movementXselectCropObj.h += -movementY} else if (isExistsCls('top-center')) {selectCropObj.y += movementYselectCropObj.h += -movementY} else if (isExistsCls('bottom-left')) {selectCropObj.x += movementXselectCropObj.y += movementYselectCropObj.w += -movementXselectCropObj.h += movementY} else if (isExistsCls('bottom-right')) {selectCropObj.w += movementXselectCropObj.h += movementY} else if (isExistsCls('bottom-center')) {selectCropObj.h += movementY} else if (isExistsCls('left-center')) {selectCropObj.x += movementXselectCropObj.w += -movementX} else if (isExistsCls('right-center')) {selectCropObj.w += movementX}drawClipDiv()drawModal()drawClip()imgPreview()}

 对应选择框的具体绘制方法。

		const drawClipDiv = () => {let cropClip = document.getElementById('crop-clip')cropClip.style.width = `${selectCropObj.w}px`cropClip.style.height = `${selectCropObj.h}px`cropClip.style.left = `${selectCropObj.x}px`cropClip.style.top = `${selectCropObj.y}px`}

5.画图像

就是清除之前渲染到图片canvas里面的图像,并将新的图片绘制到上面。

	// 画图像const drawImage = ({ x, y, w, h }) => {ctxImg.clearRect(0, 0, cropImg.width, cropImg.height)ctxImg.drawImage(img, x, y, w, h)}

6.图片旋转

CanvasRenderingContext2D.rotate() - Web API 接口参考 | MDN (mozilla.org)

关于图片旋转这里,就是现将原点移动canvas画布中央(图像中央),然后定义旋转角度,然后又将原点移动回去,然后画的图像就是围绕中心旋转的。

主要是围绕图像的中心原点旋转。

大家可以自行去网上找关于rotate让图像围绕中心原点旋转的问题。因为作者也没能明白。

// 主要是这段代码:

            ctxImg.translate(cropImg.width / 2, cropImg.height / 2)
            ctxImg.rotate(angle)
            ctxImg.translate(-cropImg.width / 2, -cropImg.height / 2)

	// 向右、左转(每次向左30度、向右30度)图片const imgRotate = (e, t = 1) => {if (img == null) {alert('请先上传图片')return}if (t == 1) {// 向右转,逆时针angle += -30 * (Math.PI / 180)} else {// 向左转,顺时针angle += 30 * (Math.PI / 180)}ctxImg.clearRect(0, 0, cropImg.width, cropImg.height)ctxImg.translate(cropImg.width / 2, cropImg.height / 2)ctxImg.rotate(angle)ctxImg.translate(-cropImg.width / 2, -cropImg.height / 2)drawImage(initImgObj)imgPreview()}

7.图片扩大和缩小

通过initImgObj对象,记录图像渲染到图片渲染canvas尺寸和坐标

// 图像渲染到到canvas的坐标尺寸和坐标let initImgObj = {// 主要是为了缩放/扩大图片用x: 0,y: 0,w: 0,h: 0,}

 通过computeImage()函数,计算出图像要在canvas画布中央实际渲染的坐标和尺寸

// 计算图像在canvas实际渲染的图片坐标和宽高。(居于canvas中央,尺寸小于画布尺寸)const computeImage = function ({ imgWidth, imgHeight, width, height, base = 1 }) {if (imgWidth / base < width && imgHeight / base < height) {return {x: (width - imgWidth) / 2,y: (height - imgHeight) / 2,w: imgWidth / base,h: imgHeight / base,}}return computeImage({imgWidth,imgHeight,width,height,base: base + 0.1,})}

 通过计算 initImgObj对象的放大、缩小后的宽高,然后通过computeImage()方法计算出canvas实际渲染的图像坐标和尺寸,并渲染。

// 缩小/扩大图片const imgScale = (e, t) => {if (img == null) {alert('请先上传图片')return}let { w, h } = initImgObjif (t == 1) {// 缩小w = w / 2h = h / 2} else {// 扩大w = w * 2h = h * 2}// 重新求出,缩小、放大之后的图片宽高和坐标。initImgObj = computeImage({imgWidth: w,imgHeight: h,width: cropImg.width,height: cropImg.height,})drawImage(initImgObj)imgPreview()}

8.图片预览

ImageData - Web API 接口参考 | MDN (mozilla.org)

通过选择框拖拽,记录的 selectCropObj对象的坐标和宽高信息

通过getImageData()方法获取图片渲染canvas里面的ImageData对象,并通过putImageData()方法将这个对象渲染到图片预览canvas里面。

// 图片预览const imgPreview = () => {ctxPreview.clearRect(0, 0, cropPreview.width, cropPreview.height)let { x, y, w, h } = selectCropObjlet imgData = ctxImg.getImageData(x, y, w, h)ctxPreview.putImageData(imgData, x, y)}

9.保存预览图片

HTMLCanvasElement.toBlob() - Web API 接口参考 | MDN (mozilla.org)

HTMLCanvasElement.toDataURL() - Web API 接口参考 | MDN (mozilla.org)

我们通过,canvas的toBlob()方法将canvas画布转换为blobUrl,然后就是创建一个a 元素,并将这个url封装成a的href属性,用日期作为下载的图片名字,用js模拟点击实现下载。

// 保存裁剪图片const saveImg = (e) => {if (img == null) {alert('请先上传图片')return}cropPreview.toBlob((blob) => {// 下载图片let a = document.createElement('a')a.href = window.URL.createObjectURL(blob)a.download = `${getDateStr(new Date())}.png` a.dispatchEvent(new MouseEvent('click'))})}

完整代码

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>图片裁剪工具</title><style>* {padding: 0;margin: 0;}#crop-tool {position: relative;overflow: hidden;width: 500px;height: 300px;margin: 100px auto;border: 1px solid black;border-radius: 15px;}#crop-img {z-index: -1;top: 0;left: 0;background: url('./img/mosaic.jpg');}#crop-cardbg {position: absolute;top: 0;left: 0;}#crop-modal {position: absolute;top: 0;left: 0;}#crop-clip {position: absolute;left: 25px;top: 37.1364px;cursor: all-scroll;width: 308.091px;height: 197.864px;border: 1px solid rgb(30, 158, 251);}#crop-modal {position: absolute;left: 0;top: 0;width: 500px;height: 300px;line-height: 300px;font-size: 30px;font-weight: 700;text-align: center;color: white;background-color: rgba(0, 0, 0, 0.5);}#crop-preview {border: 1px solid black;border-radius: 15px;width: 500px;height: 300px;margin: 0 700px;}#crop-but {width: 400px;margin: 0 auto;}button {width: 50px;}#crop-clip .dot {position: absolute;width: 20px;height: 20px;border-radius: 50%;background: #1e9efb;}#crop-clip .top-left {top: -10px;left: -10px;cursor: nwse-resize;}#crop-clip .top-right {top: -10px;right: -10px;cursor: nesw-resize;}#crop-clip .top-center {top: -10px;left: 50%;transform: translate(-50%);cursor: ns-resize;}#crop-clip .bottom-left {bottom: -10px;left: -10px;cursor: nesw-resize;}#crop-clip .bottom-center {bottom: -10px;left: 50%;transform: translate(-50%);cursor: ns-resize;}#crop-clip .bottom-right {bottom: -10px;right: -10px;cursor: nwse-resize;}#crop-clip .left-center {top: 50%;transform: translateY(-50%);left: -10px;cursor: ew-resize;}#crop-clip .right-center {top: 50%;transform: translateY(-50%);right: -10px;cursor: ew-resize;}</style><!-- <link rel="stylesheet" href="./css/iconfont.css" /> --></head><body><div id="crop-tool"><canvas id="crop-img" width="500" height="300"></canvas><canvas id="crop-cardbg" width="500" height="300"></canvas><div id="crop-clip" style="display: none"><div class="dot top-left"></div><div class="dot top-right"></div><div class="dot top-center"></div><div class="dot bottom-left"></div><div class="dot bottom-center"></div><div class="dot bottom-right"></div><div class="dot left-center"></div><div class="dot right-center"></div></div><div id="crop-modal">请先上传图片</div></div><canvas id="crop-preview" width="500" height="300"> </canvas><div id="crop-but"><input type="file" id="up-inp" name="文件上传" style="display: none" /><button type="button" id="up-but">上传</button><button type="button" id="add-but">+</button><button type="button" id="del-but">-</button><button type="button" id="xzz-but">left<!-- <span class="iconfont icon-xiangzuoxuanzhuan"></span> --></button><button type="button" id="xyz-but">right<!-- <span class="iconfont icon-xiangyouxuanzhuan"></span> --></button><button type="button" id="save-but">截图</button></div></body><script src="./js/Index.js"></script><script>///variable///// 按钮组let upBut = document.getElementById('up-but')let saveBut = document.getElementById('save-but')let addBut = document.getElementById('add-but')let delBut = document.getElementById('del-but')let xzzBut = document.getElementById('xzz-but')let xyzBut = document.getElementById('xyz-but')let upInp = document.getElementById('up-inp')let cropImg = document.querySelector('#crop-img') // 背景层let cropCardbg = document.querySelector('#crop-cardbg') // 裁剪层let cropPreview = document.querySelector('#crop-preview') // 裁剪层let cropModal = document.querySelector('#crop-modal') // 遮罩层let clip = document.getElementById('crop-clip') // 选择框let ctxCardbg = cropCardbg.getContext('2d')let ctxImg = cropImg.getContext('2d')let ctxPreview = cropPreview.getContext('2d')// 在canvas的裁剪框尺寸和坐标let selectCropObj = {x: 0,y: 0,w: 0,h: 0,}// 一开始存储到canvas的坐标尺寸和坐标let initImgObj = {// 主要是为了缩放/扩大图片用x: 0,y: 0,w: 0,h: 0,}let img = nulllet down = false // 中心拖拽或者按钮拖拽let angle = 0 // 旋转角度///method///upBut.addEventListener('click', (e) => {upInp.click()})// 上传图片const updateFile = function (e) {let file = e.target.files[0]if (!file.type.startsWith('image')) {alert('只允许上传图片')return}const reader = new FileReader()reader.onload = (e) => {let img1 = new Image()img1.src = e.target.resultimg = img1img1.onload = (e) => {// 读取完毕之后selectCropObj = computeImage({imgWidth: img.width,imgHeight: img.height,width: cropCardbg.width,height: cropCardbg.height,})initImgObj = JSON.parse(JSON.stringify(selectCropObj))drawImage(initImgObj)drawModal()drawClip()drawClipDiv()imgPreview()cropModal.style.display = 'none'clip.style.display = 'block'}}reader.readAsDataURL(file)}upInp.addEventListener('change', updateFile)// 保存裁剪图片const saveImg = (e) => {if (img == null) {alert('请先上传图片')return}cropPreview.toBlob((blob) => {let a = document.createElement('a')a.href = window.URL.createObjectURL(blob)a.download = `${getDateStr(new Date())}.png`a.dispatchEvent(new MouseEvent('click'))})}saveBut.addEventListener('click', saveImg)// 图片预览const imgPreview = () => {ctxPreview.clearRect(0, 0, cropPreview.width, cropPreview.height)let { x, y, w, h } = selectCropObjlet imgData = ctxImg.getImageData(x, y, w, h)ctxPreview.putImageData(imgData, x, y)}// 缩小/扩大图片const imgScale = (e, t) => {if (img == null) {alert('请先上传图片')return}let { w, h } = initImgObjif (t == 1) {// 缩小w = w / 2h = h / 2} else {// 扩大w = w * 2h = h * 2}// 重新求出,缩小,方法的图片坐标initImgObj = computeImage({imgWidth: w,imgHeight: h,width: cropImg.width,height: cropImg.height,})drawImage(initImgObj)imgPreview()}// 向右、左转(每次向左90度、向右90度)图片const imgRotate = (e, t = 1) => {if (img == null) {alert('请先上传图片')return}if (t == 1) {// 向右转,逆时针angle += -30 * (Math.PI / 180)} else {// 向左转,顺时针angle += 30 * (Math.PI / 180)}ctxImg.clearRect(0, 0, cropImg.width, cropImg.height)ctxImg.translate(cropImg.width / 2, cropImg.height / 2)ctxImg.rotate(angle)ctxImg.translate(-cropImg.width / 2, -cropImg.height / 2)drawImage(initImgObj)imgPreview()}delBut.addEventListener('click', (e) => {imgScale(e, 1)})addBut.addEventListener('click', (e) => {imgScale(e, 2)})xzzBut.addEventListener('click', (e) => {imgRotate(e, 1)})xyzBut.addEventListener('click', (e) => {imgRotate(e, 2)})// 画模版const drawModal = function () {ctxCardbg.clearRect(0, 0, cropCardbg.width, cropCardbg.height)ctxCardbg.fillStyle = 'rgba(0,0,0,0.5)'ctxCardbg.fillRect(0, 0, cropCardbg.width, cropCardbg.height)}// 计算图片在canvas实际坐标和尺寸const computeImage = function ({ imgWidth, imgHeight, width, height, base = 1 }) {if (imgWidth / base < width && imgHeight / base < height) {return {x: (width - imgWidth) / 2,y: (height - imgHeight) / 2,w: imgWidth / base,h: imgHeight / base,}}return computeImage({imgWidth,imgHeight,width,height,base: base + 0.1,})}// 画图像const drawImage = function ({ x, y, w, h }) {ctxImg.clearRect(0, 0, cropImg.width, cropImg.height)ctxImg.drawImage(img, x, y, w, h)}// 挖空const drawClip = function () {ctxCardbg.save()ctxCardbg.clearRect(selectCropObj.x, selectCropObj.y, selectCropObj.w, selectCropObj.h)ctxCardbg.restore()}// 挖空时,选择框的变化const drawClipDiv = () => {let cropClip = document.getElementById('crop-clip')cropClip.style.width = `${selectCropObj.w}px`cropClip.style.height = `${selectCropObj.h}px`cropClip.style.left = `${selectCropObj.x}px`cropClip.style.top = `${selectCropObj.y}px`}// 卡片拖拽事件,调用的方法(将选择框中央拖拽 ,和点拖拽一个方法处理)const carMouseMove = (e) => {// 两个都是false,证明我们一个没按下if (img === null) {return}if (!down) {return}let ele = e.targetlet { movementX, movementY } = econst isExistsCls = (_cls) => {for (let i = 0; i < ele.classList.length; i++) {const val = ele.classList[i]if (val === _cls) {return true}}return false}// 中央拖拽if (ele.id == 'crop-clip') {selectCropObj.x += movementXselectCropObj.y += movementY// 点拖拽} else if (isExistsCls('top-left')) {// 坐标和宽高的都要变selectCropObj.x += movementXselectCropObj.y += movementYselectCropObj.w += -movementXselectCropObj.h += -movementY} else if (isExistsCls('top-right')) {selectCropObj.y += movementYselectCropObj.w += movementXselectCropObj.h += -movementY} else if (isExistsCls('top-center')) {selectCropObj.y += movementYselectCropObj.h += -movementY} else if (isExistsCls('bottom-left')) {selectCropObj.x += movementXselectCropObj.y += movementYselectCropObj.w += -movementXselectCropObj.h += movementY} else if (isExistsCls('bottom-right')) {selectCropObj.w += movementXselectCropObj.h += movementY} else if (isExistsCls('bottom-center')) {selectCropObj.h += movementY} else if (isExistsCls('left-center')) {selectCropObj.x += movementXselectCropObj.w += -movementX} else if (isExistsCls('right-center')) {selectCropObj.w += movementX}drawClipDiv()drawModal()drawClip()imgPreview()}// 注册选择框拖拽事件const registerEvents = () => {// 注册那8个拖拽点事件const register = (_class) => {let node = document.querySelector(`.${_class}`)node.addEventListener('mousedown', (e) => {down = true})node.addEventListener('mousemove', carMouseMove)node.addEventListener('mouseup', function (e) {down = false})}register('top-center')register('bottom-center')register('left-center')register('right-center')register('bottom-right')register('bottom-left')register('top-right')register('top-left')// 注册拖拽中央移动的事件let clip = document.getElementById('crop-clip')clip.addEventListener('mousedown', (e) => {down = true})clip.addEventListener('mousemove', carMouseMove)clip.addEventListener('mouseup', (e) => {down = false})}registerEvents()</script>
</html>
const getDateStr = (time, tag1 = '-', tag2 = ':') => {const date = new Date(time)let y = date.getFullYear()let M = date.getMonth() + 1let d = date.getDate()let h = date.getHours()let m = date.getMinutes()let s = date.getSeconds()return `${y}${tag1}${M}${tag1}${d} ${h}${tag2}${m}${tag2}${s}`
}

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

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

相关文章

【华为机试】2023年真题B卷(python)-发广播

一、题目 题目描述&#xff1a; 某地有N个广播站&#xff0c;站点之间有些有连接&#xff0c;有些没有。有连接的站点在接受到广播后会互相发送。 给定一个N*N的二维数组matrix,数组的元素都是字符’0’或者’1’。 matrix[i][j]‘1’,则代表i和j站点之间有连接&#xff0c;mat…

web前端游戏项目-辨色大比拼【附源码】

web前端游戏项目-辨色大比拼【附源码】 《辨色大比拼》是一个旨在测试和提升玩家颜色识别能力的在线游戏。在游戏中&#xff0c;玩家将通过辨识颜色来解谜并推进游戏进程。辨色大比拼也是一个寓教于乐的游戏&#xff0c;它不仅提供了一个有趣的辨色挑战&#xff0c;还能帮助玩…

leetcode2两数加和问题(链表)

题目思路&#xff1a; ①创建一个int类型的局部变量&#xff0c;用来存储两个结点的Val值。 ②判断该Val值与10求余(mod)后是否大于0,如果大于0, 则需要在下一个结点进位。 ③最关键的步骤&#xff1a;实现l1&#xff0c;l2结点数值相加后构建新的存储求和后的结点&#xff0…

IntelliJ IDEA插件

插件安装目录&#xff1a;C:\Users\<username>\AppData\Roaming\JetBrains\IntelliJIdea2021.2\plugins aiXcoder Code Completer&#xff1a;代码补全 Bookmark-X&#xff1a;书签分类 使用方法&#xff1a;鼠标移动到某一行&#xff0c;按ALT SHIFT D

修改PCIE 设备控制寄存器DevCtl2参数

如何修改PCIE 设备控制寄存器DevCtl2参数&#xff1f; 参考书籍&#xff1a;PCI_Express_Base_Spec 如图所示&#xff1a;输入Lspci -s 00&#xff1a;08&#xff1a;00 -vvv|grep - i deve 输出DevCap、DevCtl、DevCap2、DevCtl2参数&#xff0c;本节重点分析UEFI BIOS怎么设置…

FPGA分频电路设计(2)

实验要求&#xff1a; 采用 4 个开关以二进制形式设定分频系数&#xff08;0-10&#xff09;&#xff0c;实现对已知信号的分频。 类似实验我之前做过一次&#xff0c;但那次的方法实在是太笨了&#xff1a; 利用VHDL实现一定系数范围内的信号分频电路 需要重做以便将来应对更…

JUC并发编程 09——队列同步器AQS

目录 一.Lock接口 1.1Lock的使用 1.2Lock接口提供的 synchronized 不具备的主要特性 1.3Lock接口的所有方法 二.队列同步器(AQS) 2.1队列同步器的接口与示例 2.2AQS实现源码分析 ①同步队列 ②获取锁 ③释放锁 一.Lock接口 说起锁&#xff0c;你肯定会想到 synchron…

Android Studio 如何实现软件英文变中文教程

目录 前言 一、确认版本号 二、下载汉化包 三、汉化包安装 四、如何实现中英文切换 五、更多资源 前言 Android Studio是一款功能强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;用于开发Android应用程序。默认情况下&#xff0c;Android Studio的界面和…

js中Math.min(...arr)和Math.max(...arr)的注意点

当arr变量为空数组时&#xff0c;这两个函数和不传参数时的结果是一样的 Math.max() // -Infinity Math.max(...[]) // -InfinityMath.min() // Infinity Math.min(...[]) // Infinity

如何编写高效清晰的嵌入式C程序

作为嵌入式工程师&#xff0c;怎么写出效率高、思路清晰的C语言程序呢? 要用C语言的思维方式来进行程序的构架构建 要有良好的C语言算法基础&#xff0c;以此来实现程序的逻辑构架 灵活运用C语言的指针操作 虽然看起来以上的说法很抽象&#xff0c;给人如坠雾里的感觉&…

内存之-LeakCanary

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、概览三、使用四、原理分析4.1 自动初始化4.1.1 初始化…

人工智能_机器学习074_SVM支持向量机_软间隔与优化目标函数构建_C参数由来_惩罚误差点的惩罚度---人工智能工作笔记0114

然后我们接着上一节再来看一下这里我们说有个 min_faces_per_person = 0 这个可以看到如果我们写上0,就意味着要加载所有的人脸图片,就会花费的时间久对吧 我们可以试试,这里我们 min_faces_per_person = 0 改成0然后 我们等一会加载完了以后,我们用 display(X.shape,faces.sh…

Jenkins安装与设置(插件安装失败,版本问题解决)

早期的使用docker安装jenkins的方法会出现插件无法安装的问题&#xff0c;是由于docker拉取的jenkins版本太低了 jdk安装 Linux系统安装JDK1.8 详细流程 maven安装&#xff1a; centos7下安装Maven 使用docker进行安装jenkins&#xff1a; 先把镜像和容器卸干净 docker ps -a…

vue data变量不能以“_”开头,否则会产生很多怪异问题

1、 比如给子组件赋值&#xff0c;子组件无法得到这个值&#xff08;也不是一直无法得到&#xff0c;设置后this.$forceUpdate() 居然可以得到&#xff09;&#xff0c; 更无法watch到 <zizujian :config"_config1"> </zizujian>this._config1 { ...…

短视频矩阵系统的崛起和影响

近年来&#xff0c;短视频矩阵系统已经成为了社交媒体中的一股新势力。这个新兴的社交媒体形式以其独特的魅力和吸引力&#xff0c;迅速吸引了大量的用户。这个系统简单来说就是将海量短视频整合在一个平台上&#xff0c;使用户可以方便地观看和分享好玩有趣的短视频。 短视频…

50个免费的 AI 工具,提升工作效率(附网址)

上次我们已经介绍了20个精选的提高工作效率的免费AI工具&#xff0c;但如果你觉得这些AI工具还不过瘾的话&#xff0c;想进一步成为职场中最了解AI的人&#xff0c;本文将汇总介绍免费最新的50个AI工具。 DeepSwap DeepSwap 是一个基于 AI 的工具&#xff0c;适用于想要制作令人…

【内存泄漏】内存泄漏及常见的内存泄漏检测工具介绍

内存泄漏介绍 什么是内存泄漏 内存泄漏是指程序分配了一块内存&#xff08;通常是动态分配的堆内存&#xff09;&#xff0c;但在不再需要这块内存的情况下未将其释放。内存泄漏会导致程序浪费系统内存资源&#xff0c;持续的内存泄漏还导致系统内存的逐渐耗尽&#xff0c;最…

android内存管理机制概览

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、概览三、相关概念3.1 垃圾回收3.2 应用内存的分配与回…

插入排序详解(C语言)

前言 插入排序是一种简单直观的排序算法&#xff0c;在小规模数据排序或部分有序的情况下插入排序的表现十分良好&#xff0c;今天我将带大家学习插入排序的使用。let’s go ! ! ! 插入排序 插入排序的基本思想是将待排序的序列分为已排序和未排序两部分。初始时&#xff0c…

商务大厦安装电气火灾监控系统,从源头监控电气火灾

安科瑞电气股份有限公司 上海嘉定 201801 摘要&#xff1a;介绍分析剩余电流式电气火灾监控系统的特点、组成、设计依据、监控原理和实施方案&#xff0c;并结合上海市某大厦工程设计实例探讨该系统在高层建筑中的设置要求和应用方式&#xff0c;供设计人员参考。 关键词&…