深入理解代替单纯记忆
先看问题:原图正常,App 大图却扭曲
用户发布的是一张横拍 JPEG。把原图下载下来或在浏览器中打开,内容完全正常;但 iOS App 的大图页会显示为横向条纹和拉伸。


问题样本的原始像素是 4000 × 3000 ,EXIF Orientation 是 6(Right) 。这表示:像素数据本身是横向存储的,展示前还需要向右旋转 90°。
因此,问题不在 CDN、下载过程或图片上传结果,而在客户端对这张"需要旋转"的 JPEG 进行解码和方向归一化时。
真正的处理过程:src、tmp、dest 分别是什么
YYImage是如何做图像的旋转的?
下面用一张由"绿植、杯子、苹果、手机"等局部组成的缩小照片举例。
每个格子代表照片的一块区域:旋转后,照片内容已在 tmp 中转正;dest 的照片内容相同,但它是 vImage 为最终显示格式新分配的另一块内存。YYImage 最终还要将这块内存数据转为可使用的CGImage格式

为什么需要 tmp? 旋转不能在原图内存上边读边写:先写入的新位置可能覆盖尚未读取的原始像素。因此需要 tmp 作为新的旋转画布。
为什么还需要 dest? tmp 使用旋转函数方便处理的 ARGB 格式;如果最终显示需要不同的字节序或 alpha 格式,vImage 会新建 dest 做格式转换。若格式相同,代码会直接返回 tmpImage,不会创建 dest。
错误怎样变成条纹
-
dest是 vImage 生成的vImage_Buffer。vImage 为了让下一行从更适合 CPU / SIMD 读取的内存地址开始,可能把每行实际占用的字节数补齐到 16、32 或其他对齐边界- 下图中的灰色部分就是这段对齐填充字节:它占用内存,但不是图像像素,正常情况下不会显示出来。
-
错误发生在dest转为
CGImage的时候( 对应YYImageCoder.m中YYCGImageCreateAffineTransformCopy方法) -
创建
CGImage时错误地使用了destBytesPerRow,而不是dest.rowBytes。这会让CGImage按错误的行距解释同一块dest.data:第一行通常正常,从第二行开始会从错误偏移读取像素,最终绘制成条纹/拉伸
下图展示了出错的过程:

scss
// 此处 dest 由 vImageBuffer_Init 在 helper 内部重新分配
YYCGImageDecodeToBitmapBufferWith32BitFormat(tmpImage, &dest, destBitmapInfo);
// 错:dest.data 的实际行距应是 dest.rowBytes
destImage = CGImageCreate(destWidth, destHeight, 8, 32,
destBytesPerRow, ...);
为什么YYImage 使用错误的destBytesPerRow? destBytesPerRow 是 YYImage 按宽度推算出来的,这是它认为的最终 dest 每行应有的字节数,并非实际的字节数
怎么修:使用 vImage 实际给出的行距
修复只改两处:Provider 的总字节数和 CGImage 的每行字节数都以 dest.rowBytes 为准。
ini
// dest.rowBytes 是 vImage 实际输出缓冲区的行距
destProvider = CGDataProviderCreateWithData(dest.data, dest.data,
dest.height * dest.rowBytes,
YYCGDataProviderReleaseDataCallback);
destImage = CGImageCreate(destWidth, destHeight, 8, 32,
dest.rowBytes, // 与 dest.data 的真实内存布局一致
YYCGColorSpaceGetDeviceRGB(), destBitmapInfo,
destProvider, NULL, false,
kCGRenderingIntentDefault);
这是组件层的根因修复:不需要业务侧关闭 EXIF,也不需要把每张图先重绘、再重新编码。所有经过该旋转函数的路径都会使用正确的内存布局。
什么场景更容易复现
| 特征 | 为什么值得重点排查 |
|---|---|
| JPEG 的 EXIF Orientation 不是 Up | 需要执行方向旋转,才会进入这段代码;Right(6)和 Left(8)尤其典型。 |
| Android / 三星手机横拍图片 | 本次问题样本属于这一类。不是三星专属,但这类来源的横拍 JPEG 更容易带有需要旋转的 EXIF 元数据。 |
| 原图、浏览器正常,只有 iOS App 异常 | 说明文件内容大概率正常,应优先检查 App 是否由 YYImage 执行了方向归一化。 |
| 同类图偶现而非必现 | 实际行距是否与预计行距不同受图像尺寸、像素格式、系统和内存对齐影响;这正是问题看起来罕见的原因。 |
补充:YYImage Issue #131 也在方向修正后出现乱码,最终同样会走到这个旋转函数;该 issue 的业务层做法是先手动转正以绕开问题,而本修复直接更正了底层行距。