RSKImageCropViewController.m 43 KB

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