iOS 常见实践图片程序员

iOS实录5:iOS中本地图片的缩放、裁剪和压缩

2017-04-18  本文已影响1123人  南华coder

导语:图片的缩放、裁剪和压缩等处理,总是在不经意间遇到,如果在考虑不周全的情况下,写出的图片处理代码一不小心就埋下了坑(性能损耗或达不到理想效果)。

图片处理的目标

1、 iOS性能优化中希望UIImageView设置的图片不要超出UIImageView的大小,这时候最好缩放处理一下。
2、 iOS性能优化中常提到设置圆角会引发离屏渲染,较好的方案一般是自己裁剪出圆角图片。
3、图片上传时候,后台希望上传的图片小,产品要求上传的图片够清晰。较好的方案是有节制的压缩

一、图片的缩放处理

1、在UIImage的分类中,提供了四个相关接口

其中最重要的是scaleImageWithSize: 方法,其他三个方法是通过根据参数计算出Size,然后调用scaleImageWithSize处理的。接口如下:

/**
 缩放图片到指定Size
 */
- (UIImage *)scaleImageWithSize:(CGSize)size;

/**
 按比例缩放图片,scale就是缩放比例
 */
- (UIImage *)scaleImageWithScale:(CGFloat)scale;

/**
 缩放图片到指定宽
 */
- (UIImage *)scaleImageToTargetWidth:(CGFloat)targetW;

/**
 缩放图片到指定高
 */
- (UIImage *)scaleImageToTargetHeight:(CGFloat)targetH;
2、scaleImageWithSize的具体代码实现
/**
 缩放图片到指定Size
 */
- (UIImage *)scaleImageWithSize:(CGSize)size{

    //创建上下文
    UIGraphicsBeginImageContextWithOptions(size, YES, self.scale);

    //绘图
    [self drawInRect:CGRectMake(0, 0, size.width, size.height)];

    //获取新图片
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return newImage;
}
3、UIGraphicsBeginImageContextWithOptions方法说明(含性能损耗的坑)
//UIGraphicsBeginImageContextWithOptions函数原型
void   UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale)

二、图片的裁剪处理

1、在UIImage的分类中,提供了三个相关接口

其中clipImageWithCornerRadius:bgColor:是最常用的,通常可以用来设置UIImageView的圆角图片。它是设置好圆角Path,然后调用clipImageWithPath:bgColor:实现的。接口如下:

/**
 根据贝塞尔路径来裁剪
 */
- (UIImage *)clipImageWithPath:(UIBezierPath *)path bgColor:(UIColor *)bgColor;

/**
   裁剪出圆角矩形
 @param cornerRadius 圆角半径
 @param bgColor 背景色
 */
- (UIImage *)clipImageWithCornerRadius:(CGFloat)cornerRadius bgColor:(UIColor *)bgColor;

/**
 从指定的rect裁剪出图片
 */
- (UIImage *)clipImageWithRect:(CGRect)rect;
2、代码实现
- (UIImage *)clipImageWithPath:(UIBezierPath *)path bgColor:(UIColor *)bgColor{

    CGSize imageSize = self.size;
    CGRect rect = CGRectMake(0, 0, imageSize.width, imageSize.height);

    //创建位图上下文
    UIGraphicsBeginImageContextWithOptions(rect.size, YES, self.scale);
    if (bgColor) {
        UIBezierPath *bgRect = [UIBezierPath bezierPathWithRect:rect];
        [bgColor setFill];
        [bgRect fill];
    }
    //裁剪
    [path addClip];
    //绘制
    [self drawInRect:rect];
    UIImage *clipImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return clipImage;
}

/**
 裁剪圆角图片
 */
- (UIImage *)clipImageWithCornerRadius:(CGFloat)cornerRadius bgColor:(UIColor *)bgColor{

    CGSize imageSize = self.size;
    CGRect rect = CGRectMake(0, 0, imageSize.width, imageSize.height);

    UIBezierPath *roundRectPath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:cornerRadius];
    return [self clipImageWithPath:roundRectPath bgColor:bgColor];
}

/**
 从指定的rect裁剪出图片
 */
