前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS小技能:图片压缩、图像格式的判断、获取gif图片循环次数和时长

iOS小技能:图片压缩、图像格式的判断、获取gif图片循环次数和时长

作者头像
公众号iOS逆向
发布2022-08-22 12:35:39
1.5K0
发布2022-08-22 12:35:39
举报
文章被收录于专栏:iOS逆向与安全

I、压缩数据

1.1 图片压缩

iOS图片压缩compress【解决压缩之后图片模糊的问题】https://blog.csdn.net/z929118967/article/details/105414506

1.2 gzip compressed data for iOS (Gzip压缩数据)

  • Gzip.h
代码语言:javascript
复制
    #import <zlib.h>
    @interface DDGzip : NSObject
    /**
     *  Gzip压缩数据
     *
     *  @param data 需要压缩的数据
     *
     *  @return 压缩后的结果
     */
    +(NSData *)compress:(NSData * )data;
    /**
     *  Gzip解压数据
     *
     *  @param data 需要解压的数据
     *
     *  @return 解压后的结果
     */
    +(NSData *)decompress:(NSData *)data;
    @end
    

  • Gzip.m
代码语言:javascript
复制
    #import "DDGzip.h"
    
    @implementation DDGzip
    +(NSData *)compress:(NSData *)data{
        if(!data||[data length]==0){
            NSLog(@"%s:Error:Can't compress an empty or null NSData object.",__func__);
            return nil;
        }
        z_stream zlibStreamStruct;
        zlibStreamStruct.zalloc=Z_NULL;
        zlibStreamStruct.zfree=Z_NULL;
        zlibStreamStruct.opaque=Z_NULL;
        zlibStreamStruct.total_out=0;
        zlibStreamStruct.next_in=(Bytef*)[data bytes];
        zlibStreamStruct.avail_in=(int)[data length];
        int initError=deflateInit2(&zlibStreamStruct,
                                   Z_DEFAULT_COMPRESSION,
                                   Z_DEFLATED,
                                   (15+16),
                                   8,
                                   Z_DEFAULT_STRATEGY);
        if(initError!=Z_OK){
            NSString * errMsg=nil;
            switch (initError) {
                case Z_STREAM_ERROR:
                    errMsg=@"Invalid parameter passed in to function." ;
                    break;
                case Z_MEM_ERROR:
                    errMsg=@"Insufficient memory." ;
                    break;
                case Z_VERSION_ERROR:
                    errMsg=@"The version of zlib.h and the version of the library linked do not match." ;
                    break;
                default:
                    errMsg= @"Unknown error code." ;
                    break;
            }
            NSLog(@"%s:deflateInit2()Error:[%@] Message:[%s]",__func__,errMsg,zlibStreamStruct.msg);
            return nil;
        }
        NSMutableData * compressedData=[NSMutableData dataWithLength:[data length]*1.01+12];
        int deflateStatus;
        do{
            zlibStreamStruct.next_out=[compressedData mutableBytes]+zlibStreamStruct.total_out;
            zlibStreamStruct. avail_out = (unsigned int)[compressedData length ] - (unsigned int)zlibStreamStruct. total_out ;
            deflateStatus = deflate (&zlibStreamStruct, Z_FINISH );
        }while ( deflateStatus == Z_OK );
        if (deflateStatus != Z_STREAM_END )
        {
            NSString *errorMsg = nil ;
            switch (deflateStatus)
            {
                case Z_ERRNO :
                    errorMsg = @"Error occured while reading file." ;
                    break ;
                case Z_STREAM_ERROR :
                    errorMsg = @"The stream state was inconsistent (e.g., next_in or next_out was NULL)." ;
                    break ;
                case Z_DATA_ERROR :
                    errorMsg = @"The deflate data was invalid or incomplete." ;
                    break ;
                case Z_MEM_ERROR :
                    errorMsg = @"Memory could not be allocated for processing." ;
                    break ;
                case Z_BUF_ERROR :
                    errorMsg = @"Ran out of output buffer for writing compressed bytes." ;
                    break ;
                case Z_VERSION_ERROR :
                    errorMsg = @"The version of zlib.h and the version of the library linked do not match." ;
                    break ;
                default :
                    errorMsg = @"Unknown error code." ;
                    break ;
            }
            NSLog ( @"%s: zlib error while attempting compression:[%@] Message:[%s]" , __func__, errorMsg, zlibStreamStruct. msg );
            deflateEnd(&zlibStreamStruct);
            return nil;
        }
        deflateEnd(&zlibStreamStruct);
        [compressedData setLength:zlibStreamStruct.total_out];
        return compressedData;
    }
    +(NSData *)decompress:(NSData *)data{
        z_stream zStream;
        zStream. zalloc = Z_NULL ;
        zStream. zfree = Z_NULL ;
        zStream. opaque = Z_NULL ;
        zStream. avail_in = 0 ;
        zStream. next_in = 0 ;
        int status = inflateInit2 (&zStream, ( 15 + 32 ));
        if (status != Z_OK ) {
            return nil ;
        }
        
        Bytef *bytes = ( Bytef *)[data bytes ];
        NSUInteger length = [data length ];
        NSUInteger halfLength = length/ 2 ;
        NSMutableData *uncompressedData = [ NSMutableData dataWithLength :length+halfLength];
        zStream. next_in = bytes;
        zStream. avail_in = ( unsigned int )length;
        zStream. avail_out = 0 ;
        
        NSInteger bytesProcessedAlready = zStream. total_out ;
        while (zStream. avail_in != 0 ) {
            if (zStream. total_out - bytesProcessedAlready >= [uncompressedData length ]) {
                [uncompressedData increaseLengthBy :halfLength];
            }
            
            zStream. next_out = ( Bytef *)[uncompressedData mutableBytes ] + zStream. total_out -bytesProcessedAlready;
            zStream. avail_out = ( unsigned int )([uncompressedData length ] - (zStream. total_out -bytesProcessedAlready));
            status = inflate (&zStream, Z_NO_FLUSH );
            if (status == Z_STREAM_END ) {
                break ;
            } else if (status != Z_OK ) {
                return nil ;
                
            }
        }
        status = inflateEnd (&zStream);
        if (status != Z_OK ) {
            return nil ;
        }
        [uncompressedData setLength : zStream. total_out -bytesProcessedAlready];  // Set real length
        return uncompressedData;
    }
    @end
    


  • 用法
