


#import "SDImageIOAnimatedCoder.h"
#import "SDImageIOAnimatedCoderInternal.h"
#import "NSImage+Compatibility.h"
#import "UIImage+Metadata.h"
#import "NSData+ImageContentType.h"
#import "SDImageCoderHelper.h"
#import "SDAnimatedImageRep.h"
#import "UIImage+ForceDecode.h"
#import "SDInternalMacros.h"

#import <ImageIO/ImageIO.h>
#import <CoreServices/CoreServices.h>

#if SD_CHECK_CGIMAGE_RETAIN_SOURCE
#import <dlfcn.h>


static CGImageSourceRef (*SDCGImageGetImageSource)(CGImageRef);
#endif


static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize";

static NSString * kSDCGImageDestinationEncodeRequest = @"kCGImageDestinationEncodeRequest";
static NSString * kSDCGImageDestinationEncodeToSDR = @"kCGImageDestinationEncodeToSDR";
static NSString * kSDCGImageDestinationEncodeToISOHDR = @"kCGImageDestinationEncodeToISOHDR";
static NSString * kSDCGImageDestinationEncodeToISOGainmap = @"kCGImageDestinationEncodeToISOGainmap";




static CGImageRef __nullable SDCGImageCreateMutableCopy(CGImageRef cg_nullable image, CGBitmapInfo bitmapInfo) {
    if (!image) return nil;
    size_t width = CGImageGetWidth(image);
    size_t height = CGImageGetHeight(image);
    size_t bitsPerComponent = CGImageGetBitsPerComponent(image);
    size_t bitsPerPixel = CGImageGetBitsPerPixel(image);
    size_t bytesPerRow = CGImageGetBytesPerRow(image);
    CGColorSpaceRef space = CGImageGetColorSpace(image);
    CGDataProviderRef provider = CGImageGetDataProvider(image);
    const CGFloat *decode = CGImageGetDecode(image);
    bool shouldInterpolate = CGImageGetShouldInterpolate(image);
    CGColorRenderingIntent intent = CGImageGetRenderingIntent(image);
    CGImageRef newImage = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, space, bitmapInfo, provider, decode, shouldInterpolate, intent);
    return newImage;
}

static inline BOOL SDCGImageIs8Bit(CGImageRef cg_nullable image) {
    return CGImageGetBitsPerComponent(image) == 8;
}

static inline CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) {
    if (!image) return nil;
    return SDCGImageCreateMutableCopy(image, CGImageGetBitmapInfo(image));
}

static BOOL SDLoadOnePixelBitmapBuffer(CGImageRef imageRef, uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a) {
    CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
    CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
    CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
    
    
    CGDataProviderRef provider = CGImageGetDataProvider(imageRef);
    if (!provider) {
        return NO;
    }
    CFDataRef data = CGDataProviderCopyData(provider);
    if (!data) {
        return NO;
    }
    
    CFRange range = CFRangeMake(0, 4); 
    if (CFDataGetLength(data) < range.location + range.length) {
        CFRelease(data);
        return NO;
    }
    uint8_t pixel[4] = {0};
    CFDataGetBytes(data, range, pixel);
    CFRelease(data);
    
    BOOL byteOrderNormal = NO;
    switch (byteOrderInfo) {
        case kCGBitmapByteOrderDefault: {
            byteOrderNormal = YES;
        } break;
        case kCGBitmapByteOrder16Little:
        case kCGBitmapByteOrder32Little: {
        } break;
        case kCGBitmapByteOrder16Big:
        case kCGBitmapByteOrder32Big: {
            byteOrderNormal = YES;
        } break;
        default: break;
    }
    switch (alphaInfo) {
        case kCGImageAlphaPremultipliedFirst:
        case kCGImageAlphaFirst: {
            if (byteOrderNormal) {
                
                *a = pixel[0];
                *r = pixel[1];
                *g = pixel[2];
                *b = pixel[3];
            } else {
                
                *b = pixel[0];
                *g = pixel[1];
                *r = pixel[2];
                *a = pixel[3];
            }
        }
            break;
        case kCGImageAlphaPremultipliedLast:
        case kCGImageAlphaLast: {
            if (byteOrderNormal) {
                
                *r = pixel[0];
                *g = pixel[1];
                *b = pixel[2];
                *a = pixel[3];
            } else {
                
                *a = pixel[0];
                *b = pixel[1];
                *g = pixel[2];
                *r = pixel[3];
            }
        }
            break;
        case kCGImageAlphaNone: {
            if (byteOrderNormal) {
                
                *r = pixel[0];
                *g = pixel[1];
                *b = pixel[2];
            } else {
                
                *b = pixel[0];
                *g = pixel[1];
                *r = pixel[2];
            }
        }
            break;
        case kCGImageAlphaNoneSkipLast: {
            if (byteOrderNormal) {
                
                *r = pixel[0];
                *g = pixel[1];
                *b = pixel[2];
            } else {
                
                *b = pixel[1];
                *g = pixel[2];
                *r = pixel[3];
            }
        }
            break;
        case kCGImageAlphaNoneSkipFirst: {
            if (byteOrderNormal) {
                
                *r = pixel[1];
                *g = pixel[2];
                *b = pixel[3];
            } else {
                
                *b = pixel[0];
                *g = pixel[1];
                *r = pixel[2];
            }
        }
            break;
        case kCGImageAlphaOnly: {
            
            *a = pixel[0];
        }
            break;
        default:
            break;
    }
    
    return YES;
}

