HarmonyOS开发案例:【图片编辑】

介绍

本篇Codelab是基于ArkTS的声明式开发范式的样例,主要介绍了图片编辑实现过程。样例主要包含以下功能:

  1. 图片的解码。
  2. 使用PixelMap进行图片编辑,如裁剪、旋转、亮度、透明度、饱和度等。
  3. 图片的编码。

相关概念

  • [图片解码]:读取不同格式的图片文件,无压缩的解码为位图格式。
  • [PixelMap]:图片解码后的状态,用于对图片像素进行处理。
  • [图片编码]:图片经过像素处理完成之后,需要重新进行编码打包,生成需要的图片格式。

环境搭建

软件要求

  • [DevEco Studio]版本:DevEco Studio 3.1 Release。
  • OpenHarmony SDK版本:API version 9。

硬件要求

  • 开发板类型:[润和RK3568开发板]。
  • OpenHarmony系统:3.2 Release。

环境搭建

完成本篇Codelab我们首先要完成开发环境的搭建,本示例以RK3568开发板为例,参照以下步骤进行:

  1. [获取OpenHarmony系统版本]:标准系统解决方案(二进制)。以3.2 Release版本为例:

  2. 搭建烧录环境。

    1. [完成DevEco Device Tool的安装]
    2. [完成RK3568开发板的烧录]
  3. 搭建开发环境。

    1. 开始前请参考[工具准备],完成DevEco Studio的安装和开发环境配置。
    2. 开发环境配置完成后,请参考[使用工程向导]创建工程(模板选择“Empty Ability”)。
    3. 工程创建完成后,选择使用[真机进行调测]。
    4. HarmonyOS与OpenHarmony鸿蒙文档籽料:mau123789是v直接拿

搜狗高速浏览器截图20240326151450.png

代码结构解读

本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在gitee中提供。

├──entry/src/main/ets                            // 代码区
│  ├──common                         
│  │  └──constant
│  │     └──CommonConstant.ets                   // 常量类
│  ├──entryability
│  │  └──EntryAbility.ts                         // 本地启动ability           
│  ├──pages
│  │  └──HomePage.ets                            // 本地主页面    
│  ├──utils
│  │  ├──AdjustUtil.ets                          // 调节工具类
│  │  ├──CropUtil.ets                            // 裁剪工具类
│  │  ├──DecodeUtil.ets                          // 解码工具类
│  │  ├──DrawingUtils.ets                        // Canvas画图工具类
│  │  ├──EncodeUtil.ets                          // 编码工具类
│  │  ├──LoggerUtil.ets                          // 日志工具类
│  │  ├──MathUtils.ets                           // 坐标转换工具类
│  │  └──OpacityUtil.ets                         // 透明度调节工具类
│  ├──view
│  │  ├──AdjustContentView.ets                   // 色域调整视图     
│  │  └──ImageSelect.ets                         // Canvas选择框实现类   
│  ├──viewmodel
│  │  ├──CropShow.ets                            // 选择框显示控制类
│  │  ├──CropType.ets                            // 按比例选取图片
│  │  ├──IconListViewModel.ets                   // icon数据
│  │  ├──ImageEditCrop.ets                       // 图片编辑操作类
│  │  ├──ImageFilterCrop.ets                     // 图片操作收集类
│  │  ├──ImageSizeItem.ets                       // 图片尺寸
│  │  ├──Line.ets                                // 线封装类
│  │  ├──MessageItem.ets                         // 多线程封装消息
│  │  ├──OptionViewModel.ets                     // 图片处理封装类
│  │  ├──PixelMapWrapper.ets                     // PixelMap封装类
│  │  ├──Point.ets                               // 点封装类
│  │  ├──Ratio.ets                               // 比例封装类
│  │  ├──Rect.ets                                // 矩形封装类
│  │  ├──RegionItem.ets                          // 区域封装类
│  │  └──ScreenManager.ts                        // 屏幕尺寸计算工具类
│  └──workers
│     ├──AdjustBrightnessWork.ts                 // 亮度异步调节
│     └──AdjustSaturationWork.ts                 // 饱和度异步调节
└──entry/src/main/resources                      // 资源文件目录

鸿蒙开发指导文档:gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。

图片解码

在这个章节中,需要完成图片解码的操作,并将解码后的图片展示。效果如图所示:

