离屏渲染及图片压缩、滤镜等原理解析

一张图像是像素点的集合,每一个像素都是一个独立,明了的颜色RGBA。
当成百上千万的像素集合到一起后,就构成了图像。

就像下图这样:


image.png

图片压缩

  • 采用系统的方式进行压缩
    //PNG格式 文件属性格式并不会被压缩,压缩的是图片内容(像素)
    //压缩的时候,最好不要采取这种方式,而是去用上下文重新生成一个图片,这样的图片才是最小的
    //png这类文件本身已经是图像压缩格式了,再用压缩格式去压缩,很可能会出现压缩不了的情况,再加上压缩文件本身是有文件结构信息的(也就是压缩文件头之类的),所以体积自然会增大。所以对于png、mp3等等这类格式,压缩它们往往只是出于打包到一起的目的,并不是为了减少体积。

    NSData *pngData = UIImagePNGRepresentation(_albumImage);  // 这种压缩耗时长,体积大,但是质量高
    NSData *jpgData = UIImageJPEGRepresentation(_albumImage, 0.1);  // 耗时短,体积小,质量低
    
    _pngImageView.image = [UIImage imageWithData:pngData];
    _jpgImageView.image = [UIImage imageWithData:jpgData];

最好不要采取这种方式压缩,除了有可能压缩下来,体积反而会增大。而且当压缩的时候,只能压缩图片的内容,会装换成bitmap格式,这样所占用的内存会非常大。比如下图:


3.jpg

大小虽然只有836KB,但是当转成bitmap格式进行压缩的时候,所占用的内存至少达:
4288 * 2848 * 4 / 1024 / 1024 = 46.6MB
4288 和 2848 是 图片宽高,4 是 一个像素的字节数。

  • 采用上下文,生成一个新的图片
// 因为生成一个bitmap,所以压缩的很小
- (UIImage *)scaleImage:(UIImage *)image size:(CGSize)imageSize {
    
    UIGraphicsBeginImageContext(imageSize);
    [image drawInRect:CGRectMake(0, 0, imageSize.width, imageSize.height)];
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return newImage;
}

推荐使用这种方式,因为不会占内存,而且也压缩的很小。

图片过滤

我们先要明白一个东西,就是如果要对data类型的数据进行修改,我们需要将它转换成字符数组类型,然后取出数组里面的每个值,修改过后再放回原来的位置。就好像下面这样:

- (void)demoDataC {
    
    //NSString *testStr = ;//@"RGBARGBA" ;//@"255255255255"

    NSData *data = [@"0123456789" dataUsingEncoding:NSUTF8StringEncoding];
    NSInteger testDataLength = data.length;
    Byte *testC = (Byte *)[data bytes];  // 字符数组 testC = "0123456789"
    
    for (NSInteger i = 0; i < testDataLength; i++) {
        Byte testChar = (Byte)testC[i];  // 获取每一个元素,进行修改  //  当i=0 testChar=0
        printf("%c", testChar);
        testChar = testChar + 1;
        testC[i] = testChar;             // 修改完成后,再赋值回去
    }
    printf("\n");

    
    for (NSInteger i = 0; i < testDataLength; i++) {
        Byte testChar = (Byte)testC[i];  // 修改完成后  当i=0 testChar=1
        printf("%c", testChar);
    }
    
}

所以我们要过滤图片,即是要修改图片的data。我们也要先将它转换成字符数组,修改完成过后,再按照原来的样子通过上下文,生成一个bitmap,一一还原。原来的样子,就包括它的宽,高,每一行的字节数和位数,颜色空间和AlphaInfo等。

/*
 从图片文件把 图片数据 的像素拿出来(RGBA), 对像素进行操作, 进行一个转换(Bitmap (GPU))
 修改完之后,还原(图片的属性 RGBA,RGBA (宽度,高度,色值空间,拿到宽度和高度,每一个画多少个像素,画多少行))
 //256(11111111)
 */

- (void)filterImage {
    
    CGImageRef imageRef = self.image.CGImage;
    
    // 一个字节 = 8bit(位) 每行有 17152 个字节,即每行有 17152 * 8 个位   一个像素有 4 个字节
    size_t width  = CGImageGetWidth(imageRef);
    size_t height = CGImageGetHeight(imageRef);
    size_t bits   = CGImageGetBitsPerComponent(imageRef);  // 8
    size_t bitsPerrow = CGImageGetBytesPerRow(imageRef);   // width * 4  (每一行的字节 = 宽度 * 4(即RGBA各对应一个字节))
    
    
    // 颜色空间
    CGColorSpaceRef colorSpace = CGImageGetColorSpace(imageRef);
    // AlphaInfo : RGBA  AGBR  RGB
    CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef);
    
    // 把图片转化为 bitmap 的数据
    CGDataProviderRef providerRef = CGImageGetDataProvider(imageRef);
    CFDataRef bitmapData = CGDataProviderCopyData(providerRef);
    
    NSInteger pixLength = CFDataGetLength(bitmapData);
    // 像素byte数组   要将data类型的装换成为C字符的数组类型才能进行操作
    Byte *pixbuff = CFDataGetMutableBytePtr((CFMutableDataRef)bitmapData);
    
    // 处理像素 RGBA为一个单元
    for (int i = 0; i < pixLength; i += 4) {
        
        // 在这里可以计算出每个像素的具体位置。比如:int pixX = i / width(类似这样,但是肯定这不是正确的表达式);

        [self eocImageFiletPixBuf:pixbuff offset:i];
    }
    
    // 处理好数据过后,就要准备用处理完的数组进行绘制图片了
    // bitmap 生成一个上下文,然后用上下文生成一个过滤后的图片 (用上前面保存的图片数据和属性)
    CGContextRef contextRef = CGBitmapContextCreate(pixbuff, width, height, bits, bitsPerrow, colorSpace, alphaInfo);
    
    CGImageRef filterImageRef = CGBitmapContextCreateImage(contextRef); // 用上下文生成图片
    
    UIImage *filterImage = [UIImage imageWithCGImage:filterImageRef];
    
    _filterImageV.image = filterImage;
    
    
}
// RGBA 为一个单元  彩色照变黑白照  pixBuf是一个数组 offset是每个RGBA的起点
- (void)eocImageFiletPixBuf:(Byte*)pixBuf offset:(int)offset{
    
    int offsetR = offset;
    int offsetG = offset + 1;
    int offsetB = offset + 2;
    int offsetA = offset + 3;
    
    int red = pixBuf[offsetR];
    int gre = pixBuf[offsetG];
    int blu = pixBuf[offsetB];
   // int alp = pixBuf[offsetA];
    
    int gray = (red + gre + blu)/3;
    
    pixBuf[offsetR] = gray;
    pixBuf[offsetG] = gray;
    pixBuf[offsetB] = gray;
    
}