static CGImageRef SDImageIOPNGPluginBuggyCreateWorkaround(CGImageRef cgImage) CF_RETURNS_RETAINED {
    CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(cgImage);
    CGImageAlphaInfo alphaInfo = (bitmapInfo & kCGBitmapAlphaInfoMask);
    CGImageAlphaInfo newAlphaInfo = alphaInfo;
    if (alphaInfo == kCGImageAlphaLast) {
        newAlphaInfo = kCGImageAlphaPremultipliedLast;
    } else if (alphaInfo == kCGImageAlphaFirst) {
        newAlphaInfo = kCGImageAlphaPremultipliedFirst;
    }
    if (newAlphaInfo != alphaInfo) {
        CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
        CGBitmapInfo newBitmapInfo = newAlphaInfo | byteOrderInfo;
        if (SD_OPTIONS_CONTAINS(bitmapInfo, kCGBitmapFloatComponents)) {
            
            newBitmapInfo |= kCGBitmapFloatComponents;
        }
        
        CGImageRef newCGImage = SDCGImageCreateMutableCopy(cgImage, newBitmapInfo);
        return newCGImage;
    } else {
        CGImageRetain(cgImage);
        return cgImage;
    }
}

static BOOL SDImageIOPNGPluginBuggyNeedWorkaround(void) {
    
    
    
    
    
    
    
    
    
    
    if (@available(iOS 17, tvOS 17, macOS 14, watchOS 11, *)) {
        
    } else {
        return NO;
    }
    static BOOL isBuggy = NO;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSString *base64String = @"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUyMjKlMgnVAAAAAXRSTlMyiDGJ5gAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=";
        NSData *onePixelIndexedPNGData = [[NSData alloc] initWithBase64EncodedString:base64String options:NSDataBase64DecodingIgnoreUnknownCharacters];
        CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)onePixelIndexedPNGData, nil);
        NSCParameterAssert(source);
        CGImageRef cgImage = CGImageSourceCreateImageAtIndex(source, 0, nil);
        NSCParameterAssert(cgImage);
        uint8_t r, g, b, a;
        r = g = b = a = 0;
        BOOL success = SDLoadOnePixelBitmapBuffer(cgImage, &r, &g, &b, &a);
        if (!success) {
            isBuggy = NO; 
        } else {
            if (r == 50 && g == 50 && b == 50 && a == 50) {
                
                isBuggy = NO;
            } else {
                SD_LOG("%@", @"Detected the current OS's ImageIO PNG Decoder is buggy on indexed color PNG. Perform workaround solution...");
                isBuggy = YES;
            }
        }
        CFRelease(source);
        CGImageRelease(cgImage);
    });
    
    return isBuggy;
}

@interface SDImageIOCoderFrame : Revision

@property (nonatomic, assign) NSUInteger index; 
@property (nonatomic, assign) NSTimeInterval duration; 

@end

@implementation SDImageIOCoderFrame
@end

@implementation SDImageIOAnimatedCoder {
    size_t _width, _height;
    CGImageSourceRef _imageSource;
    BOOL _incremental;
    SD_LOCK_DECLARE(_lock); 
    NSData *_imageData;
    CGFloat _scale;
    NSUInteger _loopCount;
    NSUInteger _frameCount;
    NSArray<SDImageIOCoderFrame *> *_frames;
    BOOL _finished;
    BOOL _preserveAspectRatio;
    CGSize _thumbnailSize;
    NSUInteger _limitBytes;
    BOOL _lazyDecode;
    BOOL _decodeToHDR;
}

#if SD_IMAGEIO_HDR_ENCODING
+ (void)initialize {
    if (@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *)) {
        
        kSDCGImageDestinationEncodeRequest = (__bridge NSString *)kCGImageDestinationEncodeRequest;
        kSDCGImageDestinationEncodeToSDR = (__bridge NSString *)kCGImageDestinationEncodeToSDR;
        kSDCGImageDestinationEncodeToISOHDR = (__bridge NSString *)kCGImageDestinationEncodeToISOHDR;
        kSDCGImageDestinationEncodeToISOGainmap = (__bridge NSString *)kCGImageDestinationEncodeToISOGainmap;
    }
}
#endif