在进行图片编辑前需要先加载图片,当前文档是在生命周期aboutToAppear开始加载。具体实现步骤。

  1. 读取资源文件。
  2. 将获取的fd创建成图片实例,通过实例获取其pixelMap。
  3. 将解析好的pixelMap通过Image组件加载显示。
// HomePage.ets
aboutToAppear() {
  this.pixelInit();
  ...
}

build() {
  Column() {
    ...
    Column() {
      if (this.isCrop && this.showCanvas && this.statusBar > 0) {
        if (this.isSaveFresh) {
          ImageSelect({
            statusBar: this.statusBar
          })
        }
        ...
      } else {
        if (this.isPixelMapChange) {
          Image(this.pixelMap)
            .scale({ x: this.imageScale, y: this.imageScale, z: 1 })
            .objectFit(ImageFit.None)
        }
        ...
      }
    }
    ...
  }
  ...
}

async getResourceFd(filename: string) {
  const resourceMgr = getContext(this).resourceManager;
  const context = getContext(this);
  if (filename === CommonConstants.RAW_FILE_NAME) {
    let imageBuffer = await resourceMgr.getMediaContent($r("app.media.ic_low"))
    let filePath = context.cacheDir + '/' + filename;
    let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
    let writeLen = fs.writeSync(file.fd, imageBuffer.buffer);
    fs.copyFileSync(filePath, context.cacheDir + '/' + CommonConstants.RAW_FILE_NAME_TEST);
    return file.fd;
  } else {
    let filePath = context.cacheDir + '/' + filename;
    let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
    return file.fd;
  }
}

async getPixelMap(fileName: string) {
  const fd = await this.getResourceFd(fileName);
  const imageSourceApi = image.createImageSource(fd);
  if (!imageSourceApi) {
    Logger.error(TAG, 'imageSourceAPI created failed!');
    return;
  }
  const pixelMap = await imageSourceApi.createPixelMap({
    editable: true
  });
  return pixelMap;
}

图片处理

当前章节需要完成图片的裁剪、旋转、色域调节(本章只介绍亮度、透明度、饱和度)等功能。

  • 裁剪:选取图片中的部分进行裁剪生成新的图片。

  • 旋转:将图片按照不同的角度进行旋转,生成新的图片。

  • 色域调节:当前Codelab色域调节的亮度、透明度和饱和度,使用色域模型RGB-HSV来实现的。

    • RGB:是我们接触最多的颜色空间,分别为红色®,绿色(G)和蓝色(B)。

    • HSV:是用色相H,饱和度S,明亮度V来描述颜色的变化。

      • H:色相H取值范围为0°~360°,从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°。
      • S:饱和度S越高,颜色则深而艳。光谱色的白光成分为0,饱和度达到最高。通常取值范围为0%~100%,值越大,颜色越饱和。
      • V:明度V表示颜色明亮的程度,对于光源色,明度值与发光体的光亮度有关;对于物体色,此值和物体的透射比或反射比有关。通常取值范围为0%(黑)到100%(白)。
