iOS 图片渐进式下载

为了省去制作图片的麻烦,我就直接拿YYWebImage里面的图片了,我个人也是建议使用这个图片框架来做渐进式下载。

先看下YYKit中做的效果图。

渐进式图片

图片加载很美观,用户体验性非常棒。当我第一次看到的时候,就兴奋的直接拿着代码去用,但是发现并不行,没有效果。后来查了资料才知道这种下载是有要求的。大家若测试,可以用我下面这个代码URL。

 NSURL *url = [NSURL URLWithString:@"http://og3u5glro.bkt.clouddn.com/%E6%B8%90%E8%BF%9B%E5%BC%8F%E5%9B%BE%E7%89%87.jpg"];
 [self.iconImageView yy_setImageWithURL:url
                                   options:YYWebImageOptionProgressiveBlur];

但是,仅仅吃人家给的鱼干,远远满足不了我等对学习的渴望。于是,在和一个群友讨论后,决定自己写出这样的一个效果。

下面我先简单解释下图片格式 然后我会贴上自己实现这个渐进式下载的代码思路 最后当然是对你们来说最关心的,在文末我将提供一个简单的Demo

言归正传,先来解释下,为什么你从百度随便弄一张图的链接放上去,但没有渐进式的下载效果。

实际上这和图片的格式支持有关。 一般来说我们碰到的图片大多都是Baseline JPEG这种格式的图片,我们也称为标准格式。然而渐进式图片下载,则需要Progressive JPEG这个格式,称之为渐进式,当然还有Interlaced格式等等,说到这里也许你已经明白了,是图片格式本身就不支持,不是代码没作用!下面说下这两个格式。

Baseline JPEG

标准格式的图片,是从上往下的方式逐行进行扫描。也就是看起来是一行一行的下载绘制,细心的同学会发现,YYWebImage里面就有这样的下载设置,代码如下:

[imageView yy_setImageWithURL:url options:YYWebImageOptionProgressive];

那看起来大概是这样的

上下扫描下载

Progressive JPEG

渐进式格式图片的扫描是多次的,再打开图片的过程中,先显示他的轮廓,在慢慢扫描绘制所有的色块,最终清晰。效果最上面大家已经看过了,这种技术被广泛应用于大图的下载显示上。 渐进式图片的一些小缺点:最初绘制的模糊图片,实际上与原图的大小有相差、这种绘制更加消耗CPU...

那么,这种图片如何制作呢?

很简单,在photoshop中有存储为web所用格式,打开后选择连续就是Progressive JPEG

这样美观的渐进式下载,我如何实现呢?

图片解码需要用到这个框架处理

#import <ImageIO/ImageIO.h>

首先使用CGImageSourceCreateIncremental(NULL)创建图片源,然后在网络请求代理中拼接每次返回的图片data,使用CGImageSourceUpdateData更新图片数据,最后使用CGImageSourceCreateImageAtIndex来创建图片显示

其实过程就这么多,ok,贴上主要代码,运行看看效果。

@interface ViewController ()<NSURLSessionDelegate> {
    
    NSMutableData * _recieveData;//当前下载date
    long long _expectedLeght;//预估大小
    CGImageSourceRef _incrementallyImgSource;
}
@property(nonatomic, strong) UIImageView *imageView;
/**
 渐进式加载图片
 */
- (void)loadImage {
    
    _incrementallyImgSource = CGImageSourceCreateIncremental(NULL);
    
    _recieveData = [[NSMutableData alloc] init];
    
    [self.view addSubview:self.imageView];
    
    NSURL *url = [NSURL URLWithString:@"http://og3u5glro.bkt.clouddn.com/%E6%B8%90%E8%BF%9B%E5%BC%8F%E5%9B%BE%E7%89%87.jpg"];
    
    NSURLSession *session=[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    
    NSURLRequest *request=[NSURLRequest requestWithURL:url];
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
    [task resume];
    
}

// 1.接收到服务器的响应
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
    // 允许处理服务器的响应,才会继续接收服务器返回的数据
    completionHandler(NSURLSessionResponseAllow);
    
    _expectedLeght = response.expectedContentLength;
    NSLog(@"_expectedLeght   %lld",_expectedLeght);
    
}