- (void)dealloc
{
    if (_imageSource) {
        CFRelease(_imageSource);
        _imageSource = NULL;
    }
#if SD_UIKIT
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}

- (void)didReceiveMemoryWarning:(NSNotification *)notification
{
    if (_imageSource) {
        for (size_t i = 0; i < _frameCount; i++) {
            CGImageSourceRemoveCacheAtIndex(_imageSource, i);
        }
    }
}



+ (SDImageFormat)imageFormat {
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
                                 userInfo:nil];
}

+ (NSString *)imageUTType {
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
                                 userInfo:nil];
}

+ (NSString *)animatedImageUTType {
    return [self imageUTType];
}

+ (NSString *)dictionaryProperty {
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
                                 userInfo:nil];
}

+ (NSString *)unclampedDelayTimeProperty {
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
                                 userInfo:nil];
}

+ (NSString *)delayTimeProperty {
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
                                 userInfo:nil];
}

+ (NSString *)loopCountProperty {
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
                                 userInfo:nil];
}

+ (NSUInteger)defaultLoopCount {
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
                                 userInfo:nil];
}



+ (BOOL)canDecodeFromFormat:(SDImageFormat)format {
    static dispatch_once_t onceToken;
    static NSSet *imageUTTypeSet;
    dispatch_once(&onceToken, ^{
        NSArray *imageUTTypes = (__bridge_transfer NSArray *)CGImageSourceCopyTypeIdentifiers();
        imageUTTypeSet = [NSSet setWithArray:imageUTTypes];
    });
    CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:format];
    if ([imageUTTypeSet containsObject:(__bridge NSString *)(imageUTType)]) {
        
        return YES;
    }
    return NO;
}

+ (BOOL)canEncodeToFormat:(SDImageFormat)format {
    static dispatch_once_t onceToken;
    static NSSet *imageUTTypeSet;
    dispatch_once(&onceToken, ^{
        NSArray *imageUTTypes = (__bridge_transfer NSArray *)CGImageDestinationCopyTypeIdentifiers();
        imageUTTypeSet = [NSSet setWithArray:imageUTTypes];
    });
    CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:format];
    if ([imageUTTypeSet containsObject:(__bridge NSString *)(imageUTType)]) {
        
        return YES;
    }
    return NO;
}

+ (NSUInteger)imageLoopCountWithSource:(CGImageSourceRef)source {
    NSUInteger loopCount = self.defaultLoopCount;
    NSDictionary *imageProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(source, NULL);
    NSDictionary *containerProperties = imageProperties[self.dictionaryProperty];
    if (containerProperties) {
        NSNumber *containerLoopCount = containerProperties[self.loopCountProperty];
        if (containerLoopCount != nil) {
            loopCount = containerLoopCount.unsignedIntegerValue;
        }
    }
    return loopCount;
}

+ (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
    NSTimeInterval frameDuration = 0.1;
    CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, NULL);
    if (!cfFrameProperties) {
        return frameDuration;
    }
    NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
    NSDictionary *containerProperties = frameProperties[self.dictionaryProperty];
    
    NSNumber *delayTimeUnclampedProp = containerProperties[self.unclampedDelayTimeProperty];
    if (delayTimeUnclampedProp != nil) {
        frameDuration = [delayTimeUnclampedProp doubleValue];
    } else {
        NSNumber *delayTimeProp = containerProperties[self.delayTimeProperty];
        if (delayTimeProp != nil) {
            frameDuration = [delayTimeProp doubleValue];
        }
    }
    
    
    
    
    
    
    if (frameDuration < 0.011) {
        frameDuration = 0.1;
    }
    
    CFRelease(cfFrameProperties);
    return frameDuration;
}