// AdjustUtil.ets
// rgb转hsv
function rgb2hsv(red: number, green: number, blue: number) {
  let hsvH: number = 0, hsvS: number = 0, hsvV: number = 0;
  const rgbR: number = colorTransform(red);
  const rgbG: number = colorTransform(green);
  const rgbB: number = colorTransform(blue);
  const maxValue = Math.max(rgbR, Math.max(rgbG, rgbB));
  const minValue = Math.min(rgbR, Math.min(rgbG, rgbB));
  hsvV = maxValue * CommonConstants.CONVERT_INT;
  if (maxValue === 0) {
    hsvS = 0;
  } else {
    hsvS = Number((1 - minValue / maxValue).toFixed(CommonConstants.DECIMAL_TWO)) * CommonConstants.CONVERT_INT;
  }
  if (maxValue === minValue) {
    hsvH = 0;
  }
  if (maxValue === rgbR && rgbG >= rgbB) {
    hsvH = Math.floor(CommonConstants.ANGLE_60 * ((rgbG - rgbB) / (maxValue - minValue)));
  }
  if (maxValue === rgbR && rgbG < rgbB) {
    hsvH = Math.floor(CommonConstants.ANGLE_60 * ((rgbG - rgbB) / (maxValue - minValue)) + CommonConstants.ANGLE_360);
  }
  if (maxValue === rgbG) {
    hsvH = Math.floor(CommonConstants.ANGLE_60 * ((rgbB - rgbR) / (maxValue - minValue)) + CommonConstants.ANGLE_120);
  }
  if (maxValue === rgbB) {
    hsvH = Math.floor(CommonConstants.ANGLE_60 * ((rgbR - rgbG) / (maxValue - minValue)) + CommonConstants.ANGLE_240);
  }
  return [hsvH, hsvS, hsvV];
}
// hsv转rgb
function hsv2rgb(hue: number, saturation: number, value: number) {
  let rgbR: number = 0, rgbG: number = 0, rgbB: number = 0;
  if (saturation === 0) {
    rgbR = rgbG = rgbB = Math.round((value * CommonConstants.COLOR_LEVEL_MAX) / CommonConstants.CONVERT_INT);
    return { rgbR, rgbG, rgbB };
  }
  const cxmC = (value * saturation) / (CommonConstants.CONVERT_INT * CommonConstants.CONVERT_INT);
  const cxmX = cxmC * (1 - Math.abs((hue / CommonConstants.ANGLE_60) % CommonConstants.MOD_2 - 1));
  const cxmM = (value - cxmC * CommonConstants.CONVERT_INT) / CommonConstants.CONVERT_INT;
  const hsvHRange = Math.floor(hue / CommonConstants.ANGLE_60);
  switch (hsvHRange) {
    case AngelRange.ANGEL_0_60:
      rgbR = (cxmC + cxmM) * CommonConstants.COLOR_LEVEL_MAX;
      rgbG = (cxmX + cxmM) * CommonConstants.COLOR_LEVEL_MAX;
      rgbB = (0 + cxmM) * CommonConstants.COLOR_LEVEL_MAX;
      break;
    case AngelRange.ANGEL_60_120:
      rgbR = (cxmX + cxmM) * CommonConstants.COLOR_LEVEL_MAX;
      rgbG = (cxmC + cxmM) * CommonConstants.COLOR_LEVEL_MAX;
      rgbB = (0 + cxmM) * CommonConstants.COLOR_LEVEL_MAX;
      break;
    case AngelRange.ANGEL_120_180:
      rgbR = (0 + cxmM) * CommonConstants.COLOR_LEVEL_MAX;
      rgbG = (cxmC + cxmM) * CommonConstants.COLOR_LEVEL_MAX;
      rgbB = (cxmX + cxmM) * CommonConstants.COLOR_LEVEL_MAX;
      break;
    case AngelRange.ANGEL_180_240:
      rgbR = (0 + cxmM) * CommonConstants.COLOR_LEVEL_MAX;
      rgbG = (cxmX + cxmM) * CommonConstants.COLOR_LEVEL_MAX;
      rgbB = (cxmC + cxmM) * CommonConstants.COLOR_LEVEL_MAX;
      break;
    case AngelRange.ANGEL_240_300:
      rgbR = (cxmX + cxmM) * CommonConstants.COLOR_LEVEL_MAX;
      rgbG = (0 + cxmM) * CommonConstants.COLOR_LEVEL_MAX;
      rgbB = (cxmC + cxmM) * CommonConstants.COLOR_LEVEL_MAX;
      break;
    case AngelRange.ANGEL_300_360:
      rgbR = (cxmC + cxmM) * CommonConstants.COLOR_LEVEL_MAX;
      rgbG = (0 + cxmM) * CommonConstants.COLOR_LEVEL_MAX;
      rgbB = (cxmX + cxmM) * CommonConstants.COLOR_LEVEL_MAX;
      break;
    default:
      break;
  }
  return [
    Math.round(rgbR),
    Math.round(rgbG),
    Math.round(rgbB)
  ];
}

图片裁剪

  1. 通过pixelMap获取图片尺寸,为后续裁剪做准备。
  2. 确定裁剪的方式,当前裁剪默认有自由选取、1:1选取、4:3选取、16:9选取。
  3. 通过pixelMap调用接口crop()进行裁剪操作。

说明: 当前裁剪功能采用pixelMap裁剪能力直接做切割,会有叠加效果,后续会通过增加选取框对当前功能进行优化。

