专栏名称: BourbonZ
iOS
目录
相关文章推荐
高分子科技  ·  中国科大陈立锋教授课题组 Adv. ... ·  3 天前  
高分子科学前沿  ·  帝国理工学院Device: 通过嵌入式打印实 ... ·  3 天前  
好地讯江苏  ·  姑苏区金门挂牌1宗宅地,容积率1.15,楼面 ... ·  4 天前  
51好读  ›  专栏  ›  BourbonZ

iOS 大尺寸图片的旋转和缩放

BourbonZ  · 掘金  ·  · 2018-09-12 10:40

正文

由于 iPhone 的硬件性能限制,直到 iPhone 6s 开始,才将最大内存拓展到 2G
可即使是如此,也不代表一个应用可使用的空间是 2G
一张 10000 x 10000 的图片,如果通过 UIImageJPEGRepresentation 方法将图片转成内存数据,会有一个峰值波动。
这里的峰值其实是图片在解压时产生的位图数据所占空间,然后才转换成我们可以操作的 NSData
其计算公式是 W x H x 4 / 1024 / 1024 也就是 10000 x 10000 x4 /1024 / 1024 = 381.4(M)
这里会产生381M的消耗,及时会被回收,但是想一下,如果图片尺寸很大,数量很多的时候,很容易就会发生异常了。

接下来说下具体的操作

旋转

我们知道如果对一个 UIImage 对象进行旋转操作,可以有如下的方式

通过 CGContextDrawImage 进行图片绘制

