ARTICLE DETAIL

资讯详情

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

深入解析 libspng 上下文 API:基于 source-sdk-2013 中捆绑的 spng_ctx 数据模型与通用接口

深入解析 libspng 上下文 API:基于 source-sdk-2013 中捆绑的 spng_ctx 数据模型与通用接口 深入解析 libspng 上下文 API基于 source-sdk-2013 中捆绑的 spng_ctx 数据模型与通用接口【免费下载链接】source-sdk-2013The 2013 edition of the Source SDK项目地址: https://gitcode.com/GitHub_Trending/so/source-sdk-2013导读libspngsimple png是一个以安全与易用为核心目标的 C 语言 PNG 编解码库其 API 与 libpng 互不兼容、且更为精简。本文以该仓库捆绑的 libspng v0.7.2 官方文档 context.md 为主体系统讲解其最核心的上下文context数据模型——从spng_ctx句柄、像素格式与过滤枚举到上下文创建/销毁、输入输出流绑定、资源上限与选项管理等一系列通用 API。读完本文你将掌握 libspng 全部解码、编码流程所依赖的公共基础设施并能直接在 Source SDK 2013 捆绑源码 中对照验证每个数据类型的真实定义。1. 为什么 libspng 的一切都围绕spng_ctx展开libspng 将所有状态封装在一个不透明句柄spng_ctx中——无论是解码一张 PNG 还是编码一张 PNG你都要先创建一个 context再通过它完成所有操作。这一设计与 libpng 的png_structp思路类似但 API 更收敛上下文本身没有公开成员用户只能通过文档化的函数来驱动它。typedef struct spng_ctx spng_ctx;在 spng.h 中该结构体以struct spng_ctx的形式定义头文件对外只暴露不透明指针内部实现细节完全隐藏。这意味着库的 ABI 与内部布局解耦后续版本可以自由调整内部字段而不破坏二进制兼容用户无法绕过 API 直接读写上下文状态杜绝误用导致的未定义行为同一份 context 既可承载解码状态读方向也可承载编码状态写方向取决于创建时传入的 flags。spng_ctx同时充当仓库角色所有解析出的 PNG chunk 数据IHDR、PLTE、tRNS、文本、ICC 等都存储在上下文中可随时通过spng_get_*()系列函数取出或通过spng_set_*()系列函数写入。因此理解上下文模型是理解 chunk 数据访问 与 解码/编码流程 的前提。1.1 创建标志spng_ctx_flagscontext 的读/写方向由创建标志决定定义在 context.mdenum spng_ctx_flags { SPNG_CTX_IGNORE_ADLER32 1, /* Ignore checksum in DEFLATE streams */ SPNG_CTX_ENCODER 2 /* Create an encoder context */ };标志值含义SPNG_CTX_IGNORE_ADLER321忽略 DEFLATE 流中的 Adler-32 校验和。注意仅当编译时使用 zlib 1.2.11 才支持使用 miniz 后端时不可用SPNG_CTX_ENCODER2创建一个编码器上下文而非默认的解码器上下文不传任何标志即传0得到的就是解码器上下文这也是 README 基础示例的用法。2. 像素格式、过滤与行信息解码/编码共用的核心数据类型context.md定义了若干贯穿解码与编码全过程的基础类型下面逐一说明。2.1 输出/输入像素格式spng_formatenum spng_format { SPNG_FMT_RGBA8 1, SPNG_FMT_RGBA16 2, SPNG_FMT_RGB8 4, SPNG_FMT_GA8 16, SPNG_FMT_GA16 32, SPNG_FMT_G8 64, /* No conversion or scaling */ SPNG_FMT_PNG 256, /* host-endian */ SPNG_FMT_RAW 512 /* big-endian */ };要点转换型格式RGBA8/RGBA16/RGB8/GA8/GA16/G8解码时把任意 PNG 格式转换到指定输出格式编码时则把该格式转换为目标 PNG 格式直通型格式SPNG_FMT_PNG/SPNG_FMT_RAW不做任何颜色转换或位深缩放像素布局与 PNG 文件内部完全一致区别只在于字节序——SPNG_FMT_PNG为主机字节序host-endianSPNG_FMT_RAW为固定大端序big-endian后者不支持 gamma 校正等变换通道顺序始终是 byte-order 表示法RGBA 顺序而非 planar 布局Alpha 通道始终是straight alpha直通 alpha库不支持预乘 alphapremultiplied alpha。2.2 行过滤类型spng_filterPNG 压缩前会对每个扫描行应用一种可逆过滤以提升压缩率libspng 用该枚举标识当前行采用的过滤器enum spng_filter { SPNG_FILTER_NONE 0, SPNG_FILTER_SUB 1, SPNG_FILTER_UP 2, SPNG_FILTER_AVERAGE 3, SPNG_FILTER_PAETH 4 };这五种过滤器与 PNG 规范完全对应None不过滤、Sub与左侧像素差分、Up与上方像素差分、Average取左/上均值、PaethPaeth 预测器。解码端每行的filter字段即来自该枚举。2.3 行信息spng_row_infostruct spng_row_info { uint32_t scanline_idx; uint32_t row_num; int pass; uint8_t filter; };该结构用于渐进式progressive解码与编码当逐行处理图像时通过spng_get_row_info()获取当前待处理行的信息。其中scanline_idx为扫描行序号row_num为最终图像中的行号pass为 Adam7 交错interlace的当前 pass 编号非交错图恒为 0filter为当前行使用的过滤类型。对于非交错图row_num随处理进度线性递增对于交错图行会被多次、非顺序地访问必须依靠row_num定位目标缓冲区——这正是 渐进式解码 与 渐进式编码 文档中循环示例的核心。2.4 选项枚举spng_optionspng_option是统一管理解码选项 编码选项的键枚举通过spng_set_option()/spng_get_option()读写enum spng_option { SPNG_KEEP_UNKNOWN_CHUNKS 1, SPNG_IMG_COMPRESSION_LEVEL, SPNG_IMG_WINDOW_BITS, SPNG_IMG_MEM_LEVEL, SPNG_IMG_COMPRESSION_STRATEGY, SPNG_TEXT_COMPRESSION_LEVEL, SPNG_TEXT_WINDOW_BITS, SPNG_TEXT_MEM_LEVEL, SPNG_TEXT_COMPRESSION_STRATEGY, SPNG_FILTER_CHOICE, SPNG_CHUNK_COUNT_LIMIT, SPNG_ENCODE_TO_BUFFER, };其中IMG_*系列控制图像数据IDAT的 zlib 压缩参数TEXT_*系列控制文本 chunkzTXt/iTXt的压缩参数SPNG_KEEP_UNKNOWN_CHUNKS决定未知 chunk 是否保留SPNG_CHUNK_COUNT_LIMIT限制可存储 chunk 的数量默认 1000自 v0.7.0 起引入独立于 chunk 缓存上限SPNG_ENCODE_TO_BUFFER让编码器输出到库内部管理的缓冲区。各选项对解码端/编码端的影响及默认值见本文第 6 节。2.5 过滤选择位掩码spng_filter_choice编码端用于告诉 zlib 允许使用哪些过滤器做压缩预过滤按位或组合enum spng_filter_choice { SPNG_DISABLE_FILTERING 0, SPNG_FILTER_CHOICE_NONE 8, SPNG_FILTER_CHOICE_SUB 16, SPNG_FILTER_CHOICE_UP 32, SPNG_FILTER_CHOICE_AVG 64, SPNG_FILTER_CHOICE_PAETH 128, SPNG_FILTER_CHOICE_ALL (8|16|32|64|128) };注意SPNG_DISABLE_FILTERING 0与SPNG_FILTER_CHOICE_NONE 8的区别前者完全禁用过滤等价于强制 NONE后者仍允许行过滤器为 None 但会参与正常的过滤决策流程。默认值是SPNG_FILTER_CHOICE_ALL即 8|16|32|64|128。3. 上下文生命周期创建、定制分配器与销毁3.1spng_ctx_new()创建上下文spng_ctx *spng_ctx_new(int flags);flags取自spng_ctx_flags。创建解码器传0创建编码器传SPNG_CTX_ENCODER。成功返回非 NULL 的上下文句柄失败返回 NULL。3.2spng_ctx_new2()自定义内存分配器spng_ctx *spng_ctx_new2(struct spng_alloc *alloc, int flags);除了flags外额外接受一个struct spng_alloc分配器该分配器会被透传给 zlib用于控制 DEFLATE 内部的内存申请。要求alloc及其成员指针都必须非 NULL。在内存受限的嵌入式场景例如需要在 Source 引擎的 tier0 内存系统上接管所有分配中这是定制内存行为的关键入口。3.3spng_ctx_free()释放上下文void spng_ctx_free(spng_ctx *ctx);释放上下文及其内部资源。重要语义存储在上下文中的文本数据、建议调色板、未知 chunk 数据、EXIF 数据等以及通过SPNG_ENCODE_TO_BUFFER产生的内部编码缓冲区若未通过spng_get_png_buffer()取走都会随之释放。因此任何指向这些数据的指针在spng_ctx_free()之后都必须视为失效相关警告见 chunk.md 与 encode.md。4. 绑定输入/输出源流、文件与内存缓冲区libspng 支持三种数据源/目标。解码时它们提供输入编码时它们接收输出——由上下文类型是否SPNG_CTX_ENCODER自动决定方向。4.1spng_set_png_stream()回调流typedef int spng_read_fn(spng_ctx *ctx, void *user, void *dest, size_t length); typedef int spng_write_fn(spng_ctx *ctx, void *user, void *src, size_t length); int spng_set_png_stream(spng_ctx *ctx, spng_rw_fn *rw_func, void *user);读回调解码器向dest拷贝length字节成功返回0出错返回SPNG_IO_EOF数据不足/文件结束或SPNG_IO_ERROR写回调编码器处理写出src中的length字节成功返回0失败返回SPNG_IO_ERRORuser是随回调透传的自定义指针可用于携带文件句柄、网络连接或自定义缓冲结构。该 API 让 libspng 可以对接任意 I/O 抽象这正是它能在 Source SDK 2013 的包文件系统VPK/PAK环境中灵活读取资源的基础。4.2spng_set_png_file()标准文件流int spng_set_png_file(spng_ctx *ctx, FILE *file);直接绑定一个已打开的FILE*。与流回调一样每个上下文只能绑定一次This can only be done once per context。若 PNG 是从受信磁盘文件读取这是最简用法。4.3spng_set_png_buffer()内存缓冲区int spng_set_png_buffer(spng_ctx *ctx, void *buf, size_t size);绑定内存中的完整 PNG 数据decode.md。README 中的快速入门即采用此方式整块数据已在内存时免去 I/O 抽象性能最佳。共同语义解码器会把输入一直读到文件结束标记IEND为止这与 libpng 行为一致IEND 之后不再做任何解析与校验多余的尾部数据会被静默丢弃。5. 安全护栏图像与 chunk 的资源上限libspng 面向解码不可信文件的场景设计context.md提供了两对上限 API配合 usage.md 的安全清单 使用。5.1 图像尺寸上限int spng_set_image_limits(spng_ctx *ctx, uint32_t width, uint32_t height); int spng_get_image_limits(spng_ctx *ctx, uint32_t *width, uint32_t *height);设置/获取图像宽高上限上限值本身不得超过 2³¹-1解码时若文件声明的尺寸超出上限返回用户错误码如SPNG_EUSER_WIDTH/SPNG_EUSER_HEIGHT这是png_set_user_limits()的等价物spng_get_image_limits()要求width、height非 NULL。在解码之前先设好宽高上限可避免恶意 PNG 声称超大尺寸而诱导调用方分配巨量内存。5.2 chunk 尺寸与缓存上限int spng_set_chunk_limits(spng_ctx *ctx, size_t chunk_size, size_t cache_limit); int spng_get_chunk_limits(spng_ctx *ctx, size_t *chunk_size, size_t *cache_limit);默认 chunk 尺寸上限为 2³¹-1默认 chunk 缓存上限为SIZE_MAX解码过程中达到任一上限都会按内存不足错误OOM处理即致命错误文档明确指出该机制只用于限制内存占用大多数标准 chunk 本身不需要额外内存、不占用缓存额度因此即使设置了很小的缓存上限标准 chunk 仍会被照常存储。5.3 解码安全最小实践来自 usage.md对不可信输入官方要求至少做到三件事用spng_set_image_limits()设置图像宽高上限用spng_decoded_image_size()计算输出缓冲大小并与一个常量上限比对后再分配内存用spng_set_chunk_limits()限制 chunk 长度与缓存避免 OOM自 v0.6.0 起超限按 OOM 处理。这三条正是 usage.md 对安全解码不可信文件的完整定义是任何服务端/解析器集成必须遵守的底线。6. 选项管理spng_set_option()与spng_get_option()int spng_set_option(spng_ctx *ctx, enum spng_option option, int value); int spng_get_option(spng_ctx *ctx, enum spng_option option, int *value);所有选项通过键值对方式读写函数成功返回0。设置时若value超出该选项的合法范围返回非零错误码。选项的默认值与适用方向如下。6.1 解码端选项详见 decode.md 的 Decode options选项默认值说明SPNG_KEEP_UNKNOWN_CHUNKS0设为非零保留未知 chunk默认丢弃SPNG_IMG_COMPRESSION_LEVEL-1解码后可读取估计的压缩级别0-9SPNG_IMG_WINDOW_BITS15*图像解压使用的 zlib window bitsSPNG_CHUNK_COUNT_LIMIT1000已知与未知 chunk 共享的存储数量上限* 未显式设置时该选项可能被内部优化。未列出的选项对解码器无效。6.2 编码端选项详见 encode.md 的 Encode options选项默认值说明SPNG_IMG_COMPRESSION_LEVELZ_DEFAULT_COMPRESSION图像压缩级别0-9SPNG_IMG_WINDOW_BITS15*图像 zlib window bits9-15SPNG_IMG_MEM_LEVEL8图像的 zlibmemLevelSPNG_IMG_COMPRESSION_STRATEGYZ_FILTERED*图像压缩策略SPNG_TEXT_COMPRESSION_LEVELZ_DEFAULT_COMPRESSION文本压缩级别0-9SPNG_TEXT_WINDOW_BITS15文本 zlib window bits9-15SPNG_TEXT_MEM_LEVEL8文本的 zlibmemLevelSPNG_TEXT_COMPRESSION_STRATEGYZ_DEFAULT_STRATEGY文本压缩策略SPNG_FILTER_CHOICESPNG_FILTER_CHOICE_ALL*配置或禁用过滤SPNG_ENCODE_TO_BUFFER0编码到库内部缓冲区* 未显式设置时该选项可能被内部优化。编码端还有一个值得注意的行为编码器会基于 PNG 格式与压缩级别自动优化选项如果手动覆盖诸如过滤等选项可能使部分优化失效见 encode.md。7. 行信息查询spng_get_row_info()int spng_get_row_info(spng_ctx *ctx, struct spng_row_info *row_info);把当前待解码或待编码行的信息拷贝到row_info。它是渐进式处理交错Adam7图像时的定位依据。典型用法来自 decode.md 渐进式示例int error; struct spng_row_info row_info; do { error spng_get_row_info(ctx, row_info); if(error) break; void *row image image_width * row_info.row_num; error spng_decode_row(ctx, row, len); } while(!error) if(error SPNG_EOI) /* success */对于非交错图row_num线性递增对于交错图行被多次、非顺序访问务必通过row_num定位目标行缓冲。该模式同样适用于渐进式编码把spng_decode_row换成spng_encode_row。8. 上下文 API 的完整最小工作流把 context 层 API 串起来即构成官方 README 与 usage.md 展示的最小解码/编码闭环。8.1 解码任意 PNG 为 8 位 RGBA#include spng.h /* 1. 创建解码上下文 */ spng_ctx *ctx spng_ctx_new(0); /* 2. 绑定输入缓冲区 */ spng_set_png_buffer(ctx, buf, buf_size); /* 3. 计算输出图像大小 */ spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, out_size); /* 4. 解码为 8 位 RGBA与 PNG 原始格式无关 */ spng_decode_image(ctx, out, out_size, SPNG_FMT_RGBA8, 0); /* 5. 释放上下文 */ spng_ctx_free(ctx);注意步骤 3 与 4 中out_size必须一致out缓冲区长度需不小于spng_decoded_image_size()的计算结果否则返回SPNG_EBUFSIZ。若传入SPNG_DECODE_PROGRESSIVE标志out/len会被忽略改由spng_decode_row()逐行取数。8.2 编码到库内部缓冲区/* 1. 创建编码上下文 */ spng_ctx *enc spng_ctx_new(SPNG_CTX_ENCODER); /* 2. 启用内部输出缓冲区 */ spng_set_option(enc, SPNG_ENCODE_TO_BUFFER, 1); /* 3. 设置图像头宽高、颜色类型、位深、交错方式 */ struct spng_ihdr ihdr { .width w, .height h, ... }; spng_set_ihdr(enc, ihdr); /* 4. 编码并最终化 PNG */ spng_encode_image(enc, img, img_size, SPNG_FMT_RGBA8, SPNG_ENCODE_FINALIZE); /* 5. 取回编码结果调用方负责释放 */ size_t png_size; void *png spng_get_png_buffer(enc, png_size, error); /* 6. 释放上下文 */ spng_ctx_free(enc);若未启用SPNG_ENCODE_TO_BUFFER则需先通过spng_set_png_stream()或spng_set_png_file()指定输出目标无论哪种方式PNG 都必须显式最终化——要么在spng_encode_image()传SPNG_ENCODE_FINALIZE要么之后调用spng_encode_chunks()写出 IEND 标记encode.md。9. 错误处理约定与在 Source SDK 2013 中的集成形态9.1 错误码约定所有 spng 函数遵循统一约定成功返回0SPNG_OK失败返回非零错误码SPNG_IO_EOF -1、SPNG_IO_ERROR -2为特殊负数其余错误码均为正枚举值。完整错误码清单见 spng.h 的enum spng_errno涵盖签名错误SPNG_ESIGNATURE、尺寸超限SPNG_EUSER_WIDTH/SPNG_EUSER_HEIGHT、chunk CRC 错误SPNG_ECHUNK_CRC、zlib 错误SPNG_EZLIB、格式不支持SPNG_EFMT、渐进式结束SPNG_EOI等。关键语义errors.md 与 decode.md 错误处理不可恢复错误整数溢出、OOM、解码错误等会导致上下文进入坏状态此后所有函数调用一律返回SPNG_EBADSTATE防止未定义行为解码端非关键错误默认策略刻意模拟 libpng 以兼容现有图片——CRC 无效的辅助 chunk 被丢弃、非法调色板索引按黑色不透明像素处理、截断数据一律视为关键错误可用spng_strerror(err)把错误码转成人类可读的错误消息字符串。9.2 在 source-sdk-2013 中的集成方式在本仓库中libspng 被完整捆绑于 src/thirdparty/libspng并做了面向引擎的适配spng.h 顶部带有// VALVE注释并定义SPNG_STATIC 1强制以静态库方式链接避免在 Source 引擎的 DLL 边界上产生导出符号冲突仓库根下已预编译出 libspng.a 静态库并配套 libspng.vpc 工程描述文件说明该库被纳入 Source SDK 2013 的 VPC 构建体系该库自带 示例程序、测试套件 与 模糊测试入口后两者与 README 声称的 OSS-Fuzz 模糊测试实践相印证是验证上述上下文 API 行为边界的最直接依据。因此在阅读或扩展 Source SDK 2013 中任何与 PNG 解码相关的代码时context.md 所描述的这组通用 API 就是全部操作的公共底座先spng_ctx_new拿到句柄再绑定数据源、设好限制与选项最后执行解码/编码并检查每个返回值。10. 总结context 层 API 是掌握 libspng 的钥匙spng_ctx上下文模型把创建与销毁spng_ctx_new/spng_ctx_new2/spng_ctx_free、数据源绑定spng_set_png_stream/spng_set_png_file/spng_set_png_buffer、安全护栏spng_set_image_limits/spng_set_chunk_limits、选项管理spng_set_option/spng_get_option与渐进式行信息spng_get_row_info统一在了一个小而完备的 API 面里。配合spng_format、spng_filter、spng_filter_choice等枚举以及解码/编码文档中更细化的标志位与组合表你可以不依赖 libpng 的复杂回调机制仅凭十几个函数完成从不可信输入到像素缓冲或反向的完整闭环。继续深入可依次阅读仓库内的 usage.md基本用法、chunk.mdchunk 读写语义、decode.md解码 API 与渐进式解码与 encode.md编码 API 与渐进式编码并在 spng.h 中核对每个数据类型的真实定义——上下文层之后就是像素级的世界。【免费下载链接】source-sdk-2013The 2013 edition of the Source SDK项目地址: https://gitcode.com/GitHub_Trending/so/source-sdk-2013创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表