// 2.接收到服务器的数据(可能调用多次)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    
    [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
        [_recieveData appendBytes:bytes length:byteRange.length];
    }];
    
    BOOL isloadFinish = NO;
    if (_expectedLeght == _recieveData.length) {
        isloadFinish = YES;
    }
   
    CGImageSourceUpdateData(_incrementallyImgSource, (CFDataRef)_recieveData, isloadFinish);
    CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_incrementallyImgSource, 0, NULL);
    UIImage *imageaa = [UIImage imageWithCGImage:imageRef];

    self.imageView.image = imageaa;
    CGImageRelease(imageRef);
    
    NSLog(@"_recieveData  %lu",(unsigned long)_recieveData.length);
    
}

效果图

yscrollsss.gif

可以看到,一个明显的下拉效果,仔细看确实是有了模糊到清晰的过程。可是这个下拉似乎不怎么好看啊,而且这个模糊效果真心不好,好像是太清晰了,这不是我们需要的优美的体验。

后面通过打印日志我发现,大概下载整张图片的0.12左右可以显示整个图片,那么就好办了。

 //不希望出现下拉效果 
    if (_recieveData.length <= _expectedLeght*0.12) {
        return;
    }

在接收数据方法中直接限制图片绘制,这样解决了下拉。 那么,模糊效果不好怎么处理呢?我第一时间想到的就是,我要再加一层毛玻璃!以此获得更好的用户体验。

毛玻璃我采用#import <Accelerate/Accelerate.h>框架来做。

//毛玻璃处理
- (UIImage *)boxblurImage:(UIImage *)image withBlurNumber:(CGFloat)blur {
    if (blur < 0.f || blur > 1.f) {
        blur = 0.5f;
    }
    int boxSize = (int)(blur * 40);
    boxSize = boxSize - (boxSize % 2) + 1;
    CGImageRef img = image.CGImage;
    vImage_Buffer inBuffer, outBuffer;
    vImage_Error error;
    void *pixelBuffer;
    //从CGImage中获取数据
    CGDataProviderRef inProvider = CGImageGetDataProvider(img);
    CFDataRef inBitmapData = CGDataProviderCopyData(inProvider);
    //设置从CGImage获取对象的属性
    inBuffer.width = CGImageGetWidth(img);
    inBuffer.height = CGImageGetHeight(img);
    inBuffer.rowBytes = CGImageGetBytesPerRow(img);
    inBuffer.data = (void*)CFDataGetBytePtr(inBitmapData);
    pixelBuffer = malloc(CGImageGetBytesPerRow(img) * CGImageGetHeight(img));
    if(pixelBuffer == NULL)
        NSLog(@"No pixelbuffer");
    outBuffer.data = pixelBuffer;
    outBuffer.width = CGImageGetWidth(img);
    outBuffer.height = CGImageGetHeight(img);
    outBuffer.rowBytes = CGImageGetBytesPerRow(img);
    error = vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, NULL, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
    if (error) {
        NSLog(@"error from convolution %ld", error);
    }
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef ctx = CGBitmapContextCreate( outBuffer.data, outBuffer.width, outBuffer.height, 8, outBuffer.rowBytes, colorSpace, kCGImageAlphaNoneSkipLast);
    CGImageRef imageRef = CGBitmapContextCreateImage (ctx);
    UIImage *returnImage = [UIImage imageWithCGImage:imageRef];
    //clean up CGContextRelease(ctx);
    CGColorSpaceRelease(colorSpace);
    free(pixelBuffer);
    CFRelease(inBitmapData);
    CGColorSpaceRelease(colorSpace);
    CGImageRelease(imageRef);
    return returnImage;
}

方法写好,那么调用看看吧!

