跪拜 Guibai
← All articles
Swift · iOS

YYImage's Wrong Row Stride Turns EXIF-Rotated JPEGs into Stripes on iOS

By songgeb ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

iOS apps that normalize camera photos with YYImage can silently corrupt landscape EXIF-tagged JPEGs until they adopt this two-line stride fix; the symptom looks like a download or encoding problem at the business layer, but the bug lives in the image library's memory handling.

Summary

Landscape JPEGs that carry EXIF orientation 6 or 8 trigger YYImage's rotation pipeline. Inside that pipeline, vImage writes the rotated pixels into a temporary 'dest' buffer and pads each row to a CPU/SIMD-friendly alignment boundary. YYImage then creates a CGImage from that buffer, passing destBytesPerRow, its own width-based estimate, instead of dest.rowBytes, the actual stride vImage reports. The first row lands correctly; every subsequent row reads from a drifting offset, which is why users see horizontal stripes on the large-image page.

The fix touches two lines: give both the data provider and CGImageCreate the true dest.rowBytes from vImage. That corrects the memory layout for every path through the affine transform helper, so teams no longer need to re-encode images or strip EXIF orientation. The bug shows up most reliably with landscape photos from Android or Samsung devices, because those shots commonly carry rotation metadata, and it is intermittent because padding depends on image size, pixel format, system, and memory alignment.

Takeaways
— YYImage's rotation path creates a vImage_Buffer as dest; vImage may pad each row's bytes to a CPU/SIMD-friendly boundary.
— The bug is in YYCGImageCreateAffineTransformCopy: CGImageCreate receives destBytesPerRow (estimated from width) instead of dest.rowBytes (actual stride).
— Only images whose EXIF Orientation is not 'Up' enter the rotation path; Right (6) and Left (8) are the typical triggers.
— Landscape JPEGs from Android/Samsung devices are common culprits because they often carry rotation metadata.
— Fix both the CGDataProvider total byte count and CGImageCreate's bytes-per-row using dest.rowBytes; a business-side workaround like re-encoding or stripping EXIF becomes unnecessary.
Conclusions

The failure's intermittency is a direct consequence of vImage's alignment padding: whether the computed stride differs from the actual rowBytes depends on image dimensions and system alignment, so the same class of photo may render fine in one build and corrupt in another.

The bug is a reminder that bridging vImage buffers into CGImage requires trusting the buffer's reported rowBytes, not a recomputed width-based value; one-off-by-a-few bytes silently corrupts every row after the first.

The same underlying mismatch appears in YYImage issue #131, where a workaround re-oriented the image client-side; fixing the stride once is cheaper than patching every consumer.

Concepts & terms
rowBytes
The actual number of bytes vImage uses for each row of a buffer after alignment padding, as reported by vImage_Buffer. It can be larger than width × bytesPerPixel, and CGImage creation must use it as bytesPerRow.
EXIF Orientation
A metadata field that tells the renderer how to rotate or flip the stored pixel data before display; values 6 and 8 mean 90° clockwise and counter-clockwise rotations, so the raw pixel rows are stored in a different physical order.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