JPImageresizerView - 专门裁剪图片、GIF、视频的轮子(简单易用、功能丰富)

效果图

简介(当前版本:1.11.4)

一个专门裁剪图片、GIF、视频的轮子,简单易用,功能丰富(高自由度的参数设定、支持旋转和镜像翻转、蒙版、压缩等),能满足绝大部分裁剪的需求。

Feature:
    ✅ 能自适应裁剪区域的缩放;
    ✅ 高自由度的参数设定,包括裁剪区域的间距、裁剪宽高比、是否自适应缩放等;
    ✅ 支持最多8个拖拽方向的裁剪区域;
    ✅ 支持上左下右的旋转;
    ✅ 水平和垂直的镜像翻转;
    ✅ 两种边框样式;
    ✅ 支持圆框裁剪;
    ✅ 自定义毛玻璃样式、边框颜色、背景颜色、遮罩透明度;
    ✅ 自定义边框图片;
    ✅ 可动态修改视图区域和裁剪区域间距,支持横竖屏切换;
    ✅ 可自定义蒙版图片裁剪;
    ✅ 可裁剪本地视频整段画面或某一帧画面;
    ✅ 可截取某一段本地视频,裁剪后并转成GIF;
    ✅ 可裁剪GIF;
    ✅ 可保存当前裁剪状态;
    ✅ 图片支持N宫格裁剪;
    ✅ 兼容Swift&SwiftUI环境。

TODO:
    🔘 Swift版本;
    🔘 固定不缩放裁剪区域;
    🔘 视频不再需要修正方向再裁剪;
    🔘 裁剪远程视频;
    🔘 持久化缓存裁剪历史;
    🔘 将视频裁剪部分(AVFoundation模块)分离出来;
    🔘 实现苹果相册裁剪功能中的自由拖拽旋转、翻转角度的效果。

注意:由于autoLayout不利于手势控制,所以目前使用的是frame布局,暂不支持autoLayout。

最新改动

兼容Swift&SwiftUI环境。

如何使用

初始化

1. 配置初始参数

可设置的裁剪元素(图片、GIF、视频),只能选择其中一个,并且不能为nil:
    - image:裁剪的图片/GIF(以UIImage传入)
    - imageData:裁剪的图片/GIF(以NSData传入)
    - videoURL:裁剪的本地视频(以NSURL传入)
    - videoAsset:裁剪的本地视频(以AVURLAsset传入)
    
其他部分可配置参数(更多可查看JPImageresizerView的头文件):
    - blurEffect:毛玻璃样式
    - borderImage:边框图片
    - frameType & strokeColor:边框样式&颜色
    - bgColor:背景色
    - maskAlpha:遮罩透明度
    - resizeWHScale:裁剪的宽高比
    - contentInsets:裁剪区域与视图的间距
    - maskImage:蒙版图片

图片/GIF

//【裁剪的图片/GIF】以UIImage传入
JPImageresizerConfigure *configure = [JPImageresizerConfigure defaultConfigureWithImage:image make:^(JPImageresizerConfigure *configure) {
    // 到这里已经有了默认参数值,可以在这里另外设置你想要的参数值(使用了链式编程方式)
    configure
    .jp_maskAlpha(0.5)
    .jp_strokeColor([UIColor yellowColor])
    .jp_frameType(JPClassicFrameType)
    .jp_contentInsets(contentInsets)
    .jp_bgColor([UIColor orangeColor])
    .jp_isClockwiseRotation(YES)
    .jp_animationCurve(JPAnimationCurveEaseOut);
}];

// 如果想要初始化为正方形,可设置 JPImageresizerConfigure 的 resizeWHScale 属性
configure.resizeWHScale = 1; // 默认为0,完全显示
// 另外如果还需要固定比例的话:
configure.isArbitrarily = YES; // 默认为YES

