微信小程序 - 龙骨图集拆分
微信小程序 - 龙骨图集拆分
- 注意
- 目录结构
- 演示动画
- 废话一下
- 业务逻辑
- 注意点
- 龙骨JSON图集结构
- 源码分享
- dragonbones-split.js
- dragonbones-split.json
- dragonbones-split.wxml
- dragonbones-split.wxss
- imgUtil.js
- 参考资料
注意
只支持了JSON版本
目录结构

演示动画
Spine播放器1.5.0_PC端
Spine播放器1.5.1_移动端
废话一下
这是 SpinePlayer 2D骨骼动画播放器 - 微信小程序版 工具箱中的一个功能。
功能很简单,80% 的代码都在处理交互逻辑,以及移动端PC端兼容方面的问题。
业务逻辑
- 读取JSON文件和PNG图片。
- 解析JSON得到所有小图在图集(PNG图片)中的 x,y 坐标和高宽。
- 将PNG图集绘制到2d画布,然后使用 canvasToTempFilePath 逐个截取区域,保存为小图。
- 最后将所有小图打包为 zip 供用户保存即可。
注意点
- 为了保证截取图片的清晰度,
画布尺寸需要用图片大小乘以设备像素比。 - 图片填充完整,再截图。否则会空白。所以会成两步操作比较稳妥。当然也可以自己控制延时自动调用,一气呵成。
- 因为 2d 画布不便于直接显示,所以使用一个 image 组件来实现预览。
3.1. 方法是将PNG读取为base64赋给image组件<image src="{{textureBase64}}" />
3.2. 读取PNG为base64就这句fs.readFileSync(‘临时图片路径’, 'base64')当然用的时候还要拼接一下头,详情看源码吧。
龙骨JSON图集结构
可以看出结构非常简单,直接读 SubTexture 数组,遍历它进行截取就可以了。
{"imagePath": "body_tex.png","width": 1024,"SubTexture": [{"height": 472,"y": 1,"width": 295,"name": "body/a_arm_L","x": 720}略。。。],"name": "body","height": 1024
}
源码分享
dragonbones-split.js
// packageTools/pages/dragonbones-split/dragonbones-split.js
const fileUtil = require('../../../utils/fileUtil.js');
const imgUtil = require('./imgUtil.js');
const pixelRatio = wx.getSystemInfoSync().pixelRatio; // 设备像素比
let canvas, ctx;
let globalData = getApp().globalData;
let dbsplit = globalData.PATH.dbsplit;
Page({/*** 页面的初始数据*/data: {canvasWidth: 300, // 画布宽canvasHeight: 150, // 画布高texture: {}, // 龙骨图集PNG图片信息 { path, width, height, orientation, type }textureBase64: '', // 龙骨图集PNG图片的 Base64 编码subTextureList:[], // 龙骨图集JSON数据。包含拆分出的小图地址 tempFilePathshareZipFile: { name: '', path: ''}, // 最终生成的ZIPjsonValue: '', // 文本框内容(PC 端用于获取 JSON)// parseInt('0011', 2) === 3status: 0, // 工作状态:0000 初始,0001 有图,0010 有JSON,0100 已拆图,1000 已ZIPisPC: false,},/*** 生命周期函数--监听页面加载*/onLoad(options) {// 获取画布wx.createSelectorQuery().select('#myCanvas') // 在 WXML 中填入的 id.fields({ node: true, size: true }).exec((res) => {canvas = res[0].node; // Canvas 对象ctx = canvas.getContext('2d'); // 渲染上下文});// 创建工作目录fileUtil.mkdir(dbsplit); this.setData({ isPC: globalData.systemInfo.isPC });// 清理场地fileUtil.clearDirSync(globalData.PATH.dbsplit);},/*** 粘贴龙骨图集 JSON 的文本框发生变化*/onChange(event) {try {if(event.detail.value === undefined){return;}let json = imgUtil.parseJSON(event.detail.value);this.data.shareZipFile = { name: `${json.name}.zip`, path: `${dbsplit}/${json.name}.zip`}; // zip路径打包时用this.setData({ jsonValue: event.detail.value,status: 2,subTextureList: json.SubTexture,});} catch (err) {console.log(err);this.setData({ jsonValue: '',status: 1, // this.data.status & 13, // parseInt('1101', 2)subTextureList: [],});wx.showToast({ title: 'JSON格式有误', icon: 'error' })}},/*** 选择龙骨图集PNG、JSON*/async choosePNGJSON(e){console.log('选择龙骨图集PNG、JSON');wx.showLoading({ title: '选择图集' });imgUtil.chooseAtlas({ count: 2}).then(res => {if(res.length != 2){wx.showToast({ title: '文件数量异常!', icon: 'error' });return;}let texture, json;if(res[0].type === 'png'){[texture, json] = res;}else{[json, texture] = res;}wx.showLoading({ title: '解析图集' });this.data.texture = texture; // 更新图集PNG的相关信息。点击预览时会用到它的 paththis.data.shareZipFile = { name: `${json.name}.zip`, path: `${dbsplit}/${json.name}.zip`}; // zip路径打包时用// 图集PNG填充画布this.fillCanvasWithImage(texture).then(()=>{// 填充完成后,在下一个时间片段更新数据this.setData({textureBase64: imgUtil.imageToBase64(texture.path, texture.type), // 更新 image 组件 srcsubTextureList: json.SubTexture, // 更新页面上的 subTexture 列表status: 2, // 已选图,JSON完成解析},()=>{wx.hideLoading();});})}).catch(err => {console.log(err);wx.showToast({ title: '图集选择失败', icon: 'error' });this.setData({textureBase64: '', // 更新 image 组件 srcsubTextureList: [], // 更新页面上的 subTexture 列表status: 0,});}).finally(()=>{wx.hideLoading() })},/*** 选择龙骨图集PNG文件*/async choosePNG(e){console.log('选择龙骨图集PNG文件');let whereFrom = globalData.systemInfo.isPC ? 'media' : 'message';let promises = imgUtil.chooseImg({ count: 1, whereFrom }); // media messageawait promises.then(res => {let texture = res[0];console.log(texture);this.setData({ texture , textureBase64: imgUtil.imageToBase64(texture.path, texture.type),status: 1,// 重选图片后,清空已选的JSONjsonValue: '', // 清除 JSON 数据subTextureList: [] // 清除 解析后的 JSON 数据}, res => {this.fillCanvasWithImage(texture); // 填充画布});}).catch(err => {console.log(err);wx.showToast({ title: '选择图片失败', icon: 'error' });this.setData({ texture: {} , textureBase64: '',status: 0, // this.data.status & 14,});});},/*** 将图片绘制到画布*/fillCanvasWithImage(imageInfo){let { path, width, height, orientation, type } = imageInfo;// 按图片大小更新画布尺寸canvas.width = width * pixelRatio;canvas.height = height * pixelRatio;ctx.scale(pixelRatio, pixelRatio);return new Promise((resolve, reject)=>{// 更新画布 渲染宽高。完成后,绘制图片到画布this.setData({ canvasWidth: width, canvasHeight: height}, res=> {const image = canvas.createImage(); // 创建图片对象image.onload = () => ctx.drawImage(image, 0, 0); // 图片加载完成,在回调中将其绘制到画布image.src = path; // 设置图片 srcresolve();});});},/*** 解析JSON并拆分图集*/async parseJsonAndSplitIMG(e){console.log('解析JSON并拆分图集');if(this.data.status < 1){wx.showToast({ title: '请选择图片', icon: 'error'});return;}if(this.data.status < 2){wx.showToast({ title: '请提供JSON', icon: 'error'});return;}this.splitIMG();},/*** 拆分龙骨图集PNG文件*/async splitIMG(e){console.log('拆分龙骨图集PNG文件');let pArr = this.data.subTextureList.map(subTexture => {return new Promise((resolve, reject)=> {let { x, y, width, height, } = subTexture;wx.canvasToTempFilePath({x, y, width, height, canvas,destWidth: width,destHeight: height,fileType: 'png',success: res => {console.log(res.tempFilePath);subTexture.tempFilePath = res.tempFilePath;resolve(subTexture);},fail: reject});});});Promise.all(pArr).then(async res => {await this.creatZip(res);this.setData({ status: 3, }); // 更新状态,完成拆图}).catch(err => {this.setData({ status: 2, });});},/*** 将拆好的小图打包为 ZIP*/async creatZip(subTextureList){console.log('将拆好的小图打包为 ZIP');try {// 图片列表let fileList = subTextureList.map(subTexture => ({ name: subTexture.name, path: subTexture.tempFilePath}));// 创建压缩包await fileUtil.zip(fileList, this.data.shareZipFile.path, progress => { wx.showLoading({ title: progress.msg, });if(progress.percent == 100){setTimeout(wx.hideLoading, 200);}});// 更新状态console.log(this.data.shareZipFile.path);this.setData({subTextureList});} catch (err) {console.error(err)wx.showToast({ icon: 'error', title: '打包失败' });this.setData({shareZipFile: {}});} finally {wx.hideLoading();}},/*** 将拆分后的文件打包导出*/saveIMG(e){console.log('将拆分后的文件打包导出');console.log(this.data.subTextureList);if(this.data.status < 3){wx.showToast({ title: '尚未拆图', icon: 'error' });return;}// 如果是电脑端,否则是手机端if(globalData.systemInfo.platform == 'windows' || globalData.systemInfo.platform == 'mac'// || globalData.systemInfo.platform == 'devtools'){wx.saveFileToDisk({filePath: this.data.shareZipFile.path,success: console.log,fail: console.error});} else {wx.shareFileMessage({filePath: this.data.shareZipFile.path,fileName: this.data.shareZipFile.name,success: console.log,fail: console.error,complete: console.log});}},async previewTexture(e){if(!!this.data.texture.path == false && globalData.systemInfo.isPC){await this.choosePNG();return;}wx.previewImage({urls: [this.data.texture.path],success: (res) => {},fail: (res) => {},complete: (res) => {},})},previewSubTexture(e){if(this.data.status < 3){wx.showToast({ title: '尚未拆分', icon:"error" });return;}wx.previewImage({urls: this.data.subTextureList.map(obj => obj.tempFilePath),current: e.currentTarget.dataset.texturePath,showmenu: true,success: (res) => {},fail: (res) => {},complete: (res) => {},})}
})
dragonbones-split.json
{"usingComponents": {}
}
dragonbones-split.wxml
<!--packageTools/pages/dragonbones-split/dragonbones-split.wxml-->
<!-- 大家好我是笨笨,笨笨的笨,笨笨的笨,谢谢!https://blog.csdn.net/jx520/ -->
<view class="main-container bg-60" ><view class="top-container poem-container poem-h bg-24" style="min-height: 200rpx;"><view>萍</view><view>无根翡翠顺江涛, 有尾鱼虾逆水潮。</view><view>行宿天涯本无路, 去留飘渺也逍遥。</view></view><view class="top-container scroll-y home-top" disabled><image class="texture chessboard" src="{{textureBase64}}" bind:tap="previewTexture"/><view wx:for="{{subTextureList}}" wx:key="name"bindtap="previewSubTexture" data-texture-name="{{item.name}}" data-texture-path="{{item.tempFilePath}}"class="sub-texture-list {{status < 3 ? 'disabled' : 'splited'}}"><view class="sub-texture-row"> <view>{{item.name}}</view><view>{{item.width}} x {{item.height}}</view></view></view><view class="sub-texture-list" wx:if="{{isPC}}"><textarea class="json-area" auto-height maxlength="-1" placeholder="请在此处粘贴龙骨图集的 JSON 内容" value="{{jsonValue}}"bindblur="onChange" bindlinechange="onChange" bindconfirm="onChange" bindinput="onChange" /></view><canvas id="myCanvas" type="2d" class="canvas2d" style="width: {{canvasWidth}}px; height: {{canvasHeight}}px;" /></view><view class="bottom-container"><!-- 移动端 --><view wx:if="{{isPC == false}}"><view class="but-row"><view class="button button-large" bindtap="choosePNGJSON"><view>选择龙骨PNG、JSON</view></view><view class="button button-large {{status < 2 ? 'disabled' : ''}}" bindtap="parseJsonAndSplitIMG"><view>拆分图集</view></view></view><view class="but-row"><view class="button button-large {{status < 3 ? 'disabled' : ''}}" bindtap="saveIMG"><view>转发到聊天</view></view></view></view><!-- PC端 --><view wx:if="{{isPC}}"><view class="but-row"><view class="button button-large" bindtap="choosePNG"><view wx:if="{{textureBase64 === ''}}">选择图片</view><view wx:if="{{textureBase64 != ''}}">重选图片</view></view><view class="button button-large {{status < 2 ? 'disabled' : ''}}" bindtap="parseJsonAndSplitIMG"><view>拆分图集</view></view></view><view class="but-row"><view class="button button-large {{status < 3 ? 'disabled' : ''}}" bindtap="saveIMG"><view>保存</view></view></view></view><view class="gb-img"></view></view>
</view>
dragonbones-split.wxss
/* packageTools/pages/dragonbones-split/dragonbones-split.wxss */
@import '/common/wxss/player-page-common.wxss';.button{display: flex;flex-direction: column;justify-content: center;align-items: center;margin: 10rpx;padding: 10rpx;color: rgb(236, 236, 236);box-sizing: border-box;
}
.button-large{display: flex;justify-content: center;text-align: center;font-size: large;width: auto;height: 100%;max-height: 50%;box-sizing: border-box;flex-grow: 1;border-radius: 10px;background-color: rgb(102, 102, 102);border-top: 1px solid rgb(112, 112, 112);border-bottom: 2px solid rgb(90, 90, 90);
}.but-row {display: flex;
}
.sub-texture-row {display: flex;align-items: center;padding: 0 20rpx;border-bottom: rgb(102, 102, 102) solid 1px;box-sizing: border-box;
}
.sub-texture-row>view {margin: 4px;
}
.sub-texture-row :nth-child(1) {width: 70%;word-wrap: break-word;border-right: #666 solid 1px;
}
.sub-texture-row :nth-child(2) {margin-left: 5px;width: 30%;height: 100%;
}.texture {padding: 6px;width: 100%;border: rgb(63, 63, 63) solid 2px;box-sizing: border-box;
}
.canvas2d {position: absolute;right: 100vw;
}
.sub-texture-list {display: flex;flex-direction: column;padding: 5rpx 20rpx;
}
.json-area {background-color: #000;border-radius: 10rpx;width: 100%;padding: 20rpx;box-sizing: border-box;font-size: x-small;min-height: 430rpx;
}.splited {color: chartreuse;
}
imgUtil.js
const fs = wx.getFileSystemManager();/*** 选择图集(PNG和JSON一对)* @param {object} options */
function chooseAtlas(_options = {}){const defaultOptions = { count: 2 };let options = { ...defaultOptions, ..._options };// 选择 PNG、JSONlet promise = wx.chooseMessageFile(options).then(res => res.tempFiles) .then(tempFiles => {return tempFiles.map(tempFilePath => {if(tempFilePath.type === 'image'){ // 图片return wx.getImageInfo({ src: tempFilePath.path }).then(res => {let { path, width, height, orientation, type } = res;let imageInfo = { path, width, height, orientation, type };return imageInfo;});}else if(tempFilePath.type === 'file' && tempFilePath.path.toLowerCase().endsWith('.json')){ // JSONreturn parseJSON(fs.readFileSync(tempFilePath.path, 'utf-8'));}else{return null;}}).filter(obj => obj != null);}).catch(err=> {console.log(err);return [];});// 全部完成再返回return promise.then(promiseArr => {return Promise.all(promiseArr).then(res => res);})
}/*** 选择图片* @param {object} options */
function chooseImg(_options = {}){const defaultOptions = {count: 9, mediaType: ['image'],whereFrom: 'message' , // 从何处选取。合法值:message media};let options = { ...defaultOptions, ..._options };let promise;// 根据参数中给的选择方式,调用对应方法。switch (options.whereFrom) {case 'media':promise = wx.chooseMedia(options).then(res => res.tempFiles.map( data => data.tempFilePath) ).catch(err=> {console.log(err);return [];});break;default:promise = wx.chooseMessageFile(options).then(res => res.tempFiles.map( data => data.path) ).catch(err=> {console.log(err);return [];});break;}// 对选择的图片,获取信息。构建好对象返回return promise.then(tempFiles => {return tempFiles.map(tempFilePath => {return wx.getImageInfo({ src: tempFilePath }).then(res => {let { path, width, height, orientation, type } = res;let imageInfo = { path, width, height, orientation, type };return imageInfo;});});}).then(promiseArr => { // 全部完成再返回return Promise.all(promiseArr).then(res => res);});
}/*** 从 tempFilePath 以 base64 格式读取文件内容* @param {*} tempFilePath * @param {*} type 图片类型是提前通过 getImageInfo 获取的*/
function imageToBase64(tempFilePath, type) {let data = fs.readFileSync(tempFilePath, 'base64');return `data:image/${type};base64,${data}`;
}/*** 解析龙骨图集的JSON* @param {string} dbJsonTxt 龙骨图集的JSON*/
function parseJSON(dbJsonTxt){// 解析JSONlet json = JSON.parse(dbJsonTxt);// 从 SubTexture 中取出图片名// 判断是否有重复,如果重复就用完整路径名,否则:就直接用图片名let arr = json.SubTexture.map(st => st.name.substr(st.name.lastIndexOf('/')+1));// { "x": 2, "y": 2, "width": 554, "height": 140, "name": "weapon_hand_r"}arr = json.SubTexture.map(subTexture => {if(arr.length !== new Set(arr).size){subTexture.name = `${subTexture.name.replace(/\//g, '-')}.png`;}else{subTexture.name = `${subTexture.name.substr(subTexture.name.lastIndexOf('/')+1)}.png`;}return subTexture;});console.log(arr);json.SubTexture = arr;json.type = 'json';return json;
}// /**
// * 选择JSON。(PC端的弹窗竟然不支持输入功能。此方法没用上)
// * @param {object} options
// */
// function chooseJSON(_options = {}){
// const defaultOptions = {
// count: 1,
// whereFrom: 'modal' , // 从何处选取。合法值: modal message
// };
// let options = { ...defaultOptions, ..._options };
// let promise;
// // 根据参数中给的选择方式,调用对应方法。
// switch (options.whereFrom) {
// case 'modal':
// promise = wx.showModal({
// title: '龙骨图集JSON',
// placeholderText: '请输入龙骨图集JSON内容',
// confirmText: '解析',
// editable: true,
// }).then(res => {
// if (res.confirm && res.errMsg === "showModal:ok" ) {
// console.log(res.content);
// return res.content;
// } else if (res.cancel) {
// console.log('用户点击取消')
// return Promise.reject();
// }
// });
// break;
// default:
// promise = wx.chooseMessageFile(options)
// .then(res => res.tempFiles.map( data => fs.readFileSync(data.path, 'utf-8')))
// .catch(err=> {
// console.log(err);
// return '';
// });
// break;
// }
// return promise;
// }module.exports = {chooseAtlas,chooseImg,imageToBase64,parseJSON,
}
参考资料
笑虾:微信小程序 - 创建 ZIP 压缩包
笑虾:微信小程序 - 文件工具类 fileUtil.js
相关文章:
微信小程序 - 龙骨图集拆分
微信小程序 - 龙骨图集拆分 注意目录结构演示动画废话一下业务逻辑注意点龙骨JSON图集结构 源码分享dragonbones-split.jsdragonbones-split.jsondragonbones-split.wxmldragonbones-split.wxssimgUtil.js 参考资料 注意 只支持了JSON版本 目录结构 演示动画 Spine播放器1.5.…...
使用React 18和WebSocket构建实时通信功能
1. 引言 WebSocket是一种在Web应用中实现双向通信的协议。它允许服务器主动向客户端推送数据,而不需要客户端发起请求。在现代的实时应用中,WebSocket经常用于实时数据传输、聊天功能、实时通知和多人协作等场景。在本篇博客中,我们将探索如…...
vue3使用vue-router嵌套路由(多级路由)
文章目录 1、Vue3 嵌套路由2、项目结构3、编写相关页面代码3.1、编写route文件下 index.ts文件3.2、main.ts文件代码:3.3、App.vue文件代码:3.4、views文件夹下的Home文件夹下的index.vue文件代码:3.5、views文件夹下的Home文件夹下的Tigerhh…...
openGauss学习笔记-164 openGauss 数据库运维-备份与恢复-导入数据-使用COPY FROM STDIN导入数据-处理错误表
文章目录 openGauss学习笔记-164 openGauss 数据库运维-备份与恢复-导入数据-使用COPY FROM STDIN导入数据-处理错误表164.1 操作场景164.2 查询错误信息164.3 处理数据导入错误 openGauss学习笔记-164 openGauss 数据库运维-备份与恢复-导入数据-使用COPY FROM STDIN导入数据-…...
QT Widget - 随便画个圆
简介 实现在界面中画一个圆, 其实目的是想画一个LED效果的圆。代码 #include <QApplication> #include <QWidget> #include <QPainter> #include <QColor> #include <QPen>class LEDWidget : public QWidget { public:LEDWidget(QWidget *pare…...
js输入框部分内容不可编辑,其余正常输入,el-input和el-select输入框和多个下拉框联动后的内容不可修改
<tr>//格式// required自定义指令<e-td :required"!read" label><span>地区:</span></e-td><td>//v-if"!read && this.data.nationCode 148"显示逻辑<divclass"table-cell-flex"sty…...
分布式文件存储系统minio了解下
什么是minio minio 是一个基于 Apache License v2.0 开源协议的对象存储服务。非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小。 是一种海量、安全、低成本、高可靠的云存储…...
迅为RK3568开发板使用OpenCV处理图像-ROI区域-位置提取ROI
在图像处理过程中,我们可能会对图像的某一个特定区域感兴趣,该区域被称为感兴趣区域(Region of Interest, ROI)。在设定感兴趣区域 ROI 后,就可以对该区域进行整体操作。 位置提取 ROI 本小节代码在配套资料“iTOP-3…...
重新认识Word——尾注
重新认识Word——尾注 参考文献格式文献自动生成器插入尾注将数字带上方括号将参考文献中的标号改为非上标 多处引用一篇文献多篇文献被一处引用插入尾注有横线怎么删除?删除尾注 前面我们学习了如何给图片,公式自动添加编号,今天我们来看看毕…...
所有学前教育专业,一定要刷到这篇啊
我是真的希望所有学前教育的宝子都能刷到这篇啊啊,只要输入需求,几秒它就给你写出来了,而且不满意还可以重新写多,每次都是不一样的内容。重复率真的不高,需求越多,生成的文字内容越精准!&#…...
colmap三维重建核心逻辑梳理
colmap三维重建核心逻辑梳理 1. 算法流程束流2. 初始化3. 重建主流程 1. 算法流程束流 重建核心逻辑见 incremental_mapper.cc 中 IncrementMapperController 中 Reconstruct 初始化变量和对象判断是否有初始重建模型,若有,则获取初始重建模型数量&am…...
查询某个类是在哪个JAR的什么版本开始出现的方法
背景 我们在依赖第三方JAR时,同时也会间接的依赖第三方JAR引用的依赖,而当我们项目中某个依赖的版本与第三方JAR依赖的版本不一致时,可能会导致第三方JAR的在运行时无法找到某些方法或类,从而无法正常使用。 如我正在开发的一个…...
Linux本地搭建StackEdit Markdown编辑器结合内网穿透实现远程访问
文章目录 1. docker部署Stackedit2. 本地访问3. Linux 安装cpolar4. 配置Stackedit公网访问地址5. 公网远程访问Stackedit6. 固定Stackedit公网地址 StackEdit是一个受欢迎的Markdown编辑器,在GitHub上拥有20.7k Star!,它支持将Markdown笔记保…...
k8s中ConfigMap、Secret创建使用演示、配置文件存储介绍
目录 一.ConfigMap(cm) 1.适用场景 2.创建并验证configmap (1)以yaml配置文件创建configmap,验证变化是是否同步 (2)--from-file以目录或文件 3.如何使用configmap (1&#x…...
Linux服务器性能优化小结
文章目录 生产环境监测常见专业名词扫盲服务器平均负载服务器平均负载的定义如何判断平均负载值以及好坏情况如果依据平均负载来判断服务器当前状况系统平均负载和CPU使用率的区别 CPU上下文切换基本概念3种上下文切换进程上下文切换线程上下文切换中断上下文切换 查看上下文切…...
ELF文件结构
ELF文件结构 前文结尾说到编译器编译源代码后生成的文件叫做目标文件,而目标文件经过编译器链接之后得到的就是可执行文件。那么目标文件到底是什么?它和可执行文件又有什么区别?链接到底又做了什么呢?接下来,我们将探…...
【C++】有关string迭代器的几道OJ题详解
目录 一、字符串最后一个单词的长度 题目描述 完整代码 二、验证回文串 题目描述 完整代码 三、反转字符串 题目描述 完整代码 四、反转字符串中的单词 题目描述 完整代码 一、字符串最后一个单词的长度 原题链接 题目描述 计算字符串最后一个单词的长度ÿ…...
谷歌宣布向云计算客户开放 Gemini Pro,开发者可用其构建应用
12 月 14 日消息,美国时间周三,谷歌宣布了一系列升级的人工智能(AI)功能,旨在为其云计算客户提供更好的服务。这家科技巨头正试图赶上竞争对手,比如微软和 OpenAI,它们都在积极利用人工智能的热…...
软件测试用例经典方法 | 单元测试法案例
单元测试又称模块测试,是对软件设计的最小单元的功能、性能、接口和设计约束等的正确性进行检验,检查程序在语法、格式和逻辑上的错误,并验证程序是否符合规范,以发现单元内部可能存在的各种缺陷。 单元测试的对象是软件设计的最…...
Leetcode 2967. Minimum Cost to Make Array Equalindromic
Leetcode 2967. Minimum Cost to Make Array Equalindromic 1. 解题思路2. 代码实现 题目链接:2967. Minimum Cost to Make Array Equalindromic 1. 解题思路 这一题其实我的思路有点笨,多少有点暴力求解的意思。 显然,如果我们给出全部的…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...
Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...
aardio 自动识别验证码输入
技术尝试 上周在发学习日志时有网友提议“在网页上识别验证码”,于是尝试整合图像识别与网页自动化技术,完成了这套模拟登录流程。核心思路是:截图验证码→OCR识别→自动填充表单→提交并验证结果。 代码在这里 import soImage; import we…...
前端调试HTTP状态码
1xx(信息类状态码) 这类状态码表示临时响应,需要客户端继续处理请求。 100 Continue 服务器已收到请求的初始部分,客户端应继续发送剩余部分。 2xx(成功类状态码) 表示请求已成功被服务器接收、理解并处…...
GeoServer发布PostgreSQL图层后WFS查询无主键字段
在使用 GeoServer(版本 2.22.2) 发布 PostgreSQL(PostGIS)中的表为地图服务时,常常会遇到一个小问题: WFS 查询中,主键字段(如 id)莫名其妙地消失了! 即使你在…...
基于django+vue的健身房管理系统-vue
开发语言:Python框架:djangoPython版本:python3.8数据库:mysql 5.7数据库工具:Navicat12开发软件:PyCharm 系统展示 会员信息管理 员工信息管理 会员卡类型管理 健身项目管理 会员卡管理 摘要 健身房管理…...
盲盒一番赏小程序:引领盲盒新潮流
在盲盒市场日益火爆的今天,如何才能在众多盲盒产品中脱颖而出?盲盒一番赏小程序给出了答案,它以创新的玩法和优质的服务,引领着盲盒新潮流。 一番赏小程序的最大特色在于其独特的赏品分级制度。赏品分为多个等级,从普…...