代码语言:javascript
复制
    #import "DDGzip.h"
    
    @implementation DDGzip
    +(NSData *)compress:(NSData *)data{
        if(!data||[data length]==0){
            NSLog(@"%s:Error:Can't compress an empty or null NSData object.",__func__);
            return nil;
        }
        z_stream zlibStreamStruct;
        zlibStreamStruct.zalloc=Z_NULL;
        zlibStreamStruct.zfree=Z_NULL;
        zlibStreamStruct.opaque=Z_NULL;
        zlibStreamStruct.total_out=0;
        zlibStreamStruct.next_in=(Bytef*)[data bytes];
        zlibStreamStruct.avail_in=(int)[data length];
        int initError=deflateInit2(&zlibStreamStruct,
                                   Z_DEFAULT_COMPRESSION,
                                   Z_DEFLATED,
                                   (15+16),
                                   8,
                                   Z_DEFAULT_STRATEGY);
        if(initError!=Z_OK){
            NSString * errMsg=nil;
            switch (initError) {
                case Z_STREAM_ERROR:
                    errMsg=@"Invalid parameter passed in to function." ;
                    break;
                case Z_MEM_ERROR:
                    errMsg=@"Insufficient memory." ;
                    break;
                case Z_VERSION_ERROR:
                    errMsg=@"The version of zlib.h and the version of the library linked do not match." ;
                    break;
                default:
                    errMsg= @"Unknown error code." ;
                    break;
            }
            NSLog(@"%s:deflateInit2()Error:[%@] Message:[%s]",__func__,errMsg,zlibStreamStruct.msg);
            return nil;
        }
        NSMutableData * compressedData=[NSMutableData dataWithLength:[data length]*1.01+12];
        int deflateStatus;
        do{
            zlibStreamStruct.next_out=[compressedData mutableBytes]+zlibStreamStruct.total_out;
            zlibStreamStruct. avail_out = (unsigned int)[compressedData length ] - (unsigned int)zlibStreamStruct. total_out ;
            deflateStatus = deflate (&zlibStreamStruct, Z_FINISH );
        }while ( deflateStatus == Z_OK );
        if (deflateStatus != Z_STREAM_END )
        {
            NSString *errorMsg = nil ;
            switch (deflateStatus)
            {
                case Z_ERRNO :
                    errorMsg = @"Error occured while reading file." ;
                    break ;
                case Z_STREAM_ERROR :
                    errorMsg = @"The stream state was inconsistent (e.g., next_in or next_out was NULL)." ;
                    break ;
                case Z_DATA_ERROR :
                    errorMsg = @"The deflate data was invalid or incomplete." ;
                    break ;
                case Z_MEM_ERROR :
                    errorMsg = @"Memory could not be allocated for processing." ;
                    break ;
                case Z_BUF_ERROR :
                    errorMsg = @"Ran out of output buffer for writing compressed bytes." ;
                    break ;
                case Z_VERSION_ERROR :
                    errorMsg = @"The version of zlib.h and the version of the library linked do not match." ;
                    break ;
                default :
                    errorMsg = @"Unknown error code." ;
                    break ;
            }
            NSLog ( @"%s: zlib error while attempting compression:[%@] Message:[%s]" , __func__, errorMsg, zlibStreamStruct. msg );
            deflateEnd(&zlibStreamStruct);
            return nil;
        }
        deflateEnd(&zlibStreamStruct);
        [compressedData setLength:zlibStreamStruct.total_out];
        return compressedData;
    }
    +(NSData *)decompress:(NSData *)data{
        z_stream zStream;
        zStream. zalloc = Z_NULL ;
        zStream. zfree = Z_NULL ;
        zStream. opaque = Z_NULL ;
        zStream. avail_in = 0 ;
        zStream. next_in = 0 ;
        int status = inflateInit2 (&zStream, ( 15 + 32 ));
        if (status != Z_OK ) {
            return nil ;
        }
        
        Bytef *bytes = ( Bytef *)[data bytes ];
        NSUInteger length = [data length ];
        NSUInteger halfLength = length/ 2 ;
        NSMutableData *uncompressedData = [ NSMutableData dataWithLength :length+halfLength];
        zStream. next_in = bytes;
        zStream. avail_in = ( unsigned int )length;
        zStream. avail_out = 0 ;
        
        NSInteger bytesProcessedAlready = zStream. total_out ;
        while (zStream. avail_in != 0 ) {
            if (zStream. total_out - bytesProcessedAlready >= [uncompressedData length ]) {
                [uncompressedData increaseLengthBy :halfLength];
            }
            
            zStream. next_out = ( Bytef *)[uncompressedData mutableBytes ] + zStream. total_out -bytesProcessedAlready;
            zStream. avail_out = ( unsigned int )([uncompressedData length ] - (zStream. total_out -bytesProcessedAlready));
            status = inflate (&zStream, Z_NO_FLUSH );
            if (status == Z_STREAM_END ) {
                break ;
            } else if (status != Z_OK ) {
                return nil ;
                
            }
        }
        status = inflateEnd (&zStream);
        if (status != Z_OK ) {
            return nil ;
        }
        [uncompressedData setLength : zStream. total_out -bytesProcessedAlready];  // Set real length
        return uncompressedData;
    }
    @end
    