// 2.【裁剪的图片/GIF】以NSData传入
JPImageresizerConfigure *configure = [JPImageresizerConfigure defaultConfigureWithImageData:imageData make:^(JPImageresizerConfigure *configure) { ...... };

视频

关于从系统相册获取的视频,视频方向有可能是修改过的(即相册中旋转、翻转过),修改后的videoTrack.preferredTransform != CGAffineTransformIdentity,图片也会,不过好歹图片有个imageOrientation属性告知具体改动了什么,由于我才疏学浅,单单从preferredTransform并不知道是经过了具体的哪些改动,如果只是旋转还好,旋转+翻转后的数值都是不一定的,这样导致最后裁剪时会错乱,目前只好先修正方向后再进行裁剪,日后改进,希望能有缘之士给予指点!

初始化后再修正(先进入页面后再修正),具体操作可参照Demo:

// 1.【视频】以NSURL传入
JPImageresizerConfigure *configure = [JPImageresizerConfigure defaultConfigureWithVideoURL:videoURL make:^(JPImageresizerConfigure *configure) { ...... } fixErrorBlock:^(NSURL *cacheURL, JPImageresizerErrorReason reason) {
    // 初始化修正视频方向的错误回调
} fixStartBlock:^{
    // 初始化修正视频方向的开始回调
} fixProgressBlock:^(float progress) {
    // 初始化修正视频方向的进度回调 
} fixCompleteBlock:^(NSURL *cacheURL) {
    // 初始化修正视频方向的完成回调
}];

// 2.【视频】以AVURLAsset传入
[JPImageresizerConfigure defaultConfigureWithVideoAsset:videoAsset 
                                                   make:^(JPImageresizerConfigure *configure) { ...... } 
                                          fixErrorBlock:^(NSURL *cacheURL, JPImageresizerErrorReason reason) { ...... } 
                                          fixStartBlock:^{ ...... } fixProgressBlock:^(float progress) { ...... } 
                                       fixCompleteBlock:^(NSURL *cacheURL) { ...... }];

又或者先修正再初始化(先修正后再进入页面),可以使用JPImageresizerTool的API来修正,具体操作可参照Demo:

// 获取视频信息
AVURLAsset *videoAsset = [AVURLAsset assetWithURL:videoURL];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[videoAsset loadValuesAsynchronouslyForKeys:@[@"duration", @"tracks"] completionHandler:^{
    dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

AVAssetTrack *videoTrack = [videoAsset tracksWithMediaType:AVMediaTypeVideo].firstObject;
if (CGAffineTransformEqualToTransform(videoTrack.preferredTransform, CGAffineTransformIdentity)) {
    // 无需修正,进入裁剪界面
    JPImageresizerConfigure *configure = [JPImageresizerConfigure defaultConfigureWithVideoAsset:videoAsset make:nil fixErrorBlock:nil fixStartBlock:nil fixProgressBlock:nil fixCompleteBlock:nil];
    ......
    return;
}

// 修正方向
[JPImageresizerTool fixOrientationVideoWithAsset:videoAsset fixErrorBlock:^(NSURL *cacheURL, JPImageresizerErrorReason reason) {
    // 修正视频方向的错误回调
} fixStartBlock:^(AVAssetExportSession *exportSession) {
    // 修正视频方向的开始回调
    // 返回exportSession,可监听进度或取消导出
} fixCompleteBlock:^(NSURL *cacheURL) {
    // 修正视频方向的完成回调
    // cacheURL:修正方向后的视频导出后的最终存放路径,默认该路径为NSTemporaryDirectory文件夹下,保存该路径,裁剪后删除视频。
    
    // 开始裁剪,进入裁剪界面
    JPImageresizerConfigure *configure = [JPImageresizerConfigure defaultConfigureWithVideoAsset:[AVURLAsset assetWithURL:cacheURL] make:nil fixErrorBlock:nil fixStartBlock:nil fixProgressBlock:nil fixCompleteBlock:nil];
    ......
}];
  • PS1:如果视频不需要修正,fixStartBlockfixProgressBlockfixErrorBlock均不会调用,会直接调用fixCompleteBlock,返回原路径;
  • PS2:如果确定是无需修正方向的视频,fixErrorBlockfixStartBlockfixProgressBlockfixCompleteBlocknil
  • PS3:更换视频:-setVideoURL: animated: fixErrorBlock: fixStartBlock: fixProgressBlock: fixCompleteBlock:-setVideoAsset: animated: fixErrorBlock: fixStartBlock: fixProgressBlock: fixCompleteBlock: 方法也与之同理,内部会判定是否需要修正;
  • PS4:如果需要初始化就固定裁剪宽高比(如圆切、蒙版等),需要设置JPImageresizerConfigureisArbitrarily属性为NO(默认为YES):
JPImageresizerConfigure *configure = [JPImageresizerConfigure darkBlurMaskTypeConfigureWithImage:nil make:^(JPImageresizerConfigure *configure) {
    configure
    .jp_maskImage([UIImage imageNamed:@"love.png"])
    .jp_isArbitrarily(NO);
}];

2. 创建JPImageresizerView对象并添加到视图上

JPImageresizerView *imageresizerView = [JPImageresizerView imageresizerViewWithConfigure:configure imageresizerIsCanRecovery:^(BOOL isCanRecovery) {
    // 可在这里监听到是否可以重置
    // 如果不需要重置(isCanRecovery为NO),可在这里做相应处理,例如将重置按钮设置为不可点或隐藏
    // PS:isCanRecovery仅针对[旋转]、[缩放]、[镜像]的变化情况,其他如裁剪宽高比、圆切等变化情况需用户自行判定能否重置
    // 具体操作可参照Demo
    // 注意循环引用
} imageresizerIsPrepareToScale:^(BOOL isPrepareToScale) {
    // 可在这里监听到裁剪区域是否预备缩放至适合范围
    // 如果预备缩放(isPrepareToScale为YES),此时裁剪、旋转、镜像功能不可用,可在这里做相应处理,例如将对应按钮设置为不可点或隐藏
    // 具体操作可参照Demo
    // 注意循环引用
}];
[self.view addSubview:imageresizerView];
self.imageresizerView = imageresizerView;

// 创建后均可动态修改configure的参数
self.imageresizerView.image = [UIImage imageNamed:@"Kobe.jpg"]; // 更换裁剪图片(默认带动画效果)
self.imageresizerView.resizeWHScale = 16.0 / 9.0; // 修改裁剪宽高比
self.imageresizerView.initialResizeWHScale = 0.0; // 默认为初始化时的resizeWHScale,调用 -recoveryByInitialResizeWHScale 方法进行重置则 resizeWHScale 会重置为该属性的值

// 注意:iOS11以下的系统,所在的controller最好设置automaticallyAdjustsScrollViewInsets为NO
// 不然imageresizerView会随导航栏或状态栏的变化产生偏移
if (@available(iOS 11.0, *)) {

} else {
    self.automaticallyAdjustsScrollViewInsets = NO;
}

在Swift中的使用

// 1.初始配置
let configure = JPImageresizerConfigure.defaultConfigure(with: image) { c in
    _ = c
        .jp_viewFrame(frame)
        .jp_bgColor(.black)
        .jp_frameType(.classicFrameType)
        .jp_contentInsets(.init(top: 16, left: 16, bottom: 16, right: 16))
        .jp_animationCurve(.easeInOut)
}

// 2.创建imageresizerView
let imageresizerView = JPImageresizerView(configure: configure) { [weak self] isCanRecovery in
    // 当不需要重置设置按钮不可点
    self?.recoveryBtn.isEnabled = isCanRecovery
} imageresizerIsPrepareToScale: { [weak self] isPrepareToScale in
    // 当预备缩放设置按钮不可点,结束后可点击
    self?.operationView.isUserInteractionEnabled = !isPrepareToScale
}

// 3.添加到视图上
view.insertSubview(imageresizerView, at: 0)
self.imageresizerView = imageresizerView

具体使用可以去Github下载Demo查看(JPCropViewController)。

裁剪

裁剪说明:
    1.裁剪过程是在子线程中执行,进度、错误、完成的回调都会切回主线程执行,如果是高清图片,裁剪前可添加HUD提示;
    2.compressScale:图片和GIF的压缩比例,大于等于1按原图尺寸裁剪,小于等于0则返回nil(例:compressScale = 0.5,1000 x 500 --> 500 x 250);
    3.cacheURL:缓存路径,可设置为nil,图片和GIF则不缓存,而视频会默认缓存到系统的NSTemporaryDirectory文件夹下,视频名为当前时间戳,格式为mp4;
    4.错误原因 JPCropErrorReason 说明:
        - JPCEReason_NilObject:裁剪元素为空
        - JPCEReason_CacheURLAlreadyExists:缓存路径已存在其他文件
        - JPCEReason_NoSupportedFileType:不支持的文件类型
        - JPCEReason_VideoAlreadyDamage:视频文件已损坏
        - JPCEReason_VideoExportFailed:视频导出失败
        - JPCEReason_VideoExportCancelled:视频导出取消
    5.注意:缓存路径的图片格式会自动修正,例如原本写的是`xxx/xxx.jpeg`,由于使用了蒙版,裁剪后则会修正为`xxx/xxx.png`了,最终的缓存路径要以裁剪回调`completeBlock`中的`result.cacheURL`为准。

裁剪图片

// 1.以原图尺寸进行裁剪
[self.imageresizerView cropPictureWithCacheURL:cacheURL errorBlock:^(NSURL *cacheURL, JPImageresizerErrorReason reason) {
    // 错误的回调
    // reason:错误原因
    // 注意循环引用
} completeBlock:^(JPImageresizerResult *result) {
    // 裁剪完成
    // result:裁剪后的结果(JPImageresizerResult)
    // result.image:裁剪后的图片(已解码好的)
    // result.cacheURL:目标存放路径
    // result.isCacheSuccess:是否缓存成功(缓存不成功则cacheURL为nil)
    // 注意循环引用
}];


// 2.自定义压缩比例裁剪图片
// compressScale --- 压缩比例,大于等于1按原图尺寸裁剪,小于等于0则返回nil(例:compressScale = 0.5,1000 x 500 --> 500 x 250)
// completeBlock --- 裁剪完成的回调(返回裁剪后的结果,包含已解码好的图片、缓存路径)
- (void)cropPictureWithCompressScale:(CGFloat)compressScale
                            cacheURL:(NSURL *)cacheURL
                          errorBlock:(JPImageresizerErrorBlock)errorBlock
                       completeBlock:(JPCropDoneBlock)completeBlock;
  • 裁剪N宫格图片
image
// 1.自定义N宫格裁剪
// columnCount:N宫格的列数(最小1列)
// rowCount:N宫格的行数(最小1行)
// compressScale --- 压缩比例,大于等于1按原图尺寸裁剪,小于等于0则返回nil(例:compressScale = 0.5,1000 x 500 --> 500 x 250)
// bgColor --- N宫格的背景色(如果图片有透明区域,或者设置了蒙版的情况才生效,设置隐藏(透明)区域的背景色)
[self.imageresizerView cropGirdPicturesWithColumnCount:4 rowCount:2 compressScale:1 bgColor:UIColor.redColor cacheURL:cacheURL errorBlock:^(NSURL *cacheURL, JPImageresizerErrorReason reason) {
    // 错误的回调
    // reason:错误原因
    // 注意循环引用
} completeBlock:^(JPImageresizerResult *originResult, NSArray<JPImageresizerResult *> *fragmentResults, NSInteger columnCount, NSInteger rowCount) {
    // 裁剪完成
    // originResult:裁剪后的原图结果(开始N宫格之前)
    // fragmentResults:裁剪后的原图被裁剪成N宫格图片的结果集合(共 columnCount * rowCount 个)
    // columnCount:调用该方法时传入的列数
    // rowCount:调用该方法时传入的行数
    // 注意循环引用
}];

// 2.九宫格裁剪(3行3列)
- (void)cropNineGirdPicturesWithCompressScale:(CGFloat)compressScale
                                      bgColor:(UIColor *)bgColor
                                     cacheURL:(NSURL *)cacheURL
                                   errorBlock:(JPImageresizerErrorBlock)errorBlock
                                completeBlock:(JPCropNGirdDoneBlock)completeBlock;

分享至朋友圈(建议使用resizeWHScale = 1进行裁剪):

image

裁剪GIF

image
image
// 1.原图尺寸裁剪GIF
[self.imageresizerView cropGIFWithCacheURL:cacheURL errorBlock:^(NSURL *cacheURL, JPImageresizerErrorReason reason) {
    // 错误的回调
    // reason:错误原因
    // 注意循环引用
} completeBlock:^(JPImageresizerResult *result) {
    // 裁剪完成
    // result:裁剪后的结果(JPImageresizerResult)
    // result.image:裁剪后的GIF(已解码好的)
    // result.cacheURL:目标存放路径
    // result.isCacheSuccess:是否缓存成功(缓存不成功则cacheURL为nil)
    // 注意循环引用
}];

// 2.自定义压缩比例裁剪GIF
// completeBlock --- 裁剪完成的回调(返回裁剪后的结果,包含已解码好的GIF、缓存路径)
- (void)cropGIFWithCompressScale:(CGFloat)compressScale
                        cacheURL:(NSURL *)cacheURL
                      errorBlock:(JPImageresizerErrorBlock)errorBlock
                   completeBlock:(JPCropDoneBlock)completeBlock;

// 3.自定义裁剪GIF
// isReverseOrder --- 是否倒放
// rate --- 速率
// completeBlock --- 裁剪完成的回调(返回裁剪后的结果,包含已解码好的GIF、缓存路径)
- (void)cropGIFWithCompressScale:(CGFloat)compressScale
                  isReverseOrder:(BOOL)isReverseOrder
                            rate:(float)rate
                        cacheURL:(NSURL *)cacheURL
                      errorBlock:(JPImageresizerErrorBlock)errorBlock
                   completeBlock:(JPCropDoneBlock)completeBlock;

裁剪GIF的其中一帧

// 1.原图尺寸裁剪GIF当前帧画面
// completeBlock --- 裁剪完成的回调(返回裁剪后的结果,包含已解码好的图片、缓存路径)
- (void)cropGIFCurrentIndexWithCacheURL:(NSURL *)cacheURL
                             errorBlock:(JPImageresizerErrorBlock)errorBlock
                          completeBlock:(JPCropDoneBlock)completeBlock;

// 2.自定义压缩比例裁剪GIF当前帧画面
// completeBlock --- 裁剪完成的回调(返回裁剪后的结果,包含已解码好的图片、缓存路径)
- (void)cropGIFCurrentIndexWithCompressScale:(CGFloat)compressScale
                                    cacheURL:(NSURL *)cacheURL
                                  errorBlock:(JPImageresizerErrorBlock)errorBlock
                               completeBlock:(JPCropDoneBlock)completeBlock;

// 3.自定义压缩比例裁剪GIF指定帧画面
// index --- 第几帧画面
// compressScale --- 压缩比例,大于等于1按原图尺寸裁剪,小于等于0则返回nil(例:compressScale = 0.5,1000 x 500 --> 500 x 250)
- (void)cropGIFWithIndex:(NSUInteger)index
           compressScale:(CGFloat)compressScale
                cacheURL:(NSURL *)cacheURL
              errorBlock:(JPImageresizerErrorBlock)errorBlock
           completeBlock:(JPCropDoneBlock)completeBlock;
  • PS:可以设置isLoopPlaybackGIF自主选择裁剪哪一帧(默认为NO,设置为YES会自动播放GIF)
image
self.imageresizerView.isLoopPlaybackGIF = NO;

裁剪本地视频

裁剪本地视频
  • PS:目前只针对本地视频,远程视频暂未适配。
// 裁剪整段视频
// cacheURL:如果为nil,会默认缓存到系统的NSTemporaryDirectory文件夹下,视频名为当前时间戳,格式为mp4
[self.imageresizerView cropVideoWithCacheURL:cacheURL errorBlock:^(NSURL *cacheURL, JPCropErrorReason reason) {
    // 错误的回调
    // reason:错误原因
    // 注意循环引用
} progressBlock:^(float progress) {
    // 监听进度
    // progress:0~1
    // 注意循环引用
} completeBlock:^(JPImageresizerResult *result) {
    // 裁剪完成
    // result:裁剪后的结果(JPImageresizerResult)
    // result.cacheURL:目标存放路径,如果为nil,会默认缓存到系统的NSTemporaryDirectory文件夹下,视频名为当前时间戳,格式为mp4
    // result.isCacheSuccess:是否缓存成功(缓存不成功则cacheURL为nil)
    // 注意循环引用
}];

// 可设置视频导出质量
// presetName --- 系统的视频导出质量,如:AVAssetExportPresetLowQuality,AVAssetExportPresetMediumQuality,AVAssetExportPresetHighestQuality等
- (void)cropVideoWithPresetName:(NSString *)presetName
                       cacheURL:(NSURL *)cacheURL 
                     errorBlock:(JPCropErrorBlock)errorBlock
                  progressBlock:(JPExportVideoProgressBlock)progressBlock
                  completeBlock:(JPCropDoneBlock)completeBlock;

// 取消视频导出
// 当视频正在导出时调用即可取消导出,触发errorBlock回调(JPCEReason_ExportCancelled)
- (void)videoCancelExport;
  • PS:由于视频的宽高都必须是16的整数倍,否则导出后系统会自动对尺寸进行校正,不足的地方会以绿边的形式进行填充,因此我在方法内部对裁剪尺寸做了对16除余的修改,所以最后导出视频的宽高比有可能跟指定的宽高比有些许差异。

裁剪视频的其中一帧

// 1.原图尺寸裁剪视频当前帧画面
// cacheURL --- 缓存路径(可设置为nil,则不会缓存)
// completeBlock --- 裁剪完成的回调(返回裁剪后的结果,包含已解码好的图片、缓存路径)
- (void)cropVideoCurrentFrameWithCacheURL:(NSURL *)cacheURL
                               errorBlock:(JPImageresizerErrorBlock)errorBlock
                            completeBlock:(JPCropDoneBlock)completeBlock;

// 2.自定义压缩比例裁剪视频当前帧画面
// cacheURL --- 缓存路径(可设置为nil,则不会缓存)
// completeBlock --- 裁剪完成的回调(返回裁剪后的结果,包含已解码好的图片、缓存路径)
- (void)cropVideoCurrentFrameWithCompressScale:(CGFloat)compressScale
                                      cacheURL:(NSURL *)cacheURL
                                    errorBlock:(JPImageresizerErrorBlock)errorBlock
                                 completeBlock:(JPCropDoneBlock)completeBlock;

// 3.自定义压缩比例裁剪视频指定帧画面
// second --- 第几秒画面
// cacheURL --- 缓存路径(可设置为nil,则不会缓存)
// completeBlock --- 裁剪完成的回调(返回裁剪后的结果,包含已解码好的图片、缓存路径)
- (void)cropVideoOneFrameWithSecond:(float)second
                      compressScale:(CGFloat)compressScale
                           cacheURL:(NSURL *)cacheURL
                         errorBlock:(JPImageresizerErrorBlock)errorBlock
                      completeBlock:(JPCropDoneBlock)completeBlock;

截取视频某一段裁剪后转GIF

截取视频转GIF
// 1.视频从当前时间开始截取指定秒数画面转GIF(fps = 10,rate = 1,maximumSize = 500 * 500)
// duration --- 截取多少秒
// completeBlock --- 裁剪完成的回调(返回裁剪后的结果,包含已解码好的GIF、缓存路径)
- (void)cropVideoToGIFFromCurrentSecondWithDuration:(NSTimeInterval)duration
                                           cacheURL:(NSURL *)cacheURL
                                         errorBlock:(JPImageresizerErrorBlock)errorBlock
                                      completeBlock:(JPCropDoneBlock)completeBlock;

// 2.视频自定义截取指定秒数画面转GIF
// duration --- 截取多少秒
// fps --- 帧率(设置为0则以视频真身帧率)
// rate --- 速率
// maximumSize --- 截取的尺寸(设置为0则以视频真身尺寸)
// completeBlock --- 裁剪完成的回调(返回裁剪后的结果,包含已解码好的GIF、缓存路径)
- (void)cropVideoToGIFFromStartSecond:(NSTimeInterval)startSecond
                             duration:(NSTimeInterval)duration
                                  fps:(float)fps
                                 rate:(float)rate
                          maximumSize:(CGSize)maximumSize
                             cacheURL:(NSURL *)cacheURL
                           errorBlock:(JPImageresizerErrorBlock)errorBlock
                        completeBlock:(JPCropDoneBlock)completeBlock;
  • PS:裁剪整段视频画面圆切、蒙版的功能不能使用,目前只对图片和GIF有效。

蒙版

mask
// 设置蒙版图片(目前仅支持png图片)
self.imageresizerView.maskImage = [UIImage imageNamed:@"love.png"];

// 直接设置该值即是调用 -setMaskImage: isToBeArbitrarily: animated: 方法,其中默认 isToBeArbitrarily = (maskImage ? NO : self.isArbitrarily),isAnimated = YES

// 移除蒙版图片
self.imageresizerView.maskImage = nil;
maskdone
  • PS:如果使用了蒙版图片,那么最后裁剪出来的是png图片,因此裁剪后体积有可能会比原本的图片更大。


    吴彦祖换脸

圆切

圆切
// 设置圆切
// 设置后,resizeWHScale为1:1,半径为宽高的一半,边框的上、左、下、右的中部均可拖动。
self.imageresizerView.isRoundResize = YES;

// 直接设置该值即是调用 -setIsRoundResize: isToBeArbitrarily: animated: 方法,其中默认 isToBeArbitrarily = (isRoundResize ? NO : self.isArbitrarily),isAnimated = YES

// 还原矩形
self.imageresizerView.isRoundResize = NO;
// 或者只需设置一下resizeWHScale为任意值即可
self.imageresizerView.resizeWHScale = 0.0;

横竖屏切换

screenswitching
// 需要用户去监听横竖屏的切换,或自己手动切换时,调用该方法刷新
// 1.updateFrame:刷新的Frame(例如横竖屏切换,传入self.view.bounds即可)
// 2.contentInsets:裁剪区域与主视图的内边距
// 3.duration:刷新时长(大于0即带有动画效果)
//【具体操作可参照Demo】
[self.imageresizerView updateFrame:self.view.bounds contentInsets:contentInsets duration:duration];

更改边框样式

简洁风格
经典风格
// 目前只提供两种边框样式,分别是简洁样式JPConciseFrameType,和经典样式JPClassicFrameType
// 可在初始化或直接设置frameType属性来修改边框样式
self.imageresizerView.frameType = JPClassicFrameType;

自定义边框图片

拉伸模式
平铺模式
// 使用自定义边框图片(例:平铺模式)
UIImage *tileBorderImage = [[UIImage imageNamed:@"jp_dotted_line"] resizableImageWithCapInsets:UIEdgeInsetsMake(14, 14, 14, 14) resizingMode:UIImageResizingModeTile];

// 设置边框图片与边线的偏移量(即CGRectInset,用于调整边框图片与边线的差距)
self.imageresizerView.borderImageRectInset = CGPointMake(-1.75, -1.75);

// 设置边框图片(若为nil则使用frameType的边框)
self.imageresizerView.borderImage = tileBorderImage;

切换裁剪宽高比

  • PS:设置裁剪宽高比会自动移除圆切和蒙版
// 1.自定义参数切换
/**
 * resizeWHScale:    目标裁剪宽高比(0则为任意比例)
 * isToBeArbitrarily:切换之后 resizeWHScale 是否为任意比例(若为YES,最后 resizeWHScale = 0)
 * animated:         是否带动画效果
 */
[self.imageresizerView setResizeWHScale:(16.0 / 9.0) isToBeArbitrarily:YES animated:YES];

// 2.直接切换
self.imageresizerView.resizeWHScale = 1.0;
// 默认切换之后保存最新的 resizeWHScale,且自带动画效果,如果设置为0,以当前裁剪框的宽高比设置,并且最后 isArbitrarily = YES,相当于:
[self.imageresizerView setResizeWHScale:1.0 isToBeArbitrarily:(resizeWHScale <= 0) animated:YES];

// 是否可以任意比例拖拽(包括圆切、蒙版)
self.imageresizerView.isArbitrarily = !self.imageresizerView.isArbitrarily;

// 更多API可查看 JPImageresizerView.h 上的注释

自定义毛玻璃样式、边框颜色、背景颜色、遮罩透明度

// 设置毛玻璃样式(默认带动画效果)
self.imageresizerView.blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];

// 设置边框颜色(默认带动画效果)
self.imageresizerView.strokeColor = UIColor.whiteColor;

// 设置背景颜色(默认带动画效果)
self.imageresizerView.bgColor = UIColor.blackColor;

// 设置遮罩透明度(默认带动画效果)
// PS:跟毛玻璃互斥,当设置了毛玻璃则遮罩为透明
self.imageresizerView.maskAlpha = 0.5; // blurEffect = nil 才生效

// 一步设置毛玻璃样式、边框颜色、背景颜色、遮罩透明度
[self.imageresizerView setupStrokeColor:strokeColor blurEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark] bgColor:UIColor.blackColor maskAlpha: 0.5 animated:YES];

镜像翻转

效果图.gif
// 垂直镜像,YES->沿着Y轴旋转180°,NO->还原
BOOL isVerticalityMirror = !self.imageresizerView.verticalityMirror;
[self.imageresizerView setVerticalityMirror:isVerticalityMirror animated:YES];

// 水平镜像,YES->沿着X轴旋转180°,NO->还原
BOOL isHorizontalMirror = !self.imageresizerView.horizontalMirror;
[self.imageresizerView setHorizontalMirror:isHorizontalMirror animated:YES];

旋转

// 1.顺/逆时针旋转90°(默认逆时针)
[self.imageresizerView rotation];

// 若需要顺时针旋转可设置isClockwiseRotation属性为YES
self.imageresizerView.isClockwiseRotation = YES;

// 2.自定义旋转至目标方向(支持4个方向,分别是垂直向上、水平向左、垂直向下、水平向右)
[self.imageresizerView rotationToDirection:JPImageresizerVerticalDownDirection];

重置

重置目标状态,方向垂直向上,可重置为不同的resizeWHScale、圆切、蒙版

1. 一切按当前状态重置

- (void)recovery;

2. 以resizeWHScale重置(会移除圆切、蒙版)

// 2.1 按初始裁剪宽高比(initialResizeWHScale)进行重置
- (void)recoveryByInitialResizeWHScale;
- (void)recoveryByInitialResizeWHScale:(BOOL)isToBeArbitrarily;

// 2.2 按当前裁剪宽高比进行重置(如果resizeWHScale为0,则重置到整个裁剪元素区域)
- (void)recoveryByCurrentResizeWHScale;
- (void)recoveryByCurrentResizeWHScale:(BOOL)isToBeArbitrarily;

// 2.3 按目标裁剪宽高比进行重置(如果resizeWHScale为0,则重置到整个裁剪元素区域)
// targetResizeWHScale:目标裁剪宽高比
// isToBeArbitrarily:重置之后 resizeWHScale 是否为任意比例(若为YES,最后 resizeWHScale = 0)
- (void)recoveryToTargetResizeWHScale:(CGFloat)targetResizeWHScale isToBeArbitrarily:(BOOL)isToBeArbitrarily;

3. 以圆切重置

- (void)recoveryToRoundResize;
- (void)recoveryToRoundResize:(BOOL)isToBeArbitrarily;

4. 以蒙版图片重置

// 4.1 按当前蒙版图片重置
- (void)recoveryByCurrentMaskImage;
- (void)recoveryByCurrentMaskImage:(BOOL)isToBeArbitrarily;

// 4.2 指定蒙版图片重置
- (void)recoveryToMaskImage:(UIImage *)maskImage isToBeArbitrarily:(BOOL)isToBeArbitrarily;

预览

// 预览模式:隐藏边框,停止拖拽操作,用于预览裁剪后的区域

// 1.默认自带动画效果
self.imageresizerView.isPreview = YES;

// 2.自定义是否带动画效果
[self.imageresizerView setIsPreview:YES animated:NO]

保存当前裁剪状态

// 1.很Easy,直接调用saveCurrentConfigure方法获取当前裁剪的状态,可用一个全局变量来保存该对象
JPImageresizerConfigure *savedConfigure = [self.imageresizerView saveCurrentConfigure];

// 2.重新打开裁剪历史
JPImageresizerView *imageresizerView = [JPImageresizerView imageresizerViewWithConfigure:savedConfigure imageresizerIsCanRecovery:^(BOOL isCanRecovery) {
    ......
} imageresizerIsPrepareToScale:^(BOOL isPrepareToScale) {
    ......
}];
[self.view addSubview:imageresizerView];
self.imageresizerView = imageresizerView;

// 3.可以设置JPImageresizerConfigure的isCleanHistoryAfterInitial属性为YES,当初始化结束后自动清空历史(默认为YES)
// 或者直接调用cleanHistory方法清空历史
save
  • PS1:若保存的savedConfigure.history.viewFrame跟当前的viewFrame不一致,界面会导致错乱,需要自行判断是否一致才可重新打开;
  • PS2:另外目前只能在App使用期间进行保存,暂未实现持久化缓存。

其他

// 锁定裁剪区域,锁定后无法拖动裁剪区域,NO则解锁
self.imageresizerView.isLockResizeFrame = YES;

// 旋转至水平方向时是否自适应裁剪区域大小
// 当图片宽度比图片高度小时,该属性默认YES,可手动设为NO
self.imageresizerView.isAutoScale = NO;

安装

JPImageresizerView 可通过CocoaPods安装,只需添加下面一行到你的podfile:

pod 'JPImageresizerView'

反馈地址

邮箱:zhoujianping24@hotmail.com
扣扣:184669029
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容