Demo集合从五开始——iOS图片处理探索之路

项目3:制作图片下载、缓存、图像处理的iOS静态库

2019-04-29  本文已影响1人  肠粉白粥_Hoben

一. 项目需求

二. 项目架构

三. 图片下载、处理接口管理

1. 下载、处理图片

对于暴露给外界的接口,根据图片的URL地址来向图片下载缓存管理请求返回image和下载进度,获得image后再向图片处理管理请求处理图片,请求完毕后根据回调返回结果给调用接口的方法。

/**
*
 根据处理类型下载图片
 
 @param url             图片的URL地址
 @param processType     图片处理类型
 @param progressBlock   包含CGFloat类型的下载进度回调
 @param completedBlock  包含UIImage类型的下载完成回调
*
**/
- (void)requestImageWithUrl:(NSString *)url
                processType:(HobenImageProcessType)processType
              progressBlock:(HobenImageProgressBlock)progressBlock
             completedBlock:(HobenImageCompletedBlock)completedBlock {
    [[HobenImageCache sharedInstance] requestImageWithUrl:url progressBlock:progressBlock completedBlock:^(UIImage * _Nullable image) {
        [[HobenImageProcessManager sharedInstance] processImage:image processType:processType completedBlock:completedBlock];
    }];
}

2. 清除缓存

清除缓存主要向图片下载缓存管理请求清除缓存,清除缓存完成后会返回一个完成的回调。

/**
 *
 清除图片缓存
 
 @param completionBlock  清除缓存成功后的回调
 *
 **/
- (void)removeCacheWithCompletionBlock:(void (^)(void))completionBlock {
    [[HobenImageCache sharedInstance] removeCacheWithCompletionBlock:completionBlock];
}

四. 图片下载、缓存管理

1. 图片的下载和缓存

流程图如下,为确保图片不被重复下载,该模块使用单例模式,且保存了正在请求图片的URL,如果存在缓存,则直接回调,如果不存在,则调用下载方法,下载完成后缓存该图片,并及时回调图片和进度。

流程图

图片的缓存和下载主要使用了第三方库SDWebImage

1) 读取缓存图片

以URL为key,直接返回SDImageCache存储的图片。

- (UIImage *)_imageWithUrl:(NSString *)url {
    return [[SDImageCache sharedImageCache] imageFromCacheForKey:url];
}
2) 下载图片和回调进度

调用SDWebImageloadImageWithURL:options:progress:completed:方法,加载完成后,第三方库会自动将该图片缓存起来。

- (void)_requestImageWithUrl:(NSString *)url
               progressBlock:(HobenImageProgressBlock)progreeBlock
              completedBlock:(HobenImageCompletedBlock)completedBlock {
    WEAK_SELF_DECLARED
    [[SDWebImageManager sharedManager] loadImageWithURL:[NSURL URLWithString:url] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
        CGFloat progress = receivedSize * 1.f / expectedSize;
        if (progreeBlock) {
            progreeBlock(progress);
        }
    } completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
        STRONG_SELF_BEGIN
        if (image) {
            [strongSelf _cacheImageWithUrl:url];
            [strongSelf.requestList removeObject:url];
            if (completedBlock) {
                completedBlock(image);
            }
        } else {
            NSLog(@"Cache ERROR in URL:%@", url);
            [strongSelf.requestList removeObject:url];
            if (completedBlock) {
                completedBlock(nil);
            }
        }
        STRONG_SELF_END
    }];
}

2. 清除图片缓存

调用SDImageCache的相应方法以清除缓存。

- (void)removeCacheWithCompletionBlock:(void (^)(void))completionBlock {
    [self.requestList removeAllObjects];
    [[SDImageCache sharedImageCache] clearMemory];
    [[SDImageCache sharedImageCache] clearDiskOnCompletion:completionBlock];
}

五. 图像处理

图像处理提供的方法主要有以下三种:

typedef NS_ENUM(NSUInteger, HobenImageProcessType) {
    HobenImageProcessTypeCommon = 0,        // 不处理
    HobenImageProcessTypeGaussian,          // 高斯模糊
    HobenImageProcessTypeWatermark,         // 水印
};

1. 高斯模糊处理

iOS绘图系列:图像模糊之均值模糊详细描述了相关原理,在此不赘述。

- (void)_processGaussianImage:(UIImage *)image completedBlock:(void (^)(UIImage * _Nullable image))completeBlock {
    CGFloat blur = 0.5f;
    int boxSize = (int)(blur * 40);
    
    // 确保为奇数
    boxSize = boxSize - (boxSize % 2) + 1;
    
    CGImageRef imageRef = image.CGImage;
    vImage_Buffer inBuffer, outBuffer;
    vImage_Error error;
    void *pixelBuffer;
    
    // 从CGImage中获取数据
    CGDataProviderRef inProvider = CGImageGetDataProvider(imageRef);
    CFDataRef inBitmapData = CGDataProviderCopyData(inProvider);
    
    // 设置从CGImage获取对象的属性
    pixelBuffer = malloc(CGImageGetBytesPerRow(imageRef) * CGImageGetHeight(imageRef));
    
    outBuffer.width = inBuffer.width = CGImageGetWidth(imageRef);
    outBuffer.height = inBuffer.height = CGImageGetHeight(imageRef);
    outBuffer.rowBytes = inBuffer.rowBytes = CGImageGetBytesPerRow(imageRef);
    inBuffer.data = (void *)CFDataGetBytePtr(inBitmapData);
    outBuffer.data = pixelBuffer;
    
    // 均值模糊
    error = vImageBoxConvolve_ARGB8888(&inBuffer,
                                       &outBuffer,
                                       NULL,
                                       0,
                                       0,
                                       boxSize,
                                       boxSize,
                                       NULL,
                                       kvImageEdgeExtend);
    
    if (error) {
        NSLog(@"Error from convolution %ld", error);
    }
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(outBuffer.data, outBuffer.width, outBuffer.height, 8, outBuffer.rowBytes, colorSpace, CGImageGetBitmapInfo(imageRef));
    CGImageRef resultImageRef = CGBitmapContextCreateImage(context);
    UIImage *resultImage = [UIImage imageWithCGImage:resultImageRef];
    
    CGColorSpaceRelease(colorSpace);
    free(pixelBuffer);
    CFRelease(inBitmapData);
    CGColorSpaceRelease(colorSpace);
    CGImageRelease(resultImageRef);
    
    if (completeBlock) {
        completeBlock(resultImage);
    }
}

