OpenHarmony轻松玩转GIF数据渲染

news2024/9/21 14:31:23

OpenAtom OpenHarmony(以下简称“OpenHarmony”)提供了Image组件支持GIF动图的播放,但是缺乏扩展能力,不支持播放控制等。今天介绍一款三方库——ohos-gif-drawable三方组件,带大家一起玩转GIF的数据渲染,搞定GIF动图的各种需求。

效果演示

本文将从5个小节来带领大家使用ohos-gif-drawable这一款三方库,其中1、2、3这3个小节,主要介绍了ohos-gif-drawable的核心能力、GIF软解码和GIF绘制。4和5小节主要是扩展讨论,如何添加滤镜效果和软解码遇到的耗时问题。

1.GIF的文件格式理论基础

工欲善其事必先利其器。首先我们需要为自己打下理论基础。了解GIF的数据格式,为后续解码GIF提供理论支持。

通过学习GIF的文件格式,我们对于GIF的组成格式有了一定的了解,并且有助于理解后面GIF的解码。

在开始介绍之前,我想让大家了解一下整体的结构思路如下图:

其中gifuct-js三方库主要完成了解码的工作。

ohos-gif-drawable三方库则是在gifuct-js的三方库之上,进行了封装。并结合了OpenHarmony的Canvas绘制能力,达到了播放和控制GIF的能力。

2.GIF软解码:gifuct-js三方库介绍

GIF解码我们使用了gifuct-js这个库,它是一个纯JavaScript的GIF解码库。首先我们需要了解基础用法。

2.1 参考样例将一个文件ArrayBuffer转换为GIF解码后的帧数据数组。

//javascript
var gif = parseGIF(arraybuffer)
var frames = decompressFrames(gif, true)

2.2 由于OpenHarmony的Image生成PixelMap需要的数据是BGRA数据,而2.1生成的frames所有数组中的patch字段则是RGBA数据,所以我们需要使用

//javascript
var gif = parseGIF(arraybuffer)
var frames = decompressFrames(gif, false)

然后将frame目前还未生成的patch字段数据,通过generatePatch 函数,将RGBA的数据更换为BGRA即可,如下代码所示:

//javascript
const generatePatch = image => {
  const totalPixels = image.pixels.length
  const patchData = new Uint8ClampedArray(totalPixels * 4)
  for (var i = 0; i < totalPixels; i++) {
    const pos = i * 4
    const colorIndex = image.pixels[i]
    const color = image.colorTable[colorIndex] || [0, 0, 0]
    patchData[pos] = color[2] // B
    patchData[pos + 1] = color[1]// G
    patchData[pos + 2] = color[0] // R
    patchData[pos + 3] = colorIndex !== image.transparentIndex ? 255 : 0//A
  }
  return patchData
}

generatePatch函数,在这里会根据颜色表colorTable和基于颜色表的图像数据pixels以及透明度transparentIndex生成BGRA格式的patchData,这个数据和Canvas中getImageData获取的ImageData数据是一致的,都是Uint8ClampedArray类型,可以直接使用putImageData让canvas绘制。

最后,生成的patchData赋值给Frame的patch字段。

这里我们并没有直接使用Canvas的putImageData直接绘制。为了提升扩展性,我们使用了Image的能力来生成PixelMap,这样处理为后续滤镜效果提供了可能,也方便后续绘制流程。

好了,到这里我们就基本上把gifuct-js库的基础使用简单介绍完了。

如何使用GIF:ohos-gif-drawable三方库的介绍。

我们先来看看整个ohos-gif-drawable组件的模型图,通过模型图,我们可以看到,用户只要关注GIFComponent组件,和GIFComponent.ControllerOptions配置参数以及控制参数autoPlay和resetGif即可,非常简单!

  1. 支持的功能列表如下

● 支持播放GIF图片。
● 支持控制GIF播放/暂停。
● 支持重置GIF播放动画。
● 支持调节GIF播放速率。
● 支持监听GIF所有帧显示完成后的回调。
● 支持设置显示大小。
● 支持7种不同的展示类型。
● 支持设置显示区域背景颜色。

  1. 如何使用ohos-gif-drawable

首先需要使用npm下载ohos-gif-drawable三方库

