@@ -106,20 +106,42 @@ - (void)prepareForDisplay:(void (^)(UIImage *_Nullable))completionHandler{
106106
107107
108108 dispatch_async ([self prepareQueue ], ^{
109- UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc ]
110- initWithSize: theImage.size
111- format: [UIGraphicsImageRendererFormat preferredFormat ]];
112- UIImage *prepared = [renderer imageWithActions: ^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
113- [theImage drawAtPoint: CGPointZero];
114- }];
115-
116- typeof (self) strongSelf = weakSelf;
117- if (strongSelf){
118- @synchronized (strongSelf){
119- if (prepared){
120- strongSelf->_image = prepared;
109+ @autoreleasepool {
110+ UIImage *prepared = nil ;
111+
112+ // Check image dimensions to prevent memory spikes
113+ CGFloat imageWidth = theImage.size .width * theImage.scale ;
114+ CGFloat imageHeight = theImage.size .height * theImage.scale ;
115+ const CGFloat maxDimension = 4096.0 ; // Max dimension limit
116+
117+ // Skip rendering for oversized images to avoid memory spikes
118+ if (imageWidth > maxDimension || imageHeight > maxDimension) {
119+ // Use original image for oversized images
120+ prepared = theImage;
121+ } else {
122+ UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat preferredFormat ];
123+ CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo (theImage.CGImage );
124+ BOOL hasAlpha = (alphaInfo != kCGImageAlphaNone &&
125+ alphaInfo != kCGImageAlphaNoneSkipFirst &&
126+ alphaInfo != kCGImageAlphaNoneSkipLast );
127+ format.opaque = !hasAlpha;
128+
129+ UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc ]
130+ initWithSize: theImage.size
131+ format: format];
132+ prepared = [renderer imageWithActions: ^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
133+ [theImage drawAtPoint: CGPointZero];
134+ }];
135+ }
136+
137+ typeof (self) strongSelf = weakSelf;
138+ if (strongSelf){
139+ @synchronized (strongSelf){
140+ if (prepared){
141+ strongSelf->_image = prepared;
142+ }
143+ completionHandler (prepared);
121144 }
122- completionHandler (prepared);
123145 }
124146 }
125147 });
@@ -133,57 +155,127 @@ - (void)prepareForDisplay:(void (^)(UIImage *_Nullable))completionHandler{
133155}
134156
135157- (UIImage *)image {
136- if (!_image) {
137- UIImage *tmp;
158+ @synchronized (self) {
159+ if (_image) {
160+ return _image;
161+ }
162+ }
163+
164+ @autoreleasepool {
165+ UIImage *decodedImage = nil ;
166+
138167 if (_data) {
139- CGFloat view_width = _imageViewSize.width ;
140- CGFloat view_height = _imageViewSize.height ;
141- if (_downSample && view_width > 0 && view_height > 0 ) {
142- CGFloat scale = self.scale ;
143- NSDictionary *options = @{ (NSString *)kCGImageSourceShouldCache : @(NO ) };
144- CGImageSourceRef ref = CGImageSourceCreateWithData ((__bridge CFDataRef)_data, (__bridge CFDictionaryRef)options);
145- if (ref) {
146- NSInteger width = 0 , height = 0 ;
147- CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex (ref, 0 , NULL );
148- if (properties) {
149- CFTypeRef val = CFDictionaryGetValue (properties, kCGImagePropertyPixelHeight );
150- if (val)
151- CFNumberGetValue (val, kCFNumberLongType , &height);
152- val = CFDictionaryGetValue (properties, kCGImagePropertyPixelWidth );
153- if (val)
154- CFNumberGetValue (val, kCFNumberLongType , &width);
155- if (width > (view_width * scale) || height > (view_height * scale)) {
156- NSInteger maxDimensionInPixels = MAX (view_width, view_height) * scale;
157- NSDictionary *downsampleOptions = @{
158- (NSString *)kCGImageSourceCreateThumbnailFromImageAlways : @(YES ),
159- (NSString *)kCGImageSourceShouldCacheImmediately : @(YES ),
160- (NSString *)kCGImageSourceCreateThumbnailWithTransform : @(YES ),
161- (NSString *)kCGImageSourceThumbnailMaxPixelSize : @(maxDimensionInPixels)
162- };
163- CGImageRef downsampleImageRef = CGImageSourceCreateThumbnailAtIndex (ref, 0 , (__bridge CFDictionaryRef)downsampleOptions);
164- tmp = [UIImage imageWithCGImage: downsampleImageRef scale: scale orientation: UIImageOrientationUp];
165- CGImageRelease (downsampleImageRef);
166- }
167- CFRelease (properties);
168- }
169- CFRelease (ref);
170- }
171- }
168+ decodedImage = [self decodeImageFromData ];
172169 } else {
173- tmp = [self imageAtFrame: 0 ];
170+ decodedImage = [self imageAtFrame: 0 ];
174171 }
175- if (!tmp){
176- tmp = [UIImage imageWithData: _data scale: self .scale];
172+
173+ // Fallback to simple decoding if needed
174+ if (!decodedImage) {
175+ decodedImage = [UIImage imageWithData: _data scale: self .scale];
177176 }
177+
178178 @synchronized (self) {
179- if ( _image == nil ) {
180- _image = tmp ;
179+ if (! _image) {
180+ _image = decodedImage ;
181181 }
182+ return _image;
182183 }
183184 }
184- @synchronized (self) {
185- return _image;
185+ }
186+
187+ - (nullable UIImage *)decodeImageFromData {
188+ if (!_data) {
189+ return nil ;
186190 }
191+
192+ // Try downsampling if enabled and view size is available
193+ if (_downSample && _imageViewSize.width > 0 && _imageViewSize.height > 0 ) {
194+ UIImage *downsampledImage = [self createDownsampledImageFromData ];
195+ if (downsampledImage) {
196+ return downsampledImage;
197+ }
198+ }
199+
200+ return nil ;
201+ }
202+
203+ - (nullable UIImage *)createDownsampledImageFromData {
204+ CGFloat scale = self.scale ;
205+ CGFloat targetWidth = _imageViewSize.width * scale;
206+ CGFloat targetHeight = _imageViewSize.height * scale;
207+
208+ // Create image source without caching
209+ NSDictionary *sourceOptions = @{ (NSString *)kCGImageSourceShouldCache : @(NO ) };
210+ CGImageSourceRef imageSource = CGImageSourceCreateWithData ((__bridge CFDataRef)_data,
211+ (__bridge CFDictionaryRef)sourceOptions);
212+ if (!imageSource) {
213+ return nil ;
214+ }
215+
216+ UIImage *result = nil ;
217+ CGSize imageSize = [self getImageSizeFromSource: imageSource];
218+
219+ // Only downsample if image is larger than target size
220+ if (imageSize.width > targetWidth || imageSize.height > targetHeight) {
221+ result = [self createThumbnailFromSource: imageSource
222+ targetSize: _imageViewSize
223+ scale: scale];
224+ }
225+
226+ CFRelease (imageSource);
227+ return result;
228+ }
229+
230+ - (CGSize)getImageSizeFromSource : (CGImageSourceRef)imageSource {
231+ CGSize size = CGSizeZero;
232+
233+ CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex (imageSource, 0 , NULL );
234+ if (!properties) {
235+ return size;
236+ }
237+
238+ CFTypeRef widthValue = CFDictionaryGetValue (properties, kCGImagePropertyPixelWidth );
239+ CFTypeRef heightValue = CFDictionaryGetValue (properties, kCGImagePropertyPixelHeight );
240+
241+ NSInteger width = 0 , height = 0 ;
242+ if (widthValue) {
243+ CFNumberGetValue (widthValue, kCFNumberLongType , &width);
244+ }
245+ if (heightValue) {
246+ CFNumberGetValue (heightValue, kCFNumberLongType , &height);
247+ }
248+
249+ size = CGSizeMake (width, height);
250+ CFRelease (properties);
251+
252+ return size;
253+ }
254+
255+ - (nullable UIImage *)createThumbnailFromSource : (CGImageSourceRef)imageSource
256+ targetSize : (CGSize)targetSize
257+ scale : (CGFloat)scale {
258+ NSInteger maxDimension = MAX (targetSize.width , targetSize.height ) * scale;
259+
260+ NSDictionary *thumbnailOptions = @{
261+ (NSString *)kCGImageSourceCreateThumbnailFromImageAlways : @(YES ),
262+ (NSString *)kCGImageSourceShouldCacheImmediately : @(YES ),
263+ (NSString *)kCGImageSourceCreateThumbnailWithTransform : @(YES ),
264+ (NSString *)kCGImageSourceThumbnailMaxPixelSize : @(maxDimension)
265+ };
266+
267+ CGImageRef thumbnailImage = CGImageSourceCreateThumbnailAtIndex (imageSource, 0 ,
268+ (__bridge CFDictionaryRef)thumbnailOptions);
269+ if (!thumbnailImage) {
270+ return nil ;
271+ }
272+
273+ UIImage *result = [UIImage imageWithCGImage: thumbnailImage
274+ scale: scale
275+ orientation: UIImageOrientationUp];
276+ CGImageRelease (thumbnailImage);
277+
278+ return result;
187279}
188280
189281- (UIImage *)imageAtFrame : (NSUInteger )index {
0 commit comments