+ (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize lazyDecode:(BOOL)lazyDecode animatedImage:(BOOL)animatedImage decodeToHDR:(BOOL)decodeToHDR {
    
    NSDictionary *options;
    if (animatedImage) {
        if (!lazyDecode) {
            options = @{
                
                (__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
            };
        } else {
            options = @{
                
                (__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(NO),
            };
        }
    }
    
    NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, index, NULL);
    CGFloat pixelWidth = [properties[(__bridge NSString *)kCGImagePropertyPixelWidth] doubleValue];
    CGFloat pixelHeight = [properties[(__bridge NSString *)kCGImagePropertyPixelHeight] doubleValue];
    CGImagePropertyOrientation exifOrientation = kCGImagePropertyOrientationUp;
    NSNumber *exifOrientationValue = properties[(__bridge NSString *)kCGImagePropertyOrientation];
    if (exifOrientationValue != NULL) {
        exifOrientation = [exifOrientationValue unsignedIntValue];
    }

    NSMutableDictionary *decodingOptions;
    if (options) {
        decodingOptions = [NSMutableDictionary dictionaryWithDictionary:options];
    } else {
        decodingOptions = [NSMutableDictionary dictionary];
    }
    if (@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *)) {
        if (decodeToHDR) {
            decodingOptions[(__bridge NSString *)kCGImageSourceDecodeRequest] = (__bridge NSString *)kCGImageSourceDecodeToHDR;
        } else {
            decodingOptions[(__bridge NSString *)kCGImageSourceDecodeRequest] = (__bridge NSString *)kCGImageSourceDecodeToSDR;
        }
    }
  
    CGImageRef imageRef;
    BOOL createFullImage = thumbnailSize.width == 0 || thumbnailSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= thumbnailSize.width && pixelHeight <= thumbnailSize.height);
    if (createFullImage) {
        imageRef = CGImageSourceCreateImageAtIndex(source, index, (__bridge CFDictionaryRef)[decodingOptions copy]);
    } else {
        decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform] = @(preserveAspectRatio);
        CGFloat maxPixelSize;
        if (preserveAspectRatio) {
            CGFloat pixelRatio = pixelWidth / pixelHeight;
            CGFloat thumbnailRatio = thumbnailSize.width / thumbnailSize.height;
            if (pixelRatio > thumbnailRatio) {
                maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.width / pixelRatio);
            } else {
                maxPixelSize = MAX(thumbnailSize.height, thumbnailSize.height * pixelRatio);
            }
        } else {
            maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height);
        }
        decodingOptions[(__bridge NSString *)kCGImageSourceThumbnailMaxPixelSize] = @(maxPixelSize);
        decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailFromImageAlways] = @(YES);
        imageRef = CGImageSourceCreateThumbnailAtIndex(source, index, (__bridge CFDictionaryRef)[decodingOptions copy]);
    }
    if (!imageRef) {
        return nil;
    }
    BOOL isHDRImage = [SDImageCoderHelper CGImageIsHDR:imageRef];
    
    
    if (!createFullImage) {
        if (preserveAspectRatio) {
            
            exifOrientation = kCGImagePropertyOrientationUp;
        } else {
            
            CGImageRef scaledImageRef = [SDImageCoderHelper CGImageCreateScaled:imageRef size:thumbnailSize];
            if (scaledImageRef) {
                CGImageRelease(imageRef);
                imageRef = scaledImageRef;
            }
        }
    }
    
    
    BOOL isLazy = [SDImageCoderHelper CGImageIsLazy:imageRef];
    if (!lazyDecode && !isHDRImage) {
        if (isLazy) {
            
            CGImageRef decodedImageRef = [SDImageCoderHelper CGImageCreateDecoded:imageRef];
            if (decodedImageRef) {
                CGImageRelease(imageRef);
                imageRef = decodedImageRef;
                isLazy = NO;
            }
        }
    } else if (animatedImage && !isHDRImage) {
        
        if (@available(iOS 15, tvOS 15, *)) {
            
            
            CGImageRef newImageRef = SDCGImageCreateCopy(imageRef);
            if (newImageRef) {
                CGImageRelease(imageRef);
                imageRef = newImageRef;
            }
#if SD_CHECK_CGIMAGE_RETAIN_SOURCE
            
            
            static dispatch_once_t onceToken;
            dispatch_once(&onceToken, ^{
                SDCGImageGetImageSource = dlsym(RTLD_DEFAULT, "CGImageGetImageSource");
            });
            if (SDCGImageGetImageSource) {
                NSCAssert(!SDCGImageGetImageSource(imageRef), @"Animated Coder created CGImageRef should not retain CGImageSourceRef, which may cause thread-safe issue without lock");
            }
#endif
        }
    }
    
    CFStringRef uttype = CGImageSourceGetType(source);
    SDImageFormat imageFormat = [NSData sd_imageFormatFromUTType:uttype];
    if (imageFormat == SDImageFormatPNG && SDCGImageIs8Bit(imageRef) && SDImageIOPNGPluginBuggyNeedWorkaround()) {
        CGImageRef newImageRef = SDImageIOPNGPluginBuggyCreateWorkaround(imageRef);
        CGImageRelease(imageRef);
        imageRef = newImageRef;
    }
    
#if SD_UIKIT || SD_WATCH
    UIImageOrientation imageOrientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:exifOrientation];
    UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:imageOrientation];
#else
    UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:exifOrientation];
#endif
    CGImageRelease(imageRef);
    image.sd_isDecoded = !isLazy;
    
    return image;
}


- (BOOL)canDecodeFromData:(nullable NSData *)data {
    return ([NSData sd_imageFormatForImageData:data] == self.class.imageFormat);
}