npm install @ohos/ohos-gif-drawable --save

接下来我们需要配置一个worker给gifuct-js解码使用。

配置worker,在应用工程的entry/src/main/ets/pages目录下新建workers文件夹,并且创建文件 gifParseWorker.ts ,文件内容如下:

import arkWorker from '@ohos.worker';
import { handler } from '@ohos/ohos-gif-drawable/src/main/ets/components/gif/worker/GifWorker'
// handler封装了子线程逻辑,但worker目前只能在entry中进行创建arkWorker.parentPort.onmessage = handler;

然后在entry目录的build-profile.json5文件中,添加如下内容:

"buildOption": { 
"sourceOption": {   
"workers": [    
       "./src/main/ets/pages/workers/gifParseWorker.ts"
] 
}
},

到这里我们worker就配置好了。

下面就到了正式使用环节,我们只要在UI界面需要的地方写上自定义控件GIFComponent,然后传入GIFComponent.ControllerOptions,gifAutoPlay,gifReset这三个参数就能控制gif动画。

import { GIFComponent, ResourceLoader } from '@ohos/ohos-gif-drawable'
// gif绘制组件用户属性设置
@State model:GIFComponent.ControllerOptions = new GIFComponent.ControllerOptions();
// 是否自动播放
@State gifAutoPlay:boolean = true;
// 重置GIF播放,每次取反都能生效
@State gifReset:boolean = true;
// 在ARKUI的其他容器组件中添加该组件
GIFComponent({model:$model, autoPlay:$gifAutoPlay, resetGif:this.gifReset})

举个简单的例子说明一下

// 创建worker
let worker = new ArkWorker.Worker('entry/ets/pages/workers/gifParseWorker.ts', {type: 'classic',name: 'loadUrlByWorker'})
// 关闭动画     
this.gifAutoPlay = false;
// 销毁上一次资源
this.model.destroy();
// 新创建一个modelx,用于配置用户参数
let modelx = new GIFComponent.ControllerOptions()
modelx 
// 配置回调动画结束监听,和耗时监听   
.setLoopFinish((loopTime) => {  
this.gifLoopCount++;  
this.loopHint = '当前gif循环了' + this.gifLoopCount + '次,耗时=' + loopTime + 'ms'  
}) 
// 设置组件大小   
.setSize({ width: this.compWidth, height: this.compHeight }) 
// 设置图像和组件的适配类型 
.setScaleType(this.scaleType) 
// 设置播放速率 
.setSpeedFactor(this.speedFactor) 
// 设置背景 
.setBackgroundColor(Color.Grey)
// 加载网络图片,getContext(this)中的this指向page页面或者组件都可以ResourceLoader.downloadDataWithContext(getContext(this), {   url: 'https://pic.ibaotu.com/gif/18/17/16/51u888piCtqj.gif!fwpaa70/fw/700'   }, (sucBuffer) => {   
// 网络资源sucBuffer返回后处理  
modelx.loadBuffer(sucBuffer, () => {      console.log('网络加载解析成功回调绘制!')   
// 开启自动播放     
this.gifAutoPlay = true;   
// 给组件数据赋新的用户配置参数,达到后续gif动画效果     
this.model = modelx;   }, worker)}, (err) => {  
// 用户根据返回的错误信息,进行业务处理(展示一张失败占位图、再次加载一次、加载其他图片等)
})

这里ResourceLoader内置了加载网络资源GIF,本地工程资源GIF和本地路径资源GIF文件数据的能力。

如果你已经有了GIF文件的arraybuffer数据,也可以直接调用modelx.loadBuffer(buffer: ArrayBuffer, readyRender: (err?) => void, worker: any)进行GIF播放。

甚至你已经生成了GIF解析数据,比如调用了2.2中的解码代码,那么你也可以直接调用modelx.setFrames(images?: GIFFrame[])来进行gif播放。

1.控制GIF的播放与暂停:

this.gifAutoPlay = true 开启动画
this.gifAutoPlay = false 暂停动画

组件内部会监听该参数的变化,用户只要改变值即可达到控制效果

2. 重置GIF的播放

this.gifReset = !this.gifReset 每次变化都会重置gif播放。

由于重置不需要状态管理,所以组件内监听到数据变化就会重置gif播放

