libspng 解码指南source-sdk-2013 内置 PNG 解码库的格式转换、错误处理与渐进式解码实战【免费下载链接】source-sdk-2013The 2013 edition of the Source SDK项目地址: https://gitcode.com/GitHub_Trending/so/source-sdk-2013libspngsimple PNG是随 source-sdk-2013 仓库一起引入的 C 语言 PNG 读写库本指南以其官方文档 decode.md 为骨架围绕解码器decoder展开先讲清spng_crc_action、spng_decode_flags两类核心数据类型再剖析关键/非关键错误与校验和处理策略随后逐个讲解spng_set_png_buffer()、spng_decode_image()、spng_decode_scanline()、spng_decode_row()等解码 API 的签名、语义与组合矩阵最后给出一次性解码与渐进式解码两种实战代码范式。读完本文你将能在自己的 C/C 项目中安全、高效地完成任意 PNG 到指定输出格式的解码并掌握 libpng 迁移时对应的行为映射。背景libspng 在 source-sdk-2013 中的位置libspng 位于仓库的 src/thirdparty/libspng 目录随 SDK 以源码形式分发其核心实现集中在 spng/spng.c 与 spng/spng.h。从 spng.h 可以看到本仓库内置的版本为 0.7.2SPNG_VERSION_MAJOR 0 / MINOR 7 / PATCH 2且该头文件在 spng.h 中通过#define SPNG_STATIC 1强制静态链接VALVE 定制意味着解码能力直接内置于构建产物无需额外动态库。官方对库的定位是以安全和易用为目标的 PNG 读写库与 libpng 是相互独立的项目、API 不兼容见 README.md。核心数据类型解码行为从枚举开始spng_crc_action块校验错误的处置策略enum spng_crc_action { /* Default for critical chunks */ SPNG_CRC_ERROR 0, /* Discard chunk, invalid for critical chunks, since v0.6.2: default for ancillary chunks */ SPNG_CRC_DISCARD 1, /* Ignore and dont calculate checksum */ SPNG_CRC_USE 2 };三个取值分别对应SPNG_CRC_ERROR0关键块critical chunk的默认策略。CRC 错误即视为关键错误解码失败。SPNG_CRC_DISCARD1丢弃该块继续解析。自 v0.6.2 起是辅助块ancillary chunk的默认策略对关键块无效。SPNG_CRC_USE2忽略校验和甚至不计算。自 v0.6.2 起对任一块类型启用该值DEFLATE 流内的 Adler-32 校验也会一并被忽略。spng_decode_flags解码标志位enum spng_decode_flags { SPNG_DECODE_USE_TRNS 1, /* Deprecated */ SPNG_DECODE_USE_GAMA 2, /* Deprecated */ SPNG_DECODE_TRNS 1, /* Apply transparency */ SPNG_DECODE_GAMMA 2, /* Apply gamma correction */ SPNG_DECODE_PROGRESSIVE 256 /* Initialize for progressive reads */ };注意同一数值存在新旧两套命名SPNG_DECODE_USE_TRNS与SPNG_DECODE_USE_GAMA已被标记 Deprecated当前应使用SPNG_DECODE_TRNS应用 tRNS 块定义的透明信息与SPNG_DECODE_GAMMA应用 gamma 校正SPNG_DECODE_PROGRESSIVE256用于把解码器初始化为渐进式读取模式。在 spng.h 中还额外定义了SPNG_DECODE_USE_SBIT 8未文档化说明该枚举存在内部扩展空间。错误处理模型关键错误与非关键错误解码错误被严格划分为两类详见 decode.md 与配套的 errors.md。关键错误critical不可恢复一旦发生必须假定解码已完全失败任何部分图像输出都无效即使损坏的 PNG 每次可能解码出相同的部分图像也不能依赖这一行为。关键错误会立即停止后续解析、使上下文失效并返回对应错误码。此后的大多数函数都会检查上下文状态并返回SPNG_EBADSTATE以防未定义行为。因此文档强烈建议检查每一个返回值。非关键错误non-critical可确定性处理它们本质上是文件损坏问题可通过忽略校验和或丢弃无效块来容忍。图像仍能被一致地提取出来但可能丢失色彩精度、透明度等信息。解码器默认行为刻意模拟 libpng 以兼容现存文件因此大多数非关键错误被直接忽略目前解码器的严格程度配置仅限于校验和。具体到行为规则decode.md无效的调色板索引按黑色、不透明像素处理含非拉丁字符的tEXt、zTXt块视为合法辅助块在以下任一情况被丢弃块 CRC 无效辅助块默认SPNG_CRC_DISCARD、DEFLATE 流无效默认包含 Adler-32 校验错误、块自身语义错误长度异常、取值越界等关键块出现 CRC 或 Adler-32 错误则停止解析除非另行配置多余的尾部图像数据被静默丢弃IEND结束标记之后不再做任何解析或验证。截断的 PNG 与截断的图像数据始终按关键错误处理渐进式解码可能得到部分图像但无法保证在所有情况下都成立——解码器发出的读取回调可能跨越多个行甚至整幅图像部分读取不会被处理。此外 libspng 在以下两种情形会停止解码图像单行超过 4 GB发生算术溢出32 位平台上的极端情况。校验和CRC-32 与 Adler-32PNG 使用两类校验和块数据的 32 位 CRC以及 DEFLATE 流内的 Adler-32。以SPNG_CTX_IGNORE_ADLER32标志创建上下文会使 zlib 忽略 Adler-32 校验压缩元数据与图像数据都生效。该选项仅支持 zlib 1.2.11且编译进 miniz 时不可用。块 CRC 的处置由spng_set_crc_action()配置对任一块类型使用SPNG_CRC_USE时DEFLATE 流中的 Adler-32 校验也会被忽略。当对两类块都设置SPNG_CRC_USE时效果等同于SPNG_CTX_IGNORE_ADLER32——但反之不成立。目前不对 DEFLATE 流中的 Adler-32 错误与其他错误作区分统一映射到SPNG_EZLIB错误码。libpng 对照png_set_crc_action()的功能子集libpngspng备注PNG_CRC_ERROR_QUITSPNG_CRC_ERROR出错时不中止PNG_CRC_WARN_DISCARDSPNG_CRC_DISCARD无警告系统PNG_CRC_QUIET_USESPNG_CRC_USE—SPNG_CTX_IGNORE_ADLER32上下文标志则与 libpng 中png_set_option()配合PNG_IGNORE_ADLER32的效果相同。所有错误码的完整清单含SPNG_ECHUNK_CRC、SPNG_EZLIB、SPNG_EBADSTATE、SPNG_EOI等定义在 spng.h可通过spng_strerror()取得可读字符串。内存使用模型与限额解码器的内存占用遵循以下规则decode.md库始终分配一个上下文缓冲区输入为流或文件时还会额外创建读缓冲区解码一幅图像至少需要在内存中保留 2 行像素隔行扫描interlaced图像需要 3 行当输出格式为每通道 16 位如SPNG_FMT_RGBA16且启用 gamma 校正时还需要额外 128 KB 查找表用spng_set_image_limits()设置图像宽高上限以限制解码内存等价于 libpng 的png_set_user_limits()用spng_decoded_image_size()计算输出尺寸并可在分配输出内存前与硬性上限比对任意长度的块文本、色彩配置文件等会额外占用内存spng_set_chunk_limits()用于设定块长度上限与缓存上限触及任一上限按致命错误处理自 v0.7.0 起SPNG_CHUNK_COUNT_LIMIT选项控制可存储的块数量默认1000可通过spng_set_option()配置见 context.md该限制独立于块缓存上限。对应到源码spng.c 中spng_decoded_image_size()会先触发read_chunks()解析块、经check_decode_fmt()校验输出格式合法性再调用calculate_image_size()计算尺寸这解释了为什么该函数要求必须先设置输入 PNG。解码 API 逐个解析spng_set_png_buffer()int spng_set_png_buffer(spng_ctx *ctx, void *buf, size_t size)设置输入 PNG 缓冲区。每个上下文只能调用一次。注意 spng.h 中该函数参数实际为const void *buf可安全传入只读内存。除缓冲区外还可通过 context.md 中介绍的spng_set_png_stream()自定义读写回调spng_read_fn/spng_write_fn或spng_set_png_file()FILE *指定输入源三者择一、同样只能调用一次。spng_set_crc_action()int spng_set_crc_action(spng_ctx *ctx, int critical, int ancillary)分别设置关键块与辅助块 CRC 错误时的处理方式取值即上文spng_crc_action枚举。spng_decoded_image_size()int spng_decoded_image_size(spng_ctx *ctx, int fmt, size_t *out)计算给定输出格式下的解码图像缓冲区大小。要求必须已设置输入 PNG。如 spng.c 所示该函数内部会完成块解析、格式合法性检查与尺寸计算失败时返回对应错误码。spng_decode_chunks()int spng_decode_chunks(spng_ctx *ctx)解码图像数据IDAT流之前或之后的所有块具体取决于解码器所处状态。若图像已解码该函数会读取到文件结束标记IEND。在spng_decode_image()之前调用它是可选的。spng_decode_image()int spng_decode_image(spng_ctx *ctx, void *out, size_t len, int fmt, int flags)一次性解码整幅 PNG 并写入out同时把图像从 PNG 格式转换到目标格式fmt。关键语义隔行图像会被去隔行deinterlace16 位图像转换为宿主机字节序out必须指向长度为len的缓冲区len必须等于或大于spng_decoded_image_size()用同一输出格式算出的值若设置SPNG_DECODE_PROGRESSIVE则仅用fmt与flags初始化解码器进入渐进式模式out、len的值被忽略SPNG_DECODE_TRNS标志在 PNG 不含 tRNS 块或不适用于当前颜色类型时被静默忽略每个上下文只能调用一次。从源码看非渐进式路径内部正是循环调用spng_decode_row()逐行填充输出直到返回SPNG_EOI为止spng.c并以ri-row_num * ctx-image_width计算行偏移——与文档中给出的手动渐进式写法完全一致。支持的格式与标志组合下表来自 decode.md* 表示标准定义的任意颜色类型与位深组合** 表示 gamma 校正未实现PNG 格式输出格式标志备注任意格式*SPNG_FMT_RGBA8全部从任意 PNG 格式与位深转换任意格式SPNG_FMT_RGBA16全部从任意 PNG 格式与位深转换任意格式SPNG_FMT_RGB8全部从任意 PNG 格式与位深转换灰度 ≤8 位SPNG_FMT_G8无**仅对 1、2、4、8 位灰度 PNG 有效灰度 16 位SPNG_FMT_GA16全部**仅对 16 位灰度 PNG 有效灰度 ≤8 位SPNG_FMT_GA8全部**仅对 1、2、4、8 位灰度 PNG 有效任意格式SPNG_FMT_PNG无**PNG 自身格式宿主机字节序任意格式SPNG_FMT_RAW无PNG 自身格式大端字节序SPNG_DECODE_PROGRESSIVE在所有组合下都受支持。所有输出格式的通道始终按字节序表示alpha 通道一律为直通 alphastraight alpha不支持预乘 alpha见 context.md。SPNG_FMT_PNG/SPNG_FMT_RAW这两种零转换格式不做任何缩放变换如 gamma 校正且小于 8 位的图像保持字节打包byte-packed索引色图像输出的是调色板索引——example.c 中正是据此在检测到索引色后改用SPNG_FMT_RGB8展开。渐进式图像解码设置SPNG_DECODE_PROGRESSIVE标志后解码器以fmt和flags初始化out、len被忽略随后逐行读取。非隔行图像可直接按行序调用spng_decode_row()最后一行返回SPNG_EOIint error; size_t image_width image_size / ihdr.height; for(i 0; i ihdr.height; i) { void *row image image_width * i; error spng_decode_row(ctx, row, image_width); if(error) break; } if(error SPNG_EOI) /* success */但隔行图像的行会被多次且非顺序地访问必须配合spng_get_row_info()获取当前行号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会线性递增同样成立。spng_row_info结构体包含scanline_idx、row_num去隔行后的行索引、pass、filter四个字段spng.hexample.c 的完整渐进式解码范例在出错时还会利用row_info.pass与row_info.scanline_idx打印最后一次 pass / 扫描线以辅助定位损坏位置。源码层面spng_get_row_info()在解码器未初始化或已到达结束状态时分别返回错误或SPNG_EOIspng.c。spng_decode_scanline()int spng_decode_scanline(spng_ctx *ctx, void *out, size_t len)解码一条扫描线到out。要求先以SPNG_DECODE_PROGRESSIVE标志调用spng_decode_image()完成初始化。最宽扫描线为解码图像尺寸除以ihdr.height。最后一条扫描线及此后的调用返回SPNG_EOI。spng_decode_row()int spng_decode_row(spng_ctx *ctx, void *out, size_t len)解码并去隔行一条扫描线到out。同样要求渐进式初始化。行宽为解码图像尺寸除以ihdr.height。对隔行图像行会被多次且非顺序访问需用spng_get_row_info()获取行号。最后一行及后续调用返回SPNG_EOI。对非隔行图像其行为与spng_decode_scanline()完全一致源码中两者共享核心路径spng.c 附近可看到spng_decode_row的实现。解码选项一览通过spng_set_option()/spng_get_option()见 context.md可配置以下对解码器生效的选项选项默认值说明SPNG_KEEP_UNKNOWN_CHUNKS0设置为保留或丢弃未知块SPNG_IMG_COMPRESSION_LEVEL-1spng_decode_image()之后可能暴露一个估计值0-9SPNG_IMG_WINDOW_BITS15*设置图像解压所用的 zlib 窗口位数SPNG_CHUNK_COUNT_LIMIT1000已知与未知块共用的块数量上限* 未显式设置时该选项可能被优化。文档明确提示未列出的选项对解码器无效。实战安全解码完整流程将上述知识串起来一次安全的解码调用应遵循 usage.md 给出的最小流程#include spng.h /* Create a context */ spng_ctx *ctx spng_ctx_new(0); /* Set an input buffer */ spng_set_png_buffer(ctx, buf, buf_size); /* Calculate output image size */ spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, out_size); /* Get an 8-bit RGBA image regardless of PNG format */ spng_decode_image(ctx, out, out_size, SPNG_FMT_RGBA8, 0); /* Free context memory */ spng_ctx_free(ctx);面向不可信文件时usage.md 明确要求至少做三件事用spng_set_image_limits()设置图像宽高上限用spng_decoded_image_size()计算输出尺寸并与常量上限比对后再分配内存用spng_set_chunk_limits()设定块大小与块缓存上限以避免内存耗尽自 v0.6.0 起超限按内存不足错误处理。example.c 提供了加固范例先用spng_set_crc_action(ctx, SPNG_CRC_USE, SPNG_CRC_USE)忽略并跳过 CRC 计算再把块大小与缓存上限都设为1024 * 1024 * 6464 MB再设置输入源。项目的测试体系进一步印证了这些边界tests/下既有覆盖全部颜色类型/位深与格式/标志组合的正确性测试要求与 libpng 输出逐位一致、gamma 校正图像误差在 2% 以内见 tests/README.md也有crashers/目录中针对超大块huge_*_chunk.png、错误 CRCbadcrc.png、错误 Adler-32badadler.png、缺失 PLTEmissing_plte.png等回归用例可作为解码器健壮性验证与模糊测试的参考素材。小结libspng 的解码设计围绕安全、简单、兼容 libpng 行为三个目标展开用 CRC/Adler-32 双层校验与关键/非关键错误分级来定义故障模型用 8 种输出格式与有限的解码标志组合来覆盖全部合法 PNG 规格用一次性与渐进式两条解码路径来适配不同内存与流式场景并用图像/块/块数量三级限额来约束不可信输入的资源占用。掌握了 decode.md 中的数据类型、错误处理、内存模型、API 语义与两张组合表再对照 spng.h、spng.c 与 example.c 的源码实现即可在 source-sdk-2013 项目中安全、稳定地集成 PNG 解码能力。【免费下载链接】source-sdk-2013The 2013 edition of the Source SDK项目地址: https://gitcode.com/GitHub_Trending/so/source-sdk-2013创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考