微信小程序 - 龙骨图集拆分

2023-12-18 22:15

本文主要是介绍微信小程序 - 龙骨图集拆分,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

微信小程序 - 龙骨图集拆分

  • 注意
  • 目录结构
  • 演示动画
  • 废话一下
    • 业务逻辑
    • 注意点
    • 龙骨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端兼容方面的问题。

业务逻辑

  1. 读取JSON文件和PNG图片。
  2. 解析JSON得到所有小图在图集(PNG图片)中的 x,y 坐标和高宽。
  3. 将PNG图集绘制到2d画布,然后使用 canvasToTempFilePath 逐个截取区域,保存为小图。
  4. 最后将所有小图打包为 zip 供用户保存即可。

注意点

  1. 为了保证截取图片的清晰度,画布尺寸需要用图片大小乘以设备像素比
  2. 图片填充完整,再截图。否则会空白。所以会成两步操作比较稳妥。当然也可以自己控制延时自动调用,一气呵成。
  3. 因为 2d 画布不便于直接显示,所以使用一个 image 组件来实现预览。
    3.1. 方法是将 PNG 读取为 base64 赋给 image 组件 <image src="{{textureBase64}}" />
    3.2. 读取 PNGbase64 就这句 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>选择龙骨PNGJSON</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

这篇关于微信小程序 - 龙骨图集拆分的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/509942

相关文章

在不同系统间迁移Python程序的方法与教程

《在不同系统间迁移Python程序的方法与教程》本文介绍了几种将Windows上编写的Python程序迁移到Linux服务器上的方法,包括使用虚拟环境和依赖冻结、容器化技术(如Docker)、使用An... 目录使用虚拟环境和依赖冻结1. 创建虚拟环境2. 冻结依赖使用容器化技术(如 docker)1. 创

W外链微信推广短连接怎么做?

制作微信推广链接的难点分析 一、内容创作难度 制作微信推广链接时,首先需要创作有吸引力的内容。这不仅要求内容本身有趣、有价值,还要能够激起人们的分享欲望。对于许多企业和个人来说,尤其是那些缺乏创意和写作能力的人来说,这是制作微信推广链接的一大难点。 二、精准定位难度 微信用户群体庞大,不同用户的需求和兴趣各异。因此,制作推广链接时需要精准定位目标受众,以便更有效地吸引他们点击并分享链接

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

EMLOG程序单页友链和标签增加美化

单页友联效果图: 标签页面效果图: 源码介绍 EMLOG单页友情链接和TAG标签,友链单页文件代码main{width: 58%;是设置宽度 自己把设置成与您的网站宽度一样,如果自适应就填写100%,TAG文件不用修改 安装方法:把Links.php和tag.php上传到网站根目录即可,访问 域名/Links.php、域名/tag.php 所有模板适用,代码就不粘贴出来,已经打

跨系统环境下LabVIEW程序稳定运行

在LabVIEW开发中,不同电脑的配置和操作系统(如Win11与Win7)可能对程序的稳定运行产生影响。为了确保程序在不同平台上都能正常且稳定运行,需要从兼容性、驱动、以及性能优化等多个方面入手。本文将详细介绍如何在不同系统环境下,使LabVIEW开发的程序保持稳定运行的有效策略。 LabVIEW版本兼容性 LabVIEW各版本对不同操作系统的支持存在差异。因此,在开发程序时,尽量使用

CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析 未完

一、题目阅读 (最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。 一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。 例如,当原序列为 [1,2,1,2] 时,要计算子序列 [

这些心智程序你安装了吗?

原文题目:《为什么聪明人也会做蠢事(四)》 心智程序 大脑有两个特征导致人类不够理性,一个是处理信息方面的缺陷,一个是心智程序出了问题。前者可以称为“认知吝啬鬼”,前几篇文章已经讨论了。本期主要讲心智程序这个方面。 心智程序这一概念由哈佛大学认知科学家大卫•帕金斯提出,指个体可以从记忆中提取出的规则、知识、程序和策略,以辅助我们决策判断和解决问题。如果把人脑比喻成计算机,那心智程序就是人脑的

uniapp设置微信小程序的交互反馈

链接:uni.showToast(OBJECT) | uni-app官网 (dcloud.net.cn) 设置操作成功的弹窗: title是我们弹窗提示的文字 showToast是我们在加载的时候进入就会弹出的提示。 2.设置失败的提示窗口和标签 icon:'error'是设置我们失败的logo 设置的文字上限是7个文字,如果需要设置的提示文字过长就需要设置icon并给

基于SpringBoot的宠物服务系统+uniapp小程序+LW参考示例

系列文章目录 1.基于SSM的洗衣房管理系统+原生微信小程序+LW参考示例 2.基于SpringBoot的宠物摄影网站管理系统+LW参考示例 3.基于SpringBoot+Vue的企业人事管理系统+LW参考示例 4.基于SSM的高校实验室管理系统+LW参考示例 5.基于SpringBoot的二手数码回收系统+原生微信小程序+LW参考示例 6.基于SSM的民宿预订管理系统+LW参考示例 7.基于

Spring Roo 实站( 一 )部署安装 第一个示例程序

转自:http://blog.csdn.net/jun55xiu/article/details/9380213 一:安装 注:可以参与官网spring-roo: static.springsource.org/spring-roo/reference/html/intro.html#intro-exploring-sampleROO_OPTS http://stati