3. 设置GIF动画播放速度

let modelx = new GIFComponent.ControllerOptions()
modelx.setSpeedFactor(2)// 将速率提升到2倍

调用setSpeedFactor(speed: number)即可调整播放速度speed 为对比原始速率的乘积因子,比如设置0.5即为原始速率的0.5倍,设置为2即为原始速率的2倍。

4. 监听GIF动画播放回调(比如第一次动画结束)和获取动画实际播放总时长

let modelx = new GIFComponent.ControllerOptions()
modelx.setLoopFinish((loopTime?) => {
// loopTime为GIF动画一周期耗时,回调时间为GIF动画一周期结束时间节点
})

调用setLoopFinish(fn: (loopTime?) => void)可以通过回调得到GIF动画运行一周期耗时和一周期结束时间节点。

5. 显示GIF任意一帧

let modelx = new GIFComponent.ControllerOptions()
modelx.setSeekTo(5) // 直接展示该gif第5帧图像

调用setSeekTo(gifPosition: number)可以直接展示该gif的某一帧图像。

到这里ohos-gif-drawable三方库的主要能力都介绍完了,是不是很简单呢!

6. 适配组件的大小

let modelx = new GIFComponent.ControllerOptions()

modelx.setScaleType(ScaleType.FIT_CENTER) // 将图像缩放适配组件大小调用setScaleType(scaletype: ScaleType)可以将图像和组件大小进行适配。

目前支持的类型如下图所示:

GIFComponent.ScaleType

为什么要配置worker

在具体实践过程中我们会发现,当我们按下解码按钮的时候,主界面会有一点卡顿的情况。特别是大的GIF文件进行解码的时候效果更明显。这是因为我们在主线程中进行了CPU的密集型计算,这是一个耗时且占用CPU的操作。主线程中是不能执行耗时操作的。但是JavaScript只有一个线程啊?那么解码这一块操作该如何处理会比较好呢?带着疑惑,我去查阅了资料发现JavaScript虽然属于单线程环境。但是通过引入Worker的能力,引入子线程worker,可以实现JavaScript的“多线程”技术。

OpenHarmony如何在子线程中处理耗时任务

为了争取良好的用户体验,我们需要将耗时操作封装至子线程中。

这里简单描述一下worker的能力:

能够让主页面运行的JavaScript线程中加载运行另外单独的一个或者多个JavaScript线程,但是它的多线程编程能力区别于传统意义上的多线程编程。主线程和Worker线程之间,不会共享任何作用域和资源,他们的通信方式是基于事件监听机制的 message。

接下来我们参考OpenHarmony文档下的worker能力

1. OpenHarmony环境下Worker的API接口列表

2. Worker的使用简单案例

经过了解之后,我们可以把解码的耗时封装到worker中处理,避免主线程耗时操作占用CPU导致卡顿问题。提升用户体验。

这也是使用ohos-gif-drawable三方库需要配置worker的原因。

扩展部分

GIF的滤镜效果

1. 灰白滤镜

//javascript
// 重点代码更改 
  let avg = (color[0] + color[1] + color[2]) / 3
  patchData[pos] = avg;
  patchData[pos + 1] = avg;
  patchData[pos + 2] = avg;
  patchData[pos + 3] = colorIndex !== image.transparentIndex ? 255 : 0;

2. 反转滤镜

//javascript
// 重点代码更改
  patchData[pos] = 255 - color[0];
  patchData[pos + 1] = 255 - color[1];
  patchData[pos + 2] = 255 - color[2];
  patchData[pos + 3] = colorIndex !== image.transparentIndex ? 255 : 0;

3. 高级滤镜效果

假设我们这边已经拿到了patch: Uint8ClampedArray像素数据,这里我需要先将其变换为一张PixelMap数据,参考GIFComponent中patch数据转换为PixelMap的代码。

//typescript
import image from "@ohos.multimedia.image"
let colorBuffer = patch.buffer
let pixelmap = await image.createPixelMap(colorBuffer, {
  'size': {
    'height': frame.dims.height as number,
    'width': frame.dims.width as number
  }
})

4. 高斯模糊