II 、获取gif图片循环次数和时长

循环次数的key:kCGImagePropertyGIFLoopCount 时间间隔key:kCGImagePropertyGIFUnclampedDelayTime

代码语言:javascript
复制
//获取gif图片的总时长和循环次数
- (NSTimeInterval)durationForGifData:(NSData *)data{
    //将GIF图片转换成对应的图片源
    CGImageSourceRef gifSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
    //获取其中图片源个数,即由多少帧图片组成
    size_t frameCout = CGImageSourceGetCount(gifSource);
    //定义数组存储拆分出来的图片
    NSMutableArray* frames = [[NSMutableArray alloc] init];
    NSTimeInterval totalDuration = 0;
    for (size_t i=0; i<frameCout; i++) {
        //从GIF图片中取出源图片
        CGImageRef imageRef = CGImageSourceCreateImageAtIndex(gifSource, i, NULL);
        //将图片源转换成UIimageView能使用的图片源
        UIImage* imageName = [UIImage imageWithCGImage:imageRef];
        //将图片加入数组中
        [frames addObject:imageName];
        NSTimeInterval duration = [self gifImageDeleyTime:gifSource index:i];
        totalDuration += duration;
        CGImageRelease(imageRef);
    }
    
    //获取循环次数
    NSInteger loopCount;//循环次数
    CFDictionaryRef properties = CGImageSourceCopyProperties(gifSource, NULL);
    if (properties) {
        CFDictionaryRef gif = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary);
        if (gif) {
            CFTypeRef loop = CFDictionaryGetValue(gif, kCGImagePropertyGIFLoopCount);
            if (loop) {
                //如果loop == NULL,表示不循环播放,当loopCount  == 0时,表示无限循环;
                CFNumberGetValue(loop, kCFNumberNSIntegerType, &loopCount);
            };
        }
    }
    
    CFRelease(gifSource);
    return totalDuration;
}