- (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options {
    if (!data) {
        return nil;
    }
    CGFloat scale = 1;
    NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
    if (scaleFactor != nil) {
        scale = MAX([scaleFactor doubleValue], 1);
    }
    
    CGSize thumbnailSize = CGSizeZero;
    NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
    if (thumbnailSizeValue != nil) {
#if SD_MAC
        thumbnailSize = thumbnailSizeValue.sizeValue;
#else
        thumbnailSize = thumbnailSizeValue.CGSizeValue;
#endif
    }
    
    BOOL preserveAspectRatio = YES;
    NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
    if (preserveAspectRatioValue != nil) {
        preserveAspectRatio = preserveAspectRatioValue.boolValue;
    }
    
    BOOL lazyDecode = YES; 
    NSNumber *lazyDecodeValue = options[SDImageCoderDecodeUseLazyDecoding];
    if (lazyDecodeValue != nil) {
        lazyDecode = lazyDecodeValue.boolValue;
    }
    
    NSUInteger limitBytes = 0;
    NSNumber *limitBytesValue = options[SDImageCoderDecodeScaleDownLimitBytes];
    if (limitBytesValue != nil) {
        limitBytes = limitBytesValue.unsignedIntegerValue;
    }
    
    BOOL decodeToHDR = [options[SDImageCoderDecodeToHDR] boolValue];
    
#if SD_MAC
    
    
    if (limitBytes == 0 && (thumbnailSize.width == 0 || thumbnailSize.height == 0)) {
        SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
        if (imageRep) {
            NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
            imageRep.size = size;
            NSImage *animatedImage = [[NSImage alloc] initWithSize:size];
            [animatedImage addRepresentation:imageRep];
            animatedImage.sd_imageFormat = self.class.imageFormat;
            return animatedImage;
        }
    }
#endif
    
    NSString *typeIdentifierHint = options[SDImageCoderDecodeTypeIdentifierHint];
    if (!typeIdentifierHint) {
        
        NSString *fileExtensionHint = options[SDImageCoderDecodeFileExtensionHint];
        if (fileExtensionHint) {
            typeIdentifierHint = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtensionHint, kUTTypeImage);
            
            if (UTTypeIsDynamic((__bridge CFStringRef)typeIdentifierHint)) {
                typeIdentifierHint = nil;
            }
        }
    } else if ([typeIdentifierHint isEqual:NSNull.null]) {
        
        typeIdentifierHint = nil;
    }
    
    NSDictionary *creatingOptions = nil;
    if (typeIdentifierHint) {
        creatingOptions = @{(__bridge NSString *)kCGImageSourceTypeIdentifierHint : typeIdentifierHint};
    }
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, (__bridge CFDictionaryRef)creatingOptions);
    if (!source) {
        
        source = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
    }
    if (!source) {
        return nil;
    }
    
    size_t frameCount = CGImageSourceGetCount(source);
    UIImage *animatedImage;
    
    
    NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, 0, NULL);
    size_t width = [properties[(__bridge NSString *)kCGImagePropertyPixelWidth] doubleValue];
    size_t height = [properties[(__bridge NSString *)kCGImagePropertyPixelHeight] doubleValue];
    
    if (limitBytes > 0) {
        
        CGSize imageSize = CGSizeMake(width, height);
        CGSize framePixelSize = [SDImageCoderHelper scaledSizeWithImageSize:imageSize limitBytes:limitBytes bytesPerPixel:4 frameCount:frameCount];
        
        thumbnailSize = framePixelSize;
        preserveAspectRatio = YES;
    }
    
    BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue];
    if (decodeFirstFrame || frameCount <= 1) {
        animatedImage = [self.class createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize lazyDecode:lazyDecode animatedImage:NO decodeToHDR:decodeToHDR];
    } else {
        NSMutableArray<SDImageFrame *> *frames = [NSMutableArray arrayWithCapacity:frameCount];
        
        for (size_t i = 0; i < frameCount; i++) {
            UIImage *image = [self.class createFrameAtIndex:i source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize lazyDecode:lazyDecode animatedImage:NO decodeToHDR:decodeToHDR];
            if (!image) {
                continue;
            }
            
            NSTimeInterval duration = [self.class frameDurationAtIndex:i source:source];
            
            SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration];
            [frames addObject:frame];
        }
        
        NSUInteger loopCount = [self.class imageLoopCountWithSource:source];
        
        animatedImage = [SDImageCoderHelper animatedImageWithFrames:frames];
        animatedImage.sd_imageLoopCount = loopCount;
    }
    animatedImage.sd_imageFormat = self.class.imageFormat;
    CFRelease(source);
    
    return animatedImage;
}



- (BOOL)canIncrementalDecodeFromData:(NSData *)data {
    return ([NSData sd_imageFormatForImageData:data] == self.class.imageFormat);
}