然后对PixelMap像素数据进行高斯模糊, 调用 blur(pixelmap,10,true, (outPixelMap)=>{ // 模糊后的pixelmap数据})在回调中获取模糊后的pixelmap。以下是模糊处理的算法:

export async function blur(bitmap: any, radius: number, canReuseInBitmap: boolean, func: AsyncTransform<PixelMap>) {
  if (radius < 1) {
    func("error,radius must be greater than 1 ", null);
    return;
  }
 
  let imageInfo = await bitmap.getImageInfo();
  let size = {
    width: imageInfo.size.width,
    height: imageInfo.size.height
  }
 
  if (!size) {
    func(new Error("fastBlur The image size does not exist."), null)
    return;
  }
 
  let w = size.width;
  let h = size.height;
  var pixEntry: Array<PixelEntry> = new Array()
  var pix: Array<number> = new Array()
 
 
  let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber());
  await bitmap.readPixelsToBuffer(bufferData);
  let dataArray = new Uint8Array(bufferData);
 
  for (let index = 0; index < dataArray.length; index+=4) {
    const r = dataArray[index];
    const g = dataArray[index+1];
    const b = dataArray[index+2];
    const f = dataArray[index+3];
 
    let entry = new PixelEntry();
    entry.a = 0;
    entry.b = b;
    entry.g = g;
    entry.r = r;
    entry.f = f;
    entry.pixel = ColorUtils.rgb(entry.r, entry.g, entry.b);
    pixEntry.push(entry);
    pix.push(ColorUtils.rgb(entry.r, entry.g, entry.b));
  }
 
  let wm = w - 1;
  let hm = h - 1;
  let wh = w * h;
  let div = radius + radius + 1;
 
  let r = CalculatePixelUtils.createIntArray(wh);
  let g = CalculatePixelUtils.createIntArray(wh);
  let b = CalculatePixelUtils.createIntArray(wh);
 
  let rsum, gsum, bsum, x, y, i, p, yp, yi, yw: number;
  let vmin = CalculatePixelUtils.createIntArray(Math.max(w, h));
 
  let divsum = (div + 1) >> 1;
  divsum *= divsum;
  let dv = CalculatePixelUtils.createIntArray(256 * divsum);
  for (i = 0; i < 256 * divsum; i++) {
    dv[i]=(i / divsum);
}
 
  yw = yi =0;
  let stack = CalculatePixelUtils.createInt2DArray(div,3);
  let stackpointer, stackstart, rbs, routsum, goutsum, boutsum, rinsum, ginsum, binsum: number;
  let sir: Array<number>;
  let r1 = radius +1;
for(y =0; y < h; y++){
    rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum =0;
for(i =-radius; i <= radius; i++){
      p = pix[yi + Math.min(wm, Math.max(i,0))];
      sir = stack[i + radius];
      sir[0]=(p &0xff0000)>>16;
      sir[1]=(p &0x00ff00)>>8;
      sir[2]=(p &0x0000ff);
      rbs = r1 - Math.abs(i);
      rsum += sir[0]* rbs;
      gsum += sir[1]* rbs;
      bsum += sir[2]* rbs;
if(i >0){
        rinsum += sir[0];
        ginsum += sir[1];
        binsum += sir[2];
}else{
        routsum += sir[0];
        goutsum += sir[1];
        boutsum += sir[2];
}
}
    stackpointer = radius;
 
for(x =0; x < w; x++){
 
      r[yi]= dv[rsum];
      g[yi]= dv[gsum];
      b[yi]= dv[bsum];
 
      rsum -= routsum;
      gsum -= goutsum;
      bsum -= boutsum;
 
      stackstart = stackpointer - radius + div;
      sir = stack[stackstart % div];
 
      routsum -= sir[0];
      goutsum -= sir[1];
      boutsum -= sir[2];
 
if(y ==0){
        vmin[x]= Math.min(x + radius +1, wm);
}
      p = pix[yw + vmin[x]];
 
      sir[0]=(p &0xff0000)>>16;
      sir[1]=(p &0x00ff00)>>8;
      sir[2]=(p &0x0000ff);
 
      rinsum += sir[0];
      ginsum += sir[1];
      binsum += sir[2];
 
      rsum += rinsum;
      gsum += ginsum;
      bsum += binsum;
 
      stackpointer =(stackpointer +1)% div;
      sir = stack[(stackpointer)% div];
 
      routsum += sir[0];
      goutsum += sir[1];
      boutsum += sir[2];
 
      rinsum -= sir[0];
      ginsum -= sir[1];
      binsum -= sir[2];
 
      yi++;
}
    yw += w;
}
for(x =0; x < w; x++){
    rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum =0;
    yp =-radius * w;
for(i =-radius; i <= radius; i++){
      yi = Math.max(0, yp)+ x;
 
      sir = stack[i + radius];
 
      sir[0]= r[yi];
      sir[1]= g[yi];
      sir[2]= b[yi];
 
      rbs = r1 - Math.abs(i);
 
      rsum += r[yi]* rbs;
      gsum += g[yi]* rbs;
      bsum += b[yi]* rbs;
 
if(i >0){
        rinsum += sir[0];
        ginsum += sir[1];
        binsum += sir[2];
}else{
        routsum += sir[0];
        goutsum += sir[1];
        boutsum += sir[2];
}
 
if(i < hm){
        yp += w;
}
}
    yi = x;
    stackpointer = radius;
for(y =0; y < h; y++){
// Preserve alpha channel: ( 0xff000000 & pix[yi] )
      pix[yi]=(0xff000000& pix[Math.round(yi)])|(dv[Math.round(rsum)]<<16)|(dv[
      Math.round(gsum)]<<8)| dv[Math.round(bsum)];
 
      rsum -= routsum;
      gsum -= goutsum;
      bsum -= boutsum;
 
      stackstart = stackpointer - radius + div;
      sir = stack[stackstart % div];
 
      routsum -= sir[0];
      goutsum -= sir[1];
      boutsum -= sir[2];
 
if(x ==0){
        vmin[y]= Math.min(y + r1, hm)* w;
}
      p = x + vmin[y];
 
      sir[0]= r[p];
      sir[1]= g[p];
      sir[2]= b[p];
 
      rinsum += sir[0];
      ginsum += sir[1];
      binsum += sir[2];
 
      rsum += rinsum;
      gsum += ginsum;
      bsum += binsum;
 
      stackpointer =(stackpointer +1)% div;
      sir = stack[stackpointer];
 
      routsum += sir[0];
      goutsum += sir[1];
      boutsum += sir[2];
 
      rinsum -= sir[0];
      ginsum -= sir[1];
      binsum -= sir[2];
 
      yi += w;
}
}
 
  let bufferNewData =newArrayBuffer(bitmap.getPixelBytesNumber());
  let dataNewArray =newUint8Array(bufferNewData);
  let index =0;
 
for(let i =0; i < dataNewArray.length; i +=4){
    dataNewArray[i]= ColorUtils.red(pix[index]);
    dataNewArray[i+1]= ColorUtils.green(pix[index]);
    dataNewArray[i+2]= ColorUtils.blue(pix[index]);
    dataNewArray[i+3]= pixEntry[index].f;
    index++;
}
  await bitmap.writeBufferToPixels(bufferNewData);
if(func){
func("success", bitmap);
}
}

如果需要高级滤镜效果可以参考ImageKnife组件的transform部分,这里仅仅展示模糊效果。

由于滤镜效果目前ohos-gif-drawable三方库并没有开发接口提供出来,所以开发者可以根据实际需求重写自定义组件GIFComponent.,只需要在生成PixelMap的代码片段中加入滤镜代码,即可利用滤镜效果开发更多精彩的应用。

经常有很多小伙伴抱怨说:不知道学习鸿蒙开发哪些技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?

为了能够帮助到大家能够有规划的学习,这里特别整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、WebGL、元服务、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植等等)鸿蒙(HarmonyOS NEXT)技术知识点。

