RSKImageCropViewController.m 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110
  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.image) {
  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.image) {
  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. CAAnimation *animation = (CAAnimation *)[self.overlayView actionForLayer:self.overlayView.layer forKey:@"backgroundColor"];
  479. if ([animation isKindOfClass:[CAAnimation class]]) {
  480. CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
  481. pathAnimation.duration = animation.duration;
  482. pathAnimation.timingFunction = animation.timingFunction;
  483. [self.maskLayer addAnimation:pathAnimation forKey:@"path"];
  484. }
  485. self.maskLayer.path = [clipPath CGPath];
  486. }
  487. }
  488. - (void)setRotationAngle:(CGFloat)rotationAngle
  489. {
  490. if (self.rotationAngle != rotationAngle) {
  491. CGFloat rotation = (rotationAngle - self.rotationAngle);
  492. CGAffineTransform transform = CGAffineTransformRotate(self.imageScrollView.transform, rotation);
  493. self.imageScrollView.transform = transform;
  494. [self layoutImageScrollView];
  495. }
  496. }
  497. - (void)setRotationEnabled:(BOOL)rotationEnabled
  498. {
  499. if (_rotationEnabled != rotationEnabled) {
  500. _rotationEnabled = rotationEnabled;
  501. self.rotationGestureRecognizer.enabled = rotationEnabled;
  502. }
  503. }
  504. - (void)setZoomScale:(CGFloat)zoomScale
  505. {
  506. self.imageScrollView.zoomScale = zoomScale;
  507. }
  508. #pragma mark - Action handling
  509. - (void)onCancelButtonTouch:(UIBarButtonItem *)sender
  510. {
  511. [self cancelCrop];
  512. }
  513. - (void)onChooseButtonTouch:(UIBarButtonItem *)sender
  514. {
  515. [self cropImage];
  516. }
  517. - (void)handleDoubleTap:(UITapGestureRecognizer *)gestureRecognizer
  518. {
  519. if (self.imageScrollView.zoomScale == self.zoomScaleDefaultValue) {
  520. [self.imageScrollView zoomToLocation:[gestureRecognizer locationInView:self.imageScrollView] animated:YES];
  521. } else {
  522. [self reset:YES];
  523. }
  524. }
  525. - (void)handleRotation:(UIRotationGestureRecognizer *)gestureRecognizer
  526. {
  527. CGFloat rotation = gestureRecognizer.rotation;
  528. CGAffineTransform transform = CGAffineTransformRotate(self.imageScrollView.transform, rotation);
  529. self.imageScrollView.transform = transform;
  530. gestureRecognizer.rotation = 0;
  531. if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
  532. [UIView animateWithDuration:kLayoutImageScrollViewAnimationDuration
  533. delay:0.0
  534. options:UIViewAnimationOptionBeginFromCurrentState
  535. animations:^{
  536. [self layoutImageScrollView];
  537. }
  538. completion:nil];
  539. }
  540. }
  541. - (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
  542. {
  543. rect = [self.imageScrollView convertRect:rect fromView:self.view];
  544. rect = [self.imageScrollView convertRect:rect toCoordinateSpace:self.imageScrollView.imageViewCoordinateSpace];
  545. [self.imageScrollView zoomToRect:rect animated:animated];
  546. }
  547. #pragma mark - Public
  548. - (BOOL)isPortraitInterfaceOrientation
  549. {
  550. return CGRectGetHeight(self.view.bounds) > CGRectGetWidth(self.view.bounds);
  551. }
  552. #pragma mark - Private
  553. - (void)reset:(BOOL)animated
  554. {
  555. if (animated) {
  556. [UIView beginAnimations:@"rsk_reset" context:NULL];
  557. [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
  558. [UIView setAnimationDuration:kResetAnimationDuration];
  559. [UIView setAnimationBeginsFromCurrentState:YES];
  560. }
  561. [self resetRotation];
  562. [self resetZoomScale];
  563. [self resetContentOffset];
  564. [self centerImage];
  565. if (animated) {
  566. [UIView commitAnimations];
  567. }
  568. }
  569. - (void)resetContentOffset
  570. {
  571. CGSize boundsSize = self.imageScrollView.bounds.size;
  572. CGRect frameToCenter = self.imageScrollView.imageViewFrame;
  573. CGPoint contentOffset;
  574. if (CGRectGetWidth(frameToCenter) > boundsSize.width) {
  575. contentOffset.x = (CGRectGetWidth(frameToCenter) - boundsSize.width) * 0.5f;
  576. } else {
  577. contentOffset.x = 0;
  578. }
  579. if (CGRectGetHeight(frameToCenter) > boundsSize.height) {
  580. contentOffset.y = (CGRectGetHeight(frameToCenter) - boundsSize.height) * 0.5f;
  581. } else {
  582. contentOffset.y = 0;
  583. }
  584. self.imageScrollView.contentOffset = contentOffset;
  585. }
  586. - (void)resetRotation
  587. {
  588. [self setRotationAngle:0.0];
  589. }
  590. - (void)resetZoomScale
  591. {
  592. self.imageScrollView.zoomScale = self.zoomScaleDefaultValue;
  593. }
  594. - (CGFloat)zoomScaleDefaultValue
  595. {
  596. CGFloat zoomScale;
  597. if (CGRectGetWidth(self.view.bounds) > CGRectGetHeight(self.view.bounds)) {
  598. zoomScale = CGRectGetHeight(self.view.bounds) / self.originalImage.size.height;
  599. } else {
  600. zoomScale = CGRectGetWidth(self.view.bounds) / self.originalImage.size.width;
  601. }
  602. return zoomScale;
  603. }
  604. - (NSArray *)intersectionPointsOfLineSegment:(RSKLineSegment)lineSegment withRect:(CGRect)rect
  605. {
  606. RSKLineSegment top = RSKLineSegmentMake(CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect)),
  607. CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect)));
  608. RSKLineSegment right = RSKLineSegmentMake(CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect)),
  609. CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect)));
  610. RSKLineSegment bottom = RSKLineSegmentMake(CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect)),
  611. CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect)));
  612. RSKLineSegment left = RSKLineSegmentMake(CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect)),
  613. CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect)));
  614. CGPoint p0 = RSKLineSegmentIntersection(top, lineSegment);
  615. CGPoint p1 = RSKLineSegmentIntersection(right, lineSegment);
  616. CGPoint p2 = RSKLineSegmentIntersection(bottom, lineSegment);
  617. CGPoint p3 = RSKLineSegmentIntersection(left, lineSegment);
  618. NSMutableArray *intersectionPoints = [@[] mutableCopy];
  619. if (!RSKPointIsNull(p0)) {
  620. [intersectionPoints addObject:[NSValue valueWithCGPoint:p0]];
  621. }
  622. if (!RSKPointIsNull(p1)) {
  623. [intersectionPoints addObject:[NSValue valueWithCGPoint:p1]];
  624. }
  625. if (!RSKPointIsNull(p2)) {
  626. [intersectionPoints addObject:[NSValue valueWithCGPoint:p2]];
  627. }
  628. if (!RSKPointIsNull(p3)) {
  629. [intersectionPoints addObject:[NSValue valueWithCGPoint:p3]];
  630. }
  631. return [intersectionPoints copy];
  632. }
  633. - (void)displayImage
  634. {
  635. if (self.originalImage) {
  636. self.imageScrollView.image = self.originalImage;
  637. [self reset:NO];
  638. if ([self.delegate respondsToSelector:@selector(imageCropViewControllerDidDisplayImage:)]) {
  639. [self.delegate imageCropViewControllerDidDisplayImage:self];
  640. }
  641. }
  642. }
  643. - (void)centerImage
  644. {
  645. // center the image view of the imageScrollView as it becomes smaller than the size of the imageScrollView
  646. CGPoint contentOffset = self.imageScrollView.contentOffset;
  647. // center vertically
  648. if (self.imageScrollView.contentSize.height < CGRectGetHeight(self.imageScrollView.bounds)) {
  649. contentOffset.y = -(CGRectGetHeight(self.imageScrollView.bounds) - self.imageScrollView.contentSize.height) * 0.5f;
  650. }
  651. // center horizontally
  652. if (self.imageScrollView.contentSize.width < CGRectGetWidth(self.imageScrollView.bounds)) {
  653. contentOffset.x = -(CGRectGetWidth(self.imageScrollView.bounds) - self.imageScrollView.contentSize.width) * 0.5f;;
  654. }
  655. self.imageScrollView.contentOffset = contentOffset;
  656. }
  657. - (void)layoutImageScrollView
  658. {
  659. CGRect frame = CGRectZero;
  660. // The bounds of the image scroll view should always fill the mask area.
  661. switch (self.cropMode) {
  662. case RSKImageCropModeSquare: {
  663. if (self.rotationAngle == 0.0) {
  664. frame = self.maskRect;
  665. } else {
  666. // Step 1: Rotate the left edge of the initial rect of the image scroll view clockwise around the center by `rotationAngle`.
  667. CGRect initialRect = self.maskRect;
  668. CGFloat rotationAngle = self.rotationAngle;
  669. CGPoint leftTopPoint = CGPointMake(initialRect.origin.x, initialRect.origin.y);
  670. CGPoint leftBottomPoint = CGPointMake(initialRect.origin.x, initialRect.origin.y + initialRect.size.height);
  671. RSKLineSegment leftLineSegment = RSKLineSegmentMake(leftTopPoint, leftBottomPoint);
  672. CGPoint pivot = RSKRectCenterPoint(initialRect);
  673. CGFloat alpha = fabs(rotationAngle);
  674. RSKLineSegment rotatedLeftLineSegment = RSKLineSegmentRotateAroundPoint(leftLineSegment, pivot, alpha);
  675. // Step 2: Find the points of intersection of the rotated edge with the initial rect.
  676. NSArray *points = [self intersectionPointsOfLineSegment:rotatedLeftLineSegment withRect:initialRect];
  677. // Step 3: If the number of intersection points more than one
  678. // then the bounds of the rotated image scroll view does not completely fill the mask area.
  679. // Therefore, we need to update the frame of the image scroll view.
  680. // Otherwise, we can use the initial rect.
  681. if (points.count > 1) {
  682. // We have a right triangle.
  683. // Step 4: Calculate the altitude of the right triangle.
  684. if ((alpha > M_PI_2) && (alpha < M_PI)) {
  685. alpha = alpha - M_PI_2;
  686. } else if ((alpha > (M_PI + M_PI_2)) && (alpha < (M_PI + M_PI))) {
  687. alpha = alpha - (M_PI + M_PI_2);
  688. }
  689. CGFloat sinAlpha = sin(alpha);
  690. CGFloat cosAlpha = cos(alpha);
  691. CGFloat hypotenuse = RSKPointDistance([points[0] CGPointValue], [points[1] CGPointValue]);
  692. CGFloat altitude = hypotenuse * sinAlpha * cosAlpha;
  693. // Step 5: Calculate the target width.
  694. CGFloat initialWidth = CGRectGetWidth(initialRect);
  695. CGFloat targetWidth = initialWidth + altitude * 2;
  696. // Step 6: Calculate the target frame.
  697. CGFloat scale = targetWidth / initialWidth;
  698. CGPoint center = RSKRectCenterPoint(initialRect);
  699. frame = RSKRectScaleAroundPoint(initialRect, center, scale, scale);
  700. // Step 7: Avoid floats.
  701. frame.origin.x = floor(CGRectGetMinX(frame));
  702. frame.origin.y = floor(CGRectGetMinY(frame));
  703. frame = CGRectIntegral(frame);
  704. } else {
  705. // Step 4: Use the initial rect.
  706. frame = initialRect;
  707. }
  708. }
  709. break;
  710. }
  711. case RSKImageCropModeCircle: {
  712. frame = self.maskRect;
  713. break;
  714. }
  715. case RSKImageCropModeCustom: {
  716. frame = [self.dataSource imageCropViewControllerCustomMovementRect:self];
  717. break;
  718. }
  719. }
  720. CGAffineTransform transform = self.imageScrollView.transform;
  721. self.imageScrollView.transform = CGAffineTransformIdentity;
  722. self.imageScrollView.frame = frame;
  723. [self centerImage];
  724. self.imageScrollView.transform = transform;
  725. }
  726. - (void)layoutOverlayView
  727. {
  728. CGRect frame = CGRectMake(0, 0, CGRectGetWidth(self.view.bounds) * 2, CGRectGetHeight(self.view.bounds) * 2);
  729. self.overlayView.frame = frame;
  730. }
  731. - (void)updateMaskRect
  732. {
  733. switch (self.cropMode) {
  734. case RSKImageCropModeCircle: {
  735. CGFloat viewWidth = CGRectGetWidth(self.view.bounds);
  736. CGFloat viewHeight = CGRectGetHeight(self.view.bounds);
  737. CGFloat diameter;
  738. if ([self isPortraitInterfaceOrientation]) {
  739. diameter = MIN(viewWidth, viewHeight) - self.portraitCircleMaskRectInnerEdgeInset * 2;
  740. } else {
  741. diameter = MIN(viewWidth, viewHeight) - self.landscapeCircleMaskRectInnerEdgeInset * 2;
  742. }
  743. CGSize maskSize = CGSizeMake(diameter, diameter);
  744. CGRect maskRect = CGRectMake(floor((viewWidth - maskSize.width) * 0.5f),
  745. floor((viewHeight - maskSize.height) * 0.5f),
  746. maskSize.width,
  747. maskSize.height);
  748. self.maskRect = CGRectIntegral(maskRect);
  749. break;
  750. }
  751. case RSKImageCropModeSquare: {
  752. CGFloat viewWidth = CGRectGetWidth(self.view.bounds);
  753. CGFloat viewHeight = CGRectGetHeight(self.view.bounds);
  754. CGFloat length;
  755. if ([self isPortraitInterfaceOrientation]) {
  756. length = MIN(viewWidth, viewHeight) - self.portraitSquareMaskRectInnerEdgeInset * 2;
  757. } else {
  758. length = MIN(viewWidth, viewHeight) - self.landscapeSquareMaskRectInnerEdgeInset * 2;
  759. }
  760. CGSize maskSize = CGSizeMake(length, length);
  761. CGRect maskRect = CGRectMake(floor((viewWidth - maskSize.width) * 0.5f),
  762. floor((viewHeight - maskSize.height) * 0.5f),
  763. maskSize.width,
  764. maskSize.height);
  765. self.maskRect = CGRectIntegral(maskRect);
  766. break;
  767. }
  768. case RSKImageCropModeCustom: {
  769. self.maskRect = [self.dataSource imageCropViewControllerCustomMaskRect:self];
  770. break;
  771. }
  772. }
  773. }
  774. - (void)updateMaskPath
  775. {
  776. switch (self.cropMode) {
  777. case RSKImageCropModeCircle: {
  778. self.maskPath = [UIBezierPath bezierPathWithOvalInRect:self.rectForMaskPath];
  779. break;
  780. }
  781. case RSKImageCropModeSquare: {
  782. self.maskPath = [UIBezierPath bezierPathWithRect:self.rectForMaskPath];
  783. break;
  784. }
  785. case RSKImageCropModeCustom: {
  786. self.maskPath = [self.dataSource imageCropViewControllerCustomMaskPath:self];
  787. break;
  788. }
  789. }
  790. }
  791. - (UIImage *)imageWithImage:(UIImage *)image inRect:(CGRect)rect scale:(CGFloat)scale imageOrientation:(UIImageOrientation)imageOrientation
  792. {
  793. if (!image.images) {
  794. CGImageRef cgImage = CGImageCreateWithImageInRect(image.CGImage, rect);
  795. UIImage *image = [UIImage imageWithCGImage:cgImage scale:scale orientation:imageOrientation];
  796. CGImageRelease(cgImage);
  797. return image;
  798. } else {
  799. UIImage *animatedImage = image;
  800. NSMutableArray *images = [NSMutableArray array];
  801. for (UIImage *animatedImageImage in animatedImage.images) {
  802. UIImage *image = [self imageWithImage:animatedImageImage inRect:rect scale:scale imageOrientation:imageOrientation];
  803. [images addObject:image];
  804. }
  805. return [UIImage animatedImageWithImages:images duration:image.duration];
  806. }
  807. }
  808. - (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
  809. {
  810. // Step 1: create an image using the data contained within the specified rect.
  811. UIImage *image = [self imageWithImage:originalImage inRect:imageRect scale:originalImage.scale imageOrientation:originalImage.imageOrientation];
  812. // Step 2: fix orientation of the image.
  813. image = [image fixOrientation];
  814. // Step 3: If current mode is `RSKImageCropModeSquare` and the original image is not rotated
  815. // or mask should not be applied to the image after cropping and the original image is not rotated,
  816. // we can return the image immediately.
  817. // Otherwise, we must further process the image.
  818. if ((cropMode == RSKImageCropModeSquare || !applyMaskToCroppedImage) && rotationAngle == 0.0) {
  819. // Step 4: return the image immediately.
  820. return image;
  821. } else {
  822. // Step 4: create a new context.
  823. CGSize contextSize = cropRect.size;
  824. UIGraphicsBeginImageContextWithOptions(contextSize, NO, originalImage.scale);
  825. // Step 5: apply the mask if needed.
  826. if (applyMaskToCroppedImage) {
  827. // 5a: scale the mask to the size of the crop rect.
  828. UIBezierPath *maskPathCopy = [maskPath copy];
  829. CGFloat scale = 1.0 / zoomScale;
  830. [maskPathCopy applyTransform:CGAffineTransformMakeScale(scale, scale)];
  831. // 5b: center the mask.
  832. CGPoint translation = CGPointMake(-CGRectGetMinX(maskPathCopy.bounds) + (CGRectGetWidth(cropRect) - CGRectGetWidth(maskPathCopy.bounds)) * 0.5f,
  833. -CGRectGetMinY(maskPathCopy.bounds) + (CGRectGetHeight(cropRect) - CGRectGetHeight(maskPathCopy.bounds)) * 0.5f);
  834. [maskPathCopy applyTransform:CGAffineTransformMakeTranslation(translation.x, translation.y)];
  835. // 5c: apply the mask.
  836. [maskPathCopy addClip];
  837. }
  838. // Step 6: rotate the image if needed.
  839. if (rotationAngle != 0) {
  840. image = [image rotateByAngle:rotationAngle];
  841. }
  842. // Step 7: draw the image.
  843. CGPoint point = CGPointMake(floor((contextSize.width - image.size.width) * 0.5f),
  844. floor((contextSize.height - image.size.height) * 0.5f));
  845. [image drawAtPoint:point];
  846. // Step 8: get the cropped image affter processing from the context.
  847. UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
  848. // Step 9: remove the context.
  849. UIGraphicsEndImageContext();
  850. croppedImage = [UIImage imageWithCGImage:croppedImage.CGImage scale:originalImage.scale orientation:image.imageOrientation];
  851. // Step 10: return the cropped image affter processing.
  852. return croppedImage;
  853. }
  854. }
  855. - (void)cropImage
  856. {
  857. if ([self.delegate respondsToSelector:@selector(imageCropViewController:willCropImage:)]) {
  858. [self.delegate imageCropViewController:self willCropImage:self.originalImage];
  859. }
  860. UIImage *originalImage = self.originalImage;
  861. RSKImageCropMode cropMode = self.cropMode;
  862. CGRect cropRect = self.cropRect;
  863. CGRect imageRect = self.imageRect;
  864. CGFloat rotationAngle = self.rotationAngle;
  865. CGFloat zoomScale = self.imageScrollView.zoomScale;
  866. UIBezierPath *maskPath = self.maskPath;
  867. BOOL applyMaskToCroppedImage = self.applyMaskToCroppedImage;
  868. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  869. UIImage *croppedImage = [self croppedImage:originalImage cropMode:cropMode cropRect:cropRect imageRect:imageRect rotationAngle:rotationAngle zoomScale:zoomScale maskPath:maskPath applyMaskToCroppedImage:applyMaskToCroppedImage];
  870. dispatch_async(dispatch_get_main_queue(), ^{
  871. [self.delegate imageCropViewController:self didCropImage:croppedImage usingCropRect:cropRect rotationAngle:rotationAngle];
  872. });
  873. });
  874. }
  875. - (void)cancelCrop
  876. {
  877. if ([self.delegate respondsToSelector:@selector(imageCropViewControllerDidCancelCrop:)]) {
  878. [self.delegate imageCropViewControllerDidCancelCrop:self];
  879. }
  880. }
  881. #pragma mark - RSKImageScrollViewDelegate
  882. - (void)imageScrollViewWillBeginDragging
  883. {
  884. [self updateIsUserInteractionEnabledOfCancelAndChooseButtons];
  885. }
  886. - (void)imageScrollViewDidEndDragging:(BOOL)willDecelerate
  887. {
  888. if (willDecelerate == NO) {
  889. [self updateIsUserInteractionEnabledOfCancelAndChooseButtons];
  890. }
  891. }
  892. - (void)imageScrollViewDidEndDecelerating
  893. {
  894. [self updateIsUserInteractionEnabledOfCancelAndChooseButtons];
  895. }
  896. - (void)imageScrollViewWillBeginZooming
  897. {
  898. [self updateIsUserInteractionEnabledOfCancelAndChooseButtons];
  899. }
  900. - (void)imageScrollViewDidEndZooming
  901. {
  902. [self updateIsUserInteractionEnabledOfCancelAndChooseButtons];
  903. }
  904. - (void)updateIsUserInteractionEnabledOfCancelAndChooseButtons
  905. {
  906. BOOL isUserInteractionEnabled = (self.imageScrollView.isDragging || self.imageScrollView.isDecelerating || self.imageScrollView.isZooming) == NO;
  907. [self.cancelButton setUserInteractionEnabled:isUserInteractionEnabled];
  908. [self.chooseButton setUserInteractionEnabled:isUserInteractionEnabled];
  909. }
  910. #pragma mark - UIGestureRecognizerDelegate
  911. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
  912. {
  913. return ([gestureRecognizer isEqual:self.doubleTapGestureRecognizer] || [otherGestureRecognizer isEqual:self.doubleTapGestureRecognizer]) == NO;
  914. }
  915. @end