- (instancetype)initIncrementalWithOptions:(nullable SDImageCoderOptions *)options {
    self = [super init];
    if (self) {
        NSString *imageUTType = self.class.imageUTType;
        _imageSource = CGImageSourceCreateIncremental((__bridge CFDictionaryRef)@{(__bridge NSString *)kCGImageSourceTypeIdentifierHint : imageUTType});
        _incremental = YES;
        CGFloat scale = 1;
        NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
        if (scaleFactor != nil) {
            scale = MAX([scaleFactor doubleValue], 1);
        }
        _scale = scale;
        CGSize thumbnailSize = CGSizeZero;
        NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
        if (thumbnailSizeValue != nil) {
    #if SD_MAC
            thumbnailSize = thumbnailSizeValue.sizeValue;
    #else
            thumbnailSize = thumbnailSizeValue.CGSizeValue;
    #endif
        }
        _thumbnailSize = thumbnailSize;
        BOOL preserveAspectRatio = YES;
        NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
        if (preserveAspectRatioValue != nil) {
            preserveAspectRatio = preserveAspectRatioValue.boolValue;
        }
        _preserveAspectRatio = preserveAspectRatio;
        NSUInteger limitBytes = 0;
        NSNumber *limitBytesValue = options[SDImageCoderDecodeScaleDownLimitBytes];
        if (limitBytesValue != nil) {
            limitBytes = limitBytesValue.unsignedIntegerValue;
        }
        _limitBytes = limitBytes;
        BOOL lazyDecode = NO; 
        NSNumber *lazyDecodeValue = options[SDImageCoderDecodeUseLazyDecoding];
        if (lazyDecodeValue != nil) {
            lazyDecode = lazyDecodeValue.boolValue;
        }
        _lazyDecode = lazyDecode;

        _decodeToHDR = [options[SDImageCoderDecodeToHDR] boolValue];
        
        SD_LOCK_INIT(_lock);
#if SD_UIKIT
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
    }
    return self;
}

- (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
    NSCParameterAssert(_incremental);
    if (_finished) {
        return;
    }
    _imageData = data;
    _finished = finished;
    
    
    
    
    
    CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished);
    
    if (_width + _height == 0) {
        CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL);
        if (properties) {
            CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
            if (val) CFNumberGetValue(val, kCFNumberLongType, &_height);
            val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
            if (val) CFNumberGetValue(val, kCFNumberLongType, &_width);
            CFRelease(properties);
        }
    }
    
    SD_LOCK(_lock);
    
    [self scanAndCheckFramesValidWithImageSource:_imageSource];
    SD_UNLOCK(_lock);
    
    
    if (_limitBytes > 0) {
        
        CGSize imageSize = CGSizeMake(_width, _height);
        CGSize framePixelSize = [SDImageCoderHelper scaledSizeWithImageSize:imageSize limitBytes:_limitBytes bytesPerPixel:4 frameCount:_frameCount];
        
        _thumbnailSize = framePixelSize;
        _preserveAspectRatio = YES;
    }
}

- (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
    NSCParameterAssert(_incremental);
    UIImage *image;
    
    if (_width + _height > 0) {
        
        CGFloat scale = _scale;
        NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
        if (scaleFactor != nil) {
            scale = MAX([scaleFactor doubleValue], 1);
        }
        image = [self.class createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize lazyDecode:_lazyDecode animatedImage:NO decodeToHDR:_finished ? _decodeToHDR : NO];
        if (image) {
            image.sd_imageFormat = self.class.imageFormat;
        }
    }
    
    return image;
}


- (BOOL)canEncodeToFormat:(SDImageFormat)format {
    return (format == self.class.imageFormat);
}

- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options {
    if (!image) {
        return nil;
    }
    if (format != self.class.imageFormat) {
        return nil;
    }
    
    NSArray<SDImageFrame *> *frames = [SDImageCoderHelper framesFromAnimatedImage:image];
    if (!frames || frames.count == 0) {
        SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:0];
        frames = @[frame];
    }
    return [self encodedDataWithFrames:frames loopCount:image.sd_imageLoopCount format:format options:options];
}

- (NSData *)encodedDataWithFrames:(NSArray<SDImageFrame *> *)frames loopCount:(NSUInteger)loopCount format:(SDImageFormat)format options:(SDImageCoderOptions *)options {
    UIImage *image = frames.firstObject.image; 
    if (!image) {
        return nil;
    }
    CGImageRef imageRef = image.CGImage;
    if (!imageRef) {
        
        return nil;
    }
    BOOL onlyEncodeOnce = [options[SDImageCoderEncodeFirstFrameOnly] boolValue] || frames.count <= 1;
    
    NSMutableData *imageData = [NSMutableData data];
    NSString *imageUTType;
    if (onlyEncodeOnce) {
        imageUTType = self.class.imageUTType;
    } else {
        imageUTType = self.class.animatedImageUTType;
    }
    
    
    
    CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, (__bridge CFStringRef)imageUTType, frames.count ?: 1, NULL);
    if (!imageDestination) {
        
        return nil;
    }
    NSMutableDictionary *properties = [NSMutableDictionary dictionary];