+ (UIImage *)image:(UIImage *)image rotation:(UIImageOrientation)orientation {

    long double rotate = 0.0;
    CGRect rect;
    float translateX = 0;
    float translateY = 0;
    float scaleX = 1.0;
    float scaleY = 1.0;

    switch (orientation) {
        case UIImageOrientationLeft:
            rotate = M_PI_2;
            rect = CGRectMake(0, 0, image.size.height, image.size.width);
            translateX = 0;
            translateY = -rect.size.width;
            scaleY = rect.size.width/rect.size.height;
            scaleX = rect.size.height/rect.size.width;
        break;
        default:
            rotate = 0.0;
            rect = CGRectMake(0, 0, image.size.width, image.size.height);
            translateX = 0;
            translateY = 0;
        break;
    }

    UIGraphicsBeginImageContext(rect.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    //做CTM变换
    CGContextTranslateCTM(context, 0.0, rect.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
    CGContextRotateCTM(context, rotate);
    CGContextTranslateCTM(context, translateX, translateY);

    CGContextScaleCTM(context, scaleX, scaleY);
    //绘制图片
    CGContextDrawImage(context, CGRectMake(0, 0, rect.size.width, rect.size.height), image.CGImage);
    UIImage *newPic = UIGraphicsGetImageFromCurrentImageContext();
    return newPic;
} 

这里有一个问题是,这里会创建一个新的图片大小空间的,然后进行重新绘制。可能会存在一个隐患,就是当图片尺寸过大的时候,就会出现内存占用过高的情况

接下来介绍一种另辟蹊径的解决方法--通过给图片添加滤镜的方式。

既然操作的对象是图片,那么它就会各种滤镜展示。系统给我们提供了多大一百多种滤镜,这里的滤镜不单只颜色等状态发生变化。
这其中就有我们需要的滤镜 Key inputTransform

+ (UIImage *)getRotationImage:(UIImage *)image rotation:(CGFloat)rotation {

    CIImage *ciImage = [[CIImage alloc] initWithImage:image];
    CIFilter *filter = [CIFilter filterWithName:@"CIAffineTransform" keysAndValues:kCIInputImageKey, ciImage, nil];

    [filter setDefaults];
    CGAffineTransform transform = CATransform3DGetAffineTransform([self rotateTransform:CATransform3DIdentity clockwise:NO angle:rotation]);
    [filter setValue:[NSValue valueWithBytes:&transform objCType:@encode(CGAffineTransform)] forKey:@"inputTransform"];

    //根据滤镜设置图片
    CIContext *context = [CIContext contextWithOptions:@{kCIContextUseSoftwareRenderer : @(NO)}];
    CIImage *outputImage = [filter outputImage];
    CGImageRef cgImage = [context createCGImage:outputImage fromRect:[outputImage extent]];

    UIImage *result = [UIImage imageWithCGImage:cgImage];

    CGImageRelease(cgImage);

    return result;
}
+ (CATransform3D)rotateTransform:(CATransform3D)initialTransform clockwise:(BOOL)clockwise angle:(CGFloat)angle {

    CGFloat arg = angle*M_PI / 180.0f;
    if(!clockwise){
        arg *= -1;
    }
    //进行形变
    CATransform3D transform = initialTransform;
    transform = CATransform3DRotate(transform, arg, 0, 0, 1);
    CGFloat _flipState1 = 0;
    CGFloat _flipState2 = 0;
    transform = CATransform3DRotate(transform, _flipState1*M_PI, 0, 1, 0);
    transform = CATransform3DRotate(transform, _flipState2*M_PI, 1, 0, 0);

    return transform;
} 

通过这种操作,可以利用 GPU 来进行图片操作,可以一定程度的降低消耗,节约资源。

缩放

既然图片很大,那么我们可以通过缩放的方式,来减小图片的尺寸,减少内存消耗,进而降低异常风险。
我们通常采用 UIImage 提供的系统方法 drawInRect 及其一系列的方法,来进行图片缩放。
可是这种操作的缺陷和最开始介绍的旋转一样,其实质都是进行图片的重新绘制。

通过绘制图片的方式进行图片缩放

+ (UIImage *)image:(UIImage *)image transformtoSize:(CGSize)Newsize {
    // 创建一个bitmap的context
    UIGraphicsBeginImageContext(Newsize);
    // 绘制改变大小的图片
    [image drawInRect:CGRectMake(0, 0, Newsize.width, Newsize.height)];
    // 从当前context中创建一个改变大小后的图片
    UIImage *TransformedImg=UIGraphicsGetImageFromCurrentImageContext();
    // 使当前的context出堆栈
    UIGraphicsEndImageContext();
    // 返回新的改变大小后的图片
    return TransformedImg;
}

这里是内存消耗。通过看图可以发现,针对大图,在进行缩放的时候,内存消耗的峰值能达到426M,耗时在1.5s左右
由于我们使用的手机是 iPhone X ,在更低端的设备上,这是多么大的损耗,很容易发生异常
缩放1

既然上面的方法损耗很大,我们来看下另外的一种方式。

先看下内存消耗
缩放2

通过图上可以看出,在进行图片缩放的时候,内存有小幅增加,产生的消耗在18M,耗时也在1.5s左右。
这样的效果是非常显著的。下面来看代码

+(UIImage *)resizeImage:(UIImage *)image toSize:(CGSize)size {

    CIImage *ciImage = [[CIImage alloc] initWithImage:image];
    //创建一个input image类型的滤镜
    CIFilter *filter = [CIFilter filterWithName:@"CIAffineTransform" keysAndValues:kCIInputImageKey, ciImage, nil];
    //设置默认的滤镜效果
    [filter setDefaults];

    //设置缩放比例
    CGFloat scale = 1;
    if (size.width != CGFLOAT_MAX) {
        scale = (CGFloat) size.width / image.size.width;
    } else if (size.height != CGFLOAT_MAX) {
        scale = (CGFloat) size.height / image.size.height;
    }

    //进行赋值
    CGAffineTransform transform = CGAffineTransformMakeScale(scale, scale);
    [filter setValue:[NSValue valueWithBytes:&transform objCType:@encode(CGAffineTransform)] forKey:@"inputTransform"];

    //通过GPU的方式来进行处理
    CIContext *context = [CIContext contextWithOptions:@{kCIContextUseSoftwareRenderer : @(NO)}];
    //根据滤镜输出图片
    CIImage *outputImage = [filter outputImage];
    CGImageRef cgImage = [context createCGImage:outputImage fromRect:[outputImage extent]];
    //创建UIImage 对象,并释放资源
    UIImage *result = [UIImage imageWithCGImage:cgImage];

    CGImageRelease(cgImage);

    return result;
}

可以发现我们这里使用的和旋转是同样的方式。通过给图片添加滤镜能够很安全的实现我们的需求。







请到「今天看啥」查看全文