III、根据图像数据第一个字节来判断图像格式

  • sd_contentTypeForImageData
代码语言:javascript
复制

typedef NSInteger SDImageFormat NS_TYPED_EXTENSIBLE_ENUM;
static const SDImageFormat SDImageFormatUndefined = -1;
static const SDImageFormat SDImageFormatJPEG      = 0;
static const SDImageFormat SDImageFormatPNG       = 1;
static const SDImageFormat SDImageFormatGIF       = 2;
static const SDImageFormat SDImageFormatTIFF      = 3;
static const SDImageFormat SDImageFormatWebP      = 4;
static const SDImageFormat SDImageFormatHEIC      = 5;
static const SDImageFormat SDImageFormatHEIF      = 6;
static const SDImageFormat SDImageFormatPDF       = 7;
static const SDImageFormat SDImageFormatSVG       = 8;


+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
    if (!data) {
        return SDImageFormatUndefined;
    }
    
    // File signatures table: http://www.garykessler.net/library/file_sigs.html
    uint8_t c;
    [data getBytes:&c length:1];
    switch (c) {
        case 0xFF:
            return SDImageFormatJPEG;
        case 0x89:
            return SDImageFormatPNG;
        case 0x47:
            return SDImageFormatGIF;
        case 0x49:
        case 0x4D:
            return SDImageFormatTIFF;
        case 0x52: {
            if (data.length >= 12) {
                //RIFF....WEBP
                NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
                if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                    return SDImageFormatWebP;
                }
            }
            break;
        }
        case 0x00: {
            if (data.length >= 12) {
                //....ftypheic ....ftypheix ....ftyphevc ....ftyphevx
                NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(4, 8)] encoding:NSASCIIStringEncoding];
                if ([testString isEqualToString:@"ftypheic"]
                    || [testString isEqualToString:@"ftypheix"]
                    || [testString isEqualToString:@"ftyphevc"]
                    || [testString isEqualToString:@"ftyphevx"]) {
                    return SDImageFormatHEIC;
                }
                //....ftypmif1 ....ftypmsf1
                if ([testString isEqualToString:@"ftypmif1"] || [testString isEqualToString:@"ftypmsf1"]) {
                    return SDImageFormatHEIF;
                }
            }
            break;
        }
        case 0x25: {
            if (data.length >= 4) {
                //%PDF
                NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(1, 3)] encoding:NSASCIIStringEncoding];
                if ([testString isEqualToString:@"PDF"]) {
                    return SDImageFormatPDF;
                }
            }
        }
        case 0x3C: {
            // Check end with SVG tag
            if ([data rangeOfData:[kSVGTagEnd dataUsingEncoding:NSUTF8StringEncoding] options:NSDataSearchBackwards range: NSMakeRange(data.length - MIN(100, data.length), MIN(100, data.length))].location != NSNotFound) {
                return SDImageFormatSVG;
            }
        }
    }
    return SDImageFormatUndefined;
}

代码语言:javascript
复制
PNG:0x89 image/png ,压缩比没有 JPG 高,但是无损压缩,解压缩性能高,苹果推荐的图像格式!
JPG:0xFF image/jpeg,压缩比最高的一种图片格式,有损压缩!最多使用的场景,照相机!解压缩的性能不好!
GIF:0x47 image/gif ,序列桢动图,特点:只支持 256 种颜色!最流行的时候在 1998~1999,有专利的!
  • 简单判断图片格式
代码语言:javascript
复制
//返回图片格式
- (NSString *)contentTypeForImageData:(NSData *)data {
    
    uint8_t c;
    
    [data getBytes:&c length:1];
    
    switch (c) {
            
        case 0xFF:
            
            return @"jpeg格式";
            
        case 0x89:
            
            return @"png格式";
            
        case 0x47:
            
            return @"gif格式";
            
        case 0x49:
            
        case 0x4D:
            
            return @"tiff格式";
            
        case 0x52:
            
        default:
            break;
            
    }
    
    if ([data length] < 12) {
        
        return @"";
        
    }
    return @"";
}

IV 、动态替换app启动图

  • 背景:需要提供LaunchScreen.storyboard作为启动图,由于App支持的运行尺寸太多,不再适合用图片作为启动图
  • 应用场景:修复iOS app版本迭代过程中,更新启动图之后遇到的启动图异常问题.

让应用自动恢复正常的启动图

4.1 应用启动时加载启动图的流程

