VPImageCropperViewController.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. //
  2. // VPImageCropperViewController.m
  3. // VPolor
  4. //
  5. // Created by Vinson.D.Warm on 12/30/13.
  6. // Copyright (c) 2013 Huang Vinson. All rights reserved.
  7. //
  8. #import "VPImageCropperViewController.h"
  9. #define SCALE_FRAME_Y 100.0f
  10. #define BOUNDCE_DURATION 0.3f
  11. @interface VPImageCropperViewController ()
  12. @property (nonatomic, retain) UIImage *originalImage;
  13. @property (nonatomic, retain) UIImage *editedImage;
  14. @property (nonatomic, retain) UIImageView *showImgView;
  15. @property (nonatomic, retain) UIView *overlayView;
  16. @property (nonatomic, retain) UIView *ratioView;
  17. @property (nonatomic, assign) CGRect oldFrame;
  18. @property (nonatomic, assign) CGRect largeFrame;
  19. @property (nonatomic, assign) CGFloat limitRatio;
  20. @property (nonatomic, assign) CGRect latestFrame;
  21. @end
  22. @implementation VPImageCropperViewController
  23. - (void)dealloc {
  24. self.originalImage = nil;
  25. self.showImgView = nil;
  26. self.editedImage = nil;
  27. self.overlayView = nil;
  28. self.ratioView = nil;
  29. }
  30. - (id)initWithImage:(UIImage *)originalImage cropFrame:(CGRect)cropFrame limitScaleRatio:(NSInteger)limitRatio {
  31. self = [super init];
  32. if (self) {
  33. self.cropFrame = cropFrame;
  34. self.limitRatio = limitRatio;
  35. self.originalImage = [self fixOrientation:originalImage];
  36. }
  37. return self;
  38. }
  39. - (void)viewDidLoad
  40. {
  41. [super viewDidLoad];
  42. [self initView];
  43. [self initControlBtn];
  44. }
  45. - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
  46. return NO;
  47. }
  48. - (void)initView {
  49. self.view.backgroundColor = [UIColor blackColor];
  50. self.showImgView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
  51. [self.showImgView setMultipleTouchEnabled:YES];
  52. [self.showImgView setUserInteractionEnabled:YES];
  53. [self.showImgView setImage:self.originalImage];
  54. [self.showImgView setUserInteractionEnabled:YES];
  55. [self.showImgView setMultipleTouchEnabled:YES];
  56. // scale to fit the screen
  57. CGFloat oriWidth = self.cropFrame.size.width;
  58. CGFloat oriHeight = self.originalImage.size.height * (oriWidth / self.originalImage.size.width);
  59. CGFloat oriX = self.cropFrame.origin.x + (self.cropFrame.size.width - oriWidth) / 2;
  60. CGFloat oriY = self.cropFrame.origin.y + (self.cropFrame.size.height - oriHeight) / 2;
  61. self.oldFrame = CGRectMake(oriX, oriY, oriWidth, oriHeight);
  62. self.latestFrame = self.oldFrame;
  63. self.showImgView.frame = self.oldFrame;
  64. self.largeFrame = CGRectMake(0, 0, self.limitRatio * self.oldFrame.size.width, self.limitRatio * self.oldFrame.size.height);
  65. [self addGestureRecognizers];
  66. [self.view addSubview:self.showImgView];
  67. self.overlayView = [[UIView alloc] initWithFrame:self.view.bounds];
  68. self.overlayView.alpha = .5f;
  69. self.overlayView.backgroundColor = [UIColor blackColor];
  70. self.overlayView.userInteractionEnabled = NO;
  71. self.overlayView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  72. [self.view addSubview:self.overlayView];
  73. self.ratioView = [[UIView alloc] initWithFrame:self.cropFrame];
  74. self.ratioView.layer.borderColor = [UIColor yellowColor].CGColor;
  75. self.ratioView.layer.borderWidth = 1.0f;
  76. self.ratioView.autoresizingMask = UIViewAutoresizingNone;
  77. [self.view addSubview:self.ratioView];
  78. [self overlayClipping];
  79. }
  80. - (void)initControlBtn {
  81. UIButton *cancelBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - 50.0f, 100, 50)];
  82. cancelBtn.backgroundColor = [UIColor blackColor];
  83. cancelBtn.titleLabel.textColor = [UIColor whiteColor];
  84. [cancelBtn setTitle:@"Cancel" forState:UIControlStateNormal];
  85. [cancelBtn.titleLabel setFont:[UIFont boldSystemFontOfSize:18.0f]];
  86. [cancelBtn.titleLabel setTextAlignment:NSTextAlignmentCenter];
  87. [cancelBtn.titleLabel setLineBreakMode:NSLineBreakByWordWrapping];
  88. [cancelBtn.titleLabel setNumberOfLines:0];
  89. [cancelBtn setTitleEdgeInsets:UIEdgeInsetsMake(5.0f, 5.0f, 5.0f, 5.0f)];
  90. [cancelBtn addTarget:self action:@selector(cancel:) forControlEvents:UIControlEventTouchUpInside];
  91. [self.view addSubview:cancelBtn];
  92. UIButton *confirmBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.view.frame.size.width - 100.0f, self.view.frame.size.height - 50.0f, 100, 50)];
  93. confirmBtn.backgroundColor = [UIColor blackColor];
  94. confirmBtn.titleLabel.textColor = [UIColor whiteColor];
  95. [confirmBtn setTitle:@"OK" forState:UIControlStateNormal];
  96. [confirmBtn.titleLabel setFont:[UIFont boldSystemFontOfSize:18.0f]];
  97. [confirmBtn.titleLabel setTextAlignment:NSTextAlignmentCenter];
  98. confirmBtn.titleLabel.textColor = [UIColor whiteColor];
  99. [confirmBtn.titleLabel setLineBreakMode:NSLineBreakByWordWrapping];
  100. [confirmBtn.titleLabel setNumberOfLines:0];
  101. [confirmBtn setTitleEdgeInsets:UIEdgeInsetsMake(5.0f, 5.0f, 5.0f, 5.0f)];
  102. [confirmBtn addTarget:self action:@selector(confirm:) forControlEvents:UIControlEventTouchUpInside];
  103. [self.view addSubview:confirmBtn];
  104. }
  105. - (void)cancel:(id)sender {
  106. if (self.delegate && [self.delegate conformsToProtocol:@protocol(VPImageCropperDelegate)]) {
  107. [self.delegate imageCropperDidCancel:self];
  108. }
  109. }
  110. - (void)confirm:(id)sender {
  111. if (self.delegate && [self.delegate conformsToProtocol:@protocol(VPImageCropperDelegate)]) {
  112. [self.delegate imageCropper:self didFinished:[self getSubImage]];
  113. }
  114. }
  115. - (void)overlayClipping
  116. {
  117. CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
  118. CGMutablePathRef path = CGPathCreateMutable();
  119. // Left side of the ratio view
  120. CGPathAddRect(path, nil, CGRectMake(0, 0,
  121. self.ratioView.frame.origin.x,
  122. self.overlayView.frame.size.height));
  123. // Right side of the ratio view
  124. CGPathAddRect(path, nil, CGRectMake(
  125. self.ratioView.frame.origin.x + self.ratioView.frame.size.width,
  126. 0,
  127. self.overlayView.frame.size.width - self.ratioView.frame.origin.x - self.ratioView.frame.size.width,
  128. self.overlayView.frame.size.height));
  129. // Top side of the ratio view
  130. CGPathAddRect(path, nil, CGRectMake(0, 0,
  131. self.overlayView.frame.size.width,
  132. self.ratioView.frame.origin.y));
  133. // Bottom side of the ratio view
  134. CGPathAddRect(path, nil, CGRectMake(0,
  135. self.ratioView.frame.origin.y + self.ratioView.frame.size.height,
  136. self.overlayView.frame.size.width,
  137. self.overlayView.frame.size.height - self.ratioView.frame.origin.y + self.ratioView.frame.size.height));
  138. maskLayer.path = path;
  139. self.overlayView.layer.mask = maskLayer;
  140. CGPathRelease(path);
  141. }
  142. // register all gestures
  143. - (void) addGestureRecognizers
  144. {
  145. // add pinch gesture
  146. UIPinchGestureRecognizer *pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchView:)];
  147. [self.view addGestureRecognizer:pinchGestureRecognizer];
  148. // add pan gesture
  149. UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panView:)];
  150. [self.view addGestureRecognizer:panGestureRecognizer];
  151. }
  152. // pinch gesture handler
  153. - (void) pinchView:(UIPinchGestureRecognizer *)pinchGestureRecognizer
  154. {
  155. UIView *view = self.showImgView;
  156. if (pinchGestureRecognizer.state == UIGestureRecognizerStateBegan || pinchGestureRecognizer.state == UIGestureRecognizerStateChanged) {
  157. view.transform = CGAffineTransformScale(view.transform, pinchGestureRecognizer.scale, pinchGestureRecognizer.scale);
  158. pinchGestureRecognizer.scale = 1;
  159. }
  160. else if (pinchGestureRecognizer.state == UIGestureRecognizerStateEnded) {
  161. CGRect newFrame = self.showImgView.frame;
  162. newFrame = [self handleScaleOverflow:newFrame];
  163. newFrame = [self handleBorderOverflow:newFrame];
  164. [UIView animateWithDuration:BOUNDCE_DURATION animations:^{
  165. self.showImgView.frame = newFrame;
  166. self.latestFrame = newFrame;
  167. }];
  168. }
  169. }
  170. // pan gesture handler
  171. - (void) panView:(UIPanGestureRecognizer *)panGestureRecognizer
  172. {
  173. UIView *view = self.showImgView;
  174. if (panGestureRecognizer.state == UIGestureRecognizerStateBegan || panGestureRecognizer.state == UIGestureRecognizerStateChanged) {
  175. // calculate accelerator
  176. CGFloat absCenterX = self.cropFrame.origin.x + self.cropFrame.size.width / 2;
  177. CGFloat absCenterY = self.cropFrame.origin.y + self.cropFrame.size.height / 2;
  178. CGFloat scaleRatio = self.showImgView.frame.size.width / self.cropFrame.size.width;
  179. CGFloat acceleratorX = 1 - ABS(absCenterX - view.center.x) / (scaleRatio * absCenterX);
  180. CGFloat acceleratorY = 1 - ABS(absCenterY - view.center.y) / (scaleRatio * absCenterY);
  181. CGPoint translation = [panGestureRecognizer translationInView:view.superview];
  182. [view setCenter:(CGPoint){view.center.x + translation.x * acceleratorX, view.center.y + translation.y * acceleratorY}];
  183. [panGestureRecognizer setTranslation:CGPointZero inView:view.superview];
  184. }
  185. else if (panGestureRecognizer.state == UIGestureRecognizerStateEnded) {
  186. // bounce to original frame
  187. CGRect newFrame = self.showImgView.frame;
  188. newFrame = [self handleBorderOverflow:newFrame];
  189. [UIView animateWithDuration:BOUNDCE_DURATION animations:^{
  190. self.showImgView.frame = newFrame;
  191. self.latestFrame = newFrame;
  192. }];
  193. }
  194. }
  195. - (CGRect)handleScaleOverflow:(CGRect)newFrame {
  196. // bounce to original frame
  197. CGPoint oriCenter = CGPointMake(newFrame.origin.x + newFrame.size.width/2, newFrame.origin.y + newFrame.size.height/2);
  198. if (newFrame.size.width < self.oldFrame.size.width) {
  199. newFrame = self.oldFrame;
  200. }
  201. if (newFrame.size.width > self.largeFrame.size.width) {
  202. newFrame = self.largeFrame;
  203. }
  204. newFrame.origin.x = oriCenter.x - newFrame.size.width/2;
  205. newFrame.origin.y = oriCenter.y - newFrame.size.height/2;
  206. return newFrame;
  207. }
  208. - (CGRect)handleBorderOverflow:(CGRect)newFrame {
  209. // horizontally
  210. if (newFrame.origin.x > self.cropFrame.origin.x) newFrame.origin.x = self.cropFrame.origin.x;
  211. if (CGRectGetMaxX(newFrame) < self.cropFrame.size.width) newFrame.origin.x = self.cropFrame.size.width - newFrame.size.width;
  212. // vertically
  213. if (newFrame.origin.y > self.cropFrame.origin.y) newFrame.origin.y = self.cropFrame.origin.y;
  214. if (CGRectGetMaxY(newFrame) < self.cropFrame.origin.y + self.cropFrame.size.height) {
  215. newFrame.origin.y = self.cropFrame.origin.y + self.cropFrame.size.height - newFrame.size.height;
  216. }
  217. // adapt horizontally rectangle
  218. if (self.showImgView.frame.size.width > self.showImgView.frame.size.height && newFrame.size.height <= self.cropFrame.size.height) {
  219. newFrame.origin.y = self.cropFrame.origin.y + (self.cropFrame.size.height - newFrame.size.height) / 2;
  220. }
  221. return newFrame;
  222. }
  223. -(UIImage *)getSubImage{
  224. CGRect squareFrame = self.cropFrame;
  225. CGFloat scaleRatio = self.latestFrame.size.width / self.originalImage.size.width;
  226. CGFloat x = (squareFrame.origin.x - self.latestFrame.origin.x) / scaleRatio;
  227. CGFloat y = (squareFrame.origin.y - self.latestFrame.origin.y) / scaleRatio;
  228. CGFloat w = squareFrame.size.width / scaleRatio;
  229. CGFloat h = squareFrame.size.height / scaleRatio;
  230. if (self.latestFrame.size.width < self.cropFrame.size.width) {
  231. CGFloat newW = self.originalImage.size.width;
  232. CGFloat newH = newW * (self.cropFrame.size.height / self.cropFrame.size.width);
  233. x = 0; y = y + (h - newH) / 2;
  234. w = newH; h = newH;
  235. }
  236. if (self.latestFrame.size.height < self.cropFrame.size.height) {
  237. CGFloat newH = self.originalImage.size.height;
  238. CGFloat newW = newH * (self.cropFrame.size.width / self.cropFrame.size.height);
  239. x = x + (w - newW) / 2; y = 0;
  240. w = newH; h = newH;
  241. }
  242. CGRect myImageRect = CGRectMake(x, y, w, h);
  243. CGImageRef imageRef = self.originalImage.CGImage;
  244. CGImageRef subImageRef = CGImageCreateWithImageInRect(imageRef, myImageRect);
  245. CGSize size;
  246. size.width = myImageRect.size.width;
  247. size.height = myImageRect.size.height;
  248. UIGraphicsBeginImageContext(size);
  249. CGContextRef context = UIGraphicsGetCurrentContext();
  250. CGContextDrawImage(context, myImageRect, subImageRef);
  251. UIImage* smallImage = [UIImage imageWithCGImage:subImageRef];
  252. CGImageRelease(subImageRef);
  253. UIGraphicsEndImageContext();
  254. return smallImage;
  255. }
  256. - (UIImage *)fixOrientation:(UIImage *)srcImg {
  257. if (srcImg.imageOrientation == UIImageOrientationUp) return srcImg;
  258. CGAffineTransform transform = CGAffineTransformIdentity;
  259. switch (srcImg.imageOrientation) {
  260. case UIImageOrientationDown:
  261. case UIImageOrientationDownMirrored:
  262. transform = CGAffineTransformTranslate(transform, srcImg.size.width, srcImg.size.height);
  263. transform = CGAffineTransformRotate(transform, M_PI);
  264. break;
  265. case UIImageOrientationLeft:
  266. case UIImageOrientationLeftMirrored:
  267. transform = CGAffineTransformTranslate(transform, srcImg.size.width, 0);
  268. transform = CGAffineTransformRotate(transform, M_PI_2);
  269. break;
  270. case UIImageOrientationRight:
  271. case UIImageOrientationRightMirrored:
  272. transform = CGAffineTransformTranslate(transform, 0, srcImg.size.height);
  273. transform = CGAffineTransformRotate(transform, -M_PI_2);
  274. break;
  275. case UIImageOrientationUp:
  276. case UIImageOrientationUpMirrored:
  277. break;
  278. }
  279. switch (srcImg.imageOrientation) {
  280. case UIImageOrientationUpMirrored:
  281. case UIImageOrientationDownMirrored:
  282. transform = CGAffineTransformTranslate(transform, srcImg.size.width, 0);
  283. transform = CGAffineTransformScale(transform, -1, 1);
  284. break;
  285. case UIImageOrientationLeftMirrored:
  286. case UIImageOrientationRightMirrored:
  287. transform = CGAffineTransformTranslate(transform, srcImg.size.height, 0);
  288. transform = CGAffineTransformScale(transform, -1, 1);
  289. break;
  290. case UIImageOrientationUp:
  291. case UIImageOrientationDown:
  292. case UIImageOrientationLeft:
  293. case UIImageOrientationRight:
  294. break;
  295. }
  296. CGContextRef ctx = CGBitmapContextCreate(NULL, srcImg.size.width, srcImg.size.height,
  297. CGImageGetBitsPerComponent(srcImg.CGImage), 0,
  298. CGImageGetColorSpace(srcImg.CGImage),
  299. CGImageGetBitmapInfo(srcImg.CGImage));
  300. CGContextConcatCTM(ctx, transform);
  301. switch (srcImg.imageOrientation) {
  302. case UIImageOrientationLeft:
  303. case UIImageOrientationLeftMirrored:
  304. case UIImageOrientationRight:
  305. case UIImageOrientationRightMirrored:
  306. CGContextDrawImage(ctx, CGRectMake(0,0,srcImg.size.height,srcImg.size.width), srcImg.CGImage);
  307. break;
  308. default:
  309. CGContextDrawImage(ctx, CGRectMake(0,0,srcImg.size.width,srcImg.size.height), srcImg.CGImage);
  310. break;
  311. }
  312. CGImageRef cgimg = CGBitmapContextCreateImage(ctx);
  313. UIImage *img = [UIImage imageWithCGImage:cgimg];
  314. CGContextRelease(ctx);
  315. CGImageRelease(cgimg);
  316. return img;
  317. }
  318. @end