继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

iOS高效裁剪图片圆角算法

德玛西亚99
关注TA
已关注
手记 412
粉丝 92
获赞 559

项目有个需求:裁剪图片,针对头像,下面是要求:

 https://img.mukewang.com/5b27516d000130bd10900514.jpg

大家可以看到这张图片的圆角已经去除,下面说说我在项目利用了两种方式实现此裁剪以及查看技术文档发现更高效裁剪方式,下面一一讲解:看下来大约需要15-20分钟。

在公共类中Util类中创建类方法

1.CGContext裁剪

//CGContext裁剪+ (UIImage *)CGContextClip:(UIImage *)img cornerRadius:(CGFloat)c;

实现该方法:

复制代码

// CGContext 裁剪+ (UIImage *)CGContextClip:(UIImage *)img cornerRadius:(CGFloat)c{    int w  = img.size.width * img.scale;    int h = img.size.height * img.scale;
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(w, h), false, 1.0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextMoveToPoint(context, 0, c);
    CGContextAddArcToPoint(context, 0, 0, c, 0, c);
    CGContextAddLineToPoint(context, w-c, 0);
    CGContextAddArcToPoint(context, w, 0, w, c, c);
    CGContextAddLineToPoint(context, w, h-c);
    CGContextAddArcToPoint(context, w, h, w-c, h, c);
    CGContextAddLineToPoint(context, c, h);
    CGContextAddArcToPoint(context, 0, h, 0, h-c, c);
    CGContextAddLineToPoint(context, 0, c);
    CGContextClosePath(context);    
     // 先裁剪 context,再画图,就会在裁剪后的 path 中画    CGContextClip(context);
    [img drawInRect:CGRectMake(0, 0, w, h)];       // 画图    CGContextDrawPath(context, kCGPathFill);
    UIImage *ret = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();    
    return ret;
}

复制代码

在该需要的地方调用如下:

[Util CGContextClip:image cornerRadius:radius];

 

2.UIBezierPath 裁剪

在Util.h类中声明

//UIBezierPath 裁剪+ (UIImage *)UIBezierPathClip:(UIImage *)img cornerRadius:(CGFloat)c;

在Util.m实现方法

复制代码

//UIBezierPath 裁剪+ (UIImage *)UIBezierPathClip:(UIImage *)img cornerRadius:(CGFloat)c{    int w = img.size.width * img.scale;    int h = img.size.height * img.scale;
    CGRect rect = CGRectMake(0, 0, w, h);
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(w, h), false, 1.0);
    [[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:c] addClip];
    [img drawInRect:rect];
    
    UIImage *ret = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();    return ret;
}

复制代码

 

3.空域处理的办法,写个裁剪圆角的算法

对于图像上的一个点(x, y),判断其在不在圆角矩形内,在的话 alpha 是原值,不在的话 alpha 设为 0 即可

https://img1.mukewang.com/5b2751760001de6614101094.jpg

遍历所有像素,判断每个像素在不在4个圆的圆内就行了,4个角,每个角有一个四分之一的圆。

一个优化就是,我不需要遍历全部的像素就能裁出圆角,只需要考虑类似左下角三角形的区域就行了,左下,左上,右上,右下,一共4个三角形区域(另外3个图中没画出),for循环的时候,就循环这个4个三角形区域就行了。

所以对于一幅 w * h 的图像,设圆角大小为 n,n <= min(w, h) / 2,其复杂度为 O(n) = 2(n^2),最坏的情况计算量也不会超过 wh / 2。

对于一个像素点(x, y),判断其在不在圆内的公式:
如果  (x-cx)^2 + (y-cy)^2 <= r^2  就表示点 (x, y) 在圆内,反之不在。通过测试:此算法效率可以提高几倍之上(时间)

在Util.h中声明:

+ (UIImage *)dealImage:(UIImage *)img cornerRadius:(CGFloat)c