在这里插入图片描述

《鸿蒙 (Harmony OS)开发学习手册》(共计892页):https://gitcode.com/HarmonyOS_MN/733GH/overview

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

在这里插入图片描述

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

在这里插入图片描述

鸿蒙开发面试真题(含参考答案):https://gitcode.com/HarmonyOS_MN/733GH/overview

在这里插入图片描述

OpenHarmony 开发环境搭建

图片

《OpenHarmony源码解析》:https://gitcode.com/HarmonyOS_MN/733GH/overview

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……
  • 系统架构分析
  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

图片

OpenHarmony 设备开发学习手册:https://gitcode.com/HarmonyOS_MN/733GH/overview

图片
在这里插入图片描述

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

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

相关文章

【Java】Spring-AOP与拦截器实战 (上手图解)

Java系列文章目录 补充内容 Windows通过SSH连接Linux 第一章 Linux基本命令的学习与Linux历史 文章目录 Java系列文章目录一、前言二、学习内容&#xff1a;三、问题描述四、解决方案&#xff1a;4.1 认识依赖4.2 使用AOP与拦截器4.2.1 使用AOP4.2.1.1 设置DemoAop类4.2.2.2 设…

Linux驱动环境配置

Linux驱动环境配置 1.u-boot烧录2.Linux设置3.u-boot命令4.u-boot环境变量相关命令5.tftp安装与配置6.nfs7.配置网络环境变量8.tftp下载9.nfs挂载根文件系统 1.u-boot烧录 1.打开J-Flash 2.打开工程(Open Project) 3.Open data file 4.设置起始地址0x0 5.选择Target-Conne…

