RSKImageCropViewController.m 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037
  1. //
  2. // RSKImageCropViewController.m
  3. //
  4. // Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy
  7. // of this software and associated documentation files (the "Software"), to deal
  8. // in the Software without restriction, including without limitation the rights
  9. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. // copies of the Software, and to permit persons to whom the Software is
  11. // furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. // THE SOFTWARE.
  23. //
  24. #import "RSKImageCropViewController.h"
  25. #import "RSKTouchView.h"
  26. #import "RSKImageScrollView.h"
  27. #import "RSKInternalUtility.h"
  28. #import "UIImage+RSKImageCropper.h"
  29. #import "CGGeometry+RSKImageCropper.h"
  30. #import "UIApplication+RSKImageCropper.h"
  31. static const CGFloat kResetAnimationDuration = 0.4;
  32. static const CGFloat kLayoutImageScrollViewAnimationDuration = 0.25;
  33. @interface RSKImageCropViewController () <UIGestureRecognizerDelegate>
  34. @property (assign, nonatomic) BOOL originalNavigationControllerNavigationBarHidden;
  35. @property (strong, nonatomic) UIImage *originalNavigationControllerNavigationBarShadowImage;
  36. @property (copy, nonatomic) UIColor *originalNavigationControllerViewBackgroundColor;
  37. @property (assign, nonatomic) BOOL originalStatusBarHidden;
  38. @property (strong, nonatomic) RSKImageScrollView *imageScrollView;
  39. @property (strong, nonatomic) RSKTouchView *overlayView;
  40. @property (strong, nonatomic) CAShapeLayer *maskLayer;
  41. @property (assign, nonatomic) CGRect maskRect;
  42. @property (copy, nonatomic) UIBezierPath *maskPath;
  43. @property (readonly, nonatomic) CGRect rectForMaskPath;
  44. @property (readonly, nonatomic) CGRect rectForClipPath;
  45. @property (readonly, nonatomic) CGRect imageRect;
  46. @property (strong, nonatomic) UILabel *moveAndScaleLabel;
  47. @property (strong, nonatomic) UIButton *cancelButton;
  48. @property (strong, nonatomic) UIButton *chooseButton;
  49. @property (strong, nonatomic) UITapGestureRecognizer *doubleTapGestureRecognizer;
  50. @property (strong, nonatomic) UIRotationGestureRecognizer *rotationGestureRecognizer;
  51. @property (assign, nonatomic) BOOL didSetupConstraints;
  52. @property (strong, nonatomic) NSLayoutConstraint *moveAndScaleLabelTopConstraint;
  53. @property (strong, nonatomic) NSLayoutConstraint *cancelButtonBottomConstraint;
  54. @property (strong, nonatomic) NSLayoutConstraint *cancelButtonLeadingConstraint;
  55. @property (strong, nonatomic) NSLayoutConstraint *chooseButtonBottomConstraint;
  56. @property (strong, nonatomic) NSLayoutConstraint *chooseButtonTrailingConstraint;
  57. @end
  58. @implementation RSKImageCropViewController
  59. #pragma mark - Lifecycle
  60. - (instancetype)init
  61. {
  62. self = [super init];
  63. if (self) {
  64. _avoidEmptySpaceAroundImage = NO;
  65. _alwaysBounceVertical = NO;
  66. _alwaysBounceHorizontal = NO;
  67. _applyMaskToCroppedImage = NO;
  68. _maskLayerLineWidth = 1.0;
  69. _rotationEnabled = NO;
  70. _cropMode = RSKImageCropModeCircle;
  71. _portraitCircleMaskRectInnerEdgeInset = 15.0f;
  72. _portraitSquareMaskRectInnerEdgeInset = 20.0f;
  73. _portraitMoveAndScaleLabelTopAndCropViewTopVerticalSpace = 64.0f;
  74. _portraitCropViewBottomAndCancelButtonBottomVerticalSpace = 21.0f;
  75. _portraitCropViewBottomAndChooseButtonBottomVerticalSpace = 21.0f;
  76. _portraitCancelButtonLeadingAndCropViewLeadingHorizontalSpace = 13.0f;
  77. _portraitCropViewTrailingAndChooseButtonTrailingHorizontalSpace = 13.0;
  78. _landscapeCircleMaskRectInnerEdgeInset = 45.0f;
  79. _landscapeSquareMaskRectInnerEdgeInset = 45.0f;
  80. _landscapeMoveAndScaleLabelTopAndCropViewTopVerticalSpace = 12.0f;
  81. _landscapeCropViewBottomAndCancelButtonBottomVerticalSpace = 12.0f;
  82. _landscapeCropViewBottomAndChooseButtonBottomVerticalSpace = 12.0f;
  83. _landscapeCancelButtonLeadingAndCropViewLeadingHorizontalSpace = 13.0;
  84. _landscapeCropViewTrailingAndChooseButtonTrailingHorizontalSpace = 13.0;
  85. }
  86. return self;
  87. }
  88. - (instancetype)initWithImage:(UIImage *)originalImage
  89. {
  90. self = [self init];
  91. if (self) {
  92. _originalImage = originalImage;
  93. }
  94. return self;
  95. }
  96. - (instancetype)initWithImage:(UIImage *)originalImage cropMode:(RSKImageCropMode)cropMode
  97. {
  98. self = [self initWithImage:originalImage];
  99. if (self) {
  100. _cropMode = cropMode;
  101. }
  102. return self;
  103. }
  104. - (BOOL)prefersStatusBarHidden
  105. {
  106. return YES;
  107. }
  108. - (void)viewDidLoad
  109. {
  110. [super viewDidLoad];
  111. if ([self respondsToSelector:@selector(edgesForExtendedLayout)]) {
  112. self.edgesForExtendedLayout = UIRectEdgeNone;
  113. }
  114. if (@available(iOS 11.0, *)) {
  115. self.imageScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
  116. }
  117. else if ([self respondsToSelector:@selector(automaticallyAdjustsScrollViewInsets)] == YES) {
  118. self.automaticallyAdjustsScrollViewInsets = NO;
  119. }
  120. self.view.backgroundColor = [UIColor blackColor];
  121. self.view.clipsToBounds = YES;
  122. [self.view addSubview:self.imageScrollView];
  123. [self.view addSubview:self.overlayView];
  124. [self.view addSubview:self.moveAndScaleLabel];
  125. [self.view addSubview:self.cancelButton];
  126. [self.view addSubview:self.chooseButton];
  127. [self.view addGestureRecognizer:self.doubleTapGestureRecognizer];
  128. [self.view addGestureRecognizer:self.rotationGestureRecognizer];
  129. }
  130. - (void)viewWillAppear:(BOOL)animated
  131. {
  132. [super viewWillAppear:animated];
  133. if ([self respondsToSelector:@selector(prefersStatusBarHidden)] == NO) {
  134. UIApplication *application = [UIApplication rsk_sharedApplication];
  135. if (application) {
  136. self.originalStatusBarHidden = application.statusBarHidden;
  137. [application setStatusBarHidden:YES];
  138. }
  139. }
  140. self.originalNavigationControllerNavigationBarHidden = self.navigationController.navigationBarHidden;
  141. [self.navigationController setNavigationBarHidden:YES animated:NO];
  142. self.originalNavigationControllerNavigationBarShadowImage = self.navigationController.navigationBar.shadowImage;
  143. self.navigationController.navigationBar.shadowImage = nil;
  144. }
  145. - (void)viewDidAppear:(BOOL)animated
  146. {
  147. [super viewDidAppear:animated];
  148. self.originalNavigationControllerViewBackgroundColor = self.navigationController.view.backgroundColor;
  149. self.navigationController.view.backgroundColor = [UIColor blackColor];
  150. }
  151. - (void)viewWillDisappear:(BOOL)animated
  152. {
  153. [super viewWillDisappear:animated];
  154. if ([self respondsToSelector:@selector(prefersStatusBarHidden)] == NO) {
  155. UIApplication *application = [UIApplication rsk_sharedApplication];
  156. if (application) {
  157. [application setStatusBarHidden:self.originalStatusBarHidden];
  158. }
  159. }
  160. [self.navigationController setNavigationBarHidden:self.originalNavigationControllerNavigationBarHidden animated:animated];
  161. self.navigationController.navigationBar.shadowImage = self.originalNavigationControllerNavigationBarShadowImage;
  162. self.navigationController.view.backgroundColor = self.originalNavigationControllerViewBackgroundColor;
  163. }
  164. - (void)viewWillLayoutSubviews
  165. {
  166. [super viewWillLayoutSubviews];
  167. [self updateMaskRect];
  168. [self layoutImageScrollView];
  169. [self layoutOverlayView];
  170. [self updateMaskPath];
  171. [self.view setNeedsUpdateConstraints];
  172. }
  173. - (void)viewDidLayoutSubviews
  174. {
  175. [super viewDidLayoutSubviews];
  176. if (!self.imageScrollView.zoomView) {
  177. [self displayImage];
  178. }
  179. }
  180. - (void)updateViewConstraints
  181. {
  182. [super updateViewConstraints];
  183. if (!self.didSetupConstraints) {
  184. // ---------------------------
  185. // The label "Move and Scale".
  186. // ---------------------------
  187. NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self.moveAndScaleLabel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual
  188. toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0f
  189. constant:0.0f];
  190. [self.view addConstraint:constraint];
  191. CGFloat constant = self.portraitMoveAndScaleLabelTopAndCropViewTopVerticalSpace;
  192. self.moveAndScaleLabelTopConstraint = [NSLayoutConstraint constraintWithItem:self.moveAndScaleLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
  193. toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0f
  194. constant:constant];
  195. [self.view addConstraint:self.moveAndScaleLabelTopConstraint];
  196. // --------------------
  197. // The button "Cancel".
  198. // --------------------
  199. constant = self.portraitCancelButtonLeadingAndCropViewLeadingHorizontalSpace;
  200. self.cancelButtonLeadingConstraint = [NSLayoutConstraint constraintWithItem:self.cancelButton attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual
  201. toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1.0f
  202. constant:constant];
  203. [self.view addConstraint:self.cancelButtonLeadingConstraint];
  204. constant = self.portraitCropViewBottomAndCancelButtonBottomVerticalSpace;
  205. self.cancelButtonBottomConstraint = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual
  206. toItem:self.cancelButton attribute:NSLayoutAttributeBottom multiplier:1.0f
  207. constant:constant];
  208. [self.view addConstraint:self.cancelButtonBottomConstraint];
  209. // --------------------
  210. // The button "Choose".
  211. // --------------------
  212. constant = self.portraitCropViewTrailingAndChooseButtonTrailingHorizontalSpace;
  213. self.chooseButtonTrailingConstraint = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual
  214. toItem:self.chooseButton attribute:NSLayoutAttributeTrailing multiplier:1.0f
  215. constant:constant];
  216. [self.view addConstraint:self.chooseButtonTrailingConstraint];
  217. constant = self.portraitCropViewBottomAndChooseButtonBottomVerticalSpace;
  218. self.chooseButtonBottomConstraint = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual
  219. toItem:self.chooseButton attribute:NSLayoutAttributeBottom multiplier:1.0f
  220. constant:constant];
  221. [self.view addConstraint:self.chooseButtonBottomConstraint];
  222. self.didSetupConstraints = YES;
  223. } else {
  224. if ([self isPortraitInterfaceOrientation]) {
  225. self.moveAndScaleLabelTopConstraint.constant = self.portraitMoveAndScaleLabelTopAndCropViewTopVerticalSpace;
  226. self.cancelButtonBottomConstraint.constant = self.portraitCropViewBottomAndCancelButtonBottomVerticalSpace;
  227. self.cancelButtonLeadingConstraint.constant = self.portraitCancelButtonLeadingAndCropViewLeadingHorizontalSpace;
  228. self.chooseButtonBottomConstraint.constant = self.portraitCropViewBottomAndChooseButtonBottomVerticalSpace;
  229. self.chooseButtonTrailingConstraint.constant = self.portraitCropViewTrailingAndChooseButtonTrailingHorizontalSpace;
  230. } else {
  231. self.moveAndScaleLabelTopConstraint.constant = self.landscapeMoveAndScaleLabelTopAndCropViewTopVerticalSpace;
  232. self.cancelButtonBottomConstraint.constant = self.landscapeCropViewBottomAndCancelButtonBottomVerticalSpace;
  233. self.cancelButtonLeadingConstraint.constant = self.landscapeCancelButtonLeadingAndCropViewLeadingHorizontalSpace;
  234. self.chooseButtonBottomConstraint.constant = self.landscapeCropViewBottomAndChooseButtonBottomVerticalSpace;
  235. self.chooseButtonTrailingConstraint.constant = self.landscapeCropViewTrailingAndChooseButtonTrailingHorizontalSpace;
  236. }
  237. }
  238. }
  239. #pragma mark - Custom Accessors
  240. - (RSKImageScrollView *)imageScrollView
  241. {
  242. if (!_imageScrollView) {
  243. _imageScrollView = [[RSKImageScrollView alloc] init];
  244. _imageScrollView.clipsToBounds = NO;
  245. _imageScrollView.aspectFill = self.avoidEmptySpaceAroundImage;
  246. _imageScrollView.alwaysBounceHorizontal = self.alwaysBounceHorizontal;
  247. _imageScrollView.alwaysBounceVertical = self.alwaysBounceVertical;
  248. }
  249. return _imageScrollView;
  250. }
  251. - (RSKTouchView *)overlayView
  252. {
  253. if (!_overlayView) {
  254. _overlayView = [[RSKTouchView alloc] init];
  255. _overlayView.receiver = self.imageScrollView;
  256. [_overlayView.layer addSublayer:self.maskLayer];
  257. }
  258. return _overlayView;
  259. }
  260. - (CAShapeLayer *)maskLayer
  261. {
  262. if (!_maskLayer) {
  263. _maskLayer = [CAShapeLayer layer];
  264. _maskLayer.fillRule = kCAFillRuleEvenOdd;
  265. _maskLayer.fillColor = self.maskLayerColor.CGColor;
  266. _maskLayer.lineWidth = self.maskLayerLineWidth;
  267. _maskLayer.strokeColor = self.maskLayerStrokeColor.CGColor;
  268. }
  269. return _maskLayer;
  270. }
  271. - (UIColor *)maskLayerColor
  272. {
  273. if (!_maskLayerColor) {
  274. _maskLayerColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.7f];
  275. }
  276. return _maskLayerColor;
  277. }
  278. - (UILabel *)moveAndScaleLabel
  279. {
  280. if (!_moveAndScaleLabel) {
  281. _moveAndScaleLabel = [[UILabel alloc] init];
  282. _moveAndScaleLabel.translatesAutoresizingMaskIntoConstraints = NO;
  283. _moveAndScaleLabel.backgroundColor = [UIColor clearColor];
  284. _moveAndScaleLabel.text = RSKLocalizedString(@"Move and Scale", @"Move and Scale label");
  285. _moveAndScaleLabel.textColor = [UIColor whiteColor];
  286. _moveAndScaleLabel.opaque = NO;
  287. }
  288. return _moveAndScaleLabel;
  289. }
  290. - (UIButton *)cancelButton
  291. {
  292. if (!_cancelButton) {
  293. _cancelButton = [[UIButton alloc] init];
  294. _cancelButton.translatesAutoresizingMaskIntoConstraints = NO;
  295. [_cancelButton setTitle:RSKLocalizedString(@"Cancel", @"Cancel button") forState:UIControlStateNormal];
  296. [_cancelButton addTarget:self action:@selector(onCancelButtonTouch:) forControlEvents:UIControlEventTouchUpInside];
  297. _cancelButton.opaque = NO;
  298. }
  299. return _cancelButton;
  300. }
  301. - (UIButton *)chooseButton
  302. {
  303. if (!_chooseButton) {
  304. _chooseButton = [[UIButton alloc] init];
  305. _chooseButton.translatesAutoresizingMaskIntoConstraints = NO;
  306. [_chooseButton setTitle:RSKLocalizedString(@"Choose", @"Choose button") forState:UIControlStateNormal];
  307. [_chooseButton addTarget:self action:@selector(onChooseButtonTouch:) forControlEvents:UIControlEventTouchUpInside];
  308. _chooseButton.opaque = NO;
  309. }
  310. return _chooseButton;
  311. }
  312. - (UITapGestureRecognizer *)doubleTapGestureRecognizer
  313. {
  314. if (!_doubleTapGestureRecognizer) {
  315. _doubleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)];
  316. _doubleTapGestureRecognizer.delaysTouchesEnded = NO;
  317. _doubleTapGestureRecognizer.numberOfTapsRequired = 2;
  318. _doubleTapGestureRecognizer.delegate = self;
  319. }
  320. return _doubleTapGestureRecognizer;
  321. }
  322. - (UIRotationGestureRecognizer *)rotationGestureRecognizer
  323. {
  324. if (!_rotationGestureRecognizer) {
  325. _rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotation:)];
  326. _rotationGestureRecognizer.delaysTouchesEnded = NO;
  327. _rotationGestureRecognizer.delegate = self;
  328. _rotationGestureRecognizer.enabled = self.isRotationEnabled;
  329. }
  330. return _rotationGestureRecognizer;
  331. }
  332. - (CGRect)imageRect
  333. {
  334. float zoomScale = 1.0 / self.imageScrollView.zoomScale;
  335. CGRect imageRect = CGRectZero;
  336. imageRect.origin.x = self.imageScrollView.contentOffset.x * zoomScale;
  337. imageRect.origin.y = self.imageScrollView.contentOffset.y * zoomScale;
  338. imageRect.size.width = CGRectGetWidth(self.imageScrollView.bounds) * zoomScale;
  339. imageRect.size.height = CGRectGetHeight(self.imageScrollView.bounds) * zoomScale;
  340. imageRect = RSKRectNormalize(imageRect);
  341. CGSize imageSize = self.originalImage.size;
  342. CGFloat x = CGRectGetMinX(imageRect);
  343. CGFloat y = CGRectGetMinY(imageRect);
  344. CGFloat width = CGRectGetWidth(imageRect);
  345. CGFloat height = CGRectGetHeight(imageRect);
  346. UIImageOrientation imageOrientation = self.originalImage.imageOrientation;
  347. if (imageOrientation == UIImageOrientationRight || imageOrientation == UIImageOrientationRightMirrored) {
  348. imageRect.origin.x = y;
  349. imageRect.origin.y = floor(imageSize.width - CGRectGetWidth(imageRect) - x);
  350. imageRect.size.width = height;
  351. imageRect.size.height = width;
  352. } else if (imageOrientation == UIImageOrientationLeft || imageOrientation == UIImageOrientationLeftMirrored) {
  353. imageRect.origin.x = floor(imageSize.height - CGRectGetHeight(imageRect) - y);
  354. imageRect.origin.y = x;
  355. imageRect.size.width = height;
  356. imageRect.size.height = width;
  357. } else if (imageOrientation == UIImageOrientationDown || imageOrientation == UIImageOrientationDownMirrored) {
  358. imageRect.origin.x = floor(imageSize.width - CGRectGetWidth(imageRect) - x);
  359. imageRect.origin.y = floor(imageSize.height - CGRectGetHeight(imageRect) - y);
  360. }
  361. CGFloat imageScale = self.originalImage.scale;
  362. imageRect = CGRectApplyAffineTransform(imageRect, CGAffineTransformMakeScale(imageScale, imageScale));
  363. return imageRect;
  364. }
  365. - (CGRect)cropRect
  366. {
  367. CGRect maskRect = self.maskRect;
  368. CGFloat rotationAngle = self.rotationAngle;
  369. CGRect rotatedImageScrollViewFrame = self.imageScrollView.frame;
  370. float zoomScale = 1.0 / self.imageScrollView.zoomScale;
  371. CGAffineTransform imageScrollViewTransform = self.imageScrollView.transform;
  372. self.imageScrollView.transform = CGAffineTransformIdentity;
  373. CGRect imageScrollViewFrame = self.imageScrollView.frame;
  374. self.imageScrollView.frame = self.maskRect;
  375. CGRect imageFrame = CGRectZero;
  376. imageFrame.origin.x = CGRectGetMinX(maskRect) - self.imageScrollView.contentOffset.x;
  377. imageFrame.origin.y = CGRectGetMinY(maskRect) - self.imageScrollView.contentOffset.y;
  378. imageFrame.size = self.imageScrollView.contentSize;
  379. CGFloat tx = CGRectGetMinX(imageFrame) + self.imageScrollView.contentOffset.x + CGRectGetWidth(maskRect) * 0.5f;
  380. CGFloat ty = CGRectGetMinY(imageFrame) + self.imageScrollView.contentOffset.y + CGRectGetHeight(maskRect) * 0.5f;
  381. CGFloat sx = CGRectGetWidth(rotatedImageScrollViewFrame) / CGRectGetWidth(imageScrollViewFrame);
  382. CGFloat sy = CGRectGetHeight(rotatedImageScrollViewFrame) / CGRectGetHeight(imageScrollViewFrame);
  383. CGAffineTransform t1 = CGAffineTransformMakeTranslation(-tx, -ty);
  384. CGAffineTransform t2 = CGAffineTransformMakeRotation(rotationAngle);
  385. CGAffineTransform t3 = CGAffineTransformMakeScale(sx, sy);
  386. CGAffineTransform t4 = CGAffineTransformMakeTranslation(tx, ty);
  387. CGAffineTransform t1t2 = CGAffineTransformConcat(t1, t2);
  388. CGAffineTransform t1t2t3 = CGAffineTransformConcat(t1t2, t3);
  389. CGAffineTransform t1t2t3t4 = CGAffineTransformConcat(t1t2t3, t4);
  390. imageFrame = CGRectApplyAffineTransform(imageFrame, t1t2t3t4);
  391. CGRect cropRect = CGRectMake(0.0, 0.0, CGRectGetWidth(maskRect), CGRectGetHeight(maskRect));
  392. cropRect.origin.x = -CGRectGetMinX(imageFrame) + CGRectGetMinX(maskRect);
  393. cropRect.origin.y = -CGRectGetMinY(imageFrame) + CGRectGetMinY(maskRect);
  394. cropRect = CGRectApplyAffineTransform(cropRect, CGAffineTransformMakeScale(zoomScale, zoomScale));
  395. cropRect = RSKRectNormalize(cropRect);
  396. CGFloat imageScale = self.originalImage.scale;
  397. cropRect = CGRectApplyAffineTransform(cropRect, CGAffineTransformMakeScale(imageScale, imageScale));
  398. self.imageScrollView.frame = imageScrollViewFrame;
  399. self.imageScrollView.transform = imageScrollViewTransform;
  400. return cropRect;
  401. }
  402. - (CGRect)rectForClipPath
  403. {
  404. if (!self.maskLayerStrokeColor) {
  405. return self.overlayView.frame;
  406. } else {
  407. CGFloat maskLayerLineHalfWidth = self.maskLayerLineWidth / 2.0;
  408. return CGRectInset(self.overlayView.frame, -maskLayerLineHalfWidth, -maskLayerLineHalfWidth);
  409. }
  410. }
  411. - (CGRect)rectForMaskPath
  412. {
  413. if (!self.maskLayerStrokeColor) {
  414. return self.maskRect;
  415. } else {
  416. CGFloat maskLayerLineHalfWidth = self.maskLayerLineWidth / 2.0;
  417. return CGRectInset(self.maskRect, maskLayerLineHalfWidth, maskLayerLineHalfWidth);
  418. }
  419. }
  420. - (CGFloat)rotationAngle
  421. {
  422. CGAffineTransform transform = self.imageScrollView.transform;
  423. CGFloat rotationAngle = atan2(transform.b, transform.a);
  424. return rotationAngle;
  425. }
  426. - (CGFloat)zoomScale
  427. {
  428. return self.imageScrollView.zoomScale;
  429. }
  430. - (void)setAvoidEmptySpaceAroundImage:(BOOL)avoidEmptySpaceAroundImage
  431. {
  432. if (_avoidEmptySpaceAroundImage != avoidEmptySpaceAroundImage) {
  433. _avoidEmptySpaceAroundImage = avoidEmptySpaceAroundImage;
  434. self.imageScrollView.aspectFill = avoidEmptySpaceAroundImage;
  435. }
  436. }
  437. - (void)setAlwaysBounceVertical:(BOOL)alwaysBounceVertical
  438. {
  439. if (_alwaysBounceVertical != alwaysBounceVertical) {
  440. _alwaysBounceVertical = alwaysBounceVertical;
  441. self.imageScrollView.alwaysBounceVertical = alwaysBounceVertical;
  442. }
  443. }
  444. - (void)setAlwaysBounceHorizontal:(BOOL)alwaysBounceHorizontal
  445. {
  446. if (_alwaysBounceHorizontal != alwaysBounceHorizontal) {
  447. _alwaysBounceHorizontal = alwaysBounceHorizontal;
  448. self.imageScrollView.alwaysBounceHorizontal = alwaysBounceHorizontal;
  449. }
  450. }
  451. - (void)setCropMode:(RSKImageCropMode)cropMode
  452. {
  453. if (_cropMode != cropMode) {
  454. _cropMode = cropMode;
  455. if (self.imageScrollView.zoomView) {
  456. [self reset:NO];
  457. }
  458. }
  459. }
  460. - (void)setOriginalImage:(UIImage *)originalImage
  461. {
  462. if (![_originalImage isEqual:originalImage]) {
  463. _originalImage = originalImage;
  464. if (self.isViewLoaded && self.view.window) {
  465. [self displayImage];
  466. }
  467. }
  468. }
  469. - (void)setMaskPath:(UIBezierPath *)maskPath
  470. {
  471. if (![_maskPath isEqual:maskPath]) {
  472. _maskPath = maskPath;
  473. UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:self.rectForClipPath];
  474. [clipPath appendPath:maskPath];
  475. clipPath.usesEvenOddFillRule = YES;
  476. CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
  477. pathAnimation.duration = [CATransaction animationDuration];
  478. pathAnimation.timingFunction = [CATransaction animationTimingFunction];
  479. [self.maskLayer addAnimation:pathAnimation forKey:@"path"];
  480. self.maskLayer.path = [clipPath CGPath];
  481. }
  482. }
  483. - (void)setRotationAngle:(CGFloat)rotationAngle
  484. {
  485. if (self.rotationAngle != rotationAngle) {
  486. CGFloat rotation = (rotationAngle - self.rotationAngle);
  487. CGAffineTransform transform = CGAffineTransformRotate(self.imageScrollView.transform, rotation);
  488. self.imageScrollView.transform = transform;
  489. }
  490. }
  491. - (void)setRotationEnabled:(BOOL)rotationEnabled
  492. {
  493. if (_rotationEnabled != rotationEnabled) {
  494. _rotationEnabled = rotationEnabled;
  495. self.rotationGestureRecognizer.enabled = rotationEnabled;
  496. }
  497. }
  498. - (void)setZoomScale:(CGFloat)zoomScale
  499. {
  500. self.imageScrollView.zoomScale = zoomScale;
  501. }
  502. #pragma mark - Action handling
  503. - (void)onCancelButtonTouch:(UIBarButtonItem *)sender
  504. {
  505. [self cancelCrop];
  506. }
  507. - (void)onChooseButtonTouch:(UIBarButtonItem *)sender
  508. {
  509. [self cropImage];
  510. }
  511. - (void)handleDoubleTap:(UITapGestureRecognizer *)gestureRecognizer
  512. {
  513. [self reset:YES];
  514. }
  515. - (void)handleRotation:(UIRotationGestureRecognizer *)gestureRecognizer
  516. {
  517. [self setRotationAngle:(self.rotationAngle + gestureRecognizer.rotation)];
  518. gestureRecognizer.rotation = 0;
  519. if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
  520. [UIView animateWithDuration:kLayoutImageScrollViewAnimationDuration
  521. delay:0.0
  522. options:UIViewAnimationOptionBeginFromCurrentState
  523. animations:^{
  524. [self layoutImageScrollView];
  525. }
  526. completion:nil];
  527. }
  528. }
  529. - (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
  530. {
  531. [self.imageScrollView zoomToRect:rect animated:animated];
  532. }
  533. #pragma mark - Public
  534. - (BOOL)isPortraitInterfaceOrientation
  535. {
  536. return CGRectGetHeight(self.view.bounds) > CGRectGetWidth(self.view.bounds);
  537. }
  538. #pragma mark - Private
  539. - (void)reset:(BOOL)animated
  540. {
  541. if (animated) {
  542. [UIView beginAnimations:@"rsk_reset" context:NULL];
  543. [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
  544. [UIView setAnimationDuration:kResetAnimationDuration];
  545. [UIView setAnimationBeginsFromCurrentState:YES];
  546. }
  547. [self resetRotation];
  548. [self resetFrame];
  549. [self resetZoomScale];
  550. [self resetContentOffset];
  551. if (animated) {
  552. [UIView commitAnimations];
  553. }
  554. }
  555. - (void)resetContentOffset
  556. {
  557. CGSize boundsSize = self.imageScrollView.bounds.size;
  558. CGRect frameToCenter = self.imageScrollView.zoomView.frame;
  559. CGPoint contentOffset;
  560. if (CGRectGetWidth(frameToCenter) > boundsSize.width) {
  561. contentOffset.x = (CGRectGetWidth(frameToCenter) - boundsSize.width) * 0.5f;
  562. } else {
  563. contentOffset.x = 0;
  564. }
  565. if (CGRectGetHeight(frameToCenter) > boundsSize.height) {
  566. contentOffset.y = (CGRectGetHeight(frameToCenter) - boundsSize.height) * 0.5f;
  567. } else {
  568. contentOffset.y = 0;
  569. }
  570. self.imageScrollView.contentOffset = contentOffset;
  571. }
  572. - (void)resetFrame
  573. {
  574. [self layoutImageScrollView];
  575. }
  576. - (void)resetRotation
  577. {
  578. [self setRotationAngle:0.0];
  579. }
  580. - (void)resetZoomScale
  581. {
  582. CGFloat zoomScale;
  583. if (CGRectGetWidth(self.view.bounds) > CGRectGetHeight(self.view.bounds)) {
  584. zoomScale = CGRectGetHeight(self.view.bounds) / self.originalImage.size.height;
  585. } else {
  586. zoomScale = CGRectGetWidth(self.view.bounds) / self.originalImage.size.width;
  587. }
  588. self.imageScrollView.zoomScale = zoomScale;
  589. }
  590. - (NSArray *)intersectionPointsOfLineSegment:(RSKLineSegment)lineSegment withRect:(CGRect)rect
  591. {
  592. RSKLineSegment top = RSKLineSegmentMake(CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect)),
  593. CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect)));
  594. RSKLineSegment right = RSKLineSegmentMake(CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect)),
  595. CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect)));
  596. RSKLineSegment bottom = RSKLineSegmentMake(CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect)),
  597. CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect)));
  598. RSKLineSegment left = RSKLineSegmentMake(CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect)),
  599. CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect)));
  600. CGPoint p0 = RSKLineSegmentIntersection(top, lineSegment);
  601. CGPoint p1 = RSKLineSegmentIntersection(right, lineSegment);
  602. CGPoint p2 = RSKLineSegmentIntersection(bottom, lineSegment);
  603. CGPoint p3 = RSKLineSegmentIntersection(left, lineSegment);
  604. NSMutableArray *intersectionPoints = [@[] mutableCopy];
  605. if (!RSKPointIsNull(p0)) {
  606. [intersectionPoints addObject:[NSValue valueWithCGPoint:p0]];
  607. }
  608. if (!RSKPointIsNull(p1)) {
  609. [intersectionPoints addObject:[NSValue valueWithCGPoint:p1]];
  610. }
  611. if (!RSKPointIsNull(p2)) {
  612. [intersectionPoints addObject:[NSValue valueWithCGPoint:p2]];
  613. }
  614. if (!RSKPointIsNull(p3)) {
  615. [intersectionPoints addObject:[NSValue valueWithCGPoint:p3]];
  616. }
  617. return [intersectionPoints copy];
  618. }
  619. - (void)displayImage
  620. {
  621. if (self.originalImage) {
  622. [self.imageScrollView displayImage:self.originalImage];
  623. [self reset:NO];
  624. if ([self.delegate respondsToSelector:@selector(imageCropViewControllerDidDisplayImage:)]) {
  625. [self.delegate imageCropViewControllerDidDisplayImage:self];
  626. }
  627. }
  628. }
  629. - (void)layoutImageScrollView
  630. {
  631. CGRect frame = CGRectZero;
  632. // The bounds of the image scroll view should always fill the mask area.
  633. switch (self.cropMode) {
  634. case RSKImageCropModeSquare: {
  635. if (self.rotationAngle == 0.0) {
  636. frame = self.maskRect;
  637. } else {
  638. // Step 1: Rotate the left edge of the initial rect of the image scroll view clockwise around the center by `rotationAngle`.
  639. CGRect initialRect = self.maskRect;
  640. CGFloat rotationAngle = self.rotationAngle;
  641. CGPoint leftTopPoint = CGPointMake(initialRect.origin.x, initialRect.origin.y);
  642. CGPoint leftBottomPoint = CGPointMake(initialRect.origin.x, initialRect.origin.y + initialRect.size.height);
  643. RSKLineSegment leftLineSegment = RSKLineSegmentMake(leftTopPoint, leftBottomPoint);
  644. CGPoint pivot = RSKRectCenterPoint(initialRect);
  645. CGFloat alpha = fabs(rotationAngle);
  646. RSKLineSegment rotatedLeftLineSegment = RSKLineSegmentRotateAroundPoint(leftLineSegment, pivot, alpha);
  647. // Step 2: Find the points of intersection of the rotated edge with the initial rect.
  648. NSArray *points = [self intersectionPointsOfLineSegment:rotatedLeftLineSegment withRect:initialRect];
  649. // Step 3: If the number of intersection points more than one
  650. // then the bounds of the rotated image scroll view does not completely fill the mask area.
  651. // Therefore, we need to update the frame of the image scroll view.
  652. // Otherwise, we can use the initial rect.
  653. if (points.count > 1) {
  654. // We have a right triangle.
  655. // Step 4: Calculate the altitude of the right triangle.
  656. if ((alpha > M_PI_2) && (alpha < M_PI)) {
  657. alpha = alpha - M_PI_2;
  658. } else if ((alpha > (M_PI + M_PI_2)) && (alpha < (M_PI + M_PI))) {
  659. alpha = alpha - (M_PI + M_PI_2);
  660. }
  661. CGFloat sinAlpha = sin(alpha);
  662. CGFloat cosAlpha = cos(alpha);
  663. CGFloat hypotenuse = RSKPointDistance([points[0] CGPointValue], [points[1] CGPointValue]);
  664. CGFloat altitude = hypotenuse * sinAlpha * cosAlpha;
  665. // Step 5: Calculate the target width.
  666. CGFloat initialWidth = CGRectGetWidth(initialRect);
  667. CGFloat targetWidth = initialWidth + altitude * 2;
  668. // Step 6: Calculate the target frame.
  669. CGFloat scale = targetWidth / initialWidth;
  670. CGPoint center = RSKRectCenterPoint(initialRect);
  671. frame = RSKRectScaleAroundPoint(initialRect, center, scale, scale);
  672. // Step 7: Avoid floats.
  673. frame.origin.x = floor(CGRectGetMinX(frame));
  674. frame.origin.y = floor(CGRectGetMinY(frame));
  675. frame = CGRectIntegral(frame);
  676. } else {
  677. // Step 4: Use the initial rect.
  678. frame = initialRect;
  679. }
  680. }
  681. break;
  682. }
  683. case RSKImageCropModeCircle: {
  684. frame = self.maskRect;
  685. break;
  686. }
  687. case RSKImageCropModeCustom: {
  688. frame = [self.dataSource imageCropViewControllerCustomMovementRect:self];
  689. break;
  690. }
  691. }
  692. CGAffineTransform transform = self.imageScrollView.transform;
  693. self.imageScrollView.transform = CGAffineTransformIdentity;
  694. self.imageScrollView.frame = frame;
  695. self.imageScrollView.transform = transform;
  696. }
  697. - (void)layoutOverlayView
  698. {
  699. CGRect frame = CGRectMake(0, 0, CGRectGetWidth(self.view.bounds) * 2, CGRectGetHeight(self.view.bounds) * 2);
  700. self.overlayView.frame = frame;
  701. }
  702. - (void)updateMaskRect
  703. {
  704. switch (self.cropMode) {
  705. case RSKImageCropModeCircle: {
  706. CGFloat viewWidth = CGRectGetWidth(self.view.bounds);
  707. CGFloat viewHeight = CGRectGetHeight(self.view.bounds);
  708. CGFloat diameter;
  709. if ([self isPortraitInterfaceOrientation]) {
  710. diameter = MIN(viewWidth, viewHeight) - self.portraitCircleMaskRectInnerEdgeInset * 2;
  711. } else {
  712. diameter = MIN(viewWidth, viewHeight) - self.landscapeCircleMaskRectInnerEdgeInset * 2;
  713. }
  714. CGSize maskSize = CGSizeMake(diameter, diameter);
  715. self.maskRect = CGRectMake((viewWidth - maskSize.width) * 0.5f,
  716. (viewHeight - maskSize.height) * 0.5f,
  717. maskSize.width,
  718. maskSize.height);
  719. break;
  720. }
  721. case RSKImageCropModeSquare: {
  722. CGFloat viewWidth = CGRectGetWidth(self.view.bounds);
  723. CGFloat viewHeight = CGRectGetHeight(self.view.bounds);
  724. CGFloat length;
  725. if ([self isPortraitInterfaceOrientation]) {
  726. length = MIN(viewWidth, viewHeight) - self.portraitSquareMaskRectInnerEdgeInset * 2;
  727. } else {
  728. length = MIN(viewWidth, viewHeight) - self.landscapeSquareMaskRectInnerEdgeInset * 2;
  729. }
  730. CGSize maskSize = CGSizeMake(length, length);
  731. self.maskRect = CGRectMake((viewWidth - maskSize.width) * 0.5f,
  732. (viewHeight - maskSize.height) * 0.5f,
  733. maskSize.width,
  734. maskSize.height);
  735. break;
  736. }
  737. case RSKImageCropModeCustom: {
  738. self.maskRect = [self.dataSource imageCropViewControllerCustomMaskRect:self];
  739. break;
  740. }
  741. }
  742. }
  743. - (void)updateMaskPath
  744. {
  745. switch (self.cropMode) {
  746. case RSKImageCropModeCircle: {
  747. self.maskPath = [UIBezierPath bezierPathWithOvalInRect:self.rectForMaskPath];
  748. break;
  749. }
  750. case RSKImageCropModeSquare: {
  751. self.maskPath = [UIBezierPath bezierPathWithRect:self.rectForMaskPath];
  752. break;
  753. }
  754. case RSKImageCropModeCustom: {
  755. self.maskPath = [self.dataSource imageCropViewControllerCustomMaskPath:self];
  756. break;
  757. }
  758. }
  759. }
  760. - (UIImage *)imageWithImage:(UIImage *)image inRect:(CGRect)rect scale:(CGFloat)scale imageOrientation:(UIImageOrientation)imageOrientation
  761. {
  762. if (!image.images) {
  763. CGImageRef cgImage = CGImageCreateWithImageInRect(image.CGImage, rect);
  764. UIImage *image = [UIImage imageWithCGImage:cgImage scale:scale orientation:imageOrientation];
  765. CGImageRelease(cgImage);
  766. return image;
  767. } else {
  768. UIImage *animatedImage = image;
  769. NSMutableArray *images = [NSMutableArray array];
  770. for (UIImage *animatedImageImage in animatedImage.images) {
  771. UIImage *image = [self imageWithImage:animatedImageImage inRect:rect scale:scale imageOrientation:imageOrientation];
  772. [images addObject:image];
  773. }
  774. return [UIImage animatedImageWithImages:images duration:image.duration];
  775. }
  776. }
  777. - (UIImage *)croppedImage:(UIImage *)originalImage cropMode:(RSKImageCropMode)cropMode cropRect:(CGRect)cropRect imageRect:(CGRect)imageRect rotationAngle:(CGFloat)rotationAngle zoomScale:(CGFloat)zoomScale maskPath:(UIBezierPath *)maskPath applyMaskToCroppedImage:(BOOL)applyMaskToCroppedImage
  778. {
  779. // Step 1: create an image using the data contained within the specified rect.
  780. UIImage *image = [self imageWithImage:originalImage inRect:imageRect scale:originalImage.scale imageOrientation:originalImage.imageOrientation];
  781. // Step 2: fix orientation of the image.
  782. image = [image fixOrientation];
  783. // Step 3: If current mode is `RSKImageCropModeSquare` and the original image is not rotated
  784. // or mask should not be applied to the image after cropping and the original image is not rotated,
  785. // we can return the image immediately.
  786. // Otherwise, we must further process the image.
  787. if ((cropMode == RSKImageCropModeSquare || !applyMaskToCroppedImage) && rotationAngle == 0.0) {
  788. // Step 4: return the image immediately.
  789. return image;
  790. } else {
  791. // Step 4: create a new context.
  792. CGSize contextSize = cropRect.size;
  793. UIGraphicsBeginImageContextWithOptions(contextSize, NO, originalImage.scale);
  794. // Step 5: apply the mask if needed.
  795. if (applyMaskToCroppedImage) {
  796. // 5a: scale the mask to the size of the crop rect.
  797. UIBezierPath *maskPathCopy = [maskPath copy];
  798. CGFloat scale = 1.0 / zoomScale;
  799. [maskPathCopy applyTransform:CGAffineTransformMakeScale(scale, scale)];
  800. // 5b: center the mask.
  801. CGPoint translation = CGPointMake(-CGRectGetMinX(maskPathCopy.bounds) + (CGRectGetWidth(cropRect) - CGRectGetWidth(maskPathCopy.bounds)) * 0.5f,
  802. -CGRectGetMinY(maskPathCopy.bounds) + (CGRectGetHeight(cropRect) - CGRectGetHeight(maskPathCopy.bounds)) * 0.5f);
  803. [maskPathCopy applyTransform:CGAffineTransformMakeTranslation(translation.x, translation.y)];
  804. // 5c: apply the mask.
  805. [maskPathCopy addClip];
  806. }
  807. // Step 6: rotate the image if needed.
  808. if (rotationAngle != 0) {
  809. image = [image rotateByAngle:rotationAngle];
  810. }
  811. // Step 7: draw the image.
  812. CGPoint point = CGPointMake(floor((contextSize.width - image.size.width) * 0.5f),
  813. floor((contextSize.height - image.size.height) * 0.5f));
  814. [image drawAtPoint:point];
  815. // Step 8: get the cropped image affter processing from the context.
  816. UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
  817. // Step 9: remove the context.
  818. UIGraphicsEndImageContext();
  819. croppedImage = [UIImage imageWithCGImage:croppedImage.CGImage scale:originalImage.scale orientation:image.imageOrientation];
  820. // Step 10: return the cropped image affter processing.
  821. return croppedImage;
  822. }
  823. }
  824. - (void)cropImage
  825. {
  826. if ([self.delegate respondsToSelector:@selector(imageCropViewController:willCropImage:)]) {
  827. [self.delegate imageCropViewController:self willCropImage:self.originalImage];
  828. }
  829. UIImage *originalImage = self.originalImage;
  830. RSKImageCropMode cropMode = self.cropMode;
  831. CGRect cropRect = self.cropRect;
  832. CGRect imageRect = self.imageRect;
  833. CGFloat rotationAngle = self.rotationAngle;
  834. CGFloat zoomScale = self.imageScrollView.zoomScale;
  835. UIBezierPath *maskPath = self.maskPath;
  836. BOOL applyMaskToCroppedImage = self.applyMaskToCroppedImage;
  837. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  838. UIImage *croppedImage = [self croppedImage:originalImage cropMode:cropMode cropRect:cropRect imageRect:imageRect rotationAngle:rotationAngle zoomScale:zoomScale maskPath:maskPath applyMaskToCroppedImage:applyMaskToCroppedImage];
  839. dispatch_async(dispatch_get_main_queue(), ^{
  840. [self.delegate imageCropViewController:self didCropImage:croppedImage usingCropRect:cropRect rotationAngle:rotationAngle];
  841. });
  842. });
  843. }
  844. - (void)cancelCrop
  845. {
  846. if ([self.delegate respondsToSelector:@selector(imageCropViewControllerDidCancelCrop:)]) {
  847. [self.delegate imageCropViewControllerDidCancelCrop:self];
  848. }
  849. }
  850. #pragma mark - UIGestureRecognizerDelegate
  851. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
  852. {
  853. return YES;
  854. }
  855. @end