在Util.m中实现:

复制代码

+ (UIImage *)dealImage:(UIImage *)img cornerRadius:(CGFloat)c {    // 1.CGDataProviderRef 把 CGImage 转 二进制流
    CGDataProviderRef provider = CGImageGetDataProvider(img.CGImage);    void *imgData = (void *)CFDataGetBytePtr(CGDataProviderCopyData(provider));    int width = img.size.width * img.scale;    int height = img.size.height * img.scale;    
    // 2.处理 imgData//    dealImage(imgData, width, height);    cornerImage(imgData, width, height, c);    
    // 3.CGDataProviderRef 把 二进制流 转 CGImage
    CGDataProviderRef pv = CGDataProviderCreateWithData(NULL, imgData, width * height * 4, releaseData);
    CGImageRef content = CGImageCreate(width , height, 8, 32, 4 * width, CGColorSpaceCreateDeviceRGB(), kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast, pv, NULL, true, kCGRenderingIntentDefault);
    UIImage *result = [UIImage imageWithCGImage:content];
    CGDataProviderRelease(pv);      // 释放空间    CGImageRelease(content);    
    return result;
}void releaseData(void *info, const void *data, size_t size) {
    free((void *)data);
}// 在 img 上处理图片, 测试用void dealImage(UInt32 *img, int w, int h) {    int num = w * h;
    UInt32 *cur = img;    for (int i=0; i<num; i++, cur++) {
        UInt8 *p = (UInt8 *)cur;        // RGBA 排列        // f(x) = 255 - g(x) 求负片
        p[0] = 255 - p[0];
        p[1] = 255 - p[1];
        p[2] = 255 - p[2];
        p[3] = 255;
    }
}// 裁剪圆角void cornerImage(UInt32 *const img, int w, int h, CGFloat cornerRadius) {
    CGFloat c = cornerRadius;
    CGFloat min = w > h ? h : w;    
    if (c < 0) { c = 0; }    if (c > min * 0.5) { c = min * 0.5; }    
    // 左上 y:[0, c), x:[x, c-y)
    for (int y=0; y<c; y++) {        for (int x=0; x<c-y; x++) {
            UInt32 *p = img + y * w + x;    // p 32位指针,RGBA排列,各8位
            if (isCircle(c, c, c, x, y) == false) {                *p = 0;
            }
        }
    }    // 右上 y:[0, c), x:[w-c+y, w)
    int tmp = w-c;    for (int y=0; y<c; y++) {        for (int x=tmp+y; x<w; x++) {
            UInt32 *p = img + y * w + x;            if (isCircle(w-c, c, c, x, y) == false) {                *p = 0;
            }
        }
    }    // 左下 y:[h-c, h), x:[0, y-h+c)
    tmp = h-c;    for (int y=h-c; y<h; y++) {        for (int x=0; x<y-tmp; x++) {
            UInt32 *p = img + y * w + x;            if (isCircle(c, h-c, c, x, y) == false) {                *p = 0;
            }
        }
    }    // 右下 y~[h-c, h), x~[w-c+h-y, w)
    tmp = w-c+h;    for (int y=h-c; y<h; y++) {        for (int x=tmp-y; x<w; x++) {
            UInt32 *p = img + y * w + x;            if (isCircle(w-c, h-c, c, x, y) == false) {                *p = 0;
            }
        }
    }
}// 判断点 (px, py) 在不在圆心 (cx, cy) 半径 r 的圆内static inline bool isCircle(float cx, float cy, float r, float px, float py) {    if ((px-cx) * (px-cx) + (py-cy) * (py-cy) > r * r) {        return false;
    }    return true;
}// 其他图像效果可以自己写函数,然后在 dealImage: 中调用 otherImage 即可void otherImage(UInt32 *const img, int w, int h) {    // 自定义处理}

复制代码

上面是三种方式,可以解决图片裁剪的需求,

原文出处

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP