手摸手带你实现一个时间轴组件

这是开头

本文给大家带来一个时间轴的组件开发教程,话不多说,先看动图:

主要功能就是可以拖动时间轴来定位当前时间,可以通过鼠标滚轮来修改当前时间分辨率,也支持显示时间段功能,动图未体现,可看下面的本次demo效果示例:

如果对canvas不太熟悉的话可以先看一下教程:www.runoob.com/tags/ref-ca…

接下来进入开发时间。

开发时间

前端框架依旧使用的是vue,这个组件交互是通过canvas实现的,模板非常简单:

<template><div class="timeLineContainer" ref="timeLineContainer"><canvasref="canvas"@mousemove="onMousemove"@mouseout="onMouseout"@mousedown="onMousedown"@mousewheel="onMouseweel"></canvas></div>
</template>

绑定了四个事件,后续再细说。

准备工作

首先要做的是设置一下画布的宽高及获取画图上下文:

{methods: {init () {// 获取外层宽高let {width,height} = this.$refs.timeLineContainer.getBoundingClientRect()this.width = widththis.height = height// 设置画布宽高为外层元素宽高this.$refs.canvas.width = widththis.$refs.canvas.height = height// 获取画图上下文this.ctx = this.$refs.canvas.getContext('2d')}}
}

中间的白色竖线

中间的白色竖线代表的就是当前的时间,但是就线而言它只是一条线,所以先把它画了:

{// 这个函数是整个绘制方法,所有的绘制方法都在此调用draw () {this.drawMiddleLine()},// 画中间的白色竖线drawMiddleLine () {let lineWidth = 2// 线的x坐标是时间轴的中点,y坐标即时间轴的高度let x = this.width / 2this.drawLine(x, 0, x, this.height, lineWidth, '#fff')},// 画线段方法drawLine (x1, y1, x2, y2, lineWidth = 1, color = '#fff') {// 开始一段新路径this.ctx.beginPath()// 设置线段颜色this.ctx.strokeStyle = '#fff'// 设置线段宽度this.ctx.lineWidth = lineWidth// 将路径起点移到x1,y1this.ctx.moveTo(x1, y1)// 将路径移动到x2,y2this.ctx.lineTo(x2, y2)// 把路径画出来this.ctx.stroke()}
}

这样白色竖线就有了:

时间刻度

时间刻度是本组件的核心,支持调整时间分辨率(就是整个时间轴所表示的时间范围,也即每两刻度之间的一格代表的时间大小),暂定包含0.5, 1, 2, 6, 12, 24这五种,单位是小时,先定义几个变量:

// 一小时的毫秒数
const ONE_HOUR_STAMP = 60 * 60 * 1000
// 时间分辨率
const ZOOM = [0.5, 1, 2, 6, 12, 24]export default {data () {return {// 当前所在时间分辨率的类型索引currentZoomIndex: 5,// 当前时间currentTime: 0,// 时间轴左侧起点所代表的时间,默认为当天的0点减12小时,即昨天中午12点startTimestamp:new Date(moment().format('YYYY-MM-DD 00:00:00')).getTime() -12 * ONE_HOUR_STAMP,}}
}

时间分辨率放在ZOOM的数组里,先以24分辨率来开发,24代表的是整个时间轴表示的时间范围为24小时。但是具体用一格表示多久呢,可以1个小时1格,也可以半个小时一格,随便你,这里就用一格表示半个小时,其他分辨率也是如此,为了方便也把它们装到一个数组里:

// 时间分辨率对应的每格小时数
const ZOOM_HOUR_GRID = [1 / 60, 1 / 60, 2 / 60, 1 / 6, 0.25, 0.5]

0.5就代表1小格代表0.5个小时,既然每格代表的小时数知道了,那么时间轴一共需要画多少格也就确定了:

// 一共可以绘制的格数,时间轴的时间范围小时数除以每格代表的小时数,24/0.5=48
let gridNum = ZOOM[this.currentZoomIndex] / ZOOM_HOUR_GRID[this.currentZoomIndex]

因为时间计算都是通过毫秒进行计算,所以先算一下一格代表多少毫秒:

// 一格多少毫秒,将每格代表的小时数转成毫秒数就可以了
let msPerGrid = ZOOM_HOUR_GRID[this.currentZoomIndex] * ONE_HOUR_STAMP

接下来是关键,因为要画图,最终还是要知道像素大小,那么每格是多少像素呢:

// 每格宽度,时间轴的宽度除以总格数
let pxPerGrid = this.width / gridNum

接下来事情似乎就简单了,循环一下画出刻度就好了:

for (let i = 0; i < gridNum; i++) {// 横坐标就是当前索引乘每格宽度let x = i * pxPerGrid// 当前刻度的时间,时间轴起始时间加上当前格子数乘每格代表的毫秒数let graduationTime = this.startTimestamp + i * msPerGrid// 刻度高度为时间轴高度的20%let h = this.height * 0.2// 刻度线颜色this.ctx.fillStyle = 'rgba(151,158,167,1)'// 显示时间this.ctx.fillText(this.graduationTitle(graduationTime),x - 13,// 向左平移一半h + 15// 加上行高)this.drawLine(x, 0, x, h, 1, 'rgba(151,158,167,1)')
}

graduationTitle方法是用来格式时间的,在0点时显示日期而不是时间:

graduationTitle (datetime) {let time = moment(datetime)// 0点则显示当天日期if (time.hours() === 0 &&time.minutes() === 0 &&time.milliseconds() === 0) {return time.format('MM-DD')} else {// 否则显示小时和分钟return time.format('HH:mm')}
}

看下效果:

似乎很完美,但是这样的真的可以了吗?不妨把起始时间加上个15分钟看一下:

startTimestamp:new Date(moment().format('YYYY-MM-DD 00:00:00')).getTime() -12 * ONE_HOUR_STAMP +15 * 60 * 1000// 加15分钟

起始时间加上15分钟,则为12:15分,看下效果:

可以看到,虽然每格代表的还是半个小时,但是我们的要求应该是逢整点和半点才显示刻度的,所以起始点应该是处在12:00和12:30分两根刻度的中间才对,所以画刻度的位置就需要加上一个偏移量,这里的偏移量很明显就是12:30-12:15=15分钟,如果起始点是12:40,那么偏移量就是13:00-12:40=20分钟,那么怎么算呢?我们不妨把时间拖回到0点,从0开始也许更容易看出来:

比如间距为10,起始点为5,那么与0的偏移量当然是5,可以通过5-0也可以通过5%10来算出来,那如果起始点是14,与前一个点的偏移量是14-10=4,但问题是你不知道前一个点是多少,所以减不了,只能14%10=4来算。但是我们实际需要的是与后一个点的偏移量,很简单,间距减去它就可以了。

其实很多差距的计算都可以通过取余来算,所以:

// 时间偏移量,初始时间除每格时间取余数,
let msOffset = msPerGrid - (this.startTimestamp % msPerGrid)
// 距离偏移量,时间偏移量和每格时间比例乘每格像素
let pxOffset = (msOffset / msPerGrid) * pxPerGridfor (let i = 0; i < gridNum; i++) {let x = pxOffset + i * pxPerGridlet graduationTime = this.startTimestamp + msOffset + i * msPerGrid//...
}

效果如下:

但是这样每个刻度都显示时间没必要也有点丑,所以可以循环的时候加个判断条件来选择性的绘制,因为每种分辨率也有不同的判断条件,所以也用一个数组来表示:

// 时间分辨率对应的时间显示判断条件
const ZOOM_DATE_SHOW_RULE = [() => {// 全都显示return true},date => {// 每五分钟显示return date.getMinutes() % 5 === 0},date => {// 显示10、20、30...分钟数return date.getMinutes() % 10 === 0},date => {// 显示整点和半点小时return date.getMinutes() === 0 || date.getMinutes() === 30},date => {// 显示整点小时return date.getMinutes() === 0},date => {// 显示2、4、6...整点小时return date.getHours() % 2 === 0 && date.getMinutes() === 0}
]for (let i = 0; i < gridNum; i++) {let x = pxOffset + i * pxPerGridlet graduationTime = this.startTimestamp + msOffset + i * msPerGridlet h = 0let date = new Date(graduationTime)// 0点显示日期if (date.getHours() === 0 && date.getMinutes() === 0) {h = this.height * 0.3this.ctx.fillStyle = 'rgba(151,158,167,1)'this.ctx.fillText(this.graduationTitle(graduationTime),x - 13,h + 15)} else if (ZOOM_DATE_SHOW_RULE[this.currentZoomIndex](date)) {// 其他根据判断条件来显示h = this.height * 0.2this.ctx.fillStyle = 'rgba(151,158,167,1)'this.ctx.fillText(this.graduationTitle(graduationTime),x - 13,h + 15)} else {// 其他不显示时间h = this.height * 0.15}this.drawLine(x, 0, x, h, 1, 'rgba(151,158,167,1)')
}

效果如下:

鼠标移动时显示所在时间

鼠标在时间轴上滑动时需要实时显示鼠标所在位置的时间,效果如下:

实现方式就是获取到鼠标相对画布的位置,然后换算成距起始点的时间:

{// 最开始就绑定的鼠标移动事件onMousemove (e) {// 计算出相对画布的位置let { left } = this.$refs.canvas.getBoundingClientRect()let x = e.clientX - left// 计算出时间轴上每毫秒多少像素const PX_PER_MS =this.width / (ZOOM[this.currentZoomIndex] * ONE_HOUR_STAMP) // px/ms// 计算所在位置的时间let time = this.startTimestamp + x / PX_PER_MS// 清除画布this.clearCanvas(this.width, this.height)// 绘制this.draw()// 绘制实时的竖线及时间this.drawLine(x, 0, x, this.height * 0.3, 'rgb(194, 202, 215)', 1)this.ctx.fillStyle = 'rgb(194, 202, 215)'this.ctx.fillText(moment(time).format('YYYY-MM-DD HH:mm:ss'),x - 20,this.height * 0.3 + 20)}
}

拖动时间轴

万众瞩目的焦点来了,时间轴时间轴,当然得需要能拖动,不然那叫时间段,从效果上看好像是鼠标拖着时间轴在滑动,但是实际上并没有,跟动画类似,就是不断的刷新重绘,因为人眼的暂存效应,看起来就像在滑动一样,而时间轴渲染的依据就是起始时间点,所以本质上就是计算鼠标拖动过程中的起始时间点是多少,先看一下鼠标按下的事件处理函数:

onMousedown (e) {let { left } = this.$refs.canvas.getBoundingClientRect()// 也是计算鼠标相当于时间轴左侧的距离this.mousedownX = e.clientX - left// 设置一下标志位this.mousedown = true// 缓存一下鼠标按下时的起始时间点this.mousedownCacheStartTimestamp = this.startTimestamp
}

鼠标开始移动就又到了鼠标移动事件处理的那个函数:

onMousemove (e) {let { left } = this.$refs.canvas.getBoundingClientRect()let x = e.clientX - leftconst PX_PER_MS =this.width / (ZOOM[this.currentZoomIndex] * ONE_HOUR_STAMP) // px/msif (this.mousedown) {// 计算鼠标当前相当于鼠标按下那个点的距离let diffX = x - this.mousedownX// 用鼠标按下时的起始时间点减去拖动过程中的偏移量,往左拖是负值,减减得正,时间就是在增加,往右拖时间就是在减少this.startTimestamp =this.mousedownCacheStartTimestamp - Math.round(diffX / PX_PER_MS)// 不断刷新重绘就ok了this.clearCanvas(this.width, this.height)this.draw()} else {// 鼠标滑动显示时间的逻辑}

调整时间分辨率

调整时间分辨率说白了就是调整时间轴所表示的时间范围,我们的范围是定义在ZOOM数组里的,所以通过鼠标滚动来调整之前定义的变量currentZoomIndex,然后重新渲染画布即可,需要注意的是时间范围调整了,而时间起始点不变的话那么当前时间就会变,但是我们一般是希望当前时间是不变的,所以需要调整时要计算新的时间起始点:

onMouseweel (event) {let e = window.event || eventlet delta = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail))if (delta < 0) {// 缩小if (this.currentZoomIndex + 1 >= ZOOM.length - 1) {this.currentZoomIndex = ZOOM.length - 1} else {this.currentZoomIndex++}} else if (delta > 0) {// 放大if (this.currentZoomIndex - 1 <= 0) {this.currentZoomIndex = 0} else {this.currentZoomIndex--}}this.clearCanvas(this.width, this.height)// 重新计算起始时间点,当前时间-新的时间范围的一半this.startTimestamp =this.currentTime - (ZOOM[this.currentZoomIndex] * ONE_HOUR_STAMP) / 2 this.draw()
}

绘制时间段

时间段就是在时间轴里带颜色的矩形块,先看看时间段的数据结构:

[{beginTime: new Date('2020-06-10 09:30:00').getTime(),endTime: new Date('2020-06-10 11:20:00').getTime(),style: {background: '#5881CF'}}
]

接下来就是想办法把起始时间用给定的颜色在时间轴里画出来,首先要判断一下时间段是否在当前时间轴的范围内,如果不相交当然就不用画了:

if (item.beginTime <= this.startTimestamp + ZOOM[this.currentZoomIndex] * ONE_HOUR_STAMP && item.endTime >= this.startTimestamp) {// 绘制范围内的线段
}

绘制矩形用的是fillRect方法,它的四个参数分别是:x、y、width、height,先算起始点的坐标,y和height可以直接根据时间轴的高度来定,所以主要计算的就是x和width,x是起点值,可以用beginTime-startTimestamp再换算成像素就可以了,需要注意的是可能beginTime小于startTimestamp,负值显然是不行的,所以转成0,width就是起始时间的差值换算成像素,也需要注意小于0的情况,完整代码如下:

drawTimeSegments () {const PX_PER_MS =this.width / (ZOOM[this.currentZoomIndex] * ONE_HOUR_STAMP) // px/msthis.timeSegments.forEach(item => {if (item.beginTime <=this.startTimestamp +ZOOM[this.currentZoomIndex] * ONE_HOUR_STAMP &&item.endTime >= this.startTimestamp) {let x = (item.beginTime - this.startTimestamp) * PX_PER_MSlet wif (x < 0) {x = 0w = (item.endTime - this.startTimestamp) * PX_PER_MS} else {w = (item.endTime - item.beginTime) * PX_PER_MS}this.ctx.fillStyle = item.style.backgroundthis.ctx.fillRect(x, this.height * 0.6, w, this.height * 0.3)}})
}

为什么endTime大于时间轴最大时间的情况不用特殊处理呢,因为不管也没关系,反正长度都已经超出范围了,再长一点短一点也看不到。

当然,这样单纯的显示一下时间段意义并不大,一般使用场景是代表在当前时间段内才有视频,所以可以在时间段存在的情况下对拖动时间做一下处理,如果拖动到的时间点不在任何一个时间段内,那么就让它吸附到离它最近的一个时间段的时间点上。

多个时间轴

一个时间轴往往是不够用的,比如同时要进行多路视频回放,每个视频都有自己的时间段,那么就需要多个时间轴来进行显示,这也很简单,我们把时间段相关的代码抽到一个单独的组件里,然后把内部状态都通过props进行传递,这样就可以进行复用了:

<template><div class="timeLineContainer" ref="timeLineContainer"><canvas ref="canvas"></canvas><!--多个时间轴--><div class="windowList" ref="windowList" v-if="showWindowList && windowList && windowList.length > 1" @scroll="onWindowListScroll"><WindowListItemv-for="(item, index) in windowListInner"ref="WindowListItem":key="index":index="index":data="item":totalMS="totalMS":startTimestamp="startTimestamp":width="width":active="item.active"@click_window_timeSegments="triggerClickWindowTimeSegments"@click="toggleActive(index)"></WindowListItem></div></div>
</template>

每个单独的时间轴也是一个canvas

// WindowListItem.vue 
<template><div class="windowListItem" :class="{active: active}" ref="windowListItem" @click="onClick"><span class="order">{{ index + 1 }}</span><canvas class="windowListItemCanvas" ref="canvas"></canvas></div>
</template>

效果如下:

显示自定义元素

除了时间段,有时候我们会想在时间段上显示自定义元素,比如在某个时间点显示一张图片,因为时间轴是在动的,所以图片也得跟着动,这可以给使用者提供一个监听时间的功能,具体实现就是在上文的绘制方法draw里实时获取某个时间点的位置,然后抛出一个事件给使用者监听,使用者可以根据监听到的lefttop值来定位元素。

获取某个时间点的位置也很简单,先判断这个时间点是否在当前显示的范围内,不在的话那显然就不用显示,在的话再换算成在当前时间轴上的位置,最后加上时间轴距离页面的位置即可:

draw () {// ...// 更新观察的时间位置this.updateWatchTime()
}
// 更新观察的时间位置
updateWatchTime () {this.watchTimeList.forEach((item) => {// 当前不在显示范围内if (item.time < this.startTimestamp || item.time > this.startTimestamp + this.totalMS) {item.callback(-1, -1)} else { // 在范围内let x = (item.time - this.startTimestamp) * (this.width / this.totalMS)let y = 0let { left, top } = this.$refs.canvas.getBoundingClientRect()if (item.windowTimeLineIndex !== -1 && this.windowList.length > 1 && item.windowTimeLineIndex >= 0 && item.windowTimeLineIndex < this.windowList.length) {let rect = this.$refs.WindowListItem[item.windowTimeLineIndex].getRect()y = rect ? rect.top : top} else {y = top}item.callback(x + left, y)}})
}

使用的时候,通过给你要显示的元素设置绝对定位或固定定位,然后监听时间的当前位置来修改元素的位置,如果时间轴本身的位置都变化了,比如页面滚动了,需要手动调用updateWatchTime方法来修正。

效果如下:

总结

本文介绍了如何实现一个视频时间轴组件,可以满足一些常见的场景,笔者也开发了一个可以直接使用的组件,文档:github.com/wanglin2/Vi…,如何不满足需求,也可以在此组件基础上进行定制。

附送250套精选项目源码

源码截图

 源码获取:关注公众号「码农园区」,回复 【源码】,即可获取全套源码下载链接

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

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

相关文章

华为手机怎么找回删除的照片?掌握3个方法,恢复不是梦

由于误删、设备故障、软件更新等原因&#xff0c;我们有时可能会不慎丢失这些宝贵的照片。当面对空空如也的相册时&#xff0c;那种失落感无法言喻。华为手机该怎么找回删除的照片呢&#xff1f;但是&#xff0c;请不要绝望&#xff01;在科技的帮助下&#xff0c;我们可以采取…

tplink安防监控raw文件转码合成mp4的方法

Tplink(深圳普联)专业的网络设备生产商&#xff0c;属于安防监控市场的后来者。Tplink的安防产品恢复了很多&#xff0c;其嵌入式文件系统也一直迭代更新。今天要说的案例比较特殊&#xff0c;其不仅仅要求恢复&#xff0c;还要求能解析出音频并且要求画面和声音实现“同步”。…

zynq qspi启动、无SD卡、格式化分区emmc、调试全过程

1 背景 使用黑金开发板&#xff0c;全部开发流程避开使用SD卡调试&#xff0c;zynq开发过程中很多资料都是基于SD启动。这样就对新板卡调试带来了一定的困难&#xff0c;因为新板卡基本上没有设计SD卡。这里就一步一步实现qspi启动内核&#xff0c;格式化分区emmc&#xff0c;…

阿里拍卖资产推荐算法 召回进展年中总结

阿里拍卖是阿里巴巴旗下拍卖平台&#xff0c;覆盖房产、机动车、土地、债权等类目。召回策略作为推荐场景的第一环&#xff0c;决定了整个推荐系统的上限&#xff0c;目前包含了包括向量召回、I2I、LBS2I、C2I等多路召回。召回的核心目标是尽可能的返回用户所有可能会感兴趣的商…

DataOps真能“降本增效”?

在各行各业中&#xff0c;越来越多的公司开始重视收集数据&#xff0c;并寻找创新方法来获得真实可行的商业成果&#xff0c;并且愿意投入大量时间和金钱来实现这一目标。 据IDC称&#xff0c;数据和分析软件及云服务市场规模在 2021 年达到了 900 亿美元&#xff0c;随着企业继…

CSDN自定义模块全攻略,DIY系统原有样式打造专属个性化主页!

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 &#x1f4af;如何通过HTMLCSS自定义模板diy出自己的个性化csdn主页&#x…

明日开考!2024年全国青少年人工智能创新挑战赛及真题

Scratch实验室2024-06-21讯 2024年全国青少年人工智能创新挑战赛【编程创作与信息学专项赛】第二轮将在明天&#xff08;2024年6月22日&#xff09;举行&#xff0c;请参加的同学积极备考&#xff0c;参加选拔赛的青少年需通过“人工智能创新挑战赛”专题页面点击“参加选拔赛”…

RocketMQ快速入门:集成spring, springboot实现各类消息消费(七)附带源码

0. 引言 rocketmq支持两种消费模式&#xff1a;pull和push&#xff0c;在实际开发中这两种模式分别是如何实现的呢&#xff0c;在spring框架和springboot框架中集成有什么差异&#xff1f;今天我们一起来探究这两个问题。 1. java client实现消息消费 1、添加依赖 <depen…

正定矩阵(Positive Definite Matrix)

正定矩阵&#xff08;Positive Definite Matrix&#xff09; flyfish Positive&#xff08;正数&#xff09; &#xff1a;在数学和统计学中&#xff0c;通常指大于零的数。在矩阵理论中&#xff0c;一个矩阵被称为正定&#xff0c;是因为它的性质类似于正数的性质。 Defini…

裁员裁到大动脉,是一种什么体验!

大家好啊&#xff0c;我是董董灿。 降本增效是每个当老板的人都喜欢挂在嘴边的口头禅&#xff0c;尤其是行业不景气&#xff0c;公司发展遇到瓶颈的时候。 大部分公司降本增效的手段其实非常相似&#xff0c;比较容易实施的手段也就那几种。 要么搞设备自动化和流程自动化&a…

Anthropic 发布新AI模型Claude 3.5 Sonnet

&#x1f989; AI新闻 &#x1f680; Anthropic 发布新AI模型Claude 3.5 Sonnet 摘要&#xff1a;Anthropic 发布了其最强 AI 模型 Claude 3.5 Sonnet。速度更快、处理细微差别和幽默的能力提升&#xff0c;且支持编写、编辑和执行代码。该模型通过公司网站、iPhone 应用及 A…

数据库系统概念(第八周 第一堂)(规范化关系数据库设计)(强推学习!!!)

目录 前言 E-R模型质量低的深层原因 数据依赖 函数依赖 主属性/非主属性 逻辑蕴含与闭包 Armstrongs Axiom 求解F闭包算法 求解属性集闭包算法 属性集闭包的作用 候选码求解理论和算法 候选码求解理论 无关属性 检验方法 正则覆盖 关系模式的设计 关系…

【深度学习】GPT-2,Language Models are Unsupervised Multitask Learners,【语言建模】

论文&#xff1a;https://d4mucfpksywv.cloudfront.net/better-language-models/language_models_are_unsupervised_multitask_learners.pdf 文章目录 摘要引言方法2.1 训练数据集2.2 输入表示2.3 模型3. 实验3.1 语言建模3.2 Children’s Book Test3.3 LAMBADA3.4 Winograd Sc…

自动驾驶学习-车载摄像头ISP(2)

背景 智能驾驶ISP&#xff08;Image Signal Processor&#xff0c;图像信号处理器&#xff09;在自动驾驶和辅助驾驶系统中扮演着至关重要的角色。 典型的ISP通常会对摄像头输出的RAW数据先做黑电平矫正&#xff08;BLC&#xff09;、坏点矫正&#xff08;DPC&#xff09;、数…

如何DIY出专属个性化的CSDN主页?一招教你搞定!

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 &#x1f4af;如何通过HTMLCSS自定义模板diy出自己的个性化csdn主页&#x…

SD3发布,送你3个ComfyUI工作流

大家好&#xff0c;我是每天分享AI应用的萤火君&#xff01; 这几天AI绘画界最轰动的消息莫过于Stable Diffusion 3&#xff08;简称SD3&#xff09;的发布。SD3是一个多模态的 Diffusion Transformer 模型&#xff0c;其在图像质量、排版、复杂提示理解和资源效率方面具有显著…

ADC常用的十大滤波算法(C语言)

一、限幅滤波法 1、方法&#xff1a; 根据经验判断两次采样允许的最大偏差值&#xff08;设为A&#xff09; 每次检测到新值时判断&#xff1a; a. 如果本次值与上次值之差<A&#xff0c;则本次值有效 b. 如果本次值与上次值之差>A&#xff0c;则本次值无效&#xf…

QT MQTT (二)编译与集成

一、QT MQTT 提供 MQTT 客户端服务的 Qt 专用库基于标准化发布 / 订阅协议&#xff0c;用于在设备和组件之间可靠地共享数据。MQTT 是为保证状态正确性、满足高安全标准和交换最小数据而设计的协议&#xff0c;因此被广泛应用于各种分布式系统和物联网解决方案中。 Qt开发MQT…

【Oracle篇】Oracle数据库坏块处理:rman修复坏块实践与案例分析(第七篇,总共八篇)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux&#xff0c;也在扩展大数据方向的知识面✌️…

git配置ssh key

一、生成ssh公钥和私钥对 打开终端&#xff0c;输入命令&#xff0c;-C 后是git邮箱&#xff0c;在 Enter file in which to save the key (/home/my/.ssh/id_rsa): 后可以输入公钥和私钥对保存路径及文件名&#xff0c;默认是 /home/my/.ssh/id_rsa&#xff0c;其它的全部按回…