【Java毕业设计】基于SpringBoot+Vue+uniapp的农产品商城系统

文章目录 一、系统架构1、后端&#xff1a;SpringBoot、Mybatis2、前端&#xff1a;Vue、ElementUI4、小程序&#xff1a;uniapp3、数据库&#xff1a;MySQL 二、系统功能三、系统展示1、小程序2、后台管理系统 一、系统架构 1、后端&#xff1a;SpringBoot、Mybatis 2、前端…

【学术会议征稿】第三届智慧能源与电气工程国际学术会议(SEEE 2024)

第三届智慧能源与电气工程国际学术会议&#xff08;SEEE 2024&#xff09; 2024 3rd International Conference on Smart Energy and Electrical Engineering(SEEE 2024) 在双碳目标背景下&#xff0c;能源行业正在面临着绿色低碳转型的巨大挑战。随着我国产业结构全面调整&am…

show命令监控分析mysql实例信息

文章目录 思维导图show 查看数据库实例相关信息SHOW VARIABLES 分析数据库当前变量设置分析连接数据分析线程数分析慢查询变量分析缓存相关分析字符集相关SHOW STATUS 数据库当前实时状态分析分析连接数据分析线程数分析慢查询分析查询缓存分析排序使用情况分析文件打开数思维导…

09-03 周二 ansible部署与使用指南

09-03 周二 ansible部署与使用指南 时间版本修改人描述2024年9月3日10:08:58V0.1宋全恒新建文档&#xff0c;2024年9月4日13:57:25v0.2宋全恒调整结构&#xff0c;添加ansible-playbook和ansible-inventory 简介 首先要找一个跳板机&#xff0c;来确保所有的机器都可以访问。然…

OpenAI gym‘s breakout-v0 “pauses“

题意&#xff1a;OpenAI Gym 的 breakout-v0 “暂停” 问题背景&#xff1a; While training in the OpenAI gym environment I have the idea that the environment sometimes "stops". For many frames in a row no ball is visible/stops spawning. 在 OpenAI G…

网站代运维与建设:HTTP虚拟专线的优势

网站代运维与建设&#xff1a;HTTP虚拟专线的优势 企业和个人越来越依赖于网站来展示品牌形象、提供服务和与客户互动。然而&#xff0c;网站的建设和运维往往需要投入大量时间和资金&#xff0c;尤其是在服务器费用和技术维护方面。本文将探讨如何通过使用HTTP虚拟专线来降低…

【鸿蒙开发笔记】如何理解 UIAbility 组件以及它的生命周期

UIAbility 组件是一种包含了 UI 的应用组件&#xff0c;主要用于和用户交互。 UIAbility 组件是系统调度的基本单元&#xff0c;为应用提供绘制界面的窗口&#xff0c;一个应用可以有多个 UIAbility 组件。 每一个 UIAbility 组件实例都会在最近任务列表中显示为一个对应的任务…

【Python】Windows环境下更改pip安装源

文章目录 1.前言2.pip临时安装更改源3.pip永久更改安装源3.1方法13.2方法2 1.前言 由于pip的默认的安装源在国外,导致我们在使用pip命令安装Python 库或包时速度特别慢,因此我们可以临时使用国内的源进行下载,或者直接更改pip的下载源 2.pip临时安装更改源 pip install xxx …

