iOS开发-类似微信图片裁剪功能
当我们使用微信的时候,发送图片时候编辑会有图片裁剪功能。拖动裁剪框框即可裁剪所需要图片的大小。
在iOS中,通过使用CoreGraphics框架提供的API来完成来实现图片裁剪功能。具体来说,可以通过编写一个cropImage函数来实现图片的裁剪,通过图片调用drawAtPoint方法,通过UIGraphicsGetImageFromCurrentImageContext获取到新的image
- (UIImage *)cropImage:(UIImage *)originImage rect:(CGRect)rect {
CGPoint origin = CGPointMake(-rect.origin.x, -rect.origin.y);
UIImage *img = nil;
UIGraphicsBeginImageContextWithOptions(rect.size, NO, originImage.scale);
[originImage drawAtPoint:origin];
img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
当然通过上面的cropImage可以实现图片裁剪,当然,我们需要根据自己拖动的框的大小来裁剪图片。
裁剪框四个角有圆圈方便拖动,下面定义圆圈的view,通过drawRect来绘制圆圈。
SDClippingCircle.h
#import <UIKit/UIKit.h>
@interface SDClippingCircle : UIView
@property (nonatomic, strong) UIColor *bgColor;
@end
SDClippingCircle.m
#import "SDClippingCircle.h"
@implementation SDClippingCircle
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rct = self.bounds;
rct.origin.x = rct.size.width/2-rct.size.width/6;
rct.origin.y = rct.size.height/2-rct.size.height/6;
rct.size.width /= 3;
rct.size.height /= 3;
CGContextSetFillColorWithColor(context, self.bgColor.CGColor);
CGContextFillEllipseInRect(context, rct);
}
@end
绘制时出现的横线和竖线,这个我们使用的是QuartzCore中的CALayer。这里我们通过CALayer的drawInContext来绘制线条。
SDGridLayer.h
#import <QuartzCore/QuartzCore.h>
/**
绘制是出现的横线和竖线
*/
@interface SDGridLayer : CALayer
@property (nonatomic, assign) CGRect clippingRect;
@property (nonatomic, strong) UIColor *bgColor;
@property (nonatomic, strong) UIColor *gridColor;
@end
SDGridLayer.m
#import "SDGridLayer.h"
@implementation SDGridLayer
+ (BOOL)needsDisplayForKey:(NSString*)key
{
if ([key isEqualToString:@"clippingRect"]) {
return YES;
}
return [super needsDisplayForKey:key];
}
- (id)initWithLayer:(id)layer
{
self = [super initWithLayer:layer];
if(self && [layer isKindOfClass:[SDGridLayer class]]){
self.bgColor = ((SDGridLayer*)layer).bgColor;
self.gridColor = ((SDGridLayer*)layer).gridColor;
self.clippingRect = ((SDGridLayer*)layer).clippingRect;
}
return self;
}
- (void)drawInContext:(CGContextRef)context
{
CGRect rct = self.bounds;
CGContextSetFillColorWithColor(context, self.bgColor.CGColor);
CGContextFillRect(context, rct);
CGContextClearRect(context, _clippingRect);
CGContextSetStrokeColorWithColor(context, self.gridColor.CGColor);
CGContextSetLineWidth(context, 1);
rct = self.clippingRect;
CGContextBeginPath(context);
CGFloat dW = 0;
for(int i=0;i<4;++i){
CGContextMoveToPoint(context, rct.origin.x+dW, rct.origin.y);
CGContextAddLineToPoint(context, rct.origin.x+dW, rct.origin.y+rct.size.height);
dW += _clippingRect.size.width/3;
}
dW = 0;
for(int i=0;i<4;++i){
CGContextMoveToPoint(context, rct.origin.x, rct.origin.y+dW);
CGContextAddLineToPoint(context, rct.origin.x+rct.size.width, rct.origin.y+dW);
dW += rct.size.height/3;
}
CGContextStrokePath(context);
}
@end
在裁剪过程中,拖动的时候会有一个Ratio
SDRatio.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface SDRatio : NSObject
@property (nonatomic, assign) BOOL isLandscape;
@property (nonatomic, readonly) CGFloat ratio;
@property (nonatomic, strong) NSString *titleFormat;
- (id)initWithValue1:(CGFloat)value1 value2:(CGFloat)value2;
@end
SDRatio.m
#import "SDRatio.h"
@implementation SDRatio
{
CGFloat _longSide;
CGFloat _shortSide;
}
- (id)initWithValue1:(CGFloat)value1 value2:(CGFloat)value2
{
self = [super init];
if(self){
_longSide = MAX(fabs(value1), fabs(value2));
_shortSide = MIN(fabs(value1), fabs(value2));
}
return self;
}
- (NSString*)description
{
NSString *format = (self.titleFormat) ? self.titleFormat : @"%g : %g";
if(self.isLandscape){
return [NSString stringWithFormat:format, _longSide, _shortSide];
}
return [NSString stringWithFormat:format, _shortSide, _longSide];
}
- (CGFloat)ratio
{
if(_longSide==0 || _shortSide==0){
return 0;
}
if(self.isLandscape){
return _shortSide / (CGFloat)_longSide;
}
return _longSide / (CGFloat)_shortSide;
}
@end
定义可以拖动的裁剪框,我们需要根据拖动手势UIPanGestureRecognizer来实现。
UIPanGestureRecognizer是一种手势识别器,用于识别用户在屏幕上进行的拖动操作。通过该手势识别器,我们可以实现一些拖动相关的交互效果,比如拖动视图、改变视图大小等。
例如,我们添加手势
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGridView:)];
[self addGestureRecognizer:panGesture];
拖动手势状态有UIGestureRecognizerStateBegan、UIGestureRecognizerStateChanged、UIGestureRecognizerStateEnded、UIGestureRecognizerStateCancelled等状态
通过拖动可以确定最后裁剪的Rect,确定裁剪的位置与大小。
- (void)panGridView:(UIPanGestureRecognizer*)sender
{
static BOOL dragging = NO;
static CGRect initialRect;
if(sender.state==UIGestureRecognizerStateBegan){
CGPoint point = [sender locationInView:self];
dragging = CGRectContainsPoint(_clippingRect, point);
initialRect = self.clippingRect;
}
else if(dragging){
CGPoint point = [sender translationInView:self];
CGFloat left = MIN(MAX(initialRect.origin.x + point.x, 0), self.frame.size.width-initialRect.size.width);
CGFloat top = MIN(MAX(initialRect.origin.y + point.y, 0), self.frame.size.height-initialRect.size.height);
CGRect rct = self.clippingRect;
rct.origin.x = left;
rct.origin.y = top;
self.clippingRect = rct;
}
}
完整的裁剪框Panel代码如下
SDPubClippingPanel.h
#import "UIView.h"
#import "SDRatio.h"
#import "SDClippingCircle.h"
#import "SDGridLayer.h"
@interface SDPubClippingPanel : UIView
@property (nonatomic, assign) CGRect clippingRect;
@property (nonatomic, strong) SDRatio *clippingRatio;
- (id)initWithSuperview:(UIView*)superview frame:(CGRect)frame;
- (void)setBgColor:(UIColor*)bgColor;
- (void)setGridColor:(UIColor*)gridColor;
- (void)clippingRatioDidChange;
- (void)layoutGridLayer;
@end
SDPubClippingPanel.m
#import "SDPubClippingPanel.h"
static const CGFloat kCLImageToolAnimationDuration = 0.3;
static const CGFloat kCLImageToolFadeoutDuration = 0.2;
@interface SDPubClippingPanel ()
@end
@implementation SDPubClippingPanel
{
SDGridLayer *_gridLayer;
SDClippingCircle *_ltView;
SDClippingCircle *_lbView;
SDClippingCircle *_rtView;
SDClippingCircle *_rbView;
}
- (SDClippingCircle*)clippingCircleWithTag:(NSInteger)tag
{
SDClippingCircle *view = [[SDClippingCircle alloc] initWithFrame:CGRectMake(0, 0, 75, 75)];
view.backgroundColor = [UIColor clearColor];
view.bgColor = [UIColor blackColor];
view.tag = tag;
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panCircleView:)];
[view addGestureRecognizer:panGesture];
[self.superview addSubview:view];
return view;
}
- (id)initWithSuperview:(UIView*)superview frame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self){
[superview addSubview:self];
_gridLayer = [[SDGridLayer alloc] init];
_gridLayer.frame = self.bounds;
_gridLayer.bgColor = [UIColor colorWithWhite:1 alpha:0.6];
_gridLayer.gridColor = [UIColor colorWithWhite:0 alpha:0.6];
[self.layer addSublayer:_gridLayer];
_ltView = [self clippingCircleWithTag:0];
_lbView = [self clippingCircleWithTag:1];
_rtView = [self clippingCircleWithTag:2];
_rbView = [self clippingCircleWithTag:3];
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGridView:)];
[self addGestureRecognizer:panGesture];
self.clippingRect = self.bounds;
}
return self;
}
- (void)layoutGridLayer {
_gridLayer.frame = self.bounds;
}
- (void)removeFromSuperview
{
[super removeFromSuperview];
[_ltView removeFromSuperview];
[_lbView removeFromSuperview];
[_rtView removeFromSuperview];
[_rbView removeFromSuperview];
}
- (void)setBgColor:(UIColor *)bgColor
{
_gridLayer.bgColor = bgColor;
}
- (void)setGridColor:(UIColor *)gridColor
{
_gridLayer.gridColor = gridColor;
_ltView.bgColor = _lbView.bgColor = _rtView.bgColor = _rbView.bgColor = [gridColor colorWithAlphaComponent:1];
}
- (void)setClippingRect:(CGRect)clippingRect
{
_clippingRect = clippingRect;
_ltView.center = [self.superview convertPoint:CGPointMake(_clippingRect.origin.x, _clippingRect.origin.y) fromView:self];
_lbView.center = [self.superview convertPoint:CGPointMake(_clippingRect.origin.x, _clippingRect.origin.y+_clippingRect.size.height) fromView:self];
_rtView.center = [self.superview convertPoint:CGPointMake(_clippingRect.origin.x+_clippingRect.size.width, _clippingRect.origin.y) fromView:self];
_rbView.center = [self.superview convertPoint:CGPointMake(_clippingRect.origin.x+_clippingRect.size.width, _clippingRect.origin.y+_clippingRect.size.height) fromView:self];
_gridLayer.clippingRect = clippingRect;
[self setNeedsDisplay];
}
- (void)setClippingRect:(CGRect)clippingRect animated:(BOOL)animated
{
if(animated){
[UIView animateWithDuration:kCLImageToolFadeoutDuration
animations:^{
self->_ltView.center = [self.superview convertPoint:CGPointMake(clippingRect.origin.x, clippingRect.origin.y) fromView:self];
self->_lbView.center = [self.superview convertPoint:CGPointMake(clippingRect.origin.x, clippingRect.origin.y+clippingRect.size.height) fromView:self];
self->_rtView.center = [self.superview convertPoint:CGPointMake(clippingRect.origin.x+clippingRect.size.width, clippingRect.origin.y) fromView:self];
self->_rbView.center = [self.superview convertPoint:CGPointMake(clippingRect.origin.x+clippingRect.size.width, clippingRect.origin.y+clippingRect.size.height) fromView:self];
}
];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"clippingRect"];
animation.duration = kCLImageToolFadeoutDuration;
animation.fromValue = [NSValue valueWithCGRect:_clippingRect];
animation.toValue = [NSValue valueWithCGRect:clippingRect];
[_gridLayer addAnimation:animation forKey:nil];
_gridLayer.clippingRect = clippingRect;
_clippingRect = clippingRect;
[self setNeedsDisplay];
}
else{
self.clippingRect = clippingRect;
}
}
- (void)clippingRatioDidChange
{
CGRect rect = self.bounds;
if(self.clippingRatio){
CGFloat H = rect.size.width * self.clippingRatio.ratio;
if(H<=rect.size.height){
rect.size.height = H;
}
else{
rect.size.width *= rect.size.height / H;
}
rect.origin.x = (self.bounds.size.width - rect.size.width) / 2;
rect.origin.y = (self.bounds.size.height - rect.size.height) / 2;
}
[self setClippingRect:rect animated:YES];
}
- (void)setClippingRatio:(SDRatio *)clippingRatio
{
if(clippingRatio != _clippingRatio){
_clippingRatio = clippingRatio;
[self clippingRatioDidChange];
}
}
- (void)setNeedsDisplay
{
[super setNeedsDisplay];
[_gridLayer setNeedsDisplay];
}
- (void)panCircleView:(UIPanGestureRecognizer*)sender
{
CGPoint point = [sender locationInView:self];
CGPoint dp = [sender translationInView:self];
CGRect rct = self.clippingRect;
const CGFloat W = self.frame.size.width;
const CGFloat H = self.frame.size.height;
CGFloat minX = 0;
CGFloat minY = 0;
CGFloat maxX = W;
CGFloat maxY = H;
CGFloat ratio = (sender.view.tag == 1 || sender.view.tag==2) ? -self.clippingRatio.ratio : self.clippingRatio.ratio;
switch (sender.view.tag) {
case 0: // upper left
{
maxX = MAX((rct.origin.x + rct.size.width) - 0.1 * W, 0.1 * W);
maxY = MAX((rct.origin.y + rct.size.height) - 0.1 * H, 0.1 * H);
if(ratio!=0){
CGFloat y0 = rct.origin.y - ratio * rct.origin.x;
CGFloat x0 = -y0 / ratio;
minX = MAX(x0, 0);
minY = MAX(y0, 0);
point.x = MAX(minX, MIN(point.x, maxX));
point.y = MAX(minY, MIN(point.y, maxY));
if(-dp.x*ratio + dp.y > 0){ point.x = (point.y - y0) / ratio; }
else{ point.y = point.x * ratio + y0; }
}
else{
point.x = MAX(minX, MIN(point.x, maxX));
point.y = MAX(minY, MIN(point.y, maxY));
}
rct.size.width = rct.size.width - (point.x - rct.origin.x);
rct.size.height = rct.size.height - (point.y - rct.origin.y);
rct.origin.x = point.x;
rct.origin.y = point.y;
break;
}
case 1: // lower left
{
maxX = MAX((rct.origin.x + rct.size.width) - 0.1 * W, 0.1 * W);
minY = MAX(rct.origin.y + 0.1 * H, 0.1 * H);
if(ratio!=0){
CGFloat y0 = (rct.origin.y + rct.size.height) - ratio* rct.origin.x ;
CGFloat xh = (H - y0) / ratio;
minX = MAX(xh, 0);
maxY = MIN(y0, H);
point.x = MAX(minX, MIN(point.x, maxX));
point.y = MAX(minY, MIN(point.y, maxY));
if(-dp.x*ratio + dp.y < 0){ point.x = (point.y - y0) / ratio; }
else{ point.y = point.x * ratio + y0; }
}
else{
point.x = MAX(minX, MIN(point.x, maxX));
point.y = MAX(minY, MIN(point.y, maxY));
}
rct.size.width = rct.size.width - (point.x - rct.origin.x);
rct.size.height = point.y - rct.origin.y;
rct.origin.x = point.x;
break;
}
case 2: // upper right
{
minX = MAX(rct.origin.x + 0.1 * W, 0.1 * W);
maxY = MAX((rct.origin.y + rct.size.height) - 0.1 * H, 0.1 * H);
if(ratio!=0){
CGFloat y0 = rct.origin.y - ratio * (rct.origin.x + rct.size.width);
CGFloat yw = ratio * W + y0;
CGFloat x0 = -y0 / ratio;
maxX = MIN(x0, W);
minY = MAX(yw, 0);
point.x = MAX(minX, MIN(point.x, maxX));
point.y = MAX(minY, MIN(point.y, maxY));
if(-dp.x*ratio + dp.y > 0){ point.x = (point.y - y0) / ratio; }
else{ point.y = point.x * ratio + y0; }
}
else{
point.x = MAX(minX, MIN(point.x, maxX));
point.y = MAX(minY, MIN(point.y, maxY));
}
rct.size.width = point.x - rct.origin.x;
rct.size.height = rct.size.height - (point.y - rct.origin.y);
rct.origin.y = point.y;
break;
}
case 3: // lower right
{
minX = MAX(rct.origin.x + 0.1 * W, 0.1 * W);
minY = MAX(rct.origin.y + 0.1 * H, 0.1 * H);
if(ratio!=0){
CGFloat y0 = (rct.origin.y + rct.size.height) - ratio * (rct.origin.x + rct.size.width);
CGFloat yw = ratio * W + y0;
CGFloat xh = (H - y0) / ratio;
maxX = MIN(xh, W);
maxY = MIN(yw, H);
point.x = MAX(minX, MIN(point.x, maxX));
point.y = MAX(minY, MIN(point.y, maxY));
if(-dp.x*ratio + dp.y < 0){ point.x = (point.y - y0) / ratio; }
else{ point.y = point.x * ratio + y0; }
}
else{
point.x = MAX(minX, MIN(point.x, maxX));
point.y = MAX(minY, MIN(point.y, maxY));
}
rct.size.width = point.x - rct.origin.x;
rct.size.height = point.y - rct.origin.y;
break;
}
default:
break;
}
self.clippingRect = rct;
}
- (void)panGridView:(UIPanGestureRecognizer*)sender
{
static BOOL dragging = NO;
static CGRect initialRect;
if(sender.state==UIGestureRecognizerStateBegan){
CGPoint point = [sender locationInView:self];
dragging = CGRectContainsPoint(_clippingRect, point);
initialRect = self.clippingRect;
}
else if(dragging){
CGPoint point = [sender translationInView:self];
CGFloat left = MIN(MAX(initialRect.origin.x + point.x, 0), self.frame.size.width-initialRect.size.width);
CGFloat top = MIN(MAX(initialRect.origin.y + point.y, 0), self.frame.size.height-initialRect.size.height);
CGRect rct = self.clippingRect;
rct.origin.x = left;
rct.origin.y = top;
self.clippingRect = rct;
}
}
@end
通过裁剪框确定了clippingRect,我们需要根据imageView与image.size来确定具体裁剪的rect,最后调用cropImage
- (void)executeWithCompletionBlock:(void (^)(UIImage *, NSError *, NSDictionary *))completionBlock
{
CGFloat zoomScale = self.imageView.width / self.imageView.image.size.width;
CGRect rct = self.clippingPanel.clippingRect;
rct.size.width /= zoomScale;
rct.size.height /= zoomScale;
rct.origin.x /= zoomScale;
rct.origin.y /= zoomScale;
UIImage *result = [self cropImage:self.imageView.image rect:rct];
completionBlock(result, nil, nil);
}
展示裁剪后图片完整代码如下
SDPubDrawingView.h
#import <UIKit/UIKit.h>
#import "UIColor+Addition.h"
#import <AVFoundation/AVFoundation.h>
#import "SDPubClippingPanel.h"
@protocol SDPubDrawingViewDelegate;
@interface SDPubDrawingView : UIView
@property (nonatomic, weak) id<SDPubDrawingViewDelegate>actionDelegate;
@property (nonatomic, weak) id delegate;
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) SDPubClippingPanel *clippingPanel;
- (id)initWithFrame:(CGRect)frame;
- (void)confirmClipImage;
@end
@protocol SDPubDrawingViewDelegate <NSObject>
@end
SDPubDrawingView.m
#import "SDPubDrawingView.h"
#import "UIColor+Addition.h"
#import "UIImage+Color.h"
@interface SDPubDrawingView ()
@end
@implementation SDPubDrawingView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor colorWithHexString:@"f6f6f6"];
[self addSubview:self.imageView];
self.clippingPanel = [[SDPubClippingPanel alloc] initWithSuperview:self frame:self.imageView.frame];
self.clippingPanel.backgroundColor = [UIColor clearColor];
self.clippingPanel.bgColor = [[UIColor blackColor] colorWithAlphaComponent:0.25];
self.clippingPanel.gridColor = [[UIColor blackColor] colorWithAlphaComponent:0.25];
self.clippingPanel.clipsToBounds = NO;
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat cropAreaWidth = CGRectGetWidth(self.bounds) - 40.0;
CGFloat cropAreaHeight = CGRectGetHeight(self.bounds) - 40.0 - self.navigationHeight;
CGFloat tempWidth = 0.0;
CGFloat tempHeight = 0.0;
if (self.imageView.image) {
CGSize imageSize = self.imageView.image.size;
if (imageSize.width > 0 && imageSize.height > 0) {
CGFloat aImageScale = imageSize.width/imageSize.height;
CGFloat wScale = imageSize.width/cropAreaWidth;
CGFloat hScale = imageSize.height/cropAreaHeight;
if (wScale > hScale) {
tempWidth = cropAreaWidth;
tempHeight = tempWidth/aImageScale;
} else {
tempHeight = cropAreaHeight;
tempWidth = tempHeight*aImageScale;
}
}
}
self.imageView.frame = CGRectMake(20.0 + (cropAreaWidth - tempWidth)/2.0, self.navigationHeight + 20.0 + (cropAreaHeight - tempHeight)/2.0, tempWidth, tempHeight);
self.clippingPanel.frame = self.imageView.frame;
self.clippingPanel.clippingRect = self.clippingPanel.bounds;
[self.clippingPanel layoutGridLayer];
}
- (void)setDelegate:(id)delegate {
_delegate = delegate;
}
- (void)executeWithCompletionBlock:(void (^)(UIImage *, NSError *, NSDictionary *))completionBlock
{
CGFloat zoomScale = self.imageView.width / self.imageView.image.size.width;
CGRect rct = self.clippingPanel.clippingRect;
rct.size.width /= zoomScale;
rct.size.height /= zoomScale;
rct.origin.x /= zoomScale;
rct.origin.y /= zoomScale;
UIImage *result = [self cropImage:self.imageView.image rect:rct];
completionBlock(result, nil, nil);
}
- (void)confirmClipImage {
__weak typeof(self) weakSelf = self;
[self executeWithCompletionBlock:^(UIImage *image, NSError *error, NSDictionary *dict) {
[weakSelf.imageView setImage:image];
[weakSelf setNeedsLayout];
}];
}
- (UIImage *)cropImage:(UIImage *)originImage rect:(CGRect)rect {
CGPoint origin = CGPointMake(-rect.origin.x, -rect.origin.y);
UIImage *img = nil;
UIGraphicsBeginImageContextWithOptions(rect.size, NO, originImage.scale);
[originImage drawAtPoint:origin];
img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
#pragma mark - LAZY
- (UIImageView *)imageView {
if (!_imageView) {
_imageView = [[UIImageView alloc] initWithFrame:CGRectZero];
_imageView.image = [UIImage imageNamed:@"img_example.jpg"];
_imageView.userInteractionEnabled = YES;
_imageView.contentMode = UIViewContentModeScaleAspectFit;
}
return _imageView;
}
- (void)dealloc {
}
@end
iOS开发-类似微信图片裁剪功能
学习记录,每天不停进步。