先别急着替换图片或清理缓存。如果同一份 NSData 经 UIImage、ImageIO 等系统路径能够正常显示,经过 YYImage 后却出现规则条纹,那么首要嫌疑就不是文件损坏,而是 YYImage 后续的像素布局处理。
热文对应版本的问题出在行跨度计算的那一行代码上:JPEG 扫描线已经按解码器实际输出的格式写入缓冲区,YYImage 却又使用了另一套 bytesPerRow。第一行可能仍能正常显示,越往后偏移越明显,扫描线最终会因错位而拼成条纹。这与 iOS 14 上 YYAnimatedImageView 未正确处理 displayLayer: 导致的整张黑屏不是同一条故障路径。
同一份 JPEG,为什么系统正常、YYImage 却出条纹
排查的第一步是固定同一份数据,而不是分别找两张“看起来一样”的图片。记录文件哈希和字节长度:
shasum -a 256 sample.jpg
wc -c < sample.jpg
然后在应用内做最小 A/B 对照:
NSData *data = [NSData dataWithContentsOfFile:path];
UIImage *systemImage = [UIImage imageWithData:data];
YYImage *yyImage = [YYImage imageWithData:data];
NSLog(@"system: %@", NSStringFromCGSize(systemImage.size));
NSLog(@"yy: %@", NSStringFromCGSize(yyImage.size));
这个对照要保留原始 NSData,不要让两条路径分别读取文件。若系统结果正常,而 YYImage 结果尺寸相同但像素出现条纹,排查范围就基本收敛到 YYImage 的解码与像素缓冲转换;如果尺寸也不同,再检查方向、缩放和色彩配置。
YYImage 本身会把多格式解码、帧序列管理和 UIKit 渲染串成一条处理链,因此不能只看最终 UIImageView 的显示效果。排查时至少要分别记录原始数据尺寸、解码后 CGImage 的宽高、方向以及固定 RGBA 缓冲区中的像素结果。网络图片也要先确认实际下载得到的 NSData 与源文件哈希一致;长度或哈希不符,应先处理下载不完整、缓存污染或资源重写,而不是修改解码器。
真正出错的是那一行像素布局代码
问题定位在 YYImage/YYImageDecoder.m 的 -createCGImageAtIndex: 路径,具体是该版本 JPEG 分支计算 bytesPerRow 的代码。热文对应的错误行把每行字节数固定写成了 width * 4,但扫描线实际使用的是解码器给出的 output_components,对应字节数应由 bytesPerPixel 决定。
最小修复应类似下面这样:
- const size_t bytesPerRow = width * 4;
+ const size_t bytesPerRow = width * bytesPerPixel;
这里不能简单把 4 换成另一个固定数字。RGB、灰度等 JPEG 路径的每像素字节数可能不同,可靠做法是让 bytesPerPixel、bytesPerRow、缓冲区长度、位图上下文和最终的 CGImageCreate 参数共用同一个尺寸约定:
const size_t bytesPerPixel = /* 解码器实际输出组件数 */;
const size_t bytesPerRow = width * bytesPerPixel;
const size_t bufferSize = bytesPerRow * height;
假设实际扫描线每行占 3w 字节,位图创建却按 4w 前进,读取位置就会逐行漂移:
实际缓冲:| row 0:3w | row 1:3w | row 2:3w |
错误步长: 0 4w 8w
从第二行开始,读取位置便不再落在真实行边界上,后续扫描线被错位拼接,于是出现条纹。系统解码器正常,是因为 JPEG 文件本身仍可被正确解析;错误发生在 YYImage 对解码结果的二次像素布局环节。
补丁必须绑定具体源码提交。合并前先记录版本:
git rev-parse HEAD
git diff -- YYImage/YYImageDecoder.m
如果上游后续提交已经修复此处,就直接升级到包含该提交的版本;如果没有,就保留本地补丁及对应提交说明。不要把行号当作版本标识,也不要在未核对 bytesPerPixel 来源时机械套用 diff。
改完这一行,怎样证明不是假修复
验证可以分为四层。
第一层是同图对照。比较系统图片和修复后 YYImage 图片的尺寸、方向,再把它们渲染到尺寸与格式完全相同的 RGBA 缓冲区中比较像素。下面的 PNG 比较可以作为快速检查,但不能替代正式的像素断言:
XCTAssertTrue(CGSizeEqualToSize(systemImage.size, yyImage.size));
XCTAssertEqualObjects(UIImagePNGRepresentation(systemImage),
UIImagePNGRepresentation(yyImage));
第二层是变体样本。至少覆盖不同宽高、带或不带 ICC/色彩配置、基线式与渐进式 JPEG,并记录每个样本修复前是否触发、修复后是否一致。不能预先认定某一种编码形式一定触发,实际影响范围要以热文对应版本和本地实测为准。
第三层是验证真实数据链。本地文件与下载文件都要测;网络场景应核对响应状态、实际 NSData.length、文件哈希和缓存内容,并增加一次清缓存或冷启动测试。若网络下载后的字节已经不同,继续调像素代码没有意义。
第四层是工程落地。把最初触发问题的 JPEG 固定为回归样本,保留预期哈希和尺寸,同时覆盖本地加载、网络加载及缓存命中。上游有修复版本就升级并记录版本;没有则维护最小本地补丁。无法立即改框架时,可以暂时让受影响的静态 JPEG 走 [UIImage imageWithData:],但这只是绕过 YYImage 的相关路径,不是根治方案。
执行顺序:锁定 YYImage 的 tag 与 commit,建立同一 NSData 的最小复现,应用上述最小 diff,跑完四层验证,将固定样本加入回归测试,最后合并修复。
[email protected]
。
我们承诺在核实信息后,将第一时间删除相关内容或断开链接。






















暂无评论内容