ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PixiEditor 节点开发指南:从零创建自定义 Node 的完整实践

PixiEditor 节点开发指南:从零创建自定义 Node 的完整实践 桌面应用图像处理【免费下载链接】PixiEditorPixiEditor is a Universal Editor for all your 2D needs项目地址https://gitcode.com/GitHub_Trending/pi/PixiEditor点击查看免费下载本篇技术指南围绕 PixiEditor 节点图系统Node Graph的扩展开发展开详细讲解如何为 PixiEditor 编写全新的节点类型从继承Node基类、标注NodeInfoAttribute、为输入接入序列化工厂到借助RequestTexture安全地管理 GPU 纹理资源并规避SetPixel/GetPixel等性能陷阱。读者读完本文后将能够独立为 PixiEditor 的节点图体系贡献自定义节点并写出符合官方工程规范的、可保存、可缓存、可测试的节点代码。从文档出发官方节点创建流程概览在仓库 src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreatingNode.md 中官方以“如何创建新节点”为题给出了四条核心指引这是所有节点开发工作必须遵守的底线每个节点类必须继承Node类每个节点类必须携带NodeInfoAttribute其中包含 Unique Name 与 Display nameDisplay name 需要支持本地化Unique name 必须在所有节点中全局唯一节点输入会被序列化因此任何输入都必须有配套的SerializationFactory类否则保存文档会失败运行测试确保节点没有明显问题。下文将逐条展开并结合仓库源码把这些要求背后的机制讲清楚让开发者不仅“知其然”还“知其所以然”。第一步继承 Node 基类理解节点生命周期所有节点类型的根基是 Node.cs 中声明的抽象类public abstract class Node : IReadOnlyNode, IDisposable它是整个节点图的数据与执行核心开发者需要重点理解以下几个层面输入输出属性的创建Node为派生类提供了完整、受保护的属性创建 API节点开发几乎全部围绕它们展开方法作用典型使用场景CreateInputT(propName, displayName, defaultValue)创建可被外部连接覆盖的输入属性数值、布尔、枚举、调色板等参数输入CreateFuncInputT(propName, displayName, defaultValue)创建“函数式”输入支持 shader 表达式求值数学运算、颜色运算等需要参与着色器生成的值CreateOutputT(propName, displayName, defaultValue)创建输出属性普通数据输出CreateFuncOutputT(propName, displayName, defaultFunc)创建函数式输出由委托按需计算Result、Color等计算型输出CreateRenderInput / CreateRenderOutput创建渲染链上的输入/输出端口图层、特效、合成类节点CreateSyncedTypeInput / CreateSyncedTypeOutput创建类型同步SyncGroup的输入/输出需要跟随连接类型变化的动态端口以 MathNode.cs 为最小范例public MathNode() { Result CreateFuncOutputFloat1(Result, RESULT, Calculate); Mode CreateInput(ModePropertyName, MATH_MODE, MathNodeMode.Add); Clamp CreateInput(Clamp, CLAMP, false); X CreateFuncInputFloat1(XPropertyName, X, 0d); Y CreateFuncInputFloat1(YPropertyName, Y, 0d); Z CreateFuncInputFloat1(ZPropertyName, Z, 0d); }注意CreateInput对内部属性名第一个参数有唯一性约束——从 Node.cs 的实现可以看到重复的InternalPropertyName会直接抛出InvalidOperationException。执行与缓存机制Node.Execute(RenderContext context)是渲染管线调用节点的入口其内部逻辑Node.cs体现了 PixiEditor 节点图的核心性能设计——缓存失效驱动执行internal void ExecuteInternal(RenderContext context) { if (_isDisposed) throw new ObjectDisposedException(Node was disposed before execution.); if (!context.FullRerender ExecuteOnlyOnCacheChange !CacheChanged(context)) { return; } OnExecute(context); if (ExecuteOnlyOnCacheChange) { UpdateCache(context); } }派生类通过两个受保护成员控制缓存行为ExecuteOnlyOnCacheChange默认false是否仅在缓存变化时执行CacheTrigger默认CacheTriggerFlags.Inputs监听哪些变化源包括Inputs输入连接变化、RenderSize渲染尺寸、ChunkResolution分块分辨率、Timeline时间线/帧、以及GetContentCacheHash()返回的内容哈希。例如 ShaderNode.cs 就声明为protected override bool ExecuteOnlyOnCacheChange true; protected override CacheTriggerFlags CacheTrigger CacheTriggerFlags.All;这意味着着色器节点会监听全部缓存触发源任何输入、尺寸、分块分辨率或时间线变化都会触发其重新执行。Dispose 与生命周期Node实现了IDisposable。在 Node.cs 的Dispose()中框架会依次断开所有连接、释放未被覆盖且实现了IDisposable的输入默认值、释放无连接的输出值、并释放所有关键帧数据。自定义节点若持有额外资源如 Shader、Paint、纹理快照务必在自己的Dispose()中先调用base.Dispose()再释放自有资源参考 ColorMapNode.cs 的写法public override void Dispose() { base.Dispose(); shader?.Dispose(); imageShader?.Dispose(); paletteShader?.Dispose(); paint?.Dispose(); }CreateCopy 与克隆每个节点还必须实现抽象的CreateCopy()返回一个同类型的新实例这是节点图复制/粘贴与文档克隆的基础。Node.Clone会复制 DisplayName、Position、输入默认值、关键帧等但输出值不会复制——从 Node.cs 的注释可以看到这是因为“输出基于输入计算得出”无需拷贝。第二步NodeInfoAttribute——节点的全局身份标识NodeInfoAttribute定义在 NodeInfoAttribute.cs是节点注册与查找的凭证[AttributeUsage(AttributeTargets.Class)] public class NodeInfoAttribute : Attribute { public string UniqueName { get; } public NodeInfoAttribute(string uniqueName) { if (!uniqueName.StartsWith(PixiEditor)) { uniqueName $PixiEditor.{uniqueName}; } UniqueName uniqueName; } }使用要点Unique Name 全局唯一属性构造器会自动为不以PixiEditor开头的名字补上PixiEditor.前缀因此文档中强调的“跨所有节点唯一”实际上是在PixiEditor.*命名空间下唯一Display name 需要本地化节点在界面上的显示名应接入本地化系统。仓库中大量节点以大写常量的方式如RESULT、MATH_MODE作为显示名正是本地化字典的键开发自定义节点时同样应使用本地化键而非硬编码英文文案运行时读取Node.cs 中的GetNodeTypeUniqueName()通过反射读取该特性未标注会抛出异常——这是节点保存/加载与菜单注册的关键路径。典型的标注方式MathNode.cs[NodeInfo(UniqueName)] public class MathNode : Node, IIterativeRenderSupport { public const string UniqueName Math; ... }第三步为输入配置 SerializationFactory保证文档可保存这是文档强调的“否则保存会失败”的关键环节。节点图的输入属性会被序列化进.pixi文档而序列化/反序列化依赖按类型注册的工厂。仓库中的工厂类集中在 src/PixiEditor/Models/Serialization/Factories涵盖Brush、Color、ColorMatrix、Document、FontFamily、Kernel、Matrix3X3等类型。以 ColorSerializationFactory.cs 为例工厂需要实现三个成员public class ColorSerializationFactory : SerializationFactorybyte[], Color { public override string DeserializationId { get; } PixiEditor.Color; public override byte[] Serialize(Color original) { byte[] result new byte[4]; result[0] original.R; result[1] original.G; result[2] original.B; result[3] original.A; return result; } public override bool TryDeserialize(object serialized, out Color original, (string serializerName, string serializerVersion) serializerData) { if (serialized is byte[] { Length: 4 } bytes) { original new Color(bytes[0], bytes[1], bytes[2], bytes[3]); return true; } original default; return false; } }从中可以提炼出工厂契约DeserializationId是文档中持久化的类型标识一旦发布就不宜随意更改否则旧文档无法反序列化Serialize负责把运行时对象转为可持久化的中间格式TryDeserialize负责把中间格式还原为运行时对象并返回是否成功。实践建议当你的节点引入新的自定义输入类型时必须为它登记对应的SerializationFactory如果输入类型属于既有工厂覆盖的范围如Color、VecD、Matrix3X3则直接复用即可。此外对于节点自身需要持久化的额外状态Node提供了SerializeAdditionalData/DeserializeAdditionalData扩展点Node.cs并暴露了OnSerializeAdditionalData/OnDeserializeAdditionalData事件。仓库中 ArrayConverterNode.cs、NestedDocumentNode.cs 等均重写了SerializeAdditionalDataInternal供需要保存额外数据的节点参考。第四步运行测试用现有测试体系兜底节点开发完成后必须运行测试。仓库为节点系统提供了专门的测试项目tests/PixiEditor.Backend.Tests/NodeSystemTests.cs节点系统级测试tests/PixiEditor.Backend.Tests/ShaderTests.cs着色器相关测试tests/PixiEditor.Backend.Tests/MockDocument.cs构造无 UI 依赖的文档上下文方便在测试中实例化节点图此外 tests/PixiEditor.Tests 覆盖序列化、渲染等更上层行为。建议新节点至少在NodeSystemTests风格的项目中补充“创建 → 连接 → 执行 → 复制 → 序列化往返”的冒烟测试重点验证NodeInfoAttribute的 Unique Name 无冲突、输入输出属性名不重复、CreateCopy()可正常克隆、Dispose 不抛出异常。重要提醒一不要直接创建 Texture请使用 RequestTexture文档给出的第一条硬性约束是不要直接创建新的 Texture除非在同一执行上下文内主动 Dispose。无管理的纹理创建会带来内存泄漏、性能下降、视觉异常甚至意外崩溃。正确的做法是调用RequestTexture由框架统一管理节点纹理的缓存与生命周期。其底层实现在 TextureCache.cspublic Texture RequestTexture(int id, VecI size, ColorSpace processingCs, bool clear true) { if (isDisposed) throw new ObjectDisposedException(nameof(TextureCache)); if (size.X 0 || size.Y 0) throw new ArgumentException(Size must be positive, nameof(size)); if (_managedTextures.TryGetValue(id, out var texture)) { if (texture.Size ! size || texture.IsDisposed || !texture.ColorSpace.Equals(processingCs)) { texture.Dispose(); texture new Texture(CreateImageInfo(size, processingCs)); _managedTextures[id] texture; return texture; } if (clear) { texture.DrawingSurface.Canvas.Clear(Colors.Transparent); texture.DrawingSurface.Canvas.SetMatrix(Matrix3X3.Identity); } return texture; } _managedTextures[id] new Texture(CreateImageInfo(size, processingCs)); return _managedTextures[id]; }从实现可以看出RequestTexture的三个关键特性按id缓存复用同一个id在同一节点实例的生命周期内对应同一块纹理尺寸或色彩空间变化时自动重建自动清空默认每次取用都会Clear为透明并重置矩阵避免脏数据残留GPU 友好通过CreateImageInfo创建RgbaF16 Premul GpuBacked true的纹理保证在渲染管线中高效使用。使用示例遍布仓库例如 ColorMapNode.cs 中的Texture target RequestTexture(0, finalSize, colorSpace);。注意RequestTexture返回的纹理由TextureCache统一管理并在节点图销毁时整体释放因此节点代码不应再对这些纹理调用Dispose()——这与前文“自持资源需自行 Dispose”是两条不同的资源规则务必区分清楚。对于一次性中间纹理using局部变量则可以直接使用Texture.ForProcessing(...)并在用完后释放参考 ColorMapNode.cs 的写法。重要提醒二远离 SetPixel / GetPixel文档第二条约束语气强烈“For the love of god, DO NOT USESetPixelandGetPixel”——这两类逐像素读写方法极慢应不惜一切代价避免。从仓库实践可以总结出三条替代路线用 Canvas 绘图代替逐像素写入例如 ColorMapNode.cs 生成调色板纹理时用DrawRect逐格绘制而不是SetPixelfor (int i 0; i count; i) { pixelPaint.Color palette[i]; paletteTexture.DrawingSurface.Canvas.DrawRect(i, 0, 1, 1, pixelPaint); }用着色器表达像素级计算PixiEditor 的节点系统大量使用 shader如ShaderNode、ColorMapNode内嵌的#version 300shader 代码把逐像素逻辑交给 GPU 并行执行用 Surface 快照/Snapshot 完成采样需要读取纹理内容作为 shader 输入时通过DrawingSurface.Snapshot().ToShader()转换而不是 CPU 端逐像素读取见 ShaderNode.cs。实战参考从现有节点吸收写法编写新节点前强烈建议通读以下几份高质量的参考实现参考节点文件值得学习的设计点MathNodeMathNode.csFuncInput/FuncOutput的组合、常量路径与 shader 路径的双分支求值ShaderNodeShaderNode.cs内嵌 shader 代码、RequestTexture缓存中间结果、uniform 输入自动生成ColorMapNodeColorMapNode.cs缓存触发配置、资源 Dispose 规范、Canvas 替代 SetPixelRenderNodeRenderNode.cs渲染类节点的基类RequestTexture的二次封装例如ShaderNode的默认 shader 模板ShaderNode.cs清晰地列出了节点内置的特殊 uniform——iResolution当前渲染输出分辨率、iNormalizedTime当前帧归一化时间 0~1、iFrame当前帧号、iImage/Background背景输入纹理开发者自定义 shader 节点时可复用这些约定。结语总结 PixiEditor 节点开发的核心纪律继承Node并正确使用属性创建 API、用NodeInfoAttribute声明全局唯一身份、为输入类型登记SerializationFactory、用RequestTexture托管纹理生命周期、绝不触碰SetPixel/GetPixel、最后用测试体系验证。遵守这些规则新节点就能无缝融入 PixiEditor 的渲染管线、缓存机制与文档持久化体系为编辑器贡献稳定可靠的新能力。赞分享桌面应用图像处理【免费下载链接】PixiEditorPixiEditor is a Universal Editor for all your 2D needs项目地址https://gitcode.com/GitHub_Trending/pi/PixiEditor点击查看免费下载相关推荐Meshroom 节点开发指南从零实现 Node、CommandLineNode 与 InitNode 自定义节点Meshroom 节点开发指南从零实现 Node、CommandLineNode 与 InitNode 自定义节点 导读 Meshroom 是一个开源的节点式计算机视觉桌面应用图形学Node-RED自定义节点开发如何创建专属功能模块的详细教程Node RED自定义节点开发如何创建专属功能模块的详细教程 Node RED作为一款强大的低代码编程工具让事件驱动应用的开发变得前所未有的简单。但对于许多低代码后端前端工作流自动化从零构建你的专属节点G6 自定义节点完整实战指南从零构建你的专属节点G6 自定义节点完整实战指南 G6 内置了 circle、rect、diamond、donut、ellipse、hexagon、html、数据可视化前端图表库上一篇使用 Instructor 从图片中提取结构化模型元数据Multimodal 结构化提取实战下一篇Cookiecutter Django 测试实战指南基于 Pytest 与 Coverage 的完整实践创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表