// HomePage.ets
cropImage(index: CropType) {
  this.currentCropIndex = index;
  switch (this.currentCropIndex) {
    case CropType.ORIGINAL_IMAGE:
      this.cropRatio = CropRatioType.RATIO_TYPE_FREE;
      break;
    case CropType.SQUARE:
      this.cropRatio = CropRatioType.RATIO_TYPE_1_1;
      break;
    case CropType.BANNER:
      this.cropRatio = CropRatioType.RATIO_TYPE_4_3;
      break;
    case CropType.RECTANGLE:
      this.cropRatio = CropRatioType.RATIO_TYPE_16_9;
      break;
    default:
      this.cropRatio = CropRatioType.RATIO_TYPE_FREE;
      break;
  }
}

// ImageFilterCrop.ets
cropImage(pixelMap: PixelMapWrapper, realCropRect: RectF, callback: () => void) {
  let offWidth = realCropRect.getWidth();
  let offHeight = realCropRect.getHeight();
  if (pixelMap.pixelMap!== undefined) {
    pixelMap.pixelMap.crop({
      size:{ height: vp2px(offHeight), width: vp2px(offWidth) },
      x: vp2px(realCropRect.left),
      y: vp2px(realCropRect.top)
    }, callback);
  }
}

图片旋转

  1. 确定旋转方向,当前支持顺时针和逆时针旋转。
  2. 通过pixelMap调用接口rotate()进行旋转操作。

// HomePage.ets
rotateImage(rotateType: RotateType) {
  if (rotateType === RotateType.CLOCKWISE) {
    try {
      if (this.pixelMap !== undefined) {
        this.pixelMap.rotate(CommonConstants.CLOCK_WISE)
          .then(() => {
            this.flushPixelMapNew();
          })
      }
    } catch (error) {
      Logger.error(TAG, `there is a error in rotate process with ${error?.code}`);
    }
  }
  if (rotateType === RotateType.ANTI_CLOCK) {
    try {
      if (this.pixelMap !== undefined) {
        this.pixelMap.rotate(CommonConstants.ANTI_CLOCK)
          .then(() => {
            this.flushPixelMapNew();
          })
      }
    } catch (error) {
      Logger.error(TAG, `there is a error in rotate process with ${error?.code}`);
    }
  }
}

亮度调节

  1. 将pixelMap转换成ArrayBuffer。
  2. 将生成好的ArrayBuffer发送到worker线程。
  3. 对每一个像素点的亮度值按倍率计算。
  4. 将计算好的ArrayBuffer发送回主线程。
  5. 将ArrayBuffer写入pixelMap,刷新UI。

说明: 当前亮度调节是在UI层面实现的,未实现细节优化算法,只做简单示例。调节后的图片会有色彩上的失真。

// AdjustContentView.ets
// 转化成pixelMap及发送buffer到worker,返回数据刷新ui
postToWorker(type: AdjustId, value: number, workerName: string) {
  let sliderValue = type === AdjustId.BRIGHTNESS ? this.brightnessLastSlider : this.saturationLastSlider;
  try {
    let workerInstance = new worker.ThreadWorker(workerName);
    const bufferArray = new ArrayBuffer(this.pixelMap.getPixelBytesNumber());
    this.pixelMap.readPixelsToBuffer(bufferArray).then(() => {
      let message = new MessageItem(bufferArray, sliderValue, value);
      workerInstance.postMessage(message);
      if (this.postState) {
        this.deviceListDialogController.open();
      }
      this.postState = false;
      workerInstance.onmessage = (event: MessageEvents) => {
        this.updatePixelMap(event)
      };
      if (type === AdjustId.BRIGHTNESS) {
        this.brightnessLastSlider = Math.round(value);
      } else {
        this.saturationLastSlider = Math.round(value);
      }
      workerInstance.onexit = () => {
        if (workerInstance !== undefined) {
          workerInstance.terminate();
        }
      }
    });
  } catch (error) {
    Logger.error(`Create work instance fail, error message: ${JSON.stringify(error)}`)
  }
}

// AdjustBrightnessWork.ts
// worker线程处理部分
workerPort.onmessage = function(event : MessageEvents) {
  let bufferArray = event.data.buf;
  let last = event.data.last;
  let cur = event.data.cur;
  let buffer = adjustImageValue(bufferArray, last, cur);
  workerPort.postMessage(buffer);
  workerPort.close();
}

// AdjustUtil.ets
// 倍率计算部分
export function adjustImageValue(bufferArray: ArrayBuffer, last: number, cur: number) {
  return execColorInfo(bufferArray, last, cur, HSVIndex.VALUE);
}