微服务——服务注册和发现(二)

服务注册和发现 1.4.服务发现 服务的消费者要去nacos订阅服务&#xff0c;这个过程就是服务发现&#xff0c;步骤如下&#xff1a; 引入依赖 配置Nacos地址 发现并调用服务 1.4.1.引入依赖 服务发现除了要引入nacos依赖以外&#xff0c;由于还需要负载均衡&#xff0c;因…

思科IP访问控制列表3

#网络安全技术实现# #任务三扩展访问控制列表的控制3# #1配置计算机的IP 地址、子网掩码和网关 #2配置Switch-A的主机名称&#xff0c;创建vlan 10,20,30,并将Fa0/1划入vlan 10&#xff0c;Fa0/2划入vlan 20&#xff0c;G0/1划入vlan 30 Switch(config)#hostname Switch-A S…

QML学习二:Qt启用qml文件实时预览编辑,以及打印日志到控制台

开发环境&#xff1a;Qt 6.5.3 LTS 1、Qt 6.5.3 LTS 2、Pyside6 3、Python 3.11.4 效果如下&#xff0c;右侧更改的代码可以实时反映到左侧的设计器中。 Qt启用qml文件实时预览编辑&#xff0c;以及打印日志到控制台 一、打开Qt Designer插件二、qml和Python文件打印输出到…

2024年高教社杯数学建模国赛C题超详细解题思路分析

本次国赛预测题目难度&#xff0c;选题人数如下所示 难度评估 A:B:C 1.8:1.3:1 D:E1.5:1 选题人数 A:B:C 1:1.5:2.8 D:E0.5:1.2 C题一直以来都是竞赛难度最低、选题人数最多的一道本科生选题&#xff0c;近三年C题的选题人数一直都是总参赛队伍的一半左右&#xff0c;2023年…

二进制方式安装K8S

⼀、安装说明 本⽂章将演示Rocky 8 ⼆进制⽅式安装⾼可⽤k8s 1.28.0版本。 ⽣产环境中&#xff0c;建议使⽤⼩版本⼤于5的Kubernetes版本&#xff0c;⽐如1.19.5 以后的才可⽤于⽣产环境。 ⼆、集群安装 2.1 基本环境配置 请统⼀替换这些⽹段&#xff0c;Pod⽹段和service和…

如何在Jmeter安装“ Stepping Thread Group“?

1、点击"选项"&#xff0c;再点击"Plugins Manager" 2、安装插件Custom Thread Groups 3、添加"Stepping Thread Group" 4、"Stepping Thread Group"的介绍

HTML/CSS/JS学习笔记 Day1(HTML)

跟着该视频学习&#xff0c;记录笔记&#xff1a;【黑马程序员pink老师前端入门教程&#xff0c;零基础必看的h5(html5)css3移动端前端视频教程】https://www.bilibili.com/video/BV14J4114768?p12&vd_source04ee94ad3f2168d7d5252c857a2bf358 Day1 内容梳理&#xff1a; …

认知升级:互联网行业中的变革引擎与团队潜能激发

一、认知升级在互联网行业的独特价值 互联网行业以其快速迭代、信息爆炸、技术创新为特点&#xff0c;对从业者提出了更高的要求。认知升级&#xff0c;作为个人成长的重要路径&#xff0c;在互联网领域展现出无可替代的价值&#xff1a; 快速适应与学习&#xff1a;互联网行业…

最基本的SELECT...FROM结构

第0种&#xff1a;最基本的查询语句 SELECT 字段名&#xff0c;字段名 FROM 表名 SELECT 1&#xff1b; SELECT 11,3*2&#xff1b; FROM SELECT 11,3*2 FROM DUAL&#xff1b;#dual&#xff1a;伪表 我们可以用它来保持一个平衡 这里我们的值不需要在任何一个表里&#xf…

傅里叶变换家族

禹晶、肖创柏、廖庆敏《数字图像处理&#xff08;面向新工科的电工电子信息基础课程系列教材&#xff09;》 禹晶、肖创柏、廖庆敏《数字图像处理》资源二维码