- (UIImage *)clipImageWithRect:(CGRect)rect{

    CGFloat scale = self.scale;
    CGImageRef clipImageRef = CGImageCreateWithImageInRect(self.CGImage,
                                                          CGRectMake(rect.origin.x * scale,
                                                                     rect.origin.y  * scale,
                                                                     rect.size.width * scale,
                                                                     rect.size.height * scale));

    CGRect smallBounds = CGRectMake(0, 0, CGImageGetWidth(clipImageRef)/scale, CGImageGetHeight(clipImageRef)/scale);


    UIGraphicsBeginImageContextWithOptions(smallBounds.size, YES, scale);
    CGContextRef context = UIGraphicsGetCurrentContext();

    // clipImage是将要绘制的UIImage图片(防止图片上下颠倒)
    CGContextTranslateCTM(context, 0, smallBounds.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
    CGContextDrawImage(context, CGRectMake(0, 0, smallBounds.size.width, smallBounds.size.height), clipImageRef);

//    CGContextDrawImage(context, smallBounds, clipImageRef);
    UIImage* clipImage = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();
    return clipImage;
}
3、CGContextDrawImage 引起的图片上下颠倒解决

1)上下颠倒的原因:坐标轴不同

解决办法:绘制到context前通过矩阵垂直翻转坐标系,代码如下:

CGContextTranslateCTM(context, 0, smallBounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawImage(context, CGRectMake(0, 0, smallBounds.size.width, smallBounds.size.height), clipImageRef);
4、clipImageWithCornerRadius:bgColor:解决的性能问题

三、图片的压缩处理

1、在UIImage的分类中,提供了两个相关接口

一个接口是尺寸压缩,一个接口是质量压缩,一般情况下,图片的压缩是先尺寸压缩,把图片压缩到合适的宽高像素(如640px,1080px),然后再压缩质量,图片质量降低到合适的字节数。接口定义如下:

/**
 压缩到指定像素px
 */
- (UIImage *)compressImageToTargetPx:(CGFloat)targetPx;


/**
 压缩到指定千字节(kb)
 */
- (NSData *)compressImageToTargetKB:(NSInteger )numOfKB;
2、代码实现
/**
 压缩到指定像素px
 */
- (UIImage *)compressImageToTargetPx:(CGFloat)targetPx{

    UIImage *compressImage = nil;

    CGSize imageSize = self.size;
    CGFloat compressScale = 0; //压缩比例

    //压缩后的目标size
    CGSize targetSize = CGSizeMake(targetPx, targetPx);
    //实际宽高比例
    CGFloat factor = imageSize.width / imageSize.height;

    if (imageSize.width < targetSize.width && imageSize.height < targetSize.height) {
        //图片实际宽高 都小于 目标宽高,没必要压缩
        compressImage = self;
    
    }else if (imageSize.width > targetSize.width && imageSize.height > targetSize.height){
        //图片实际宽高 都大于 目标宽高
        if (factor <= 2) {
            //宽高比例小于等于2,获取大的等比压缩
            compressScale = targetPx / MAX(imageSize.width,imageSize.height);
        }else{
            //宽高比例大于2,获取小的等比压缩
            compressScale = targetPx / MIN(imageSize.width,imageSize.height);
        }
   }else if(imageSize.width > targetSize.width && imageSize.height < imageSize.height){
        //宽大于目标宽,高小于目标高
        if (factor <= 2) {
            compressScale = targetSize.width / imageSize.width;
        }else{
            compressImage = self;
        }
    }else if(imageSize.width < targetSize.width && imageSize.height > imageSize.height){
        //宽小于目标宽,高大于目标高
        if (factor <= 2) {
            compressScale = targetSize.height / imageSize.height;
        }else{
            compressImage = self;
        }
    }

    //需要压缩
    if (compressScale > 0 && !compressImage) {
    
        CGSize compressSize = CGSizeMake(self.size.width * compressScale, self.size.height * compressScale);
        UIGraphicsBeginImageContextWithOptions(compressSize, YES, 1);
        [self drawInRect:CGRectMake(0, 0, compressSize.width, compressSize.height)];
        compressImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    }

    if (!compressImage) {
        compressImage = self;
    }

    return compressImage;
}

/**
 压缩到指定千字节(kb)
 */
- (NSData *)compressImageToTargetKB:(NSInteger )numOfKB{

    CGFloat compressionQuality = 0.9f;
    CGFloat compressionCount = 0;

    NSData *imageData = UIImageJPEGRepresentation(self,compressionQuality);

    while (imageData.length >= 1000 * numOfKB && compressionCount < 15) {  //15是最大压缩次数.mac中文件大小1000进制
        compressionQuality = compressionQuality * 0.9;
        compressionCount ++;
        imageData = UIImageJPEGRepresentation(self, compressionQuality);
    }
    return imageData;
}

四、图片压缩、裁剪和压缩方法的使用

:因为这些方法都是放到UIImage的分类UIImage+Tool中,所有使用前,#import "UIImage+Tool.h"就可以使用相关方法了。

1、Controller中调用这些方法,并展示在UIImagView中
- (UIImage *)testScaleImage:(UIImage *)originImage{

    UIImage *scaleImage = [originImage scaleImageWithSize:CGSizeMake(100, 100)];
//    UIImage *scaleImage = [originImage scaleImageWithScale:0.5];
//    UIImage *scaleImage = [originImage scaleImageToTargetWidth:100];
//    UIImage *scaleImage = [originImage scaleImageToTargetHeight:100];
    return scaleImage;
}

- (UIImage *)testClipImage:(UIImage *)originImage{

    UIImage *clipImage = [originImage clipImageWithRect:CGRectMake(0, 0, originImage.size.width/2, originImage.size.height)];
    return clipImage;
}

- (UIImage *)testClipRoundCornerImage:(UIImage *)originImage {

    UIImage *roundImage = [originImage clipImageWithCornerRadius:originImage.size.height/2 bgColor:[UIColor whiteColor]];
    return roundImage;
}

- (void)testCompressImage{

    UIImage *image = [UIImage imageNamed:@"image1_3.6MB_4032 × 3024.jpeg"];
    NSString *filePath = [[NSBundle mainBundle]pathForResource:@"image1_3.6MB_4032 × 3024" ofType:@"jpeg"];
    NSString *lengthStr = [[NSData dataWithContentsOfFile:filePath] lengthString];

    NSDate *startDate = [NSDate date];

    NSLog(@"\n\n压缩前尺寸大小:%@ ,质量大小:%@ ,scale = %lf", NSStringFromCGSize(image.size),lengthStr,image.scale);
    UIImage *newImage = [image compressImageToTargetPx:1080];
    NSLog(@"尺寸压缩后 的 尺寸大小:%@ ,scale = %lf", NSStringFromCGSize(newImage.size),newImage.scale);
    NSData *imageData = [newImage compressImageToTargetKB:100];

    NSTimeInterval cost = [[NSDate date]timeIntervalSinceDate:startDate];
    NSLog(@"质量压缩后 的 质量大小:%@,花费时间 = %.3lfs",[imageData lengthString],cost);
}
2、图片压缩前后,质量大小的说明

为了方便计算图片对应的NSData大小,新增了一个NSData的方法lengthString,用来计算NSData对象代表的质量大小

/**
 获取大小描述
 */
- (NSString *)lengthString{

   NSUInteger length = [self length];
    //MAC上,文件大小采用的是1000进制换算
    CGFloat scale = 1000.0f;

    CGFloat fileSize = 0.0f;
    NSString *unitStr = @"";
    if(length >= pow(scale, 3)) {
        fileSize =  length * 1.0 / pow(scale, 3);
        unitStr = @"GB";
    }else if (length >= pow(scale, 2)) {
        fileSize = length * 1.0 / pow(scale, 2);
        unitStr = @"MB";
    }else if (length >= scale) {
        fileSize = length * 1.0 / scale;
        unitStr = @"KB";
    }else {
        fileSize = length * 1.0;
        unitStr = @"B";
    }

    NSString *fileSizeInUnitStr = [NSString stringWithFormat:@"%.2f %@",
                               fileSize,unitStr];
    return fileSizeInUnitStr;

}
3、图片处理后的效果
图片处理展示效果图 图片压缩输出效果图

五、图片处理其他####

图片的处理中还有些不在缩放、裁剪和压缩之列,但是亦在项目中常用到相关处理。

1、图片的拉伸和平铺函数
 - (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets resizingMode:(UIImageResizingMode)resizingMode
2、UIImageView 的contentMode

说明:setNeedsDisplay会调用自动调用drawRect方法,这样可以拿到 UIGraphicsGetCurrentContext,就可以画画了。而setNeedsLayout会默认调用layoutSubViews,方便处理子视图中的一些数据。

END

上一篇下一篇

猜你喜欢

热点阅读