// 2.接收到服务器的数据(可能调用多次)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    
    [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
        [_recieveData appendBytes:bytes length:byteRange.length];
    }];
    
    BOOL isloadFinish = NO;
    if (_expectedLeght == _recieveData.length) {
        isloadFinish = YES;
    }
    //不希望出现下拉效果 同时避免数据太少毛玻璃绘制crash
    if (_recieveData.length <= _expectedLeght*0.12) {
        return;
    }
    CGImageSourceUpdateData(_incrementallyImgSource, (CFDataRef)_recieveData, isloadFinish);
    CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_incrementallyImgSource, 0, NULL);
    UIImage *imageaa = [UIImage imageWithCGImage:imageRef];
    
    CGFloat length = _expectedLeght;
    CGFloat dataLength = _recieveData.length;
    NSLog(@"....%f",dataLength/length);
    
    self.imageView.image = [self boxblurImage:imageaa withBlurNumber:1-dataLength/length];
    CGImageRelease(imageRef);
    NSLog(@"_recieveData  %lu",(unsigned long)_recieveData.length);

}

效果图,迫不及待~ 为了看清下载效果,找的图片很大2.8M左右。

progress.gif

Gif制作不是很清晰,实际效果要比这个好很多。 有兴趣的同学可以去我GitHub下载这个Demo

既然说了这个图片下载,还有个动态图加载图片跟大家分享一下。

大致是这样的

animation.gif

在以前,我们都是放一张静态图的,当图片下载完成,更改图片,也就是著名的 PlaceholderImage 。如果我想放一张动态的placeholderImage怎么办?我推荐使用YYImage,实现步骤如下: 第一步:把你的imageView继承 YYAnimatedImageView(似乎不继承也可以,我记不清了) 第二步:

    [self.iconImageView sd_setImageWithURL:[NSURL URLWithString:url] 
    placeholderImage:[YYImage imageNamed:@"placeImage.gif"]];
有一点不得不说:万恶的卡顿!虽然已经优化的不错了,但是仍然出现了偶尔轻微卡顿的情况,可能我有强迫症。总之,这样不能众享丝滑了。如果有老司机有办法一路滑下去,请带带我!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Alice

ios tableview 上加 textfiled

ios tableview 上加 textfiled  首先附上我项目中用曾经用到的几张图  并说明一下我的用法: 图1: ? 图2: ? 图3: ? 心在你我...

1915
来自专栏DeveWork

WordPress免插件仅代码实现“返回顶部、返回底部、评论”效果(样式一)

本文所说的”返回顶部、返回底部、评论 “相信你知道是什么东东了吧?  一般你在各大网站的右下角都能看到类似的东东,但许多网站都普遍只有“返回顶部”的效果。不过就...

1687
来自专栏破晓之歌

js写入文件的方式 转

771
来自专栏学海无涯

iOS开发之扫描二维码

自iOS7以后,iOS扫描二维码不需要借助于第三方框架了,苹果在AVFoundation中原生支持了扫描二维码的API,主要涉及到5个类,这5个类在自定义相机或...

2864
来自专栏進无尽的文章

实践-小细节Ⅵ

有时候,UITableView 的cell个数很少,可是UITableView的headView又是一个有颜色背景的View,当我们下拉的时候,拉扯出来的区域也...

582
来自专栏听雨堂

从MapX到MapXtreme2004[2]-图层操作

Mapx中基本的图层操作还是比较简单的,集中在对Layers和Layer的处理上,对别的没有太多要求。   在MapXtreme中,要完成类似功能,发生了一点...

1818
来自专栏技术总结

MJRefresh源码剖析与学习

建议查看原文:https://www.jianshu.com/p/23c876f8ae39(不定时更新)

994
来自专栏前端儿

JS 模拟手机页面文件的下拉刷新

要在仿真器下才能看到效果,比如chrome的里边(或者用手机浏览器查看,但测试发现有些浏览器有问题,目前手机猎豹是没问题的)

1861
来自专栏DeveWork

类极客公园火箭发射“返回顶部”jQuery效果(WordPress代码教程)

之前在三篇系列文章《jQuery仿极客公园火箭发射“返回顶部”效果》中已经给出了“仿”的教程,今天分享也是一个火箭发射“返回顶部”的效果,不过这个火箭更加肥胖了...

1866
来自专栏编程之旅

iOS开发——系统原生的二维码扫描

对于现在的App应用来说,扫描二维码这个功能是再正常不过的一个功能了,在早期开发这些功能的时候,大家或多或少的都接触过ZXing和ZBar这类的第三方库,但从i...

1063

扫码关注云+社区