RSKImageCropViewController.m 44 KB

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