RSKImageCropViewController.m 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038
  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. static const CGFloat kResetAnimationDuration = 0.4;
  31. static const CGFloat kLayoutImageScrollViewAnimationDuration = 0.25;
  32. @interface RSKImageCropViewController () <UIGestureRecognizerDelegate>
  33. @property (assign, nonatomic) BOOL originalNavigationControllerNavigationBarHidden;
  34. @property (strong, nonatomic) UIImage *originalNavigationControllerNavigationBarShadowImage;
  35. @property (copy, nonatomic) UIColor *originalNavigationControllerViewBackgroundColor;
  36. @property (strong, nonatomic) RSKImageScrollView *imageScrollView;
  37. @property (strong, nonatomic) RSKTouchView *overlayView;
  38. @property (strong, nonatomic) CAShapeLayer *maskLayer;
  39. @property (assign, nonatomic) CGRect maskRect;
  40. @property (copy, nonatomic) UIBezierPath *maskPath;
  41. @property (readonly, nonatomic) CGRect rectForMaskPath;
  42. @property (readonly, nonatomic) CGRect rectForClipPath;
  43. @property (readonly, nonatomic) CGRect imageRect;
  44. @property (strong, nonatomic) UILabel *moveAndScaleLabel;
  45. @property (strong, nonatomic) UIButton *cancelButton;
  46. @property (strong, nonatomic) UIButton *chooseButton;
  47. @property (strong, nonatomic) UITapGestureRecognizer *doubleTapGestureRecognizer;
  48. @property (strong, nonatomic) UIRotationGestureRecognizer *rotationGestureRecognizer;
  49. @property (assign, nonatomic) BOOL didSetupConstraints;
  50. @property (strong, nonatomic) NSLayoutConstraint *moveAndScaleLabelTopConstraint;
  51. @property (strong, nonatomic) NSLayoutConstraint *cancelButtonBottomConstraint;
  52. @property (strong, nonatomic) NSLayoutConstraint *cancelButtonLeadingConstraint;
  53. @property (strong, nonatomic) NSLayoutConstraint *chooseButtonBottomConstraint;
  54. @property (strong, nonatomic) NSLayoutConstraint *chooseButtonTrailingConstraint;
  55. @end
  56. @implementation RSKImageCropViewController
  57. #pragma mark - Lifecycle
  58. - (instancetype)init
  59. {
  60. self = [super init];
  61. if (self) {
  62. _avoidEmptySpaceAroundImage = NO;
  63. _alwaysBounceVertical = NO;
  64. _alwaysBounceHorizontal = NO;
  65. _applyMaskToCroppedImage = NO;
  66. _maskLayerLineWidth = 1.0;
  67. _rotationEnabled = NO;
  68. _cropMode = RSKImageCropModeCircle;
  69. _portraitCircleMaskRectInnerEdgeInset = 15.0f;
  70. _portraitSquareMaskRectInnerEdgeInset = 20.0f;
  71. _portraitMoveAndScaleLabelTopAndCropViewTopVerticalSpace = 64.0f;
  72. _portraitCropViewBottomAndCancelButtonBottomVerticalSpace = 21.0f;
  73. _portraitCropViewBottomAndChooseButtonBottomVerticalSpace = 21.0f;
  74. _portraitCancelButtonLeadingAndCropViewLeadingHorizontalSpace = 13.0f;
  75. _portraitCropViewTrailingAndChooseButtonTrailingHorizontalSpace = 13.0;
  76. _landscapeCircleMaskRectInnerEdgeInset = 45.0f;
  77. _landscapeSquareMaskRectInnerEdgeInset = 45.0f;
  78. _landscapeMoveAndScaleLabelTopAndCropViewTopVerticalSpace = 12.0f;
  79. _landscapeCropViewBottomAndCancelButtonBottomVerticalSpace = 12.0f;
  80. _landscapeCropViewBottomAndChooseButtonBottomVerticalSpace = 12.0f;
  81. _landscapeCancelButtonLeadingAndCropViewLeadingHorizontalSpace = 13.0;
  82. _landscapeCropViewTrailingAndChooseButtonTrailingHorizontalSpace = 13.0;
  83. }
  84. return self;
  85. }
  86. - (instancetype)initWithImage:(UIImage *)originalImage
  87. {
  88. self = [self init];
  89. if (self) {
  90. _originalImage = originalImage;
  91. }
  92. return self;
  93. }
  94. - (instancetype)initWithImage:(UIImage *)originalImage cropMode:(RSKImageCropMode)cropMode
  95. {
  96. self = [self initWithImage:originalImage];
  97. if (self) {
  98. _cropMode = cropMode;
  99. }
  100. return self;
  101. }
  102. - (BOOL)prefersStatusBarHidden
  103. {
  104. return YES;
  105. }
  106. - (void)viewDidLoad
  107. {
  108. [super viewDidLoad];
  109. if ([self respondsToSelector:@selector(edgesForExtendedLayout)]) {
  110. self.edgesForExtendedLayout = UIRectEdgeNone;
  111. }
  112. if (@available(iOS 11.0, *)) {
  113. self.imageScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
  114. }
  115. else if ([self respondsToSelector:@selector(automaticallyAdjustsScrollViewInsets)] == YES) {
  116. self.automaticallyAdjustsScrollViewInsets = NO;
  117. }
  118. self.view.backgroundColor = [UIColor blackColor];
  119. self.view.clipsToBounds = YES;
  120. [self.view addSubview:self.imageScrollView];
  121. [self.view addSubview:self.overlayView];
  122. [self.view addSubview:self.moveAndScaleLabel];
  123. [self.view addSubview:self.cancelButton];
  124. [self.view addSubview:self.chooseButton];
  125. [self.view addGestureRecognizer:self.doubleTapGestureRecognizer];
  126. [self.view addGestureRecognizer:self.rotationGestureRecognizer];
  127. }
  128. - (void)viewWillAppear:(BOOL)animated
  129. {
  130. [super viewWillAppear:animated];
  131. self.originalNavigationControllerNavigationBarHidden = self.navigationController.navigationBarHidden;
  132. [self.navigationController setNavigationBarHidden:YES animated:NO];
  133. self.originalNavigationControllerNavigationBarShadowImage = self.navigationController.navigationBar.shadowImage;
  134. self.navigationController.navigationBar.shadowImage = nil;
  135. }
  136. - (void)viewDidAppear:(BOOL)animated
  137. {
  138. [super viewDidAppear:animated];
  139. self.originalNavigationControllerViewBackgroundColor = self.navigationController.view.backgroundColor;
  140. self.navigationController.view.backgroundColor = [UIColor blackColor];
  141. }
  142. - (void)viewWillDisappear:(BOOL)animated
  143. {
  144. [super viewWillDisappear:animated];
  145. [self.navigationController setNavigationBarHidden:self.originalNavigationControllerNavigationBarHidden animated:animated];
  146. self.navigationController.navigationBar.shadowImage = self.originalNavigationControllerNavigationBarShadowImage;
  147. self.navigationController.view.backgroundColor = self.originalNavigationControllerViewBackgroundColor;
  148. }
  149. - (void)viewWillLayoutSubviews
  150. {
  151. [super viewWillLayoutSubviews];
  152. [self updateMaskRect];
  153. [self layoutImageScrollView];
  154. [self layoutOverlayView];
  155. [self updateMaskPath];
  156. [self.view setNeedsUpdateConstraints];
  157. }
  158. - (void)viewDidLayoutSubviews
  159. {
  160. [super viewDidLayoutSubviews];
  161. if (!self.imageScrollView.zoomView) {
  162. [self displayImage];
  163. }
  164. }
  165. - (void)updateViewConstraints
  166. {
  167. [super updateViewConstraints];
  168. if (!self.didSetupConstraints) {
  169. // ---------------------------
  170. // The label "Move and Scale".
  171. // ---------------------------
  172. NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self.moveAndScaleLabel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual
  173. toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0f
  174. constant:0.0f];
  175. [self.view addConstraint:constraint];
  176. CGFloat constant = self.portraitMoveAndScaleLabelTopAndCropViewTopVerticalSpace;
  177. self.moveAndScaleLabelTopConstraint = [NSLayoutConstraint constraintWithItem:self.moveAndScaleLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
  178. toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0f
  179. constant:constant];
  180. [self.view addConstraint:self.moveAndScaleLabelTopConstraint];
  181. // --------------------
  182. // The button "Cancel".
  183. // --------------------
  184. constant = self.portraitCancelButtonLeadingAndCropViewLeadingHorizontalSpace;
  185. self.cancelButtonLeadingConstraint = [NSLayoutConstraint constraintWithItem:self.cancelButton attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual
  186. toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1.0f
  187. constant:constant];
  188. [self.view addConstraint:self.cancelButtonLeadingConstraint];
  189. constant = self.portraitCropViewBottomAndCancelButtonBottomVerticalSpace;
  190. self.cancelButtonBottomConstraint = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual
  191. toItem:self.cancelButton attribute:NSLayoutAttributeBottom multiplier:1.0f
  192. constant:constant];
  193. [self.view addConstraint:self.cancelButtonBottomConstraint];
  194. // --------------------
  195. // The button "Choose".
  196. // --------------------
  197. constant = self.portraitCropViewTrailingAndChooseButtonTrailingHorizontalSpace;
  198. self.chooseButtonTrailingConstraint = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual
  199. toItem:self.chooseButton attribute:NSLayoutAttributeTrailing multiplier:1.0f
  200. constant:constant];
  201. [self.view addConstraint:self.chooseButtonTrailingConstraint];
  202. constant = self.portraitCropViewBottomAndChooseButtonBottomVerticalSpace;
  203. self.chooseButtonBottomConstraint = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual
  204. toItem:self.chooseButton attribute:NSLayoutAttributeBottom multiplier:1.0f
  205. constant:constant];
  206. [self.view addConstraint:self.chooseButtonBottomConstraint];
  207. self.didSetupConstraints = YES;
  208. } else {
  209. if ([self isPortraitInterfaceOrientation]) {
  210. self.moveAndScaleLabelTopConstraint.constant = self.portraitMoveAndScaleLabelTopAndCropViewTopVerticalSpace;
  211. self.cancelButtonBottomConstraint.constant = self.portraitCropViewBottomAndCancelButtonBottomVerticalSpace;
  212. self.cancelButtonLeadingConstraint.constant = self.portraitCancelButtonLeadingAndCropViewLeadingHorizontalSpace;
  213. self.chooseButtonBottomConstraint.constant = self.portraitCropViewBottomAndChooseButtonBottomVerticalSpace;
  214. self.chooseButtonTrailingConstraint.constant = self.portraitCropViewTrailingAndChooseButtonTrailingHorizontalSpace;
  215. } else {
  216. self.moveAndScaleLabelTopConstraint.constant = self.landscapeMoveAndScaleLabelTopAndCropViewTopVerticalSpace;
  217. self.cancelButtonBottomConstraint.constant = self.landscapeCropViewBottomAndCancelButtonBottomVerticalSpace;
  218. self.cancelButtonLeadingConstraint.constant = self.landscapeCancelButtonLeadingAndCropViewLeadingHorizontalSpace;
  219. self.chooseButtonBottomConstraint.constant = self.landscapeCropViewBottomAndChooseButtonBottomVerticalSpace;
  220. self.chooseButtonTrailingConstraint.constant = self.landscapeCropViewTrailingAndChooseButtonTrailingHorizontalSpace;
  221. }
  222. }
  223. }
  224. #pragma mark - Custom Accessors
  225. - (RSKImageScrollView *)imageScrollView
  226. {
  227. if (!_imageScrollView) {
  228. _imageScrollView = [[RSKImageScrollView alloc] init];
  229. _imageScrollView.clipsToBounds = NO;
  230. _imageScrollView.aspectFill = self.avoidEmptySpaceAroundImage;
  231. _imageScrollView.alwaysBounceHorizontal = self.alwaysBounceHorizontal;
  232. _imageScrollView.alwaysBounceVertical = self.alwaysBounceVertical;
  233. }
  234. return _imageScrollView;
  235. }
  236. - (RSKTouchView *)overlayView
  237. {
  238. if (!_overlayView) {
  239. _overlayView = [[RSKTouchView alloc] init];
  240. _overlayView.receiver = self.imageScrollView;
  241. [_overlayView.layer addSublayer:self.maskLayer];
  242. }
  243. return _overlayView;
  244. }
  245. - (CAShapeLayer *)maskLayer
  246. {
  247. if (!_maskLayer) {
  248. _maskLayer = [CAShapeLayer layer];
  249. _maskLayer.fillRule = kCAFillRuleEvenOdd;
  250. _maskLayer.fillColor = self.maskLayerColor.CGColor;
  251. _maskLayer.lineWidth = self.maskLayerLineWidth;
  252. _maskLayer.strokeColor = self.maskLayerStrokeColor.CGColor;
  253. }
  254. return _maskLayer;
  255. }
  256. - (UIColor *)maskLayerColor
  257. {
  258. if (!_maskLayerColor) {
  259. _maskLayerColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.7f];
  260. }
  261. return _maskLayerColor;
  262. }
  263. - (UILabel *)moveAndScaleLabel
  264. {
  265. if (!_moveAndScaleLabel) {
  266. _moveAndScaleLabel = [[UILabel alloc] init];
  267. _moveAndScaleLabel.translatesAutoresizingMaskIntoConstraints = NO;
  268. _moveAndScaleLabel.backgroundColor = [UIColor clearColor];
  269. _moveAndScaleLabel.text = RSKLocalizedString(@"Move and Scale", @"Move and Scale label");
  270. _moveAndScaleLabel.textColor = [UIColor whiteColor];
  271. _moveAndScaleLabel.opaque = NO;
  272. }
  273. return _moveAndScaleLabel;
  274. }
  275. - (UIButton *)cancelButton
  276. {
  277. if (!_cancelButton) {
  278. _cancelButton = [[UIButton alloc] init];
  279. _cancelButton.translatesAutoresizingMaskIntoConstraints = NO;
  280. [_cancelButton setTitle:RSKLocalizedString(@"Cancel", @"Cancel button") forState:UIControlStateNormal];
  281. [_cancelButton addTarget:self action:@selector(onCancelButtonTouch:) forControlEvents:UIControlEventTouchUpInside];
  282. _cancelButton.opaque = NO;
  283. }
  284. return _cancelButton;
  285. }
  286. - (UIButton *)chooseButton
  287. {
  288. if (!_chooseButton) {
  289. _chooseButton = [[UIButton alloc] init];
  290. _chooseButton.translatesAutoresizingMaskIntoConstraints = NO;
  291. [_chooseButton setTitle:RSKLocalizedString(@"Choose", @"Choose button") forState:UIControlStateNormal];
  292. [_chooseButton addTarget:self action:@selector(onChooseButtonTouch:) forControlEvents:UIControlEventTouchUpInside];
  293. _chooseButton.opaque = NO;
  294. }
  295. return _chooseButton;
  296. }
  297. - (UITapGestureRecognizer *)doubleTapGestureRecognizer
  298. {
  299. if (!_doubleTapGestureRecognizer) {
  300. _doubleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)];
  301. _doubleTapGestureRecognizer.delaysTouchesEnded = NO;
  302. _doubleTapGestureRecognizer.numberOfTapsRequired = 2;
  303. _doubleTapGestureRecognizer.delegate = self;
  304. }
  305. return _doubleTapGestureRecognizer;
  306. }
  307. - (UIRotationGestureRecognizer *)rotationGestureRecognizer
  308. {
  309. if (!_rotationGestureRecognizer) {
  310. _rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotation:)];
  311. _rotationGestureRecognizer.delaysTouchesEnded = NO;
  312. _rotationGestureRecognizer.delegate = self;
  313. _rotationGestureRecognizer.enabled = self.isRotationEnabled;
  314. }
  315. return _rotationGestureRecognizer;
  316. }
  317. - (CGRect)imageRect
  318. {
  319. float zoomScale = 1.0 / self.imageScrollView.zoomScale;
  320. CGRect imageRect = CGRectZero;
  321. imageRect.origin.x = self.imageScrollView.contentOffset.x * zoomScale;
  322. imageRect.origin.y = self.imageScrollView.contentOffset.y * zoomScale;
  323. imageRect.size.width = CGRectGetWidth(self.imageScrollView.bounds) * zoomScale;
  324. imageRect.size.height = CGRectGetHeight(self.imageScrollView.bounds) * zoomScale;
  325. imageRect = RSKRectNormalize(imageRect);
  326. CGSize imageSize = self.originalImage.size;
  327. CGFloat x = CGRectGetMinX(imageRect);
  328. CGFloat y = CGRectGetMinY(imageRect);
  329. CGFloat width = CGRectGetWidth(imageRect);
  330. CGFloat height = CGRectGetHeight(imageRect);
  331. UIImageOrientation imageOrientation = self.originalImage.imageOrientation;
  332. if (imageOrientation == UIImageOrientationRight || imageOrientation == UIImageOrientationRightMirrored) {
  333. imageRect.origin.x = y;
  334. imageRect.origin.y = floor(imageSize.width - CGRectGetWidth(imageRect) - x);
  335. imageRect.size.width = height;
  336. imageRect.size.height = width;
  337. } else if (imageOrientation == UIImageOrientationLeft || imageOrientation == UIImageOrientationLeftMirrored) {
  338. imageRect.origin.x = floor(imageSize.height - CGRectGetHeight(imageRect) - y);
  339. imageRect.origin.y = x;
  340. imageRect.size.width = height;
  341. imageRect.size.height = width;
  342. } else if (imageOrientation == UIImageOrientationDown || imageOrientation == UIImageOrientationDownMirrored) {
  343. imageRect.origin.x = floor(imageSize.width - CGRectGetWidth(imageRect) - x);
  344. imageRect.origin.y = floor(imageSize.height - CGRectGetHeight(imageRect) - y);
  345. }
  346. CGFloat imageScale = self.originalImage.scale;
  347. imageRect = CGRectApplyAffineTransform(imageRect, CGAffineTransformMakeScale(imageScale, imageScale));
  348. return imageRect;
  349. }
  350. - (CGRect)cropRect
  351. {
  352. CGRect maskRect = self.maskRect;
  353. CGFloat rotationAngle = self.rotationAngle;
  354. CGRect rotatedImageScrollViewFrame = self.imageScrollView.frame;
  355. float zoomScale = 1.0 / self.imageScrollView.zoomScale;
  356. CGAffineTransform imageScrollViewTransform = self.imageScrollView.transform;
  357. self.imageScrollView.transform = CGAffineTransformIdentity;
  358. CGPoint imageScrollViewContentOffset = self.imageScrollView.contentOffset;
  359. CGRect imageScrollViewFrame = self.imageScrollView.frame;
  360. self.imageScrollView.frame = self.maskRect;
  361. CGRect imageFrame = CGRectZero;
  362. imageFrame.origin.x = CGRectGetMinX(maskRect) - self.imageScrollView.contentOffset.x;
  363. imageFrame.origin.y = CGRectGetMinY(maskRect) - self.imageScrollView.contentOffset.y;
  364. imageFrame.size = self.imageScrollView.contentSize;
  365. CGFloat tx = CGRectGetMinX(imageFrame) + self.imageScrollView.contentOffset.x + CGRectGetWidth(maskRect) * 0.5f;
  366. CGFloat ty = CGRectGetMinY(imageFrame) + self.imageScrollView.contentOffset.y + CGRectGetHeight(maskRect) * 0.5f;
  367. CGFloat sx = CGRectGetWidth(rotatedImageScrollViewFrame) / CGRectGetWidth(imageScrollViewFrame);
  368. CGFloat sy = CGRectGetHeight(rotatedImageScrollViewFrame) / CGRectGetHeight(imageScrollViewFrame);
  369. CGAffineTransform t1 = CGAffineTransformMakeTranslation(-tx, -ty);
  370. CGAffineTransform t2 = CGAffineTransformMakeRotation(rotationAngle);
  371. CGAffineTransform t3 = CGAffineTransformMakeScale(sx, sy);
  372. CGAffineTransform t4 = CGAffineTransformMakeTranslation(tx, ty);
  373. CGAffineTransform t1t2 = CGAffineTransformConcat(t1, t2);
  374. CGAffineTransform t1t2t3 = CGAffineTransformConcat(t1t2, t3);
  375. CGAffineTransform t1t2t3t4 = CGAffineTransformConcat(t1t2t3, t4);
  376. imageFrame = CGRectApplyAffineTransform(imageFrame, t1t2t3t4);
  377. CGRect cropRect = CGRectMake(0.0, 0.0, CGRectGetWidth(maskRect), CGRectGetHeight(maskRect));
  378. cropRect.origin.x = -CGRectGetMinX(imageFrame) + CGRectGetMinX(maskRect);
  379. cropRect.origin.y = -CGRectGetMinY(imageFrame) + CGRectGetMinY(maskRect);
  380. cropRect = CGRectApplyAffineTransform(cropRect, CGAffineTransformMakeScale(zoomScale, zoomScale));
  381. cropRect = RSKRectNormalize(cropRect);
  382. CGFloat imageScale = self.originalImage.scale;
  383. cropRect = CGRectApplyAffineTransform(cropRect, CGAffineTransformMakeScale(imageScale, imageScale));
  384. self.imageScrollView.frame = imageScrollViewFrame;
  385. self.imageScrollView.contentOffset = imageScrollViewContentOffset;
  386. self.imageScrollView.transform = imageScrollViewTransform;
  387. return cropRect;
  388. }
  389. - (CGRect)rectForClipPath
  390. {
  391. if (!self.maskLayerStrokeColor) {
  392. return self.overlayView.frame;
  393. } else {
  394. CGFloat maskLayerLineHalfWidth = self.maskLayerLineWidth / 2.0;
  395. return CGRectInset(self.overlayView.frame, -maskLayerLineHalfWidth, -maskLayerLineHalfWidth);
  396. }
  397. }
  398. - (CGRect)rectForMaskPath
  399. {
  400. if (!self.maskLayerStrokeColor) {
  401. return self.maskRect;
  402. } else {
  403. CGFloat maskLayerLineHalfWidth = self.maskLayerLineWidth / 2.0;
  404. return CGRectInset(self.maskRect, maskLayerLineHalfWidth, maskLayerLineHalfWidth);
  405. }
  406. }
  407. - (CGFloat)rotationAngle
  408. {
  409. CGAffineTransform transform = self.imageScrollView.transform;
  410. CGFloat rotationAngle = atan2(transform.b, transform.a);
  411. return rotationAngle;
  412. }
  413. - (CGFloat)zoomScale
  414. {
  415. return self.imageScrollView.zoomScale;
  416. }
  417. - (void)setAvoidEmptySpaceAroundImage:(BOOL)avoidEmptySpaceAroundImage
  418. {
  419. if (_avoidEmptySpaceAroundImage != avoidEmptySpaceAroundImage) {
  420. _avoidEmptySpaceAroundImage = avoidEmptySpaceAroundImage;
  421. self.imageScrollView.aspectFill = avoidEmptySpaceAroundImage;
  422. }
  423. }
  424. - (void)setAlwaysBounceVertical:(BOOL)alwaysBounceVertical
  425. {
  426. if (_alwaysBounceVertical != alwaysBounceVertical) {
  427. _alwaysBounceVertical = alwaysBounceVertical;
  428. self.imageScrollView.alwaysBounceVertical = alwaysBounceVertical;
  429. }
  430. }
  431. - (void)setAlwaysBounceHorizontal:(BOOL)alwaysBounceHorizontal
  432. {
  433. if (_alwaysBounceHorizontal != alwaysBounceHorizontal) {
  434. _alwaysBounceHorizontal = alwaysBounceHorizontal;
  435. self.imageScrollView.alwaysBounceHorizontal = alwaysBounceHorizontal;
  436. }
  437. }
  438. - (void)setCropMode:(RSKImageCropMode)cropMode
  439. {
  440. if (_cropMode != cropMode) {
  441. _cropMode = cropMode;
  442. if (self.imageScrollView.zoomView) {
  443. [self reset:NO];
  444. }
  445. }
  446. }
  447. - (void)setOriginalImage:(UIImage *)originalImage
  448. {
  449. if (![_originalImage isEqual:originalImage]) {
  450. _originalImage = originalImage;
  451. if (self.isViewLoaded && self.view.window) {
  452. [self displayImage];
  453. }
  454. }
  455. }
  456. - (void)setMaskPath:(UIBezierPath *)maskPath
  457. {
  458. if (![_maskPath isEqual:maskPath]) {
  459. _maskPath = maskPath;
  460. UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:self.rectForClipPath];
  461. [clipPath appendPath:maskPath];
  462. clipPath.usesEvenOddFillRule = YES;
  463. CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
  464. pathAnimation.duration = [CATransaction animationDuration];
  465. pathAnimation.timingFunction = [CATransaction animationTimingFunction];
  466. [self.maskLayer addAnimation:pathAnimation forKey:@"path"];
  467. self.maskLayer.path = [clipPath CGPath];
  468. }
  469. }
  470. - (void)setRotationAngle:(CGFloat)rotationAngle
  471. {
  472. if (self.rotationAngle != rotationAngle) {
  473. CGFloat rotation = (rotationAngle - self.rotationAngle);
  474. CGAffineTransform transform = CGAffineTransformRotate(self.imageScrollView.transform, rotation);
  475. self.imageScrollView.transform = transform;
  476. [self layoutImageScrollView];
  477. }
  478. }
  479. - (void)setRotationEnabled:(BOOL)rotationEnabled
  480. {
  481. if (_rotationEnabled != rotationEnabled) {
  482. _rotationEnabled = rotationEnabled;
  483. self.rotationGestureRecognizer.enabled = rotationEnabled;
  484. }
  485. }
  486. - (void)setZoomScale:(CGFloat)zoomScale
  487. {
  488. self.imageScrollView.zoomScale = zoomScale;
  489. }
  490. #pragma mark - Action handling
  491. - (void)onCancelButtonTouch:(UIBarButtonItem *)sender
  492. {
  493. [self cancelCrop];
  494. }
  495. - (void)onChooseButtonTouch:(UIBarButtonItem *)sender
  496. {
  497. [self cropImage];
  498. }
  499. - (void)handleDoubleTap:(UITapGestureRecognizer *)gestureRecognizer
  500. {
  501. [self reset:YES];
  502. }
  503. - (void)handleRotation:(UIRotationGestureRecognizer *)gestureRecognizer
  504. {
  505. CGFloat rotation = gestureRecognizer.rotation;
  506. CGAffineTransform transform = CGAffineTransformRotate(self.imageScrollView.transform, rotation);
  507. self.imageScrollView.transform = transform;
  508. gestureRecognizer.rotation = 0;
  509. if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
  510. [UIView animateWithDuration:kLayoutImageScrollViewAnimationDuration
  511. delay:0.0
  512. options:UIViewAnimationOptionBeginFromCurrentState
  513. animations:^{
  514. [self layoutImageScrollView];
  515. }
  516. completion:nil];
  517. }
  518. }
  519. - (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
  520. {
  521. [self.imageScrollView zoomToRect:rect animated:animated];
  522. }
  523. #pragma mark - Public
  524. - (BOOL)isPortraitInterfaceOrientation
  525. {
  526. return CGRectGetHeight(self.view.bounds) > CGRectGetWidth(self.view.bounds);
  527. }
  528. #pragma mark - Private
  529. - (void)reset:(BOOL)animated
  530. {
  531. if (animated) {
  532. [UIView beginAnimations:@"rsk_reset" context:NULL];
  533. [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
  534. [UIView setAnimationDuration:kResetAnimationDuration];
  535. [UIView setAnimationBeginsFromCurrentState:YES];
  536. }
  537. [self resetRotation];
  538. [self resetZoomScale];
  539. [self resetContentOffset];
  540. if (animated) {
  541. [UIView commitAnimations];
  542. }
  543. }
  544. - (void)resetContentOffset
  545. {
  546. CGSize boundsSize = self.imageScrollView.bounds.size;
  547. CGRect frameToCenter = self.imageScrollView.zoomView.frame;
  548. CGPoint contentOffset;
  549. if (CGRectGetWidth(frameToCenter) > boundsSize.width) {
  550. contentOffset.x = (CGRectGetWidth(frameToCenter) - boundsSize.width) * 0.5f;
  551. } else {
  552. contentOffset.x = 0;
  553. }
  554. if (CGRectGetHeight(frameToCenter) > boundsSize.height) {
  555. contentOffset.y = (CGRectGetHeight(frameToCenter) - boundsSize.height) * 0.5f;
  556. } else {
  557. contentOffset.y = 0;
  558. }
  559. self.imageScrollView.contentOffset = contentOffset;
  560. }
  561. - (void)resetRotation
  562. {
  563. [self setRotationAngle:0.0];
  564. }
  565. - (void)resetZoomScale
  566. {
  567. CGFloat zoomScale;
  568. if (CGRectGetWidth(self.view.bounds) > CGRectGetHeight(self.view.bounds)) {
  569. zoomScale = CGRectGetHeight(self.view.bounds) / self.originalImage.size.height;
  570. } else {
  571. zoomScale = CGRectGetWidth(self.view.bounds) / self.originalImage.size.width;
  572. }
  573. self.imageScrollView.zoomScale = zoomScale;
  574. }
  575. - (NSArray *)intersectionPointsOfLineSegment:(RSKLineSegment)lineSegment withRect:(CGRect)rect
  576. {
  577. RSKLineSegment top = RSKLineSegmentMake(CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect)),
  578. CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect)));
  579. RSKLineSegment right = RSKLineSegmentMake(CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect)),
  580. CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect)));
  581. RSKLineSegment bottom = RSKLineSegmentMake(CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect)),
  582. CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect)));
  583. RSKLineSegment left = RSKLineSegmentMake(CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect)),
  584. CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect)));
  585. CGPoint p0 = RSKLineSegmentIntersection(top, lineSegment);
  586. CGPoint p1 = RSKLineSegmentIntersection(right, lineSegment);
  587. CGPoint p2 = RSKLineSegmentIntersection(bottom, lineSegment);
  588. CGPoint p3 = RSKLineSegmentIntersection(left, lineSegment);
  589. NSMutableArray *intersectionPoints = [@[] mutableCopy];
  590. if (!RSKPointIsNull(p0)) {
  591. [intersectionPoints addObject:[NSValue valueWithCGPoint:p0]];
  592. }
  593. if (!RSKPointIsNull(p1)) {
  594. [intersectionPoints addObject:[NSValue valueWithCGPoint:p1]];
  595. }
  596. if (!RSKPointIsNull(p2)) {
  597. [intersectionPoints addObject:[NSValue valueWithCGPoint:p2]];
  598. }
  599. if (!RSKPointIsNull(p3)) {
  600. [intersectionPoints addObject:[NSValue valueWithCGPoint:p3]];
  601. }
  602. return [intersectionPoints copy];
  603. }
  604. - (void)displayImage
  605. {
  606. if (self.originalImage) {
  607. [self.imageScrollView displayImage:self.originalImage];
  608. [self reset:NO];
  609. if ([self.delegate respondsToSelector:@selector(imageCropViewControllerDidDisplayImage:)]) {
  610. [self.delegate imageCropViewControllerDidDisplayImage:self];
  611. }
  612. }
  613. }
  614. - (void)centerImageScrollViewZoomView
  615. {
  616. // center imageScrollView.zoomView as it becomes smaller than the size of the screen
  617. CGPoint contentOffset = self.imageScrollView.contentOffset;
  618. // center vertically
  619. if (self.imageScrollView.contentSize.height < CGRectGetHeight(self.imageScrollView.bounds)) {
  620. contentOffset.y = -(CGRectGetHeight(self.imageScrollView.bounds) - self.imageScrollView.contentSize.height) * 0.5f;
  621. }
  622. // center horizontally
  623. if (self.imageScrollView.contentSize.width < CGRectGetWidth(self.imageScrollView.bounds)) {
  624. contentOffset.x = -(CGRectGetWidth(self.imageScrollView.bounds) - self.imageScrollView.contentSize.width) * 0.5f;;
  625. }
  626. self.imageScrollView.contentOffset = contentOffset;
  627. }
  628. - (void)layoutImageScrollView
  629. {
  630. CGRect frame = CGRectZero;
  631. // The bounds of the image scroll view should always fill the mask area.
  632. switch (self.cropMode) {
  633. case RSKImageCropModeSquare: {
  634. if (self.rotationAngle == 0.0) {
  635. frame = self.maskRect;
  636. } else {
  637. // Step 1: Rotate the left edge of the initial rect of the image scroll view clockwise around the center by `rotationAngle`.
  638. CGRect initialRect = self.maskRect;
  639. CGFloat rotationAngle = self.rotationAngle;
  640. CGPoint leftTopPoint = CGPointMake(initialRect.origin.x, initialRect.origin.y);
  641. CGPoint leftBottomPoint = CGPointMake(initialRect.origin.x, initialRect.origin.y + initialRect.size.height);
  642. RSKLineSegment leftLineSegment = RSKLineSegmentMake(leftTopPoint, leftBottomPoint);
  643. CGPoint pivot = RSKRectCenterPoint(initialRect);
  644. CGFloat alpha = fabs(rotationAngle);
  645. RSKLineSegment rotatedLeftLineSegment = RSKLineSegmentRotateAroundPoint(leftLineSegment, pivot, alpha);
  646. // Step 2: Find the points of intersection of the rotated edge with the initial rect.
  647. NSArray *points = [self intersectionPointsOfLineSegment:rotatedLeftLineSegment withRect:initialRect];
  648. // Step 3: If the number of intersection points more than one
  649. // then the bounds of the rotated image scroll view does not completely fill the mask area.
  650. // Therefore, we need to update the frame of the image scroll view.
  651. // Otherwise, we can use the initial rect.
  652. if (points.count > 1) {
  653. // We have a right triangle.
  654. // Step 4: Calculate the altitude of the right triangle.
  655. if ((alpha > M_PI_2) && (alpha < M_PI)) {
  656. alpha = alpha - M_PI_2;
  657. } else if ((alpha > (M_PI + M_PI_2)) && (alpha < (M_PI + M_PI))) {
  658. alpha = alpha - (M_PI + M_PI_2);
  659. }
  660. CGFloat sinAlpha = sin(alpha);
  661. CGFloat cosAlpha = cos(alpha);
  662. CGFloat hypotenuse = RSKPointDistance([points[0] CGPointValue], [points[1] CGPointValue]);
  663. CGFloat altitude = hypotenuse * sinAlpha * cosAlpha;
  664. // Step 5: Calculate the target width.
  665. CGFloat initialWidth = CGRectGetWidth(initialRect);
  666. CGFloat targetWidth = initialWidth + altitude * 2;
  667. // Step 6: Calculate the target frame.
  668. CGFloat scale = targetWidth / initialWidth;
  669. CGPoint center = RSKRectCenterPoint(initialRect);
  670. frame = RSKRectScaleAroundPoint(initialRect, center, scale, scale);
  671. // Step 7: Avoid floats.
  672. frame.origin.x = floor(CGRectGetMinX(frame));
  673. frame.origin.y = floor(CGRectGetMinY(frame));
  674. frame = CGRectIntegral(frame);
  675. } else {
  676. // Step 4: Use the initial rect.
  677. frame = initialRect;
  678. }
  679. }
  680. break;
  681. }
  682. case RSKImageCropModeCircle: {
  683. frame = self.maskRect;
  684. break;
  685. }
  686. case RSKImageCropModeCustom: {
  687. frame = [self.dataSource imageCropViewControllerCustomMovementRect:self];
  688. break;
  689. }
  690. }
  691. CGAffineTransform transform = self.imageScrollView.transform;
  692. self.imageScrollView.transform = CGAffineTransformIdentity;
  693. self.imageScrollView.frame = frame;
  694. [self centerImageScrollViewZoomView];
  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