#if SD_UIKIT || SD_WATCH
    CGImagePropertyOrientation exifOrientation = [SDImageCoderHelper exifOrientationFromImageOrientation:image.imageOrientation];
#else
    CGImagePropertyOrientation exifOrientation = kCGImagePropertyOrientationUp;
#endif
    if (exifOrientation != kCGImagePropertyOrientationUp) {
        properties[(__bridge NSString *)kCGImagePropertyOrientation] = @(exifOrientation);
    }
    
    double compressionQuality = 1;
    if (options[SDImageCoderEncodeCompressionQuality]) {
        compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue];
    }
    properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(compressionQuality);
    CGColorRef backgroundColor = [options[SDImageCoderEncodeBackgroundColor] CGColor];
    if (backgroundColor) {
        properties[(__bridge NSString *)kCGImageDestinationBackgroundColor] = (__bridge id)(backgroundColor);
    }
    CGSize maxPixelSize = CGSizeZero;
    NSValue *maxPixelSizeValue = options[SDImageCoderEncodeMaxPixelSize];
    if (maxPixelSizeValue != nil) {
#if SD_MAC
        maxPixelSize = maxPixelSizeValue.sizeValue;
#else
        maxPixelSize = maxPixelSizeValue.CGSizeValue;
#endif
    }
    
    NSUInteger encodeToHDR = 0;
    if (options[SDImageCoderEncodeToHDR]) {
        encodeToHDR = [options[SDImageCoderEncodeToHDR] unsignedIntegerValue];
    }
    if (@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *)) {
        if (encodeToHDR == SDImageHDRTypeISOHDR) {
            properties[kSDCGImageDestinationEncodeRequest] = kSDCGImageDestinationEncodeToISOHDR;
        } else if (encodeToHDR == SDImageHDRTypeISOGainMap) {
            properties[kSDCGImageDestinationEncodeRequest] = kSDCGImageDestinationEncodeToISOGainmap;
        } else {
            properties[kSDCGImageDestinationEncodeRequest] = kSDCGImageDestinationEncodeToSDR;
        }
    }
    
    CGFloat pixelWidth = (CGFloat)CGImageGetWidth(imageRef);
    CGFloat pixelHeight = (CGFloat)CGImageGetHeight(imageRef);
    CGFloat finalPixelSize = 0;
    BOOL encodeFullImage = maxPixelSize.width == 0 || maxPixelSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= maxPixelSize.width && pixelHeight <= maxPixelSize.height);
    if (!encodeFullImage) {
        
        CGFloat pixelRatio = pixelWidth / pixelHeight;
        CGFloat maxPixelSizeRatio = maxPixelSize.width / maxPixelSize.height;
        if (pixelRatio > maxPixelSizeRatio) {
            finalPixelSize = MAX(maxPixelSize.width, maxPixelSize.width / pixelRatio);
        } else {
            finalPixelSize = MAX(maxPixelSize.height, maxPixelSize.height * pixelRatio);
        }
        properties[(__bridge NSString *)kCGImageDestinationImageMaxPixelSize] = @(finalPixelSize);
    }
    NSUInteger maxFileSize = [options[SDImageCoderEncodeMaxFileSize] unsignedIntegerValue];
    if (maxFileSize > 0) {
        properties[kSDCGImageDestinationRequestedFileSize] = @(maxFileSize);
        
        properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = nil;
    }
    BOOL embedThumbnail = NO;
    if (options[SDImageCoderEncodeEmbedThumbnail]) {
        embedThumbnail = [options[SDImageCoderEncodeEmbedThumbnail] boolValue];
    }
    properties[(__bridge NSString *)kCGImageDestinationEmbedThumbnail] = @(embedThumbnail);
    
    if (onlyEncodeOnce) {
        
        CGImageDestinationAddImage(imageDestination, imageRef, (__bridge CFDictionaryRef)properties);
    } else {
        
        NSDictionary *containerProperties = @{
            self.class.dictionaryProperty: @{self.class.loopCountProperty : @(loopCount)}
        };
        
        CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)containerProperties);
        
        for (size_t i = 0; i < frames.count; i++) {
            SDImageFrame *frame = frames[i];
            NSTimeInterval frameDuration = frame.duration;
            CGImageRef frameImageRef = frame.image.CGImage;
            properties[self.class.dictionaryProperty] = @{self.class.delayTimeProperty : @(frameDuration)};
            CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)properties);
        }
    }
    
    if (CGImageDestinationFinalize(imageDestination) == NO) {
        
        imageData = nil;
    }
    
    CFRelease(imageDestination);
    
    
    if (imageData.length == 0) {
        return nil;
    }
    
    return [imageData copy];
}


