iOS图片压缩compress【解决压缩之后图片模糊的问题】https://blog.csdn.net/z929118967/article/details/105414506
#import <zlib.h>
@interface DDGzip : NSObject
/**
* Gzip压缩数据
*
* @param data 需要压缩的数据
*
* @return 压缩后的结果
*/
+(NSData *)compress:(NSData * )data;
/**
* Gzip解压数据
*
* @param data 需要解压的数据
*
* @return 解压后的结果
*/
+(NSData *)decompress:(NSData *)data;
@end
#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
#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
循环次数的key:kCGImagePropertyGIFLoopCount 时间间隔key:kCGImagePropertyGIFUnclampedDelayTime
//获取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;
}
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;
}
PNG:0x89 image/png ,压缩比没有 JPG 高,但是无损压缩,解压缩性能高,苹果推荐的图像格式!
JPG:0xFF image/jpeg,压缩比最高的一种图片格式,有损压缩!最多使用的场景,照相机!解压缩的性能不好!
GIF:0x47 image/gif ,序列桢动图,特点:只支持 256 种颜色!最流行的时候在 1998~1999,有专利的!
//返回图片格式
- (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 @"";
}
让应用自动恢复正常的启动图
查找沙盒目录中是否存在可用的缓存启动图,如有则直接使用,否则根据 LaunchScreen.storyboard 生成新的启动图,并将其缓存至沙盒目录/Library/SplashBoard/Snapshots/<Bundle identifier> - {DEFAULT GROUP}/
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 以下:无权限。
根据上面的流程,采用替换系统生成的缓存启动图
方法进行实现
即用户安装应用后,系统会自动生成启动图并缓存至沙盒目录,接着用户启动应用时,通过代码将沙盒目录下缓存的启动图文件全部替换为通过代码生成的启动图。 1、替换图片时,保持缓存目录下文件名不变 2、适配iOS10:无删除权限的时候,采用removeItemAtPath进行间接达到删除的目的 3、横竖屏适配:在替换时进行校验,只有当替换的启动图与缓存启动图宽高一致时才执行,即竖屏只替换竖屏、横屏只替换横屏 4、使用ImageIO API 对缓存图KTX进行大小的获取
// 通过图片尺寸匹配,竖屏方向图只替换竖屏,横屏方向图只替换横屏
+ (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进行大小的获取
)/// 获取图片大小
+ (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;
}
iOS端尺寸类型有五种:
iPhone、iPad竖屏、iPad横屏、iPad浮窗、iPad分屏
支持分屏要求App的主Window需要使用系统UIWindow,不能继承,并且要通过init方法或initWithFrame:[UIScreen mainScreen].bounds
方式初始化。
开启浮窗、分屏能力后默认所有ViewController支持所有屏幕方向:
支持分屏的App必须所有页面适配所有屏幕方向https://developer.apple.com/forums/thread/19578