透明度调节

  1. 获取pixelMap。
  2. 调用接口opacity()进行透明度调节。

// OpacityUtil.ets
export async function adjustOpacity(pixelMap: PixelMap, value: number) {
  if (!pixelMap) {
    return;
  }
  const newPixelMap = pixelMap;
  await newPixelMap.opacity(value / CommonConstants.SLIDER_MAX);
  return newPixelMap;
}

饱和度调节

  1. 将pixelMap转换成ArrayBuffer。
  2. 将生成好的ArrayBuffer发送到worker线程。
  3. 对每一个像素点的饱和度按倍率计算。
  4. 将计算好的ArrayBuffer发送回主线程。
  5. 将ArrayBuffer写入pixelMap,刷新UI。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 说明: 当前饱和度调节是在UI层面实现的,未实现细节优化算法,只做简单示例。调节后的图片会有色彩上的失真。

// AdjustContentView.ets
// 转化成pixelMap及发送buffer到worker,返回数据刷新ui
postToWorker(type: AdjustId, value: number, workerName: string) {
  let sliderValue = type === AdjustId.BRIGHTNESS ? this.brightnessLastSlider : this.saturationLastSlider;
  try {
    let workerInstance = new worker.ThreadWorker(workerName);
    const bufferArray = new ArrayBuffer(this.pixelMap.getPixelBytesNumber());
    this.pixelMap.readPixelsToBuffer(bufferArray).then(() => {
      let message = new MessageItem(bufferArray, sliderValue, value);
      workerInstance.postMessage(message);
      if (this.postState) {
        this.deviceListDialogController.open();
      }
      this.postState = false;
      workerInstance.onmessage = (event: MessageEvents) => {
        this.updatePixelMap(event)
      };
      if (type === AdjustId.BRIGHTNESS) {
        this.brightnessLastSlider = Math.round(value);
      } else {
        this.saturationLastSlider = Math.round(value);
      }
      workerInstance.onexit = () => {
        if (workerInstance !== undefined) {
          workerInstance.terminate();
        }
      }
    });
  } catch (error) {
    Logger.error(`Create work instance fail, error message: ${JSON.stringify(error)}`);
  }
}

// AdjustSaturationWork.ts
// worker线程处理部分
workerPort.onmessage = function(event : MessageEvents) {
  let bufferArray = event.data.buf;
  let last = event.data.last;
  let cur = event.data.cur;
  let buffer = adjustSaturation(bufferArray, last, cur)
  workerPort.postMessage(buffer);
  workerPort.close();
}

// AdjustUtil.ets
// 倍率计算部分
export function adjustSaturation(bufferArray: ArrayBuffer, last: number, cur: number) {
  return execColorInfo(bufferArray, last, cur, HSVIndex.SATURATION);
}

图片编码

图片位图经过处理之后,还是属于解码的状态,还需要进行打包编码成对应的格式,本章讲解编码的具体过程。

  1. 通过image组件创建打包工具packer。
  2. 使用PackingOption进行打包参数设定,比如格式、压缩质量等。
  3. 打包成图片信息数据imageData。
  4. 创建媒体库media,获取公共路径。
  5. 创建媒体文件asset,获取其fd。
  6. 使用fs将打包好的图片数据写入到媒体文件asset中。