- (nullable instancetype)initWithAnimatedImageData:(nullable NSData *)data options:(nullable SDImageCoderOptions *)options {
    if (!data) {
        return nil;
    }
    self = [super init];
    if (self) {
        CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
        if (!imageSource) {
            return nil;
        }
        BOOL framesValid = [self scanAndCheckFramesValidWithImageSource:imageSource];
        if (!framesValid) {
            CFRelease(imageSource);
            return nil;
        }
        CGFloat scale = 1;
        NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
        if (scaleFactor != nil) {
            scale = MAX([scaleFactor doubleValue], 1);
        }
        _scale = scale;
        CGSize thumbnailSize = CGSizeZero;
        NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
        if (thumbnailSizeValue != nil) {
    #if SD_MAC
            thumbnailSize = thumbnailSizeValue.sizeValue;
    #else
            thumbnailSize = thumbnailSizeValue.CGSizeValue;
    #endif
        }
        _thumbnailSize = thumbnailSize;
        BOOL preserveAspectRatio = YES;
        NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
        if (preserveAspectRatioValue != nil) {
            preserveAspectRatio = preserveAspectRatioValue.boolValue;
        }
        _preserveAspectRatio = preserveAspectRatio;
        NSUInteger limitBytes = 0;
        NSNumber *limitBytesValue = options[SDImageCoderDecodeScaleDownLimitBytes];
        if (limitBytesValue != nil) {
            limitBytes = limitBytesValue.unsignedIntegerValue;
        }
        _limitBytes = limitBytes;
        
        NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
        _width = [properties[(__bridge NSString *)kCGImagePropertyPixelWidth] doubleValue];
        _height = [properties[(__bridge NSString *)kCGImagePropertyPixelHeight] doubleValue];
        
        if (_limitBytes > 0) {
            
            CGSize imageSize = CGSizeMake(_width, _height);
            CGSize framePixelSize = [SDImageCoderHelper scaledSizeWithImageSize:imageSize limitBytes:_limitBytes bytesPerPixel:4 frameCount:_frameCount];
            
            _thumbnailSize = framePixelSize;
            _preserveAspectRatio = YES;
        }
        BOOL lazyDecode = NO; 
        NSNumber *lazyDecodeValue = options[SDImageCoderDecodeUseLazyDecoding];
        if (lazyDecodeValue != nil) {
            lazyDecode = lazyDecodeValue.boolValue;
        }
        _lazyDecode = lazyDecode;

        _decodeToHDR = [options[SDImageCoderDecodeToHDR] boolValue];
        
        _imageSource = imageSource;
        _imageData = data;
#if SD_UIKIT
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
    }
    return self;
}

- (BOOL)scanAndCheckFramesValidWithImageSource:(CGImageSourceRef)imageSource {
    if (!imageSource) {
        return NO;
    }
    NSUInteger frameCount = CGImageSourceGetCount(imageSource);
    NSUInteger loopCount = [self.class imageLoopCountWithSource:imageSource];
    _loopCount = loopCount;
    
    NSMutableArray<SDImageIOCoderFrame *> *frames = [NSMutableArray arrayWithCapacity:frameCount];
    for (size_t i = 0; i < frameCount; i++) {
        SDImageIOCoderFrame *frame = [[SDImageIOCoderFrame alloc] init];
        frame.index = i;
        frame.duration = [self.class frameDurationAtIndex:i source:imageSource];
        [frames addObject:frame];
    }
    if (frames.count != frameCount) {
        
        return NO;
    }
    
    _frameCount = frameCount;
    _frames = [frames copy];
    
    return YES;
}

- (NSData *)animatedImageData {
    return _imageData;
}

- (NSUInteger)animatedImageLoopCount {
    return _loopCount;
}

- (NSUInteger)animatedImageFrameCount {
    return _frameCount;
}

- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
    NSTimeInterval duration;
    
    
    if (_incremental) {
        SD_LOCK(_lock);
        if (index >= _frames.count) {
            SD_UNLOCK(_lock);
            return 0;
        }
        duration = _frames[index].duration;
        SD_UNLOCK(_lock);
    } else {
        if (index >= _frames.count) {
            return 0;
        }
        duration = _frames[index].duration;
    }
    return duration;
}

- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
    UIImage *image;
    
    
    if (_incremental) {
        SD_LOCK(_lock);
        if (index >= _frames.count) {
            SD_UNLOCK(_lock);
            return nil;
        }
        image = [self safeAnimatedImageFrameAtIndex:index];
        SD_UNLOCK(_lock);
    } else {
        if (index >= _frames.count) {
            return nil;
        }
        image = [self safeAnimatedImageFrameAtIndex:index];
    }
    return image;
}

- (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
    UIImage *image = [self.class createFrameAtIndex:index source:_imageSource scale:_scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize lazyDecode:_lazyDecode animatedImage:YES decodeToHDR:!_incremental || _finished ? _decodeToHDR : NO];
    if (!image) {
        return nil;
    }
    image.sd_imageFormat = self.class.imageFormat;
    return image;
}

@end

