当前位置: 首页 > news >正文

原生鸿蒙像素画板实战 17:项目合并

把两个项目合成一个,不能理解成“把两张图贴在一起”。像素画项目里还有画布尺寸、图层名称、透明度、背景层、帧、工具配置和项目 ID。若合并直接改写任意一个原项目,用户很容易失去原始版本;若只复制可见合成图,又会丢失之后继续编辑需要的图层结构。bitArt 的合并策略是读取多个项目,创建一个新的持久化项目,把每个项目的图层放进统一画布,并保留原项目文件。

合并的产物必须是一个新项目

项目之间的画布尺寸可能不同。简单使用第一个项目的尺寸会裁掉其他项目内容,直接拉伸又会破坏像素比例。图层名称也可能重复,例如每个项目都有“线稿”或“背景”。如果新项目里保留同名图层,用户后续编辑时很难辨认来源。更重要的是,合并项目不是一次普通保存:它必须生成新 projectId,不能把原项目覆盖成混合版本。

合并流程中页面与存储层各做什么

合并项目比切换项目更需要保守:新结果必须有自己的身份,原始项目必须完整保留。只有这样,用户才敢把草稿、线稿和配色稿放进同一次合并。

先选统一画布,再放置每一层像素

当前实现先读取选中的所有项目,少于两个有效项目则直接返回 null。随后计算合并画布宽高,统计所有图层名称的总数,并在写入时为重复名称生成带编号的结果。每个源图层得到新的 layer-id,像素通过 placePixelsOnMergedCanvas 放到统一尺寸的透明数组里;合并后再补一层锁定背景。

合并项目沿用第一个项目的一部分编辑配置,例如活动工具、当前色、调色板和自动保存间隔,但图层、像素和帧由新的结果生成。帧不会把多个项目的原动画时间轴硬拼在一起,而是创建一条新的初始帧。这是一种保守但可解释的选择:先保证静态图层结构可编辑,再把跨项目动画作为独立需求处理。

合并从读取多个独立项目开始,任何有效数量不足的情况都不会产出半成品。