2. 水印处理

iOS绘图系列:Core Graphics绘图提到了相关原理,在此不赘述。

- (void)_processWatermarkImage:(UIImage *)image text:(NSString *)text completedBlock:(HobenImageCompletedBlock)completeBlock {
    UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
    [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
    
    NSDictionary *dictionary = @{NSForegroundColorAttributeName: [UIColor whiteColor], NSFontAttributeName: [UIFont boldSystemFontOfSize:image.size.height / 10]};
    [text drawAtPoint:CGPointZero withAttributes:dictionary];
    
    UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
    
    UIGraphicsEndImageContext();
    
    if (completeBlock) {
        completeBlock(resultImage);
    }
}

六. 导出Framework

之前学习过如何封装Framework,主要的步骤如下:

  1. 选择暴露的头文件
  2. 分别在真机环境和模拟器环境下导出Framework
  3. 通过lipo -create方法将两个Framework,合并成一个Framework后output出来
  4. 将得到的Framework导入到需要用的文件中

这次使用shell脚本来导出Framework,步骤如注释所示:

#工作路径
WORKSPACE="/Users/xxx/Desktop/HobenImageLib/HobenImageLib.xcworkspace"
CONFIG="Release"
USER_HOME=$(eval echo ~${SUDO_USER})
#project名字
FMK_NAME=${PROJECT_NAME}

OUTPUT_DIR=${USER_HOME}/Desktop/Framework_build_output
INSTALL_DIR=${OUTPUT_DIR}/${FMK_NAME}.framework
DEVICE_DIR=${BUILD_DIR}/${CONFIG}-iphoneos/${FMK_NAME}.framework
TEMP_DEVICE_DIR=${OUTPUT_DIR}/TEMP_DEIVCE_DIR/${FMK_NAME}.framework
SIMULATOR_DIR=${BUILD_DIR}/${CONFIG}-iphonesimulator/${FMK_NAME}.framework 

if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi

#XCode导出真机Framework,注意要先clean再build
xcodebuild -workspace "${WORKSPACE}" -configuration "Release" -scheme "${FMK_NAME}" -sdk iphoneos clean build OBJROOT="${OBJROOT}/DependentBuilds"

mkdir -p "${TEMP_DEVICE_DIR}"
cp -R "${DEVICE_DIR}/" "${TEMP_DEVICE_DIR}/"

#XCode导出模拟器Framework,注意要先clean再build
xcodebuild -workspace "${WORKSPACE}" -configuration "Release" -scheme "${FMK_NAME}" -sdk iphonesimulator clean build OBJROOT="${OBJROOT}/DependentBuilds"

mkdir -p "${INSTALL_DIR}"
cp -R "${TEMP_DEVICE_DIR}/" "${INSTALL_DIR}/"

#将两个Framework合并且导出
lipo -create "${TEMP_DEVICE_DIR}/${FMK_NAME}" "${SIMULATOR_DIR}/${FMK_NAME}" -output "${INSTALL_DIR}/${FMK_NAME}"

rm -rf "${TEMP_DEVICE_DIR}"

# 保存静态库打包的版本信息

hash=$(git log -1 --pretty=format:"%h")
date=$(git log -1 --pretty=format:"%ad" --date=format:"%Y-%m-%d %H:%M:%S")
branch=$(git symbolic-ref --short -q HEAD)

info_plist="${INSTALL_DIR}/Info.plist" 

open "${INSTALL_DIR}/../"

七. 导入Framework

在Demo中导入Framework并引用头文件,使用Framework里面暴露的头文件方法:

#import <HobenImageLib/HobenImageLib.h>

...

WEAK_SELF_DECLARED
void (^progressBlock)(CGFloat progress) = ^(CGFloat progress) {
    dispatch_async(dispatch_get_main_queue(), ^{
        STRONG_SELF_BEGIN
        NSLog(@"progress: %lf", progress);
        [strongSelf setProgressLabelValue:progress];
        strongSelf.progressView.progress = progress;
        STRONG_SELF_END
    });
};

void (^completedBlock)(UIImage * _Nullable image) = ^(UIImage * _Nullable image) {
    STRONG_SELF_BEGIN
    PreviewController *vc = [[PreviewController alloc] initWithImage:image];
    [strongSelf.navigationController pushViewController:vc animated:YES];
    STRONG_SELF_END
};

[[HobenImageManager sharedInstance] requestImageWithUrl:url
                                            processType:self.processType
                                          progressBlock:progressBlock
                                         completedBlock:completedBlock];

八. 成果展示

为了使得加载进度效果显示出来,在模拟器模拟了弱网效果。

成果展示如下:

九. Demo

我的Demo在这里,欢迎star~

上一篇下一篇

猜你喜欢

热点阅读