查找沙盒目录中是否存在可用的缓存启动图,如有则直接使用,否则根据 LaunchScreen.storyboard 生成新的启动图,并将其缓存至沙盒目录/Library/SplashBoard/Snapshots/<Bundle identifier> - {DEFAULT GROUP}/

4.2 缓存启动图在不同系统版本上的表现差异性

  • 缓存路径:

iOS13.0 及以上:Library/SplashBoard/Snapshots/${PRODUCT_BUNDLE_IDENTIFIER} - {DEFAULT GROUP};

iOS13.0 以下:Library/Caches/Snapshots/${PRODUCT_BUNDLE_IDENTIFIER};

  • 图片格式:

iOS10.0 及以上:KTX;iOS10.0 以下:PNG。

  • 系统缓存图目录读写权限:

iOS10.0 及以上:有权限;iOS10.0 以下:无权限。

4.3 解决方案

根据上面的流程,采用替换系统生成的缓存启动图方法进行实现

即用户安装应用后,系统会自动生成启动图并缓存至沙盒目录,接着用户启动应用时,通过代码将沙盒目录下缓存的启动图文件全部替换为通过代码生成的启动图。 1、替换图片时,保持缓存目录下文件名不变 2、适配iOS10:无删除权限的时候,采用removeItemAtPath进行间接达到删除的目的 3、横竖屏适配:在替换时进行校验,只有当替换的启动图与缓存启动图宽高一致时才执行,即竖屏只替换竖屏、横屏只替换横屏 4、使用ImageIO API 对缓存图KTX进行大小的获取

代码语言:javascript
复制
// 通过图片尺寸匹配,竖屏方向图只替换竖屏,横屏方向图只替换横屏
+ (BOOL)checkImage:(UIImage *)aImage sizeEqualToImage:(UIImage *)bImage {
    return CGSizeEqualToSize([self obtainImageSize:aImage], [self obtainImageSize:bImage]);
}

+ (CGSize)obtainImageSize:(UIImage *)image {
    return CGSizeMake(CGImageGetWidth(image.CGImage), CGImageGetHeight(image.CGImage));
}

  • 检查图片大小(使用ImageIO API 对缓存图KTX进行大小的获取
代码语言:javascript
复制
/// 获取图片大小
+ (CGSize)getImageSize:(NSData *)imageData {
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
    CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0, NULL);
    if (imageRef) {
        CGFloat width = CGImageGetWidth(imageRef);
        CGFloat height = CGImageGetHeight(imageRef);
        CFRelease(imageRef);
        CFRelease(source);
        return CGSizeMake(width, height);
    }
    return CGSizeZero;
}

/// 检查图片大小
+ (BOOL)checkImageMatchScreenSize:(UIImage *)image {
    CGSize screenSize = CGSizeApplyAffineTransform([UIScreen mainScreen].bounds.size,
                                                   CGAffineTransformMakeScale([UIScreen mainScreen].scale,
                                                                              [UIScreen mainScreen].scale));
    CGSize imageSize = CGSizeApplyAffineTransform(image.size,
                                                  CGAffineTransformMakeScale(image.scale, image.scale));
    if (CGSizeEqualToSize(imageSize, screenSize)) {
        return YES;
    }
    if (CGSizeEqualToSize(CGSizeMake(imageSize.height, imageSize.width), screenSize)) {
        return YES;
    }
    return NO;
}

4.4 iPad浮窗的适配

iOS端尺寸类型有五种:

iPhone、iPad竖屏、iPad横屏、iPad浮窗、iPad分屏

  • 注意: 不能勾选Requires full screen配置项或配置UIRequiresFullScreen为YES,如此会声明App全屏运行,自然表示不支持浮窗或分屏:

支持分屏要求App的主Window需要使用系统UIWindow,不能继承,并且要通过init方法或initWithFrame:[UIScreen mainScreen].bounds方式初始化。

开启浮窗、分屏能力后默认所有ViewController支持所有屏幕方向:

支持分屏的App必须所有页面适配所有屏幕方向https://developer.apple.com/forums/thread/19578

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-08-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iOS逆向 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • I、压缩数据
    • 1.1 图片压缩
      • 1.2 gzip compressed data for iOS (Gzip压缩数据)
      • II 、获取gif图片循环次数和时长
      • III、根据图像数据第一个字节来判断图像格式
      • IV 、动态替换app启动图
        • 4.1 应用启动时加载启动图的流程
          • 4.2 缓存启动图在不同系统版本上的表现差异性
            • 4.3 解决方案
              • 4.4 iPad浮窗的适配
              相关产品与服务
              文件存储
              文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档