static async mergeProjects(context: common.UIAbilityContext, projectIds: string[]) { const projects: EditorPersistentProject[] = []; for (let index = 0; index < projectIds.length; index++) { const project = await EditorPersistenceService.readProjectFile(context, projectIds[index]); if (project !== null) projects.push(project); } if (projects.length < 2) return null; const canvasWidth = EditorPersistenceService.resolveMergedCanvasWidth(projects); const canvasHeight = EditorPersistenceService.resolveMergedCanvasHeight(projects); // 后续为每个源图层分配新 id、名称和目标像素数组 }

重名图层和动画状态要有明确取舍

画布合并的关键是“放置”而非缩放。目标宽高取所有有效项目对应维度的最大值,源项目的每层像素按自己的 width 和 height 读取,再居中写入统一尺寸;没有被源图层占用的位置保持透明。放置函数仍保留边界检查,但正常合并不会因为画布较小而裁掉一个合法源项目。图层序列号从 1 重新分配,背景层固定加入最后,使新项目的内部 ID 不会和任何原项目冲突。

for (let projectIndex = 0; projectIndex < projects.length; projectIndex++) { const project = projects[projectIndex]; const sourceWidth = EditorPersistenceService.resolveProjectCanvasWidth(project); const sourceHeight = EditorPersistenceService.resolveProjectCanvasHeight(project); for (let layerIndex = 0; layerIndex < project.layers.length; layerIndex++) { mergedLayers.push({ id: 'layer-' + serial.toString(), name: EditorPersistenceService.resolveMergedLayerName(project.layers[layerIndex].name, layerNameTotals, layerNameIndexes), visible: project.layers[layerIndex].visible, locked: project.layers[layerIndex].locked, opacity: project.layers[layerIndex].opacity }); mergedLayersPixels.push(EditorPersistenceService.placePixelsOnMergedCanvas(project.layersPixels[layerIndex], sourceWidth, sourceHeight, canvasWidth, canvasHeight)); serial++; } }

合并完成后才切换到新的项目身份

工作流要求至少选择两个项目,并先结束当前笔触、保存当前项目,再调用持久化服务。合并成功时应用新项目、退出管理模式、清空 selectedProjectIds、打开图层面板并重新安排自动保存。原项目仍留在项目列表,这使“合并”成为可回退的创作动作,而不是破坏性批处理。

合并成功时才应用新项目并清空多选状态,失败则保留原项目和管理上下文供用户继续选择。

if (target.selectedProjectIds.length < 2) { hooks.showToast('至少选择两个项目'); return; } hooks.endStroke(); await hooks.saveNow(true); const mergedProject = await EditorPersistenceService.mergeProjects(context, target.selectedProjectIds); if (mergedProject !== null) { hooks.applyPersistentProject(mergedProject); target.selectedProjectIds = []; hooks.scheduleAutoSave(); }

多项目合成时不能模糊的取舍

  • 不要把合并结果保存回第一个项目,必须创建新的 projectId 和新的项目名称。
  • 不同画布尺寸应该按像素位置放置,不应在合并时做平滑缩放。
  • 不要假装多项目动画可以无损拼接;当前实现明确新建初始帧,避免时间轴语义混乱。

图层名称重名处理看似只是 UI 问题,实际影响后续自动化和用户判断。合并前先统计总数,再在写入时累加索引,能确保只对真正重复的名称追加后缀。像素数据和图层元数据都复制到新数组,避免合并后引用仍指向某个原项目的内存对象。

合并前先说清数据如何保留

合并功能最好先用可辨认的源项目做手工样本。例如项目 A 放红色对角线和名为“线稿”的图层,项目 B 放蓝色方块和同名图层,同时设置不同尺寸。合并后应能从图层面板看出两份内容都存在,名称也能区分;再分别关闭、移动和修改它们,确认它们确实是独立图层而非一张扁平合成图。

合并后的项目还要和原项目并排检查。修改新项目、保存、重开,再返回 A 与 B,原画布不应变化。因为合并保留的是新的项目身份,最重要的验收不是“画面合起来了”,而是“原始草稿仍是原始草稿”。对于创作工具,这条可逆性比自动做出复杂混合效果更值得优先保证。

怎样确认合并没有覆盖原稿

合并检查必须回到原项目确认数据未被覆盖,而不只检查新项目是否出现。

  • 选两个不同尺寸、都含“线稿”图层的项目,确认合并后出现可区分的图层名称。
  • 合并后修改新项目的某一层,返回原项目确认原像素没有变化。
  • 选择不足两个项目时,确认系统给出明确提示且不创建空项目。
  • 合并含多层和透明像素的项目,确认图层顺序、透明区域和锁定背景符合预期。
项目合并检查记录 少于两个项目不创建结果 输出项目拥有新 ID 和新名称 重复图层名可区分 原项目保持不变,合并结果可独立编辑

项目合并暂不承诺的内容

当前合并处理的是本地项目的图层集合,不提供对齐辅助线、混合模式选择、跨项目帧拼接或冲突可视化。它适合把草稿、线稿和配色稿聚到一个可继续编辑的项目中,而不是替代完整的版本控制系统。

这一篇的重点是在保留原稿的前提下创建一个新的可编辑合并项目。项目结构稳定之后,可以把一些常用图案做成素材入口;下一篇先处理素材库如何把数据变成可读的像素预览。

http://www.jsqmd.com/news/1242437/

相关文章:

  • 关注春城实时动态,合扬整合昆明全市各区当下热点信息 - 生活商业速报
  • 嵌入式软件测试:现状、痛点与专业工具突破
  • 2026哈尔滨平房区名表回收新风向:易奢福全国连锁深耕三十年,百家门店护航,上门估价1公里极速响应 - 肉松卷
  • 【Python课程设计/毕业设计】基于 Flask 框架的轻量化内容管理系统 个人知识分享博客展示与后台管理系统【附源码、数据库、万字文档】
  • 如何快速掌握DataHub:5分钟搭建你的元数据管理平台
  • 如何打通服装AI质检检测与生产流程?
  • AI推理延迟对比白皮书(2024Q2权威实测版):Llama3、GPT-4 Turbo、Claude 3.5、Qwen2.5与Gemini 2.0在12类硬件上的P99延迟排名首次公开
  • 东莞东城奢侈品回收靠谱商家推荐|30年持证鉴定+无损检测安全变现 - 回收奢侈品探店测评
  • 2026类似龙虾的私有化定制智能体平台有哪些?5款主流方案商选型指南 - 品牌深度评测
  • 为什么 GPT 越深度思考越容易出错?算力调度才是模型体感关键
  • Python毕设项目:基于 Python 框架的医疗服务预约诊断平台 轻量化线上就医预约问诊管理系统 (源码+文档,讲解、调试运行,定制等)
  • “数学一直学不好”的原因:被老师严重误导:dy与dx都不能代表数
  • TI USBSS批量传输DMA配置实战:从寄存器到描述符的完整指南
  • 2026青岛口碑好的专业防水公司推荐:卫生间漏水、楼顶漏水、外墙漏水、阳台+阳光房漏水,全场景专业防水解决方案 - 吉林同城获客
  • Python与Selenium自动化测试与爬虫开发实战指南
  • Vim与Shell脚本高效开发指南
  • [AXI]如何验证AXI5原子操作
  • 深入解析DMA技术:从原理到TMS320F2837xS实战应用
  • Meta家庭中心下周起上线新功能:家长可管控青少年Threads账户隐私、时长等
  • 高端制造/半导体集成电路 设计EDA组长工程师 18维度标准简历范本
  • 2026 川南地磅批发,电子台秤铲车秤安装,衡器采购避坑经验 - LYL仔仔
  • TMS320F2837xS Flash配置优化:从等待状态到ECC的嵌入式系统性能提升指南
  • 美育不是画画唱歌,综评美育素材很多人攒错了
  • GitHub_Trending/cla/claude-skills自定义参考库教程:构建专属知识库
  • 哈尔滨欧米茄官方售后服务网络全攻略|官方网站权威公布(2026年7月最新) - 欧米茄售后服务官网
  • 零基础转行网络安全,Linux和Kali环境到底怎么搭
  • 【零信任AI开发流水线】:从Prompt注入到模型窃取——6步构建可审计、可阻断的AI编码安全闭环
  • 鸿蒙 ArkTS 实战:Class Score Average 从成绩平均计算到成绩统计应用完整解析
  • C2000 DMA寄存器详解与多核通信实战配置指南
  • Python毕设项目:基于Python的轻量化智慧医疗预约挂号系统设计 医疗机构线上预约挂号信息化系统 (源码+文档,讲解、调试运行,定制等)