// ImageSelect.ets
async encode(pixelMap: PixelMap | undefined) {
  if (pixelMap === undefined) {
    return;
  }

  const newPixelMap = pixelMap;
  // 打包图片
  const imagePackerApi = image.createImagePacker();
  const packOptions: image.PackingOption = {
    format: CommonConstants.ENCODE_FORMAT,
    quality: CommonConstants.ENCODE_QUALITY
  }
  const imageData = await imagePackerApi.packing(newPixelMap, packOptions);
  Logger.info(TAG, `imageData's length is ${imageData.byteLength}`);
  // 获取相册路径
  const context = getContext(this);
  const media = mediaLibrary.getMediaLibrary(context);
  const publicPath = await media.getPublicDirectory(mediaLibrary.DirectoryType.DIR_IMAGE);
  const currentTime = new Date().getTime();
  // 创建图片资源
  const imageAssetInfo = await media.createAsset(
    mediaLibrary.MediaType.IMAGE,
    `${CommonConstants.IMAGE_PREFIX}_${currentTime}${CommonConstants.IMAGE_FORMAT}`,
    publicPath
  );
  const imageFd = await imageAssetInfo.open(CommonConstants.ENCODE_FILE_PERMISSION);
  await fs.write(imageFd, imageData);
  // 释放资源
  await imageAssetInfo.close(imageFd);
  imagePackerApi.release();
  await media.release();
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

NVIDIA Jetson jtop查看资源信息

sudo -H pip install -U jetson-stats 安装好之后可能需要reboot 执行jtop&#xff1a; 时间久了可能会退出&#xff0c;可参考如下再次启动。 nvidiategra-ubuntu:~$ jtop The jtop.service is not active. Please run: sudo systemctl restart jtop.service nvidiategra-ub…

3个比较不错的Linux云音乐应用程序整理

在现代音乐流媒体时代&#xff0c;基于云的音乐应用程序因其便利性和可访问性而变得非常流行。Linux 用户尤其寻求可靠且功能丰富的音乐播放器来无缝地享受他们喜爱的音乐。 在这里&#xff0c;我们探讨了三个最好的基于云的音乐应用程序&#xff0c;每个应用程序都提供专为 L…

Java技术学习|消息队列|初级RabbitMQ

学习材料声明 黑马RabbitMQ快速入门教程&#xff0c;快速掌握rabbitmq、springAMQP消息中间件 是非常初级的学习&#xff0c;听说后续的高级课程会涉及到微服务之类的&#xff0c;所以等学完微服务再回来学。还有redis的高级部分也涉及了微服务&#xff0c;所以也都暂时停止学…

OpenAI内部最受欢迎的10个GPTs推荐!建议收藏!

前面文章介绍了如何创建自己的GPTs&#xff0c;但是在应用商店里还有很多各种GPTs可以供我们使用&#xff0c;满足我们的各种应用场景。这就像手机的应用商店一样&#xff0c;我们除了可以自己编写应用程序之外&#xff0c;还可以使用使用应用商店里海量应用。有关创建自己的自…

YOLOv8 训练自己的数据集(20240423)

环境搭建请参考&#xff1a;Win10 搭建 YOLOv8 运行环境&#xff08;20240423&#xff09;-CSDN博客 环境测试请参考&#xff1a;本地运行测试 YOLOv8&#xff08;20240423&#xff09;-CSDN博客 一、使用 YOLOv8 的 coco128 数据集熟悉一下如何训练和预测 1.1、在项目根目录…

工厂提高OEE面临哪些挑战?

在工业生产中&#xff0c;提高设备的综合效率&#xff08;OEE&#xff09;是企业追求的目标之一。然而&#xff0c;实现高水平的OEE并非易事&#xff0c;工厂在这一过程中可能面临诸多挑战。本文将探讨工厂提高OEE所面临的挑战&#xff0c;并介绍PreMaint提供的解决方案。 提高…

【软件基础】反编译工具dnSpy反编译程序步骤

文章目录 一、dnSpy介绍二、使用版本三、使用步骤 一、dnSpy介绍 dnSpy是一款开源的.NET程序集反编译工具&#xff0c;它允许用户查看和编辑.NET程序集的源代码。dnSpy支持反编译.NET程序集、查看IL代码、编辑IL代码、调试.NET程序集等功能。用户可以使用dnSpy来分析和理解.NE…

【C语言__指针01__复习篇11】

目录 前言 一、什么是指针 二、计算机中常见的单位 三、CPU是怎样找到一块内存空间的 四、如何得到变量的地址 五、指针变量 六、解引用指针变量的作用 七、指针变量的大小 八、指针变量类型的意义 8.1 指针的解引用 8.2 指针-整数 九、void*指针 十、const修饰变…

国外问卷调查如何提高做题成功率?方法来了!

“为什么我做的题总是提交失败&#xff1f;” “做题太慢&#xff0c;收益太少&#xff0c;有什么做题技巧吗&#xff1f;” 以上这些问题&#xff0c;想必是新老玩家在问卷调查这条路上必定会遇到的&#xff0c;特别是新手遇到这类问题不知如何去处理&#xff0c;所以今天IPd…

el-popover放在el-table中点击无反应问题

我们想在table中给btn加弹框但是 el-popover点击按钮没有任何反应思考:通过插槽去添加这个组件el-popover的id是否绑定了一个值解决思路&#xff1a;给每个el-popover都加上单独的id 效果 &#xff1a; 代码 给每个组件都绑定ref <template slot-scope"scope"&g…

七星创客新零售系统:颠覆性商业模式的崛起

大家好&#xff0c;我是微三云周丽&#xff0c;今天给大家分析当下市场比较火爆的商业模式&#xff01; 小编今天跟大伙们分享什么是七星创客新零售系统&#xff1f; 随着经济的快速发展和科技的不断进步&#xff0c;商业模式的革新成为了企业发展的关键。在这个新旧动能转换、…

使用表格法插入公式和编号

如何将公式和编号优雅地插入到论文当中呢&#xff1f; 首先插入一个1行2列的表格 调整一下 输入公式方法一&#xff1a;感觉墨迹公式挺好用的&#xff0c;word自带的 输入公式方法二&#xff1a;图片转LATEX代码 这个方法更快 分享一个公式识别网站 图片识别得到LATEX代码&…

2024 初级信息处理技术员历史真题整理分享

2024 初级信息处理技术员历史真题整理分享 最近软考报名结束了&#xff0c;马上五月份就要考试&#xff0c;想必很多人都在迎战软考吧。 在此我分享一下我整理的一些软考&#xff08;初级信息处理技术员&#xff09;历史真题&#xff0c;供大家学习 历年真题 说明&#xff1a…

Windows本地部署Ollama+qwen本地大语言模型Web交互界面并实现公网访问

文章目录 前言1. 运行Ollama2. 安装Open WebUI2.1 在Windows系统安装Docker2.2 使用Docker部署Open WebUI 3. 安装内网穿透工具4. 创建固定公网地址 前言 本文主要介绍如何在Windows系统快速部署Ollama开源大语言模型运行工具&#xff0c;并安装Open WebUI结合cpolar内网穿透软…

汽车纵染压制专用液压机比例阀放大器

汽车纵染压制专用液压机比例阀放大器是一种专门用于汽车纵梁拉伸工艺的设备&#xff0c;它也可以用于其他金属薄板的压制成型及校正工艺。该类型的液压机通常具备独立的动力机构和电气系统&#xff0c;采用PLC技术进行控制&#xff0c;以确保操作的准确性和稳定性。除了纵梁拉伸…

什么是云专线

云专线是一种企业连接公共云服务提供商&#xff08;如亚马逊AWS、微软Azure、谷歌云等&#xff09;的专用网络连接服务。它是一种私有网络连接&#xff0c;主要目的是提供更可靠、更安全、更高性能的连接&#xff0c;以满足企业对云服务的需求&#xff0c;特别是需要大量数据传…

981: 统计利用二叉树存储的森林中树的棵数

解法&#xff1a; 在数据结构中&#xff0c;森林&#xff08;Forest&#xff09;是一组互不相交的树的集合&#xff0c;而二叉树&#xff08;Binary Tree&#xff09;是每个节点最多只有两个子节点的树。下面介绍如何在森林和二叉树之间进行转换。 森林转换为二叉树&#xff1…

页面分页打印,echarts图解决办法;生成PDF

1&#xff1a;echarts图片前端打印不是很完美&#xff0c;对于VUE2.0版本不是很有好 2&#xff1a;360浏览器不支持vue的最新版本的插件vue3-print-nb 3&#xff1a;vue-print-nb 可以打印带有echarts 一页内容&#xff0c;并且还存在bug&#xff0c;第一次点击打印没有&…

用于车载T-BOX汽车级的RA8900CE

用于车载T-BOX等高精度计时的汽车级时钟模块RTC:RA8900CE.车载实时时钟芯片RA8900CE内置32.768Khz的晶体&#xff0c;实现年、月、日、星期、小时、分钟和秒精准计时。RA8900CE满足AEC-Q200认证&#xff0c;内置温补功能&#xff0c;保证实时时钟的稳定可靠&#xff0c;功耗低至…

X86与FPGA相结合,基于PIB的AI开发——人体姿态识别

人体姿态估计是计算机视觉领域中用于理解和分析人类行为的一个关键技术。它主要涉及到检测和识别图像或视频中人体的各个关键点&#xff0c;并预测这些关键点之间的空间关系&#xff0c;从而构建出人体的骨架模型。 本文将介绍基于PIB板的人体姿态估计案例。这是一个交互式的实…
最新文章