这里取红绿蓝的平均值,并且将新的红绿蓝都赋值成这一个平均值,就能达到灰白的效果。

截图

  • 规则截图
    开启一个图像上下文,并且获取这个上下文,在将规则的path添加到上下文中,再截出上下文,最后将图片画到上下文中,最后再从上下文中获取图片,再最后结束上下文。
// 规则 截图

- (void)shotScreen {
    
    // 开启上下文
    UIGraphicsBeginImageContext(CGSizeMake(200, 200));
    
    // 获取上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // 先 clip context ,才能限制后面上下文操作的范围,使图片画在一个圆内
    CGRect rect = CGRectMake(0, 0, 200, 200);
    
    CGContextAddEllipseInRect(context, rect); // path 加一个圆到上下文中
    CGContextClip(context); // 截出上下文,对于后面进行的上下文操作起限制作用
    
    UIImage *image = [UIImage imageNamed:@"3.jpg"];
    // 将图片滑到上下文中
    [image drawInRect:rect];
    
    // 从上下文中获取图片,这里的上下文还是 (0, 0, 200, 200) 大小的上下文
    UIImage *clipImage = UIGraphicsGetImageFromCurrentImageContext();
    
    // 结束上下文
    UIGraphicsEndImageContext();
    
    _eocImageV.image = clipImage;

}
  • 非规则截图
    道理同上,只是path是一个点的数组,这里点构成的图形可以是不规则的。
// 非规则的截图 (画点,画线)
- (void)noRectClip{
    
    // 开启上下文
    UIGraphicsBeginImageContext(CGSizeMake(200, 200));
    
    // 获取上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // 非规则的path
    CGMutablePathRef pathRef = CGPathCreateMutable();
    
    // 必须是一个闭环,如果不是闭环,会自己回到最初的点
    CGPoint lines[] = {
        CGPointMake(0, 0),
        CGPointMake(150, 70),
        CGPointMake(200, 200),
        CGPointMake(50, 120),
        CGPointMake(30, 30)
    };
    
    CGPathAddLines(pathRef, NULL, lines, 5);
    CGContextAddPath(context, pathRef);
    CGContextClip(context);
    
    UIImage *imageTwo = [UIImage imageNamed:@"3.jpg"];
    [imageTwo drawInRect:CGRectMake(0, 0, 200, 200)];
    
    UIImage *clipImage = UIGraphicsGetImageFromCurrentImageContext();
    
    UIGraphicsEndImageContext();
    
    _eocImageV.image = clipImage;

    
}
  • 红色渲染
// 红色渲染
- (void)blend {
    
    UIImage *imageTwo = [UIImage imageNamed:@"3.jpg"];
    UIGraphicsBeginImageContext(CGSizeMake(200, 200));
    CGContextRef context = UIGraphicsGetCurrentContext();
    [imageTwo drawInRect:CGRectMake(0, 0, 200, 200)];
    
    // 这里要将不透明度设置为0.5,才是覆盖在上面的效果,如果为1,就直接只能看到红色,不能看到红色下面的图片
    UIColor *redColor = [UIColor colorWithRed:1 green:0 blue:0 alpha:0.5];
    
    // 给上下文设置颜色
    CGContextSetFillColorWithColor(context, redColor.CGColor);
    
    // 设置渲染模式
    CGContextSetBlendMode(context, kCGBlendModeNormal);
    
    // 填充上下文
    CGContextFillRect(context, CGRectMake(0, 0, 200, 200));
    
    CGImageRef imageRef = CGBitmapContextCreateImage(context);
    
    _eocImageV.image = [UIImage imageWithCGImage:imageRef];
    
    UIGraphicsEndImageContext();
    
}
  • 截屏
// 截屏
- (void)imageFromFullView{
    // 这里要是屏幕的大小
    UIGraphicsBeginImageContext(self.view.frame.size);
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    [self.view.layer renderInContext:context];
    
    CGImageRef imageRef =  CGBitmapContextCreateImage(context);
    
    _eocImageV.image = [UIImage imageWithCGImage:imageRef];
    
    UIGraphicsEndImageContext();
    
}

你可能感兴趣的:(离屏渲染及图片压缩、滤镜等原理解析)